فهرست منبع

Merge pull request #797 from nextcloud/mediaStoreAutoUpload

Add further media types to auto upload
Andy Scherzinger 7 سال پیش
والد
کامیت
bfe2b71210
100فایلهای تغییر یافته به همراه2799 افزوده شده و 2477 حذف شده
  1. 49 51
      build.gradle
  2. 1 0
      drawable_resources/ic_folder_star.svg
  3. 1 0
      gradle.properties
  4. 2 2
      gradle/wrapper/gradle-wrapper.properties
  5. 1 0
      lint.xml
  6. 1 1
      scripts/lint/lint-results.txt
  7. 2 2
      src/gplay/java/com/owncloud/android/utils/PushUtils.java
  8. 8 34
      src/main/AndroidManifest.xml
  9. 0 1
      src/main/java/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java
  10. 143 38
      src/main/java/com/owncloud/android/MainApp.java
  11. 3 1
      src/main/java/com/owncloud/android/authentication/PassCodeManager.java
  12. 20 45
      src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java
  13. 0 2
      src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
  14. 105 0
      src/main/java/com/owncloud/android/datamodel/FileSystemDataSet.java
  15. 186 0
      src/main/java/com/owncloud/android/datamodel/FilesystemDataProvider.java
  16. 6 3
      src/main/java/com/owncloud/android/datamodel/MediaFolder.java
  17. 53 0
      src/main/java/com/owncloud/android/datamodel/MediaFolderType.java
  18. 126 30
      src/main/java/com/owncloud/android/datamodel/MediaProvider.java
  19. 26 3
      src/main/java/com/owncloud/android/datamodel/SyncedFolder.java
  20. 6 5
      src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java
  21. 36 42
      src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java
  22. 41 6
      src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
  23. 127 143
      src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
  24. 45 10
      src/main/java/com/owncloud/android/db/OCUpload.java
  25. 30 8
      src/main/java/com/owncloud/android/db/PreferenceManager.java
  26. 17 1
      src/main/java/com/owncloud/android/db/ProviderMeta.java
  27. 6 1
      src/main/java/com/owncloud/android/db/UploadResult.java
  28. 0 223
      src/main/java/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
  29. 0 223
      src/main/java/com/owncloud/android/files/services/ConnectivityActionReceiver.java
  30. 123 50
      src/main/java/com/owncloud/android/files/services/FileUploader.java
  31. 2 2
      src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java
  32. 6 3
      src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java
  33. 1 1
      src/main/java/com/owncloud/android/jobs/ContactsImportJob.java
  34. 153 0
      src/main/java/com/owncloud/android/jobs/FilesSyncJob.java
  35. 3 3
      src/main/java/com/owncloud/android/jobs/NCJobCreator.java
  36. 69 0
      src/main/java/com/owncloud/android/jobs/NContentObserverJob.java
  37. 2 7
      src/main/java/com/owncloud/android/operations/MoveFileOperation.java
  38. 4 7
      src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java
  39. 0 13
      src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
  40. 126 86
      src/main/java/com/owncloud/android/operations/UploadFileOperation.java
  41. 1 1
      src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java
  42. 92 11
      src/main/java/com/owncloud/android/providers/FileContentProvider.java
  43. 0 237
      src/main/java/com/owncloud/android/services/AdvancedFileAlterationListener.java
  44. 0 81
      src/main/java/com/owncloud/android/services/AutoUploadJob.java
  45. 1 1
      src/main/java/com/owncloud/android/services/OperationsService.java
  46. 0 402
      src/main/java/com/owncloud/android/services/observer/AdvancedFileAlterationObserver.java
  47. 0 180
      src/main/java/com/owncloud/android/services/observer/SyncedFolderObserverService.java
  48. 1 1
      src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java
  49. 4 10
      src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
  50. 2 3
      src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java
  51. 1 1
      src/main/java/com/owncloud/android/ui/activity/FileActivity.java
  52. 11 10
      src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  53. 1 2
      src/main/java/com/owncloud/android/ui/activity/FingerprintActivity.java
  54. 1 0
      src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
  55. 1 3
      src/main/java/com/owncloud/android/ui/activity/LogHistoryActivity.java
  56. 3 4
      src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
  57. 54 150
      src/main/java/com/owncloud/android/ui/activity/Preferences.java
  58. 3 1
      src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
  59. 4 16
      src/main/java/com/owncloud/android/ui/activity/ShareActivity.java
  60. 208 67
      src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java
  61. 59 21
      src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
  62. 38 19
      src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
  63. 2 8
      src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
  64. 51 83
      src/main/java/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java
  65. 19 4
      src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
  66. 67 40
      src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java
  67. 3 1
      src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java
  68. 4 2
      src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java
  69. 2 4
      src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.java
  70. 125 25
      src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java
  71. 13 0
      src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java
  72. 10 15
      src/main/java/com/owncloud/android/ui/events/InitiateSyncedFolder.java
  73. 1 1
      src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
  74. 40 7
      src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
  75. 1 1
      src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java
  76. 2 2
      src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java
  77. 1 1
      src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
  78. 3 1
      src/main/java/com/owncloud/android/ui/helpers/UriUploader.java
  79. 2 4
      src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java
  80. 22 5
      src/main/java/com/owncloud/android/utils/BitmapUtils.java
  81. 5 0
      src/main/java/com/owncloud/android/utils/DisplayUtils.java
  82. 329 0
      src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
  83. 3 3
      src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
  84. 74 0
      src/main/java/com/owncloud/android/utils/ReceiversHelper.java
  85. 2 1
      src/main/java/com/owncloud/android/utils/ThemeUtils.java
  86. 3 1
      src/main/java/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java
  87. BIN
      src/main/res/drawable-hdpi/common_error.png
  88. 0 0
      src/main/res/drawable-hdpi/folder.png
  89. BIN
      src/main/res/drawable-hdpi/ic_action_cancel_sync.png
  90. BIN
      src/main/res/drawable-hdpi/ic_action_cancel_white.png
  91. BIN
      src/main/res/drawable-hdpi/ic_action_copy.png
  92. BIN
      src/main/res/drawable-hdpi/ic_action_delete_white.png
  93. BIN
      src/main/res/drawable-hdpi/ic_action_download.png
  94. BIN
      src/main/res/drawable-hdpi/ic_action_move.png
  95. BIN
      src/main/res/drawable-hdpi/ic_action_set_available_offline.png
  96. BIN
      src/main/res/drawable-hdpi/ic_action_set_available_offline_white.png
  97. BIN
      src/main/res/drawable-hdpi/ic_action_share.png
  98. BIN
      src/main/res/drawable-hdpi/ic_action_unset_available_offline.png
  99. BIN
      src/main/res/drawable-hdpi/ic_action_unset_available_offline_white.png
  100. BIN
      src/main/res/drawable-hdpi/ic_cellphone.png

+ 49 - 51
build.gradle

@@ -11,9 +11,10 @@ buildscript {
         maven {
         maven {
             url 'https://oss.sonatype.org/content/repositories/snapshots/'
             url 'https://oss.sonatype.org/content/repositories/snapshots/'
         }
         }
+        google()
     }
     }
     dependencies {
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.2'
+        classpath 'com.android.tools.build:gradle:3.0.0-alpha9'
         classpath 'com.google.gms:google-services:3.0.0'
         classpath 'com.google.gms:google-services:3.0.0'
     }
     }
 }
 }
@@ -29,7 +30,7 @@ configurations.all {
 }
 }
 
 
 ext {
 ext {
-    supportLibraryVersion = '25.0.0'
+    supportLibraryVersion = '25.2.0'
     googleLibraryVersion = '10.2.4'
     googleLibraryVersion = '10.2.4'
 
 
     travisBuild = System.getenv("TRAVIS") == "true"
     travisBuild = System.getenv("TRAVIS") == "true"
@@ -44,6 +45,7 @@ repositories {
     maven {
     maven {
         url 'https://oss.sonatype.org/content/repositories/snapshots/'
         url 'https://oss.sonatype.org/content/repositories/snapshots/'
     }
     }
+    google()
 
 
     flatDir {
     flatDir {
         dirs 'libs'
         dirs 'libs'
@@ -63,7 +65,7 @@ android {
     }
     }
 
 
     compileSdkVersion 25
     compileSdkVersion 25
-    buildToolsVersion '25.0.0'
+    buildToolsVersion '25.0.2'
 
 
     defaultConfig {
     defaultConfig {
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@@ -78,14 +80,18 @@ android {
         // adapt structure from Eclipse to Gradle/Android Studio expectations;
         // adapt structure from Eclipse to Gradle/Android Studio expectations;
         // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
         // see http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Configuring-the-Structure
 
 
+        flavorDimensions "default"
+
         productFlavors {
         productFlavors {
             // used for f-droid
             // used for f-droid
             generic {
             generic {
                 applicationId 'com.nextcloud.client'
                 applicationId 'com.nextcloud.client'
+                dimension "default"
             }
             }
 
 
             gplay {
             gplay {
                 applicationId 'com.nextcloud.client'
                 applicationId 'com.nextcloud.client'
+                dimension "default"
             }
             }
 
 
             modified {
             modified {
@@ -94,6 +100,7 @@ android {
                 // domain name
                 // domain name
                 // .client
                 // .client
                 applicationId 'com.custom.client'
                 applicationId 'com.custom.client'
+                dimension "default"
             }
             }
         }
         }
 
 
@@ -111,11 +118,6 @@ android {
         preDexLibraries = preDexEnabled && !travisBuild
         preDexLibraries = preDexEnabled && !travisBuild
     }
     }
 
 
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_7
-        targetCompatibility JavaVersion.VERSION_1_7
-    }
-
     packagingOptions {
     packagingOptions {
         exclude 'META-INF/LICENSE.txt'
         exclude 'META-INF/LICENSE.txt'
         exclude 'META-INF/LICENSE'
         exclude 'META-INF/LICENSE'
@@ -143,10 +145,10 @@ android {
             xml.enabled = false
             xml.enabled = false
             html.enabled = true
             html.enabled = true
             xml {
             xml {
-                destination "$project.buildDir/reports/pmd/pmd.xml"
+                destination = file("$project.buildDir/reports/pmd/pmd.xml")
             }
             }
             html {
             html {
-                destination "$project.buildDir/reports/pmd/pmd.html"
+                destination = file("$project.buildDir/reports/pmd/pmd.html")
             }
             }
         }
         }
     }
     }
@@ -165,67 +167,63 @@ android {
             xml.enabled = false
             xml.enabled = false
             html.enabled = true
             html.enabled = true
             html {
             html {
-                destination "$project.buildDir/reports/findbugs/findbugs.html"
+                destination  = file("$project.buildDir/reports/findbugs/findbugs.html")
             }
             }
         }
         }
         classpath = files()
         classpath = files()
     }
     }
     check.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint'
     check.dependsOn 'checkstyle', 'findbugs', 'pmd', 'lint'
 
 
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
 }
 }
 
 
 dependencies {
 dependencies {
     /// dependencies for app building
     /// dependencies for app building
-    compile name: 'touch-image-view'
-    compile 'com.android.support:multidex:1.0.1'
-
-    compile 'com.github.nextcloud:android-library:1.0.22'
-    compile "com.android.support:support-v4:${supportLibraryVersion}"
-    compile "com.android.support:design:${supportLibraryVersion}"
-    compile 'com.jakewharton:disklrucache:2.0.2'
-    compile "com.android.support:appcompat-v7:${supportLibraryVersion}"
-    compile "com.android.support:cardview-v7:${supportLibraryVersion}"
-    compile 'com.github.tobiasKaminsky:android-floating-action-button:1.10.2'
-    compile 'com.google.code.findbugs:annotations:2.0.1'
-    compile group: 'commons-io', name: 'commons-io', version: '2.4'
-    compile 'com.github.evernote:android-job:v1.1.9'
-    compile 'com.jakewharton:butterknife:8.4.0'
-    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
-    compile 'org.greenrobot:eventbus:3.0.0'
-    compile 'com.googlecode.ez-vcard:ez-vcard:0.10.2'
-
+    implementation name: 'touch-image-view'
+    implementation 'com.android.support:multidex:1.0.2'
+    implementation 'com.github.nextcloud:android-library:1.0.23'
+    implementation "com.android.support:support-v4:${supportLibraryVersion}"
+    implementation "com.android.support:design:${supportLibraryVersion}"
+    implementation 'com.jakewharton:disklrucache:2.0.2'
+    implementation "com.android.support:appcompat-v7:${supportLibraryVersion}"
+    implementation "com.android.support:cardview-v7:${supportLibraryVersion}"
+    implementation "com.android.support:exifinterface:${supportLibraryVersion}"
+    implementation 'com.github.tobiasKaminsky:android-floating-action-button:1.10.2'
+    implementation 'com.google.code.findbugs:annotations:2.0.1'
+    implementation 'commons-io:commons-io:2.5'
+    implementation 'com.github.evernote:android-job:v1.1.11'
+    implementation 'com.jakewharton:butterknife:8.5.1'
+    annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
+    implementation 'org.greenrobot:eventbus:3.0.0'
+    implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.2'
+    implementation 'org.lukhnos:nnio:0.2'
     // uncomment for gplay, modified
     // uncomment for gplay, modified
-    // compile "com.google.firebase:firebase-messaging:${googleLibraryVersion}"
-    // compile "com.google.android.gms:play-services-base:${googleLibraryVersion}"
-
-    compile 'org.parceler:parceler-api:1.1.6'
+    // implementation "com.google.firebase:firebase-messaging:${googleLibraryVersion}"
+    // implementation "com.google.android.gms:play-services-base:${googleLibraryVersion}"
+    implementation 'org.parceler:parceler-api:1.1.6'
     annotationProcessor 'org.parceler:parceler:1.1.6'
     annotationProcessor 'org.parceler:parceler:1.1.6'
-
-    compile 'com.github.bumptech.glide:glide:3.7.0'
-    compile 'com.caverock:androidsvg:1.2.1'
-
+    implementation 'com.github.bumptech.glide:glide:3.7.0'
+    implementation 'com.caverock:androidsvg:1.2.1'
     /// dependencies for local unit tests
     /// dependencies for local unit tests
-    testCompile 'junit:junit:4.12'
-    testCompile 'org.mockito:mockito-core:1.10.19'
-
+    testImplementation 'junit:junit:4.12'
+    testImplementation 'org.mockito:mockito-core:1.10.19'
     /// dependencies for instrumented tests
     /// dependencies for instrumented tests
     // JUnit4 Rules
     // JUnit4 Rules
-    androidTestCompile 'com.android.support.test:rules:0.5'
-
+    androidTestImplementation 'com.android.support.test:rules:0.5'
     // Android JUnit Runner
     // Android JUnit Runner
-    androidTestCompile 'com.android.support.test:runner:0.5'
-
+    androidTestImplementation 'com.android.support.test:runner:0.5'
     // Android Annotation Support
     // Android Annotation Support
-    androidTestCompile "com.android.support:support-annotations:${supportLibraryVersion}"
-
+    androidTestImplementation "com.android.support:support-annotations:${supportLibraryVersion}"
     // Espresso core
     // Espresso core
-    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
-
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
     // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests
     // UIAutomator - for cross-app UI tests, and to grant screen is turned on in Espresso tests
-    //androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
+    //androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
     // fix conflict in dependencies; see http://g.co/androidstudio/app-test-app-conflict for details
     // fix conflict in dependencies; see http://g.co/androidstudio/app-test-app-conflict for details
-    //androidTestCompile "com.android.support:support-annotations:${supportLibraryVersion}"
-
+    //androidTestImplementation "com.android.support:support-annotations:${supportLibraryVersion}"
+    implementation 'org.jetbrains:annotations:15.0'
 }
 }
 
 
 configurations.all {
 configurations.all {

+ 1 - 0
drawable_resources/ic_folder_star.svg

@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6M17.94,17L15,15.28L12.06,17L12.84,13.67L10.25,11.43L13.66,11.14L15,8L16.34,11.14L19.75,11.43L17.16,13.67L17.94,17Z" /></svg>

+ 1 - 0
gradle.properties

@@ -0,0 +1 @@
+android.enableAapt2=false

+ 2 - 2
gradle/wrapper/gradle-wrapper.properties

@@ -1,6 +1,6 @@
-#Wed Mar 15 00:10:20 CET 2017
+#Fri May 05 19:09:31 CEST 2017
 distributionBase=GRADLE_USER_HOME
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

+ 1 - 0
lint.xml

@@ -2,5 +2,6 @@
 <lint>
 <lint>
     <issue id="InvalidPackage">
     <issue id="InvalidPackage">
         <ignore path="**/freemarker-2.3.23.jar"/>
         <ignore path="**/freemarker-2.3.23.jar"/>
+        <ignore path="**/nnio-0.2.jar"/>
     </issue>
     </issue>
 </lint>
 </lint>

+ 1 - 1
scripts/lint/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 10 errors and 676 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 3 errors and 653 warnings</span>

+ 2 - 2
src/gplay/java/com/owncloud/android/utils/PushUtils.java

@@ -167,7 +167,7 @@ public class PushUtils {
                     remoteOperationResult = unregisterAccountDeviceForProxyOperation.execute(mClient);
                     remoteOperationResult = unregisterAccountDeviceForProxyOperation.execute(mClient);
 
 
                     if (remoteOperationResult.isSuccess()) {
                     if (remoteOperationResult.isSuccess()) {
-                        arbitraryDataProvider.deleteKeyForAccount(account, KEY_PUSH);
+                        arbitraryDataProvider.deleteKeyForAccount(account.name, KEY_PUSH);
                     }
                     }
                 }
                 }
             }
             }
@@ -244,7 +244,7 @@ public class PushUtils {
                                     PushConfigurationState pushArbitraryData = new PushConfigurationState(token,
                                     PushConfigurationState pushArbitraryData = new PushConfigurationState(token,
                                             pushResponse.getDeviceIdentifier(), pushResponse.getSignature(),
                                             pushResponse.getDeviceIdentifier(), pushResponse.getSignature(),
                                             pushResponse.getPublicKey(), false);
                                             pushResponse.getPublicKey(), false);
-                                    arbitraryDataProvider.storeOrUpdateKeyValue(account, KEY_PUSH,
+                                    arbitraryDataProvider.storeOrUpdateKeyValue(account.name, KEY_PUSH,
                                             gson.toJson(pushArbitraryData));
                                             gson.toJson(pushArbitraryData));
                                 }
                                 }
                             }
                             }

+ 8 - 34
src/main/AndroidManifest.xml

@@ -86,7 +86,7 @@
         <activity android:name=".ui.activity.ParticipateActivity" />
         <activity android:name=".ui.activity.ParticipateActivity" />
         <activity android:name=".ui.activity.ActivitiesListActivity"
         <activity android:name=".ui.activity.ActivitiesListActivity"
                   android:configChanges="orientation|screenSize|keyboardHidden" />
                   android:configChanges="orientation|screenSize|keyboardHidden" />
-        <activity android:name=".ui.activity.FolderSyncActivity" />
+        <activity android:name=".ui.activity.SyncedFoldersActivity"/>
         <activity android:name=".ui.activity.UploadFilesActivity" />
         <activity android:name=".ui.activity.UploadFilesActivity" />
         <activity android:name=".ui.activity.ExternalSiteWebView"
         <activity android:name=".ui.activity.ExternalSiteWebView"
                   android:configChanges="orientation|screenSize|keyboardHidden" />
                   android:configChanges="orientation|screenSize|keyboardHidden" />
@@ -120,6 +120,11 @@
             android:label="@string/app_name"
             android:label="@string/app_name"
             android:theme="@style/Theme.ownCloud.Fullscreen" />
             android:theme="@style/Theme.ownCloud.Fullscreen" />
 
 
+        <service
+            android:name=".jobs.NContentObserverJob"
+            android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service
         <service
             android:name=".authentication.AccountAuthenticatorService"
             android:name=".authentication.AccountAuthenticatorService"
             android:exported="true" >
             android:exported="true" >
@@ -131,7 +136,7 @@
                 android:name="android.accounts.AccountAuthenticator"
                 android:name="android.accounts.AccountAuthenticator"
                 android:resource="@xml/authenticator" />
                 android:resource="@xml/authenticator" />
         </service>
         </service>
-        <service android:name=".services.observer.SyncedFolderObserverService"/>
+
         <service
         <service
             android:name=".syncadapter.FileSyncService"
             android:name=".syncadapter.FileSyncService"
             android:exported="true" >
             android:exported="true" >
@@ -160,7 +165,7 @@
             android:label="@string/search_users_and_groups_hint" />
             android:label="@string/search_users_and_groups_hint" />
 
 
         <provider
         <provider
-            android:name="org.nextcloud.providers.DocumentsStorageProvider"
+            android:name=".providers.DocumentsStorageProvider"
             android:authorities="@string/document_provider_authority"
             android:authorities="@string/document_provider_authority"
             android:exported="true"
             android:exported="true"
             android:grantUriPermissions="true"
             android:grantUriPermissions="true"
@@ -226,43 +231,12 @@
         <activity android:name=".ui.activity.UploadListActivity" />
         <activity android:name=".ui.activity.UploadListActivity" />
         <activity android:name=".ui.activity.WhatsNewActivity"
         <activity android:name=".ui.activity.WhatsNewActivity"
                   android:theme="@style/Theme.ownCloud.noActionBar.Login" />
                   android:theme="@style/Theme.ownCloud.noActionBar.Login" />
-        
-        <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"/>
-                <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
-                <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
-		    </intent-filter>
-		</receiver>
-		<receiver android:name=".files.InstantUploadBroadcastReceiver">
-            <intent-filter>
 
 
-                <!-- unofficially supported by many Android phones but not by HTC devices: -->
-                <action android:name="com.android.camera.NEW_PICTURE" />
-                <!-- officially supported since Android 4.0 (SDK 14, works even for HTC devices): -->
-                <action android:name="android.hardware.action.NEW_PICTURE" />
-
-                <data android:mimeType="image/*" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.hardware.action.NEW_VIDEO" />
-
-                <data android:mimeType="video/*" />
-            </intent-filter>
-        </receiver>
         <receiver android:name=".files.BootupBroadcastReceiver" >
         <receiver android:name=".files.BootupBroadcastReceiver" >
             <intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
             </intent-filter>
             </intent-filter>
         </receiver>
         </receiver>
-        <receiver android:name=".services.ShutdownReceiver">
-            <intent-filter>
-                <action android:name="android.intent.action.ACTION_SHUTDOWN" />
-                <action android:name="android.intent.action.QUICKBOOT_POWEROFF" />
-            </intent-filter>
-        </receiver>
 
 
 
 
         <service android:name=".services.observer.FileObserverService" />
         <service android:name=".services.observer.FileObserverService" />

+ 0 - 1
src/main/java/com/afollestad/sectionedrecyclerview/SectionedRecyclerViewAdapter.java

@@ -35,7 +35,6 @@ public abstract class SectionedRecyclerViewAdapter<VH extends RecyclerView.ViewH
 
 
     private final ArrayMap<Integer, Integer> mHeaderLocationMap;
     private final ArrayMap<Integer, Integer> mHeaderLocationMap;
     private GridLayoutManager mLayoutManager;
     private GridLayoutManager mLayoutManager;
-    private ArrayMap<Integer, Integer> mSpanMap;
     private boolean mShowHeadersForEmptySections;
     private boolean mShowHeadersForEmptySections;
 
 
     public SectionedRecyclerViewAdapter() {
     public SectionedRecyclerViewAdapter() {

+ 143 - 38
src/main/java/com/owncloud/android/MainApp.java

@@ -19,34 +19,42 @@
  */
  */
 package com.owncloud.android;
 package com.owncloud.android;
 
 
+import android.Manifest;
 import android.app.Activity;
 import android.app.Activity;
-import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Environment;
-import android.os.IBinder;
 import android.support.multidex.MultiDexApplication;
 import android.support.multidex.MultiDexApplication;
 import android.support.v4.util.Pair;
 import android.support.v4.util.Pair;
+import android.support.v7.app.AlertDialog;
 
 
 import com.evernote.android.job.JobManager;
 import com.evernote.android.job.JobManager;
 import com.owncloud.android.authentication.PassCodeManager;
 import com.owncloud.android.authentication.PassCodeManager;
+import com.owncloud.android.datamodel.MediaFolder;
+import com.owncloud.android.datamodel.MediaFolderType;
+import com.owncloud.android.datamodel.MediaProvider;
 import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.db.PreferenceManager;
+import com.owncloud.android.jobs.NCJobCreator;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory.Policy;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.services.NCJobCreator;
-import com.owncloud.android.services.observer.SyncedFolderObserverService;
 import com.owncloud.android.ui.activity.Preferences;
 import com.owncloud.android.ui.activity.Preferences;
+import com.owncloud.android.ui.activity.SyncedFoldersActivity;
 import com.owncloud.android.ui.activity.WhatsNewActivity;
 import com.owncloud.android.ui.activity.WhatsNewActivity;
 import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.AnalyticsUtils;
+import com.owncloud.android.utils.FilesSyncHelper;
+import com.owncloud.android.utils.PermissionUtil;
+import com.owncloud.android.utils.ReceiversHelper;
 
 
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashMap;
@@ -79,8 +87,6 @@ public class MainApp extends MultiDexApplication {
 
 
     private static boolean mOnlyOnDevice = false;
     private static boolean mOnlyOnDevice = false;
 
 
-    private static SyncedFolderObserverService mObserverService;
-
     @SuppressWarnings("unused")
     @SuppressWarnings("unused")
     private boolean mBound;
     private boolean mBound;
 
 
@@ -118,14 +124,26 @@ public class MainApp extends MultiDexApplication {
             Log_OC.d("Debug", "start logging");
             Log_OC.d("Debug", "start logging");
         }
         }
 
 
+        updateToAutoUpload();
         cleanOldEntries();
         cleanOldEntries();
         updateAutoUploadEntries();
         updateAutoUploadEntries();
 
 
-        Log_OC.d("SyncedFolderObserverService", "Start service SyncedFolderObserverService");
-        Intent i = new Intent(this, SyncedFolderObserverService.class);
-        startService(i);
-        bindService(i, syncedFolderObserverServiceConnection, Context.BIND_AUTO_CREATE);
+        if (PermissionUtil.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+            splitOutAutoUploadEntries();
+        } else {
+            PreferenceManager.setAutoUploadSplitEntries(this, true);
+        }
+
+        initiateExistingAutoUploadEntries();
+
+        FilesSyncHelper.scheduleFilesSyncIfNeeded();
+        FilesSyncHelper.restartJobsIfNeeded();
 
 
+        ReceiversHelper.registerNetworkChangeReceiver();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            ReceiversHelper.registerPowerChangeReceiver();
+        }
 
 
         // register global protection with pass code
         // register global protection with pass code
         registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
         registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@@ -245,10 +263,6 @@ public class MainApp extends MultiDexApplication {
         return mOnlyOnDevice;
         return mOnlyOnDevice;
     }
     }
 
 
-    public static SyncedFolderObserverService getSyncedFolderObserverService() {
-        return mObserverService;
-    }
-
     // user agent
     // user agent
     public static String getUserAgent() {
     public static String getUserAgent() {
         String appString = getAppContext().getResources().getString(R.string.user_agent);
         String appString = getAppContext().getResources().getString(R.string.user_agent);
@@ -271,6 +285,48 @@ public class MainApp extends MultiDexApplication {
         return userAgent;
         return userAgent;
     }
     }
 
 
+    private void updateToAutoUpload() {
+            if (PreferenceManager.instantPictureUploadEnabled(this) ||
+                            PreferenceManager.instantPictureUploadEnabled(this)) {
+
+                // remove legacy shared preferences
+                SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
+                editor.remove("instant_uploading")
+                        .remove("instant_video_uploading")
+                        .remove("instant_upload_path")
+                        .remove("instant_upload_path_use_subfolders")
+                        .remove("instant_upload_on_wifi")
+                        .remove("instant_upload_on_charging")
+                        .remove("instant_video_upload_path")
+                        .remove("instant_video_upload_path_use_subfolders")
+                        .remove("instant_video_upload_on_wifi")
+                        .remove("instant_video_uploading")
+                        .remove("instant_video_upload_on_charging")
+                        .remove("prefs_instant_behaviour").apply();
+
+                // show info pop-up
+                new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog)
+                        .setTitle(R.string.drawer_synced_folders)
+                        .setMessage(R.string.synced_folders_new_info)
+                        .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int which) {
+                                // show Auto Upload
+                                Intent folderSyncIntent = new Intent(getApplicationContext(),
+                                        SyncedFoldersActivity.class);
+                                dialog.dismiss();
+                                startActivity(folderSyncIntent);
+                            }
+                        })
+                        .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int which) {
+                                dialog.dismiss();
+                            }
+                        })
+                        .setIcon(R.drawable.nav_synced_folders)
+                        .show();
+            }
+    }
+
     private void updateAutoUploadEntries() {
     private void updateAutoUploadEntries() {
         // updates entries to reflect their true paths
         // updates entries to reflect their true paths
         if (!PreferenceManager.getAutoUploadPathsUpdate(this)) {
         if (!PreferenceManager.getAutoUploadPathsUpdate(this)) {
@@ -280,6 +336,77 @@ public class MainApp extends MultiDexApplication {
         }
         }
     }
     }
 
 
+    private void splitOutAutoUploadEntries() {
+        if (!PreferenceManager.getAutoUploadSplitEntries(this)) {
+            // magic to split out existing synced folders in two when needed
+            // otherwise, we migrate them to their proper type (image or video)
+            Log_OC.i(TAG, "Migrate synced_folders records for image/video split");
+            ContentResolver contentResolver = this.getContentResolver();
+
+            SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver);
+
+            final List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null);
+            final List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1);
+
+            ArrayList<Long> idsToDelete = new ArrayList<>();
+            List<SyncedFolder> syncedFolders = syncedFolderProvider.getSyncedFolders();
+            long primaryKey;
+            SyncedFolder newSyncedFolder;
+            for (SyncedFolder syncedFolder : syncedFolders) {
+                idsToDelete.add(syncedFolder.getId());
+                Log_OC.i(TAG, "Migration check for synced_folders record: "
+                        + syncedFolder.getId() + " - " + syncedFolder.getLocalPath());
+
+                for (int i = 0; i < imageMediaFolders.size(); i++) {
+                    if (imageMediaFolders.get(i).absolutePath.equals(syncedFolder.getLocalPath())) {
+                        newSyncedFolder = (SyncedFolder) syncedFolder.clone();
+                        newSyncedFolder.setType(MediaFolderType.IMAGE);
+                        primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
+                        Log_OC.i(TAG, "Migrated image synced_folders record: "
+                                + primaryKey + " - " + newSyncedFolder.getLocalPath());
+                        break;
+                    }
+                }
+
+                for (int j = 0; j < videoMediaFolders.size(); j++) {
+                    if (videoMediaFolders.get(j).absolutePath.equals(syncedFolder.getLocalPath())) {
+                        newSyncedFolder = (SyncedFolder) syncedFolder.clone();
+                        newSyncedFolder.setType(MediaFolderType.VIDEO);
+                        primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder);
+                        Log_OC.i(TAG, "Migrated video synced_folders record: "
+                                + primaryKey + " - " + newSyncedFolder.getLocalPath());
+                        break;
+                    }
+                }
+            }
+
+            for (long id : idsToDelete) {
+                Log_OC.i(TAG, "Removing legacy synced_folders record: " + id);
+                syncedFolderProvider.deleteSyncedFolder(id);
+            }
+
+            PreferenceManager.setAutoUploadSplitEntries(this, true);
+        }
+    }
+
+    private void initiateExistingAutoUploadEntries() {
+        new Thread(() -> {
+            if (!PreferenceManager.getAutoUploadInit(getAppContext())) {
+                SyncedFolderProvider syncedFolderProvider =
+                        new SyncedFolderProvider(MainApp.getAppContext().getContentResolver());
+
+                for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
+                    if (syncedFolder.isEnabled()) {
+                        FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder);
+                    }
+                }
+
+                PreferenceManager.setAutoUploadInit(getAppContext(), true);
+            }
+
+        }).start();
+    }
+
     private void cleanOldEntries() {
     private void cleanOldEntries() {
         // previous versions of application created broken entries in the SyncedFolderProvider
         // previous versions of application created broken entries in the SyncedFolderProvider
         // database, and this cleans all that and leaves 1 (newest) entry per synced folder
         // database, and this cleans all that and leaves 1 (newest) entry per synced folder
@@ -302,9 +429,7 @@ public class MainApp extends MultiDexApplication {
                 }
                 }
             }
             }
 
 
-            for (Long idValue : syncedFolders.values()) {
-                ids.add(idValue);
-            }
+            ids.addAll(syncedFolders.values());
 
 
             if (ids.size() > 0) {
             if (ids.size() > 0) {
                 syncedFolderProvider.deleteSyncedFoldersNotInList(mContext, ids);
                 syncedFolderProvider.deleteSyncedFoldersNotInList(mContext, ids);
@@ -313,24 +438,4 @@ public class MainApp extends MultiDexApplication {
             }
             }
         }
         }
     }
     }
-
-    /**
-     * Defines callbacks for service binding, passed to bindService()
-     */
-    private ServiceConnection syncedFolderObserverServiceConnection = new ServiceConnection() {
-
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            SyncedFolderObserverService.SyncedFolderObserverBinder binder =
-                    (SyncedFolderObserverService.SyncedFolderObserverBinder) service;
-            mObserverService = binder.getService();
-            mBound = true;
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName arg0) {
-            mBound = false;
-        }
-    };
-
 }
 }

+ 3 - 1
src/main/java/com/owncloud/android/authentication/PassCodeManager.java

@@ -31,6 +31,7 @@ import android.view.WindowManager;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.ui.activity.FingerprintActivity;
 import com.owncloud.android.ui.activity.FingerprintActivity;
 import com.owncloud.android.ui.activity.PassCodeActivity;
 import com.owncloud.android.ui.activity.PassCodeActivity;
+import com.owncloud.android.ui.activity.Preferences;
 
 
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.Set;
@@ -131,6 +132,7 @@ public class PassCodeManager {
 
 
     private boolean fingerprintIsEnabled() {
     private boolean fingerprintIsEnabled() {
         SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(MainApp.getAppContext());
         SharedPreferences appPrefs = PreferenceManager.getDefaultSharedPreferences(MainApp.getAppContext());
-        return (appPrefs.getBoolean(FingerprintActivity.PREFERENCE_USE_FINGERPRINT, false));
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+                appPrefs.getBoolean(Preferences.PREFERENCE_USE_FINGERPRINT, false);
     }
     }
 }
 }

+ 20 - 45
src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java

@@ -47,36 +47,43 @@ public class ArbitraryDataProvider {
         this.contentResolver = contentResolver;
         this.contentResolver = contentResolver;
     }
     }
 
 
-    public int deleteKeyForAccount(Account account, String key) {
-        int result = contentResolver.delete(
+    public int deleteKeyForAccount(String account, String key) {
+        return contentResolver.delete(
                 ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA,
                 ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA,
                 ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? AND " +
                 ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? AND " +
                         ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + "= ?",
                         ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + "= ?",
-                new String[]{account.name, key}
+                new String[]{account, key}
         );
         );
+    }
 
 
-        return result;
+    public int deleteForKeyWhereAccountNotIn(ArrayList<String> accounts, String key) {
+        return contentResolver.delete(
+                ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA,
+                ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " NOT IN (?) AND " +
+                        ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + "= ?",
+                new String[]{String.valueOf(accounts), key}
+        );
     }
     }
 
 
 
 
-    public void storeOrUpdateKeyValue(Account account, String key, String newValue) {
-        ArbitraryDataSet data = getArbitraryDataSet(account, key);
+    public void storeOrUpdateKeyValue(String accountName, String key, String newValue) {
+        ArbitraryDataSet data = getArbitraryDataSet(accountName, key);
         if (data == null) {
         if (data == null) {
-            Log_OC.v(TAG, "Adding arbitrary data with cloud id: " + account.name + " key: " + key
+            Log_OC.v(TAG, "Adding arbitrary data with cloud id: " + accountName + " key: " + key
                     + " value: " + newValue);
                     + " value: " + newValue);
             ContentValues cv = new ContentValues();
             ContentValues cv = new ContentValues();
-            cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID, account.name);
+            cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID, accountName);
             cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY, key);
             cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY, key);
             cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_VALUE, newValue);
             cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_VALUE, newValue);
 
 
             Uri result = contentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA, cv);
             Uri result = contentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA, cv);
 
 
             if (result == null) {
             if (result == null) {
-                Log_OC.v(TAG, "Failed to store arbitrary data with cloud id: " + account.name + " key: " + key
+                Log_OC.v(TAG, "Failed to store arbitrary data with cloud id: " + accountName + " key: " + key
                         + " value: " + newValue);
                         + " value: " + newValue);
             }
             }
         } else {
         } else {
-            Log_OC.v(TAG, "Updating arbitrary data with cloud id: " + account.name + " key: " + key
+            Log_OC.v(TAG, "Updating arbitrary data with cloud id: " + accountName + " key: " + key
                     + " value: " + newValue);
                     + " value: " + newValue);
             ContentValues cv = new ContentValues();
             ContentValues cv = new ContentValues();
             cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID, data.getCloudId());
             cv.put(ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID, data.getCloudId());
@@ -91,7 +98,7 @@ public class ArbitraryDataProvider {
             );
             );
 
 
             if (result == 0) {
             if (result == 0) {
-                Log_OC.v(TAG, "Failed to update arbitrary data with cloud id: " + account.name + " key: " + key
+                Log_OC.v(TAG, "Failed to update arbitrary data with cloud id: " + accountName + " key: " + key
                         + " value: " + newValue);
                         + " value: " + newValue);
             }
             }
         }
         }
@@ -146,38 +153,6 @@ public class ArbitraryDataProvider {
         return getIntegerValue(account.name, key);
         return getIntegerValue(account.name, key);
     }
     }
 
 
-    private ArrayList<String> getValues(Account account, String key) {
-        Cursor cursor = contentResolver.query(
-                ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA,
-                null,
-                ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? and " +
-                        ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + " = ?",
-                new String[]{account.name, key},
-                null
-        );
-
-        if (cursor != null) {
-            ArrayList<String> list = new ArrayList<>();
-            if (cursor.moveToFirst()) {
-                do {
-                    String value = cursor.getString(cursor.getColumnIndex(
-                            ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_VALUE));
-                    if (value == null) {
-                        Log_OC.e(TAG, "Arbitrary value could not be created from cursor");
-                    } else {
-                        list.add(value);
-                    }
-                } while (cursor.moveToNext());
-            }
-            cursor.close();
-            return list;
-        } else {
-            Log_OC.e(TAG, "DB error restoring arbitrary values.");
-        }
-
-        return new ArrayList<>();
-    }
-
     /**
     /**
      * Returns stored value as string or empty string
      * Returns stored value as string or empty string
      * @return string if value found or empty string
      * @return string if value found or empty string
@@ -215,13 +190,13 @@ public class ArbitraryDataProvider {
         return "";
         return "";
     }
     }
 
 
-    private ArbitraryDataSet getArbitraryDataSet(Account account, String key) {
+    private ArbitraryDataSet getArbitraryDataSet(String accountName, String key) {
         Cursor cursor = contentResolver.query(
         Cursor cursor = contentResolver.query(
                 ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA,
                 ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA,
                 null,
                 null,
                 ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? and " +
                 ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " = ? and " +
                         ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + " = ?",
                         ProviderMeta.ProviderTableMeta.ARBITRARY_DATA_KEY + " = ?",
-                new String[]{account.name, key},
+                new String[]{accountName, key},
                 null
                 null
         );
         );
 
 

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

@@ -593,7 +593,6 @@ public class FileDataStorageManager {
                 if (localFile.isDirectory()) {
                 if (localFile.isDirectory()) {
                     success &= removeLocalFolder(localFile);
                     success &= removeLocalFolder(localFile);
                 } else {
                 } else {
-                    String path = localFile.getAbsolutePath();
                     success &= localFile.delete();
                     success &= localFile.delete();
                 }
                 }
             }
             }
@@ -602,7 +601,6 @@ public class FileDataStorageManager {
         return success;
         return success;
     }
     }
 
 
-
     /**
     /**
      * Updates database and file system for a file or folder that was moved to a different location.
      * Updates database and file system for a file or folder that was moved to a different location.
      *
      *

+ 105 - 0
src/main/java/com/owncloud/android/datamodel/FileSystemDataSet.java

@@ -0,0 +1,105 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.datamodel;
+
+/*
+    Model for filesystem data from the database
+ */
+public class FileSystemDataSet {
+
+    private int id;
+    private String localPath;
+    private long modifiedAt;
+    private boolean isFolder;
+    private boolean isSentForUpload;
+    private long foundAt;
+    private long syncedFolderId;
+
+    public FileSystemDataSet() {
+    }
+
+    public FileSystemDataSet(int id, String localPath, long modifiedAt, boolean isFolder,
+                             boolean isSentForUpload, long foundAt, long syncedFolderId) {
+        this.id = id;
+        this.localPath = localPath;
+        this.modifiedAt = modifiedAt;
+        this.isFolder = isFolder;
+        this.isSentForUpload = isSentForUpload;
+        this.foundAt = foundAt;
+        this.syncedFolderId = syncedFolderId;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getLocalPath() {
+        return localPath;
+    }
+
+    public void setLocalPath(String localPath) {
+        this.localPath = localPath;
+    }
+
+    public long getModifiedAt() {
+        return modifiedAt;
+    }
+
+    public void setModifiedAt(long modifiedAt) {
+        this.modifiedAt = modifiedAt;
+    }
+
+    public boolean isFolder() {
+        return isFolder;
+    }
+
+    public void setFolder(boolean folder) {
+        isFolder = folder;
+    }
+
+    public long getFoundAt() {
+        return foundAt;
+    }
+
+    public void setFoundAt(long foundAt) {
+        this.foundAt = foundAt;
+    }
+
+    public boolean isSentForUpload() {
+        return isSentForUpload;
+    }
+
+    public void setSentForUpload(boolean sentForUpload) {
+        isSentForUpload = sentForUpload;
+    }
+
+    public long getSyncedFolderId() {
+        return syncedFolderId;
+    }
+
+    public void setSyncedFolderId(long syncedFolderId) {
+        this.syncedFolderId = syncedFolderId;
+    }
+}

+ 186 - 0
src/main/java/com/owncloud/android/datamodel/FilesystemDataProvider.java

@@ -0,0 +1,186 @@
+/**
+ * Nextcloud Android client application
+ *
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero 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.ProviderMeta;
+import com.owncloud.android.lib.common.utils.Log_OC;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Provider for stored filesystem data.
+ */
+public class FilesystemDataProvider {
+
+    static private final String TAG = FilesystemDataProvider.class.getSimpleName();
+
+    private ContentResolver contentResolver;
+
+    public FilesystemDataProvider(ContentResolver contentResolver) {
+        if (contentResolver == null) {
+            throw new IllegalArgumentException("Cannot create an instance with a NULL contentResolver");
+        }
+        this.contentResolver = contentResolver;
+    }
+
+    public void updateFilesystemFileAsSentForUpload(String path, String syncedFolderId) {
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD, 1);
+
+        contentResolver.update(
+                ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM,
+                cv,
+                ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " = ? and " +
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " = ?",
+                new String[]{path, syncedFolderId}
+        );
+    }
+
+    public Set<String> getFilesForUpload(String localPath, String syncedFolderId) {
+        Set<String> localPathsToUpload = new HashSet<>();
+
+        String likeParam = localPath + "%";
+
+        Cursor cursor = contentResolver.query(
+                ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM,
+                null,
+                ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " LIKE ? and " +
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " = ? and " +
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD + " = ? and " +
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER + " = ?",
+                new String[]{likeParam, syncedFolderId, "0", "0"},
+                null);
+
+        if (cursor != null && cursor.moveToFirst()) {
+            do {
+                String value = cursor.getString(cursor.getColumnIndex(
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH));
+                if (value == null) {
+                    Log_OC.e(TAG, "Cannot get local path");
+                } else {
+                    localPathsToUpload.add(value);
+                }
+            } while (cursor.moveToNext());
+
+            cursor.close();
+        }
+
+        return localPathsToUpload;
+    }
+
+    public void storeOrUpdateFileValue(String localPath, long modifiedAt, boolean isFolder, SyncedFolder syncedFolder) {
+
+        FileSystemDataSet data = getFilesystemDataSet(localPath, syncedFolder);
+
+        int isFolderValue = 0;
+        if (isFolder) {
+            isFolderValue = 1;
+        }
+
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_FOUND_RECENTLY, System.currentTimeMillis());
+        cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_MODIFIED, modifiedAt);
+
+        if (data == null) {
+
+            cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH, localPath);
+            cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER, isFolderValue);
+            cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD, false);
+            cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID, syncedFolder.getId());
+
+            Uri result = contentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM, cv);
+
+            if (result == null) {
+                Log_OC.v(TAG, "Failed to insert filesystem data with local path: " + localPath);
+            }
+        } else {
+
+            if (data.getModifiedAt() != modifiedAt) {
+                cv.put(ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD, 0);
+            }
+
+
+            int result = contentResolver.update(
+                    ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM,
+                    cv,
+                    ProviderMeta.ProviderTableMeta._ID + "=?",
+                    new String[]{String.valueOf(data.getId())}
+            );
+
+            if (result == 0) {
+                Log_OC.v(TAG, "Failed to update filesystem data with local path: " + localPath);
+            }
+        }
+    }
+
+    private FileSystemDataSet getFilesystemDataSet(String localPathParam, SyncedFolder syncedFolder) {
+
+        Cursor cursor = contentResolver.query(
+                ProviderMeta.ProviderTableMeta.CONTENT_URI_FILESYSTEM,
+                null,
+                ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " = ? and " +
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " = ?",
+                new String[]{localPathParam, Long.toString(syncedFolder.getId())},
+                null
+        );
+
+        FileSystemDataSet dataSet = null;
+        if (cursor != null) {
+            if (cursor.moveToFirst()) {
+                int id = cursor.getInt(cursor.getColumnIndex(ProviderMeta.ProviderTableMeta._ID));
+                String localPath = cursor.getString(cursor.getColumnIndex(
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH));
+                long modifiedAt = cursor.getLong(cursor.getColumnIndex(
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_MODIFIED));
+                boolean isFolder = false;
+                if (cursor.getInt(cursor.getColumnIndex(
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER)) != 0) {
+                    isFolder = true;
+                }
+                long foundAt = cursor.getLong(cursor.getColumnIndex(ProviderMeta.
+                        ProviderTableMeta.FILESYSTEM_FILE_FOUND_RECENTLY));
+
+                boolean isSentForUpload = false;
+                if (cursor.getInt(cursor.getColumnIndex(
+                        ProviderMeta.ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD)) != 0) {
+                    isSentForUpload = true;
+                }
+
+                if (id == -1) {
+                    Log_OC.e(TAG, "Arbitrary value could not be created from cursor");
+                } else {
+                    dataSet = new FileSystemDataSet(id, localPath, modifiedAt, isFolder, isSentForUpload, foundAt,
+                            syncedFolder.getId());
+                }
+            }
+            cursor.close();
+        } else {
+            Log_OC.e(TAG, "DB error restoring arbitrary values.");
+        }
+
+        return dataSet;
+    }
+}

+ 6 - 3
src/main/java/com/owncloud/android/datamodel/MediaFolder.java

@@ -4,17 +4,17 @@
  * @author Andy Scherzinger
  * @author Andy Scherzinger
  * Copyright (C) 2016 Andy Scherzinger
  * Copyright (C) 2016 Andy Scherzinger
  * Copyright (C) 2016 Nextcloud
  * Copyright (C) 2016 Nextcloud
- * <p>
+ *
  * This program is free software; you can redistribute it and/or
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
  * License as published by the Free Software Foundation; either
  * License as published by the Free Software Foundation; either
  * version 3 of the License, or any later version.
  * version 3 of the License, or any later version.
- * <p>
+ *
  * This program is distributed in the hope that it will be useful,
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
  * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- * <p>
+ *
  * You should have received a copy of the GNU Affero General Public
  * You should have received a copy of the GNU Affero General Public
  * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  * License along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
  */
@@ -38,4 +38,7 @@ public class MediaFolder {
 
 
     /** total number of files in the media folder. */
     /** total number of files in the media folder. */
     public long numberOfFiles;
     public long numberOfFiles;
+
+    /** type of media folder. */
+    public MediaFolderType type;
 }
 }

+ 53 - 0
src/main/java/com/owncloud/android/datamodel/MediaFolderType.java

@@ -0,0 +1,53 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2017 Andy Scherzinger
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.datamodel;
+
+import android.util.SparseArray;
+
+/**
+ * Types of media folder.
+ */
+public enum MediaFolderType {
+    CUSTOM(0),
+    IMAGE(1),
+    VIDEO(2);
+
+    private Integer id;
+
+    private static SparseArray<MediaFolderType> reverseMap = new SparseArray<>(3);
+
+    static {
+        reverseMap.put(CUSTOM.getId(), CUSTOM);
+        reverseMap.put(IMAGE.getId(), IMAGE);
+        reverseMap.put(VIDEO.getId(), VIDEO);
+    }
+
+    MediaFolderType(Integer id) {
+        this.id = id;
+    }
+
+    public static MediaFolderType getById(Integer id) {
+        return reverseMap.get(id);
+    }
+
+    public Integer getId() {
+        return id;
+    }
+}

+ 126 - 30
src/main/java/com/owncloud/android/datamodel/MediaProvider.java

@@ -40,6 +40,8 @@ import java.io.File;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.List;
 
 
+import javax.annotation.Nullable;
+
 /**
 /**
  * Media queries to gain access to media lists for the device.
  * Media queries to gain access to media lists for the device.
  */
  */
@@ -47,12 +49,15 @@ public class MediaProvider {
     private static final String TAG = MediaProvider.class.getSimpleName();
     private static final String TAG = MediaProvider.class.getSimpleName();
 
 
     // fixed query parameters
     // fixed query parameters
-    private static final Uri MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+    private static final Uri IMAGES_MEDIA_URI = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
     private static final String[] FILE_PROJECTION = new String[]{MediaStore.MediaColumns.DATA};
     private static final String[] FILE_PROJECTION = new String[]{MediaStore.MediaColumns.DATA};
-    private static final String FILE_SELECTION = MediaStore.Images.Media.BUCKET_ID + "=";
-    private static final String[] FOLDER_PROJECTION = { "Distinct " + MediaStore.Images.Media.BUCKET_ID,
-            MediaStore.Images.Media.BUCKET_DISPLAY_NAME };
-    private static final String FOLDER_SORT_ORDER = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " ASC";
+    private static final String IMAGES_FILE_SELECTION = MediaStore.Images.Media.BUCKET_ID + "=";
+    private static final String[] IMAGES_FOLDER_PROJECTION = {"Distinct " + MediaStore.Images.Media.BUCKET_ID,
+            MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
+    private static final String IMAGES_FOLDER_SORT_ORDER = MediaStore.Images.Media.BUCKET_DISPLAY_NAME + " ASC";
+
+    private static final String[] VIDEOS_FOLDER_PROJECTION = {"Distinct " + MediaStore.Video.Media.BUCKET_ID,
+            MediaStore.Video.Media.BUCKET_DISPLAY_NAME};
 
 
     /**
     /**
      * Getting All Images Paths.
      * Getting All Images Paths.
@@ -61,11 +66,105 @@ public class MediaProvider {
      * @param itemLimit       the number of media items (usually images) to be returned per media folder.
      * @param itemLimit       the number of media items (usually images) to be returned per media folder.
      * @return list with media folders
      * @return list with media folders
      */
      */
-    public static List<MediaFolder> getMediaFolders(ContentResolver contentResolver, int itemLimit,
-                                                    final Activity activity) {
+    public static List<MediaFolder> getImageFolders(ContentResolver contentResolver, int itemLimit,
+                                                    @Nullable final Activity activity) {
         // check permissions
         // check permissions
-        if (!PermissionUtil.checkSelfPermission(activity.getApplicationContext(),
+        checkPermissions(activity);
+
+
+        // query media/image folders
+        Cursor cursorFolders;
+        if (activity != null && PermissionUtil.checkSelfPermission(activity.getApplicationContext(),
                 Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                 Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+            cursorFolders = contentResolver.query(
+                    IMAGES_MEDIA_URI,
+                    IMAGES_FOLDER_PROJECTION,
+                    null,
+                    null,
+                    IMAGES_FOLDER_SORT_ORDER
+            );
+        } else {
+            cursorFolders = contentResolver.query(
+                    IMAGES_MEDIA_URI,
+                    IMAGES_FOLDER_PROJECTION,
+                    null,
+                    null,
+                    IMAGES_FOLDER_SORT_ORDER
+            );
+        }
+        List<MediaFolder> mediaFolders = new ArrayList<>();
+        String dataPath = MainApp.getStoragePath() + File.separator + MainApp.getDataFolder();
+
+        if (cursorFolders != null) {
+            String folderName;
+            String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT " + itemLimit;
+            Cursor cursorImages;
+
+            while (cursorFolders.moveToNext()) {
+                String folderId = cursorFolders.getString(cursorFolders.getColumnIndex(MediaStore.Images.Media
+                        .BUCKET_ID));
+
+                MediaFolder mediaFolder = new MediaFolder();
+                folderName = cursorFolders.getString(cursorFolders.getColumnIndex(
+                        MediaStore.Images.Media.BUCKET_DISPLAY_NAME));
+                mediaFolder.type = MediaFolderType.IMAGE;
+                mediaFolder.folderName = folderName;
+                mediaFolder.filePaths = new ArrayList<>();
+
+                // query images
+                cursorImages = contentResolver.query(
+                        IMAGES_MEDIA_URI,
+                        FILE_PROJECTION,
+                        IMAGES_FILE_SELECTION + folderId,
+                        null,
+                        fileSortOrder
+                );
+                Log.d(TAG, "Reading images for " + mediaFolder.folderName);
+
+                if (cursorImages != null) {
+                    String filePath;
+
+                    while (cursorImages.moveToNext()) {
+                        filePath = cursorImages.getString(cursorImages.getColumnIndexOrThrow(
+                                MediaStore.MediaColumns.DATA));
+
+                        if (filePath != null) {
+                            mediaFolder.filePaths.add(filePath);
+                            mediaFolder.absolutePath = filePath.substring(0, filePath.lastIndexOf("/"));
+                        }
+                    }
+                    cursorImages.close();
+
+                    // only do further work if folder is not within the Nextcloud app itself
+                    if (!mediaFolder.absolutePath.startsWith(dataPath)) {
+
+                        // count images
+                        Cursor count = contentResolver.query(
+                                IMAGES_MEDIA_URI,
+                                FILE_PROJECTION,
+                                IMAGES_FILE_SELECTION + folderId,
+                                null,
+                                null);
+
+                        if (count != null) {
+                            mediaFolder.numberOfFiles = count.getCount();
+                            count.close();
+                        }
+
+                        mediaFolders.add(mediaFolder);
+                    }
+                }
+            }
+            cursorFolders.close();
+        }
+
+        return mediaFolders;
+    }
+
+    private static void checkPermissions(@Nullable Activity activity) {
+        if (activity != null &&
+                !PermissionUtil.checkSelfPermission(activity.getApplicationContext(),
+                        Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
             // Check if we should show an explanation
             // Check if we should show an explanation
             if (PermissionUtil.shouldShowRequestPermissionRationale(activity,
             if (PermissionUtil.shouldShowRequestPermissionRationale(activity,
                     Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                     Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
@@ -87,49 +186,46 @@ public class MediaProvider {
                 PermissionUtil.requestWriteExternalStoreagePermission(activity);
                 PermissionUtil.requestWriteExternalStoreagePermission(activity);
             }
             }
         }
         }
+    }
 
 
-        // query media/image folders
-        Cursor cursorFolders = null;
-        if (PermissionUtil.checkSelfPermission(activity.getApplicationContext(),
-                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
-            cursorFolders = contentResolver.query(MEDIA_URI, FOLDER_PROJECTION, null, null, FOLDER_SORT_ORDER);
-        }
+    public static List<MediaFolder> getVideoFolders(ContentResolver contentResolver, int itemLimit) {
+        Cursor cursorFolders = contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                VIDEOS_FOLDER_PROJECTION, null, null, null);
         List<MediaFolder> mediaFolders = new ArrayList<>();
         List<MediaFolder> mediaFolders = new ArrayList<>();
         String dataPath = MainApp.getStoragePath() + File.separator + MainApp.getDataFolder();
         String dataPath = MainApp.getStoragePath() + File.separator + MainApp.getDataFolder();
 
 
         if (cursorFolders != null) {
         if (cursorFolders != null) {
             String folderName;
             String folderName;
-            String fileSortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC LIMIT " + itemLimit;
+            String fileSortOrder = MediaStore.Video.Media.DATE_TAKEN + " DESC LIMIT " + itemLimit;
             Cursor cursorImages;
             Cursor cursorImages;
 
 
             while (cursorFolders.moveToNext()) {
             while (cursorFolders.moveToNext()) {
-                String folderId = cursorFolders.getString(cursorFolders.getColumnIndex(MediaStore.Images.Media
+                String folderId = cursorFolders.getString(cursorFolders.getColumnIndex(MediaStore.Video.Media
                         .BUCKET_ID));
                         .BUCKET_ID));
 
 
                 MediaFolder mediaFolder = new MediaFolder();
                 MediaFolder mediaFolder = new MediaFolder();
                 folderName = cursorFolders.getString(cursorFolders.getColumnIndex(
                 folderName = cursorFolders.getString(cursorFolders.getColumnIndex(
-                        MediaStore.Images.Media.BUCKET_DISPLAY_NAME));
+                        MediaStore.Video.Media.BUCKET_DISPLAY_NAME));
+                mediaFolder.type = MediaFolderType.VIDEO;
                 mediaFolder.folderName = folderName;
                 mediaFolder.folderName = folderName;
                 mediaFolder.filePaths = new ArrayList<>();
                 mediaFolder.filePaths = new ArrayList<>();
 
 
                 // query images
                 // query images
-                cursorImages = contentResolver.query(MEDIA_URI, FILE_PROJECTION, FILE_SELECTION + folderId, null,
+                cursorImages = contentResolver.query(
+                        MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                        FILE_PROJECTION,
+                        MediaStore.Video.Media.BUCKET_ID + "=" + folderId,
+                        null,
                         fileSortOrder);
                         fileSortOrder);
-                Log.d(TAG, "Reading images for " + mediaFolder.folderName);
+                Log.d(TAG, "Reading videos for " + mediaFolder.folderName);
 
 
                 if (cursorImages != null) {
                 if (cursorImages != null) {
                     String filePath;
                     String filePath;
-                    int failedImages = 0;
                     while (cursorImages.moveToNext()) {
                     while (cursorImages.moveToNext()) {
                         filePath = cursorImages.getString(cursorImages.getColumnIndexOrThrow(
                         filePath = cursorImages.getString(cursorImages.getColumnIndexOrThrow(
                                 MediaStore.MediaColumns.DATA));
                                 MediaStore.MediaColumns.DATA));
-
-                        if (filePath != null) {
-                            mediaFolder.filePaths.add(filePath);
-                            mediaFolder.absolutePath = filePath.substring(0, filePath.lastIndexOf("/"));
-                        } else {
-                            failedImages++;
-                        }
+                        mediaFolder.filePaths.add(filePath);
+                        mediaFolder.absolutePath = filePath.substring(0, filePath.lastIndexOf("/"));
                     }
                     }
                     cursorImages.close();
                     cursorImages.close();
 
 
@@ -138,14 +234,14 @@ public class MediaProvider {
 
 
                         // count images
                         // count images
                         Cursor count = contentResolver.query(
                         Cursor count = contentResolver.query(
-                                MEDIA_URI,
+                                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                                 FILE_PROJECTION,
                                 FILE_PROJECTION,
-                                FILE_SELECTION + folderId,
+                                MediaStore.Video.Media.BUCKET_ID + "=" + folderId,
                                 null,
                                 null,
                                 null);
                                 null);
 
 
                         if (count != null) {
                         if (count != null) {
-                            mediaFolder.numberOfFiles = count.getCount() - failedImages;
+                            mediaFolder.numberOfFiles = count.getCount();
                             count.close();
                             count.close();
                         }
                         }
 
 

+ 26 - 3
src/main/java/com/owncloud/android/datamodel/SyncedFolder.java

@@ -26,7 +26,7 @@ import java.io.Serializable;
 /**
 /**
  * Synced folder entity containing all information per synced folder.
  * Synced folder entity containing all information per synced folder.
  */
  */
-public class SyncedFolder implements Serializable {
+public class SyncedFolder implements Serializable, Cloneable {
     public static final long UNPERSISTED_ID = Long.MIN_VALUE;
     public static final long UNPERSISTED_ID = Long.MIN_VALUE;
     private static final long serialVersionUID = -793476118299906429L;
     private static final long serialVersionUID = -793476118299906429L;
     private long id = UNPERSISTED_ID;
     private long id = UNPERSISTED_ID;
@@ -38,6 +38,7 @@ public class SyncedFolder implements Serializable {
     private String account;
     private String account;
     private Integer uploadAction;
     private Integer uploadAction;
     private boolean enabled;
     private boolean enabled;
+    private MediaFolderType type;
 
 
     /**
     /**
      * constructor for already persisted entity.
      * constructor for already persisted entity.
@@ -51,9 +52,11 @@ public class SyncedFolder implements Serializable {
      * @param account         the account owning the synced folder
      * @param account         the account owning the synced folder
      * @param uploadAction    the action to be done after the upload
      * @param uploadAction    the action to be done after the upload
      * @param enabled         flag if synced folder config is active
      * @param enabled         flag if synced folder config is active
+     * @param type            the type of the folder
      */
      */
     public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
     public SyncedFolder(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
-                        Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) {
+                        Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
+                        MediaFolderType type) {
         this.id = id;
         this.id = id;
         this.localPath = localPath;
         this.localPath = localPath;
         this.remotePath = remotePath;
         this.remotePath = remotePath;
@@ -63,6 +66,7 @@ public class SyncedFolder implements Serializable {
         this.account = account;
         this.account = account;
         this.uploadAction = uploadAction;
         this.uploadAction = uploadAction;
         this.enabled = enabled;
         this.enabled = enabled;
+        this.type = type;
     }
     }
 
 
     /**
     /**
@@ -76,9 +80,11 @@ public class SyncedFolder implements Serializable {
      * @param account         the account owning the synced folder
      * @param account         the account owning the synced folder
      * @param uploadAction    the action to be done after the upload
      * @param uploadAction    the action to be done after the upload
      * @param enabled         flag if synced folder config is active
      * @param enabled         flag if synced folder config is active
+     * @param type            the type of the folder
      */
      */
     public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
     public SyncedFolder(String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
-                        Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled) {
+                        Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
+                        MediaFolderType type) {
         this.localPath = localPath;
         this.localPath = localPath;
         this.remotePath = remotePath;
         this.remotePath = remotePath;
         this.wifiOnly = wifiOnly;
         this.wifiOnly = wifiOnly;
@@ -87,6 +93,15 @@ public class SyncedFolder implements Serializable {
         this.account = account;
         this.account = account;
         this.uploadAction = uploadAction;
         this.uploadAction = uploadAction;
         this.enabled = enabled;
         this.enabled = enabled;
+        this.type = type;
+    }
+
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
     }
     }
 
 
     public long getId() {
     public long getId() {
@@ -160,4 +175,12 @@ public class SyncedFolder implements Serializable {
     public void setEnabled(boolean enabled) {
     public void setEnabled(boolean enabled) {
         this.enabled = enabled;
         this.enabled = enabled;
     }
     }
+
+    public MediaFolderType getType() {
+        return type;
+    }
+
+    public void setType(MediaFolderType type) {
+        this.type = type;
+    }
 }
 }

+ 6 - 5
src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java

@@ -47,11 +47,13 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
      * @param filePaths       the UI info for the file path
      * @param filePaths       the UI info for the file path
      * @param folderName      the UI info for the folder's name
      * @param folderName      the UI info for the folder's name
      * @param numberOfFiles   the UI info for number of files within the folder
      * @param numberOfFiles   the UI info for number of files within the folder
+     * @param type            the type of the folder
      */
      */
     public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
     public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
                                    Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
                                    Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
-                                   List<String> filePaths, String folderName, long numberOfFiles) {
-        super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled);
+                                   List<String> filePaths, String folderName, long numberOfFiles, MediaFolderType type)
+    {
+        super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type);
         this.filePaths = filePaths;
         this.filePaths = filePaths;
         this.folderName = folderName;
         this.folderName = folderName;
         this.numberOfFiles = numberOfFiles;
         this.numberOfFiles = numberOfFiles;
@@ -59,12 +61,11 @@ public class SyncedFolderDisplayItem extends SyncedFolder {
 
 
     public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
     public SyncedFolderDisplayItem(long id, String localPath, String remotePath, Boolean wifiOnly, Boolean chargingOnly,
                                    Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
                                    Boolean subfolderByDate, String account, Integer uploadAction, Boolean enabled,
-                                   String folderName) {
-        super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled);
+                                   String folderName, MediaFolderType type) {
+        super(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate, account, uploadAction, enabled, type);
         this.folderName = folderName;
         this.folderName = folderName;
     }
     }
 
 
-
     public List<String> getFilePaths() {
     public List<String> getFilePaths() {
         return filePaths;
         return filePaths;
     }
     }

+ 36 - 42
src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java

@@ -1,21 +1,22 @@
 /**
 /**
- *   Nextcloud Android client application
+ * Nextcloud Android client application
  *
  *
- *   Copyright (C) 2016 Andy Scherzinger
- *   Copyright (C) 2016 Nextcloud.
+ * @author Andy Scherzinger
+ * Copyright (C) 2016 Andy Scherzinger
+ * Copyright (C) 2016 Nextcloud.
  *
  *
- *   This program is free software; you can redistribute it and/or
- *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- *   License as published by the Free Software Foundation; either
- *   version 3 of the License, or any later version.
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
  *
  *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ * 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 AFFERO GENERAL PUBLIC LICENSE for more details.
  *
  *
- *   You should have received a copy of the GNU Affero General Public
- *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 package com.owncloud.android.datamodel;
 package com.owncloud.android.datamodel;
 
 
@@ -27,7 +28,6 @@ import android.database.Cursor;
 import android.net.Uri;
 import android.net.Uri;
 import android.support.annotation.NonNull;
 import android.support.annotation.NonNull;
 
 
-import com.owncloud.android.MainApp;
 import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.db.ProviderMeta;
 import com.owncloud.android.db.ProviderMeta;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -58,12 +58,12 @@ public class SyncedFolderProvider extends Observable {
     }
     }
 
 
     /**
     /**
-     * Stores an media folder sync object in database.
+     * Stores a synced folder object in database.
      *
      *
      * @param syncedFolder synced folder to store
      * @param syncedFolder synced folder to store
      * @return synced folder id, -1 if the insert process fails.
      * @return synced folder id, -1 if the insert process fails.
      */
      */
-    public long storeFolderSync(SyncedFolder syncedFolder) {
+    public long storeSyncedFolder(SyncedFolder syncedFolder) {
         Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled());
         Log_OC.v(TAG, "Inserting " + syncedFolder.getLocalPath() + " with enabled=" + syncedFolder.isEnabled());
 
 
         ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder);
         ContentValues cv = createContentValuesFromSyncedFolder(syncedFolder);
@@ -71,7 +71,6 @@ public class SyncedFolderProvider extends Observable {
         Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv);
         Uri result = mContentResolver.insert(ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS, cv);
 
 
         if (result != null) {
         if (result != null) {
-            notifyFolderSyncObservers(syncedFolder);
             return Long.parseLong(result.getPathSegments().get(1));
             return Long.parseLong(result.getPathSegments().get(1));
         } else {
         } else {
             Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db.");
             Log_OC.e(TAG, "Failed to insert item " + syncedFolder.getLocalPath() + " into folder sync db.");
@@ -118,12 +117,12 @@ public class SyncedFolderProvider extends Observable {
     /**
     /**
      * Update upload status of file uniquely referenced by id.
      * Update upload status of file uniquely referenced by id.
      *
      *
-     * @param id      folder sync id.
+     * @param id      synced folder id.
      * @param enabled new status.
      * @param enabled new status.
      * @return the number of rows updated.
      * @return the number of rows updated.
      */
      */
-    public int updateFolderSyncEnabled(long id, Boolean enabled) {
-        Log_OC.v(TAG, "Storing sync folder id" + id + " with enabled=" + enabled);
+    public int updateSyncedFolderEnabled(long id, Boolean enabled) {
+        Log_OC.v(TAG, "Storing synced folder id" + id + " with enabled=" + enabled);
 
 
         int result = 0;
         int result = 0;
         Cursor cursor = mContentResolver.query(
         Cursor cursor = mContentResolver.query(
@@ -187,7 +186,6 @@ public class SyncedFolderProvider extends Observable {
         }
         }
 
 
         return result;
         return result;
-
     }
     }
 
 
     /**
     /**
@@ -211,15 +209,12 @@ public class SyncedFolderProvider extends Observable {
      *
      *
      * @param id for the synced folder.
      * @param id for the synced folder.
      */
      */
-
     private int deleteSyncFolderWithId(long id) {
     private int deleteSyncFolderWithId(long id) {
-        int result = mContentResolver.delete(
+        return mContentResolver.delete(
                 ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
                 ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
                 ProviderMeta.ProviderTableMeta._ID + " = ?",
                 ProviderMeta.ProviderTableMeta._ID + " = ?",
                 new String[]{String.valueOf(id)}
                 new String[]{String.valueOf(id)}
         );
         );
-
-        return result;
     }
     }
 
 
 
 
@@ -274,6 +269,17 @@ public class SyncedFolderProvider extends Observable {
         return result;
         return result;
     }
     }
 
 
+    /**
+     * delete record of synchronized folder with the given id.
+     */
+    public int deleteSyncedFolder(long id) {
+        return mContentResolver.delete(
+                ProviderMeta.ProviderTableMeta.CONTENT_URI_SYNCED_FOLDERS,
+                ProviderMeta.ProviderTableMeta._ID + " = ?",
+                new String[]{String.valueOf(id)}
+        );
+    }
+
     /**
     /**
      * update given synced folder.
      * update given synced folder.
      *
      *
@@ -292,10 +298,6 @@ public class SyncedFolderProvider extends Observable {
                 new String[]{String.valueOf(syncedFolder.getId())}
                 new String[]{String.valueOf(syncedFolder.getId())}
         );
         );
 
 
-        if (result > 0) {
-            notifyFolderSyncObservers(syncedFolder);
-        }
-
         return result;
         return result;
     }
     }
 
 
@@ -325,9 +327,11 @@ public class SyncedFolderProvider extends Observable {
                     ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
                     ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION));
             Boolean enabled = cursor.getInt(cursor.getColumnIndex(
             Boolean enabled = cursor.getInt(cursor.getColumnIndex(
                     ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
                     ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ENABLED)) == 1;
+            MediaFolderType type = MediaFolderType.getById(cursor.getInt(cursor.getColumnIndex(
+                    ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE)));
 
 
             syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate,
             syncedFolder = new SyncedFolder(id, localPath, remotePath, wifiOnly, chargingOnly, subfolderByDate,
-                    accountName, uploadAction, enabled);
+                    accountName, uploadAction, enabled, type);
         }
         }
         return syncedFolder;
         return syncedFolder;
     }
     }
@@ -349,18 +353,8 @@ public class SyncedFolderProvider extends Observable {
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE, syncedFolder.getSubfolderByDate());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_ACCOUNT, syncedFolder.getAccount());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction());
         cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION, syncedFolder.getUploadAction());
-        return cv;
-    }
+        cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().getId());
 
 
-    /**
-     * Inform all observers about data change.
-     *
-     * @param syncedFolder changed, synchronized folder
-     */
-    private void notifyFolderSyncObservers(SyncedFolder syncedFolder) {
-        if (syncedFolder != null) {
-            MainApp.getSyncedFolderObserverService().restartObserver(syncedFolder);
-            Log_OC.d(TAG, "notifying folder sync data observers for changed/added: " + syncedFolder.getLocalPath());
-        }
+        return cv;
     }
     }
 }
 }

+ 41 - 6
src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java

@@ -30,6 +30,7 @@ import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Drawable;
+import android.media.MediaMetadataRetriever;
 import android.media.ThumbnailUtils;
 import android.media.ThumbnailUtils;
 import android.net.Uri;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.AsyncTask;
@@ -418,6 +419,7 @@ public class ThumbnailsCacheManager {
     }
     }
 
 
     public static class MediaThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {
     public static class MediaThumbnailGenerationTask extends AsyncTask<Object, Void, Bitmap> {
+        private enum Type {IMAGE, VIDEO}
         private final WeakReference<ImageView> mImageViewReference;
         private final WeakReference<ImageView> mImageViewReference;
         private File mFile;
         private File mFile;
         private String mImageKey = null;
         private String mImageKey = null;
@@ -439,7 +441,9 @@ public class ThumbnailsCacheManager {
                     }
                     }
 
 
                     if (MimeTypeUtil.isImage(mFile)) {
                     if (MimeTypeUtil.isImage(mFile)) {
-                        thumbnail = doFileInBackground(mFile);
+                        thumbnail = doFileInBackground(mFile, Type.IMAGE);
+                    } else if (MimeTypeUtil.isVideo(mFile)) {
+                        thumbnail = doFileInBackground(mFile, Type.VIDEO);
                     }
                     }
                 }
                 }
             } // the app should never break due to a problem with thumbnails
             } // the app should never break due to a problem with thumbnails
@@ -482,7 +486,7 @@ public class ThumbnailsCacheManager {
             }
             }
         }
         }
 
 
-        private Bitmap doFileInBackground(File file) {
+        private Bitmap doFileInBackground(File file, Type type) {
             final String imageKey;
             final String imageKey;
 
 
             if (mImageKey != null) {
             if (mImageKey != null) {
@@ -497,14 +501,45 @@ public class ThumbnailsCacheManager {
             // Not found in disk cache
             // Not found in disk cache
             if (thumbnail == null) {
             if (thumbnail == null) {
 
 
-                int px = getThumbnailDimension();
+                if (Type.IMAGE.equals(type)) {
+                    int px = getThumbnailDimension();
 
 
-                Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), px, px);
+                    Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getAbsolutePath(), px, px);
 
 
-                if (bitmap != null) {
-                    thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px);
+                    if (bitmap != null) {
+                        thumbnail = addThumbnailToCache(imageKey, bitmap, file.getPath(), px);
+                    }
+                } else if (Type.VIDEO.equals(type)) {
+                    MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+                    try {
+                        retriever.setDataSource(file.getAbsolutePath());
+                        thumbnail = retriever.getFrameAtTime(-1);
+                    } catch (Exception ex) {
+                        // can't create a bitmap
+                        Log_OC.w(TAG, "Failed to create bitmap from video " + file.getAbsolutePath());
+                    } finally {
+                        try {
+                            retriever.release();
+                        } catch (RuntimeException ex) {
+                            // Ignore failure at this point.
+                            Log_OC.w(TAG, "Failed release MediaMetadataRetriever for " + file.getAbsolutePath());
+                        }
+                    }
+
+                    if (thumbnail != null) {
+                        // Scale down bitmap if too large.
+                        int px = getThumbnailDimension();
+                        int width = thumbnail.getWidth();
+                        int height = thumbnail.getHeight();
+                        int max = Math.max(width, height);
+                        if (max > px) {
+                            thumbnail = BitmapUtils.scaleBitmap(thumbnail, px, width, height, max);
+                            thumbnail = addThumbnailToCache(imageKey, thumbnail, file.getPath(), px);
+                        }
+                    }
                 }
                 }
             }
             }
+
             return thumbnail;
             return thumbnail;
         }
         }
     }
     }

+ 127 - 143
src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java

@@ -1,22 +1,22 @@
 /**
 /**
- *  ownCloud Android client application
+ * ownCloud Android client application
  *
  *
- *  @author LukeOwncloud
- *  @author David A. Velasco
- *  @author masensio
- *  Copyright (C) 2016 ownCloud Inc.
+ * @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 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.
+ * 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/>.
+ * 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;
 package com.owncloud.android.datamodel;
 
 
@@ -26,12 +26,8 @@ import android.content.ContentValues;
 import android.content.Context;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.Cursor;
 import android.net.Uri;
 import android.net.Uri;
-import android.os.Build;
-import android.support.annotation.RequiresApi;
 
 
-import com.evernote.android.job.JobManager;
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.support.PersistableBundleCompat;
+import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.db.UploadResult;
 import com.owncloud.android.db.UploadResult;
@@ -39,14 +35,9 @@ import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.operations.UploadFileOperation;
-import com.owncloud.android.services.AutoUploadJob;
 
 
-import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Calendar;
-import java.util.Collections;
-import java.util.List;
 import java.util.Observable;
 import java.util.Observable;
-import java.util.Set;
 
 
 /**
 /**
  * Database helper for storing list of files to be uploaded, including status
  * Database helper for storing list of files to be uploaded, including status
@@ -130,6 +121,8 @@ public class UploadsStorageManager extends Observable {
         cv.put(ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER, ocUpload.isCreateRemoteFolder() ? 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_LAST_RESULT, ocUpload.getLastResult().getValue());
         cv.put(ProviderTableMeta.UPLOADS_CREATED_BY, ocUpload.getCreadtedBy());
         cv.put(ProviderTableMeta.UPLOADS_CREATED_BY, ocUpload.getCreadtedBy());
+        cv.put(ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY, ocUpload.isWhileChargingOnly() ? 1 : 0);
+        cv.put(ProviderTableMeta.UPLOADS_IS_WIFI_ONLY, ocUpload.isUseWifiOnly() ? 1 : 0);
 
 
         Uri result = getDB().insert(ProviderTableMeta.CONTENT_URI_UPLOADS, cv);
         Uri result = getDB().insert(ProviderTableMeta.CONTENT_URI_UPLOADS, cv);
 
 
@@ -163,9 +156,9 @@ public class UploadsStorageManager extends Observable {
         cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, ocUpload.getUploadEndTimestamp());
         cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, ocUpload.getUploadEndTimestamp());
 
 
         int result = getDB().update(ProviderTableMeta.CONTENT_URI_UPLOADS,
         int result = getDB().update(ProviderTableMeta.CONTENT_URI_UPLOADS,
-            cv,
-            ProviderTableMeta._ID + "=?",
-            new String[]{String.valueOf(ocUpload.getUploadId())}
+                cv,
+                ProviderTableMeta._ID + "=?",
+                new String[]{String.valueOf(ocUpload.getUploadId())}
         );
         );
 
 
         Log_OC.d(TAG, "updateUpload returns with: " + result + " for file: " + ocUpload.getLocalPath());
         Log_OC.d(TAG, "updateUpload returns with: " + result + " for file: " + ocUpload.getLocalPath());
@@ -188,15 +181,15 @@ public class UploadsStorageManager extends Observable {
 
 
             String path = c.getString(c.getColumnIndex(ProviderTableMeta.UPLOADS_LOCAL_PATH));
             String path = c.getString(c.getColumnIndex(ProviderTableMeta.UPLOADS_LOCAL_PATH));
             Log_OC.v(
             Log_OC.v(
-                TAG,
-                "Updating " + path + " with status:" + status + " and result:"
-                    + (result == null ? "null" : result.toString()) + " (old:"
-                    + upload.toFormattedString() + ")");
+                    TAG,
+                    "Updating " + path + " with status:" + status + " and result:"
+                            + (result == null ? "null" : result.toString()) + " (old:"
+                            + upload.toFormattedString() + ")");
 
 
             upload.setUploadStatus(status);
             upload.setUploadStatus(status);
             upload.setLastResult(result);
             upload.setLastResult(result);
             upload.setRemotePath(remotePath);
             upload.setRemotePath(remotePath);
-            if(localPath != null) {
+            if (localPath != null) {
                 upload.setLocalPath(localPath);
                 upload.setLocalPath(localPath);
             }
             }
             if (status == UploadStatus.UPLOAD_SUCCEEDED) {
             if (status == UploadStatus.UPLOAD_SUCCEEDED) {
@@ -221,7 +214,7 @@ public class UploadsStorageManager extends Observable {
      * @param localPath path of the file to upload in the device storage
      * @param localPath path of the file to upload in the device storage
      * @return 1 if file status was updated, else 0.
      * @return 1 if file status was updated, else 0.
      */
      */
-    public int updateUploadStatus(long id, UploadStatus status, UploadResult result, String remotePath,
+    private int updateUploadStatus(long id, UploadStatus status, UploadResult result, String remotePath,
                                   String localPath) {
                                   String localPath) {
         //Log_OC.v(TAG, "Updating "+filepath+" with uploadStatus="+status +" and result="+result);
         //Log_OC.v(TAG, "Updating "+filepath+" with uploadStatus="+status +" and result="+result);
 
 
@@ -236,7 +229,7 @@ public class UploadsStorageManager extends Observable {
 
 
         if (c.getCount() != 1) {
         if (c.getCount() != 1) {
             Log_OC.e(TAG, c.getCount() + " items for id=" + id
             Log_OC.e(TAG, c.getCount() + " items for id=" + id
-                + " available in UploadDb. Expected 1. Failed to update upload db.");
+                    + " available in UploadDb. Expected 1. Failed to update upload db.");
         } else {
         } else {
             returnValue = updateUploadInternal(c, status, result, remotePath, localPath);
             returnValue = updateUploadInternal(c, status, result, remotePath, localPath);
         }
         }
@@ -266,7 +259,7 @@ public class UploadsStorageManager extends Observable {
     public int removeUpload(OCUpload upload) {
     public int removeUpload(OCUpload upload) {
         int result = getDB().delete(
         int result = getDB().delete(
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta._ID + "=?" ,
+                ProviderTableMeta._ID + "=?",
                 new String[]{Long.toString(upload.getUploadId())}
                 new String[]{Long.toString(upload.getUploadId())}
         );
         );
         Log_OC.d(TAG, "delete returns " + result + " for upload " + upload);
         Log_OC.d(TAG, "delete returns " + result + " for upload " + upload);
@@ -287,7 +280,7 @@ public class UploadsStorageManager extends Observable {
     public int removeUpload(String accountName, String remotePath) {
     public int removeUpload(String accountName, String remotePath) {
         int result = getDB().delete(
         int result = getDB().delete(
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=? AND " + ProviderTableMeta.UPLOADS_REMOTE_PATH + "=?" ,
+                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=? AND " + ProviderTableMeta.UPLOADS_REMOTE_PATH + "=?",
                 new String[]{accountName, remotePath}
                 new String[]{accountName, remotePath}
         );
         );
         Log_OC.d(TAG, "delete returns " + result + " for file " + remotePath + " in " + accountName);
         Log_OC.d(TAG, "delete returns " + result + " for file " + remotePath + " in " + accountName);
@@ -374,68 +367,21 @@ public class UploadsStorageManager extends Observable {
         return upload;
         return upload;
     }
     }
 
 
-    /**
-     * Get all uploads which are currently being uploaded or waiting in the queue to be uploaded.
-     */
-    public OCUpload[] getCurrentAndPendingUploads() {
+    public OCUpload[] getCurrentAndPendingUploadsForCurrentAccount() {
+        Account account = AccountUtils.getCurrentOwnCloudAccount(mContext);
 
 
         OCUpload[] uploads = getUploads(
         OCUpload[] uploads = getUploads(
-            ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR " +
-            ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_WIFI.getValue() + " OR " +
-            ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_CHARGING.getValue(),
-            null
+                ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value +
+                        " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+                        " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.LOCK_FAILED.getValue() +
+                        " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                        "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+                        " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
+                new String[]{account.name}
         );
         );
 
 
-        // add pending Jobs
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
-            return uploads;
-        } else {
-            List<OCUpload> result = getPendingJobs();
-            Collections.addAll(result, uploads);
-            return result.toArray(uploads);
-        }
-    }
-
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-    private List<OCUpload> getPendingJobs() {
-        Set<JobRequest> jobRequests = JobManager.create(mContext).getAllJobRequestsForTag(AutoUploadJob.TAG);
-
-        ArrayList<OCUpload> list = new ArrayList<>();
-
-        for (JobRequest ji : jobRequests) {
-            PersistableBundleCompat extras = ji.getExtras();
-                OCUpload upload = new OCUpload(extras.getString("filePath", ""),
-                        extras.getString("remotePath", ""),
-                        extras.getString("account", ""));
-
-                list.add(upload);
-        }
-
-        return list;
-    }
+        return uploads;
 
 
-    public void cancelPendingAutoUploadJobsForAccount(Account account) {
-        JobManager jobManager = JobManager.create(mContext);
-        for (JobRequest ji: jobManager.getAllJobRequestsForTag(AutoUploadJob.TAG)) {
-            if (ji.getExtras().getString(AutoUploadJob.ACCOUNT, "").equalsIgnoreCase(account.name)) {
-                jobManager.cancel(ji.getJobId());
-            }
-        }
-    }
-
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-    public void cancelPendingJob(String accountName, String remotePath){
-        JobManager jobManager = JobManager.create(mContext);
-        Set<JobRequest> jobRequests = jobManager.getAllJobRequests();
-
-        for (JobRequest ji : jobRequests) {
-            PersistableBundleCompat extras = ji.getExtras();
-            if (remotePath.equalsIgnoreCase(extras.getString("remotePath", "")) &&
-                    accountName.equalsIgnoreCase(extras.getString("account", ""))) {
-                jobManager.cancel(ji.getJobId());
-                break;
-            }
-        }
     }
     }
 
 
     /**
     /**
@@ -447,6 +393,13 @@ public class UploadsStorageManager extends Observable {
                 ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value, null);
                 ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value, null);
     }
     }
 
 
+    public OCUpload[] getFinishedUploadsForCurrentAccount() {
+        Account account = AccountUtils.getCurrentOwnCloudAccount(mContext);
+
+        return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
+                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{account.name});
+    }
+
     /**
     /**
      * Get all uploads which where successfully completed.
      * Get all uploads which where successfully completed.
      */
      */
@@ -455,16 +408,33 @@ public class UploadsStorageManager extends Observable {
         return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value, null);
         return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value, null);
     }
     }
 
 
+    public OCUpload[] getFailedButNotDelayedUploadsForCurrentAccount() {
+        Account account = AccountUtils.getCurrentOwnCloudAccount(mContext);
+
+        return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+                        AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
+                new String[]{account.name}
+        );
+    }
+
     /**
     /**
-     * Get all failed uploads, except for those that were not performed due to lack of Wifi connection
-     * @return      Array of failed uploads, except for those that were not performed due to lack of Wifi connection.
+     * Get all failed uploads, except for those that were not performed due to lack of Wifi connection.
+     *
+     * @return Array of failed uploads, except for those that were not performed due to lack of Wifi connection.
      */
      */
     public OCUpload[] getFailedButNotDelayedUploads() {
     public OCUpload[] getFailedButNotDelayedUploads() {
 
 
         return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + AND +
         return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + AND +
-                ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND +
-                ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(),
-            null
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() + AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(),
+                null
         );
         );
     }
     }
 
 
@@ -473,13 +443,22 @@ public class UploadsStorageManager extends Observable {
     }
     }
 
 
     public long clearFailedButNotDelayedUploads() {
     public long clearFailedButNotDelayedUploads() {
+        Account account = AccountUtils.getCurrentOwnCloudAccount(mContext);
+
         long result = getDB().delete(
         long result = getDB().delete(
-            ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value + AND +
-                ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND +
-                ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(),
-            null
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
+                new String[]{account.name}
         );
         );
+
         Log_OC.d(TAG, "delete all failed uploads but those delayed for Wifi");
         Log_OC.d(TAG, "delete all failed uploads but those delayed for Wifi");
         if (result > 0) {
         if (result > 0) {
             notifyObserversNow();
             notifyObserversNow();
@@ -488,11 +467,14 @@ public class UploadsStorageManager extends Observable {
     }
     }
 
 
     public long clearSuccessfulUploads() {
     public long clearSuccessfulUploads() {
+        Account account = AccountUtils.getCurrentOwnCloudAccount(mContext);
 
 
         long result = getDB().delete(
         long result = getDB().delete(
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta.UPLOADS_STATUS + "=="+ UploadStatus.UPLOAD_SUCCEEDED.value, null
+                ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value  + AND +
+                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{account.name}
         );
         );
+
         Log_OC.d(TAG, "delete all successful uploads");
         Log_OC.d(TAG, "delete all successful uploads");
         if (result > 0) {
         if (result > 0) {
             notifyObserversNow();
             notifyObserversNow();
@@ -501,21 +483,31 @@ public class UploadsStorageManager extends Observable {
     }
     }
 
 
     public long clearAllFinishedButNotDelayedUploads() {
     public long clearAllFinishedButNotDelayedUploads() {
+        Account account = AccountUtils.getCurrentOwnCloudAccount(mContext);
 
 
-        String[] whereArgs = new String[2];
+        String[] whereArgs = new String[3];
         whereArgs[0] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value);
         whereArgs[0] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value);
         whereArgs[1] = String.valueOf(UploadStatus.UPLOAD_FAILED.value);
         whereArgs[1] = String.valueOf(UploadStatus.UPLOAD_FAILED.value);
+        whereArgs[2] = account.name;
         long result = getDB().delete(
         long result = getDB().delete(
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
                 ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta.UPLOADS_STATUS + "=? OR " + ProviderTableMeta.UPLOADS_STATUS + "=? AND " +
-                ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() + AND +
-                ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue(),
+                ProviderTableMeta.UPLOADS_STATUS + "=? OR " + ProviderTableMeta.UPLOADS_STATUS + "=?" +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.LOCK_FAILED.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_LAST_RESULT + "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+                        AND +
+                        ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
                 whereArgs
                 whereArgs
         );
         );
+
         Log_OC.d(TAG, "delete all finished uploads");
         Log_OC.d(TAG, "delete all finished uploads");
         if (result > 0) {
         if (result > 0) {
             notifyObserversNow();
             notifyObserversNow();
         }
         }
+
         return result;
         return result;
     }
     }
 
 
@@ -528,28 +520,28 @@ public class UploadsStorageManager extends Observable {
 
 
         if (uploadResult.isCancelled()) {
         if (uploadResult.isCancelled()) {
             removeUpload(
             removeUpload(
-                upload.getAccount().name,
-                upload.getRemotePath()
+                    upload.getAccount().name,
+                    upload.getRemotePath()
             );
             );
         } else {
         } else {
             String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
             String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
-                ? upload.getStoragePath() : null;
+                    ? upload.getStoragePath() : null;
 
 
             if (uploadResult.isSuccess()) {
             if (uploadResult.isSuccess()) {
                 updateUploadStatus(
                 updateUploadStatus(
-                    upload.getOCUploadId(),
-                    UploadStatus.UPLOAD_SUCCEEDED,
-                    UploadResult.UPLOADED,
-                    upload.getRemotePath(),
-                    localPath
+                        upload.getOCUploadId(),
+                        UploadStatus.UPLOAD_SUCCEEDED,
+                        UploadResult.UPLOADED,
+                        upload.getRemotePath(),
+                        localPath
                 );
                 );
             } else {
             } else {
                 updateUploadStatus(
                 updateUploadStatus(
-                    upload.getOCUploadId(),
-                    UploadStatus.UPLOAD_FAILED,
-                    UploadResult.fromOperationResult(uploadResult),
-                    upload.getRemotePath(),
-                    localPath
+                        upload.getOCUploadId(),
+                        UploadStatus.UPLOAD_FAILED,
+                        UploadResult.fromOperationResult(uploadResult),
+                        upload.getRemotePath(),
+                        localPath
                 );
                 );
             }
             }
         }
         }
@@ -560,14 +552,14 @@ public class UploadsStorageManager extends Observable {
      */
      */
     public void updateDatabaseUploadStart(UploadFileOperation upload) {
     public void updateDatabaseUploadStart(UploadFileOperation upload) {
         String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
         String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
-            ? upload.getStoragePath() : null;
+                ? upload.getStoragePath() : null;
 
 
         updateUploadStatus(
         updateUploadStatus(
-            upload.getOCUploadId(),
-            UploadStatus.UPLOAD_IN_PROGRESS,
-            UploadResult.UNKNOWN,
-            upload.getRemotePath(),
-            localPath
+                upload.getOCUploadId(),
+                UploadStatus.UPLOAD_IN_PROGRESS,
+                UploadResult.UNKNOWN,
+                upload.getRemotePath(),
+                localPath
         );
         );
     }
     }
 
 
@@ -576,7 +568,7 @@ public class UploadsStorageManager extends Observable {
      * Changes the status of any in progress upload from UploadStatus.UPLOAD_IN_PROGRESS
      * Changes the status of any in progress upload from UploadStatus.UPLOAD_IN_PROGRESS
      * to UploadStatus.UPLOAD_FAILED
      * to UploadStatus.UPLOAD_FAILED
      *
      *
-     * @return      Number of uploads which status was changed.
+     * @return Number of uploads which status was changed.
      */
      */
     public int failInProgressUploads(UploadResult fail) {
     public int failInProgressUploads(UploadResult fail) {
         Log_OC.v(TAG, "Updating state of any killed upload");
         Log_OC.v(TAG, "Updating state of any killed upload");
@@ -584,16 +576,16 @@ public class UploadsStorageManager extends Observable {
         ContentValues cv = new ContentValues();
         ContentValues cv = new ContentValues();
         cv.put(ProviderTableMeta.UPLOADS_STATUS, UploadStatus.UPLOAD_FAILED.getValue());
         cv.put(ProviderTableMeta.UPLOADS_STATUS, UploadStatus.UPLOAD_FAILED.getValue());
         cv.put(
         cv.put(
-            ProviderTableMeta.UPLOADS_LAST_RESULT,
-            fail != null ? fail.getValue() : UploadResult.UNKNOWN.getValue()
+                ProviderTableMeta.UPLOADS_LAST_RESULT,
+                fail != null ? fail.getValue() : UploadResult.UNKNOWN.getValue()
         );
         );
         cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, Calendar.getInstance().getTimeInMillis());
         cv.put(ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP, Calendar.getInstance().getTimeInMillis());
 
 
         int result = getDB().update(
         int result = getDB().update(
-            ProviderTableMeta.CONTENT_URI_UPLOADS,
-            cv,
-            ProviderTableMeta.UPLOADS_STATUS + "=?",
-            new String[]{String.valueOf(UploadStatus.UPLOAD_IN_PROGRESS.getValue())}
+                ProviderTableMeta.CONTENT_URI_UPLOADS,
+                cv,
+                ProviderTableMeta.UPLOADS_STATUS + "=?",
+                new String[]{String.valueOf(UploadStatus.UPLOAD_IN_PROGRESS.getValue())}
         );
         );
 
 
         if (result == 0) {
         if (result == 0) {
@@ -602,15 +594,7 @@ public class UploadsStorageManager extends Observable {
             Log_OC.w(TAG, Integer.toString(result) + " uploads where abruptly interrupted");
             Log_OC.w(TAG, Integer.toString(result) + " uploads where abruptly interrupted");
             notifyObserversNow();
             notifyObserversNow();
         }
         }
-        return result;
-    }
 
 
-    public int removeAccountUploads(Account account) {
-        Log_OC.v(TAG, "Delete all uploads for account " + account.name);
-        return getDB().delete(
-                ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=?",
-                new String[]{account.name});
+        return result;
     }
     }
-
 }
 }

+ 45 - 10
src/main/java/com/owncloud/android/db/OCUpload.java

@@ -40,7 +40,6 @@ import java.io.File;
 /**
 /**
  * Stores all information in order to start upload operations. PersistentUploadObject can
  * Stores all information in order to start upload operations. PersistentUploadObject can
  * be stored persistently by {@link UploadsStorageManager}.
  * be stored persistently by {@link UploadsStorageManager}.
- *
  */
  */
 public class OCUpload implements Parcelable {
 public class OCUpload implements Parcelable {
 
 
@@ -49,7 +48,7 @@ public class OCUpload implements Parcelable {
     private long mId;
     private long mId;
 
 
     /**
     /**
-     * Absolute path in the local file system to the file to be uploaded
+     * Absolute path in the local file system to the file to be uploaded.
      */
      */
     private String mLocalPath;
     private String mLocalPath;
 
 
@@ -64,7 +63,7 @@ public class OCUpload implements Parcelable {
     private String mAccountName;
     private String mAccountName;
 
 
     /**
     /**
-     * File size
+     * File size.
      */
      */
     private long mFileSize;
     private long mFileSize;
 
 
@@ -77,14 +76,17 @@ public class OCUpload implements Parcelable {
      * Overwrite destination file?
      * Overwrite destination file?
      */
      */
     private boolean mForceOverwrite;
     private boolean mForceOverwrite;
+
     /**
     /**
      * Create destination folder?
      * Create destination folder?
      */
      */
     private boolean mIsCreateRemoteFolder;
     private boolean mIsCreateRemoteFolder;
+
     /**
     /**
      * Status of upload (later, in_progress, ...).
      * Status of upload (later, in_progress, ...).
      */
      */
     private UploadStatus mUploadStatus;
     private UploadStatus mUploadStatus;
+
     /**
     /**
      * Result from last upload operation. Can be null.
      * Result from last upload operation. Can be null.
      */
      */
@@ -95,14 +97,23 @@ public class OCUpload implements Parcelable {
      */
      */
     private int mCreatedBy;
     private int mCreatedBy;
 
 
-    /*
+    /**
      * When the upload ended
      * When the upload ended
      */
      */
     private long mUploadEndTimeStamp;
     private long mUploadEndTimeStamp;
 
 
+    /**
+     * Upload only via wifi?
+     */
+    private boolean mIsUseWifiOnly;
 
 
     /**
     /**
-     * Main constructor
+     * Upload only if phone being charged?
+     */
+    private boolean mIsWhileChargingOnly;
+
+     /**
+     * Main constructor.
      *
      *
      * @param localPath         Absolute path in the local file system to the file to be uploaded.
      * @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 remotePath        Absolute path in the remote account to set to the uploaded file.
@@ -124,9 +135,8 @@ public class OCUpload implements Parcelable {
         mAccountName = accountName;
         mAccountName = accountName;
     }
     }
 
 
-
     /**
     /**
-     * Convenience constructor to reupload already existing {@link OCFile}s
+     * Convenience constructor to reupload already existing {@link OCFile}s.
      *
      *
      * @param  ocFile           {@link OCFile} instance to update in the remote server.
      * @param  ocFile           {@link OCFile} instance to update in the remote server.
      * @param  account          ownCloud {@link Account} where ocFile is contained.
      * @param  account          ownCloud {@link Account} where ocFile is contained.
@@ -135,7 +145,6 @@ public class OCUpload implements Parcelable {
         this(ocFile.getStoragePath(), ocFile.getRemotePath(), account.name);
         this(ocFile.getStoragePath(), ocFile.getRemotePath(), account.name);
     }
     }
 
 
-
     /**
     /**
      * Reset all the fields to default values.
      * Reset all the fields to default values.
      */
      */
@@ -151,6 +160,8 @@ public class OCUpload implements Parcelable {
         mUploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
         mUploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
         mLastResult = UploadResult.UNKNOWN;
         mLastResult = UploadResult.UNKNOWN;
         mCreatedBy = UploadFileOperation.CREATED_BY_USER;
         mCreatedBy = UploadFileOperation.CREATED_BY_USER;
+        mIsUseWifiOnly = true;
+        mIsWhileChargingOnly = false;
     }
     }
 
 
     // Getters & Setters
     // Getters & Setters
@@ -230,7 +241,6 @@ public class OCUpload implements Parcelable {
         mFileSize = fileSize;
         mFileSize = fileSize;
     }
     }
 
 
-
     /**
     /**
      * @return the mimeType
      * @return the mimeType
      */
      */
@@ -340,6 +350,28 @@ public class OCUpload implements Parcelable {
         }
         }
     };
     };
 
 
+    /**
+     * @return the isUseWifiOnly
+     */
+    public boolean isUseWifiOnly() {
+        return mIsUseWifiOnly;
+    }
+
+    /**
+     * @param isUseWifiOnly the isUseWifiOnly to set
+     */
+    public void setUseWifiOnly(boolean isUseWifiOnly) {
+        this.mIsUseWifiOnly = isUseWifiOnly;
+    }
+
+    public void setWhileChargingOnly(boolean isWhileChargingOnly) {
+        this.mIsWhileChargingOnly = isWhileChargingOnly;
+    }
+
+    public boolean isWhileChargingOnly() {
+        return mIsWhileChargingOnly;
+    }
+
     /**
     /**
      * Reconstruct from parcel
      * Reconstruct from parcel
      *
      *
@@ -369,9 +401,10 @@ public class OCUpload implements Parcelable {
             mLastResult = UploadResult.UNKNOWN;
             mLastResult = UploadResult.UNKNOWN;
         }
         }
         mCreatedBy = source.readInt();
         mCreatedBy = source.readInt();
+        mIsUseWifiOnly = (source.readInt() == 1);
+        mIsWhileChargingOnly = (source.readInt() == 1);
     }
     }
 
 
-
     @Override
     @Override
     public int describeContents() {
     public int describeContents() {
         return this.hashCode();
         return this.hashCode();
@@ -390,6 +423,8 @@ public class OCUpload implements Parcelable {
         dest.writeLong(mUploadEndTimeStamp);
         dest.writeLong(mUploadEndTimeStamp);
         dest.writeString(((mLastResult == null) ? "" : mLastResult.name()));
         dest.writeString(((mLastResult == null) ? "" : mLastResult.name()));
         dest.writeInt(mCreatedBy);
         dest.writeInt(mCreatedBy);
+        dest.writeInt(mIsUseWifiOnly ? 1 : 0);
+        dest.writeInt(mIsWhileChargingOnly ? 1 : 0);
     }
     }
 
 
     enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR}
     enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR}

+ 30 - 8
src/main/java/com/owncloud/android/db/PreferenceManager.java

@@ -48,6 +48,8 @@ public abstract class PreferenceManager {
     private static final String PREF__LEGACY_CLEAN = "legacyClean";
     private static final String PREF__LEGACY_CLEAN = "legacyClean";
     private static final String PREF__AUTO_UPLOAD_UPDATE_PATH = "autoUploadPathUpdate";
     private static final String PREF__AUTO_UPLOAD_UPDATE_PATH = "autoUploadPathUpdate";
     private static final String PREF__PUSH_TOKEN = "pushToken";
     private static final String PREF__PUSH_TOKEN = "pushToken";
+    private static final String PREF__AUTO_UPLOAD_SPLIT_OUT = "autoUploadEntriesSplitOut";
+    private static final String PREF__AUTO_UPLOAD_INIT = "autoUploadInit";
 
 
     public static void setPushToken(Context context, String pushToken) {
     public static void setPushToken(Context context, String pushToken) {
         saveStringPreferenceNow(context, PREF__PUSH_TOKEN, pushToken);
         saveStringPreferenceNow(context, PREF__PUSH_TOKEN, pushToken);
@@ -198,6 +200,10 @@ public abstract class PreferenceManager {
         saveBooleanPreference(context, AUTO_PREF__SORT_ASCENDING, ascending);
         saveBooleanPreference(context, AUTO_PREF__SORT_ASCENDING, ascending);
     }
     }
 
 
+    public static boolean getAutoUploadInit(Context context) {
+        return getDefaultSharedPreferences(context).getBoolean(PREF__AUTO_UPLOAD_INIT, false);
+    }
+
     /**
     /**
      * Gets the legacy cleaning flag last set.
      * Gets the legacy cleaning flag last set.
      *
      *
@@ -218,6 +224,15 @@ public abstract class PreferenceManager {
         return getDefaultSharedPreferences(context).getBoolean(PREF__AUTO_UPLOAD_UPDATE_PATH, false);
         return getDefaultSharedPreferences(context).getBoolean(PREF__AUTO_UPLOAD_UPDATE_PATH, false);
     }
     }
 
 
+    /**
+     * Gets the auto upload split out flag last set.
+     *
+     * @param context Caller {@link Context}, used to access to shared preferences manager.
+     * @return ascending order     the legacy cleaning flag, default is false
+     */
+    public static boolean getAutoUploadSplitEntries(Context context) {
+        return getDefaultSharedPreferences(context).getBoolean(PREF__AUTO_UPLOAD_SPLIT_OUT, false);
+    }
 
 
     /**
     /**
      * Saves the legacy cleaning flag which the user has set last.
      * Saves the legacy cleaning flag which the user has set last.
@@ -229,6 +244,10 @@ public abstract class PreferenceManager {
         saveBooleanPreference(context, PREF__LEGACY_CLEAN, legacyClean);
         saveBooleanPreference(context, PREF__LEGACY_CLEAN, legacyClean);
     }
     }
 
 
+    public static void setAutoUploadInit(Context context, boolean autoUploadInit) {
+        saveBooleanPreference(context, PREF__AUTO_UPLOAD_INIT, autoUploadInit);
+    }
+
     /**
     /**
      * Saves the legacy cleaning flag which the user has set last.
      * Saves the legacy cleaning flag which the user has set last.
      *
      *
@@ -239,6 +258,15 @@ public abstract class PreferenceManager {
         saveBooleanPreference(context, PREF__AUTO_UPLOAD_UPDATE_PATH, pathUpdate);
         saveBooleanPreference(context, PREF__AUTO_UPLOAD_UPDATE_PATH, pathUpdate);
     }
     }
 
 
+    /**
+     * Saves the flag for split entries magic
+     *
+     * @param context    Caller {@link Context}, used to access to shared preferences manager.
+     * @param splitOut flag if it is a auto upload path update
+     */
+    public static void setAutoUploadSplitEntries(Context context, boolean splitOut) {
+        saveBooleanPreference(context, PREF__AUTO_UPLOAD_SPLIT_OUT, splitOut);
+    }
 
 
     /**
     /**
      * Gets the uploader behavior which the user has set last.
      * Gets the uploader behavior which the user has set last.
@@ -280,7 +308,7 @@ public abstract class PreferenceManager {
         saveFloatPreference(context, AUTO_PREF__GRID_COLUMNS, gridColumns);
         saveFloatPreference(context, AUTO_PREF__GRID_COLUMNS, gridColumns);
     }
     }
 
 
-    public static void saveBooleanPreference(Context context, String key, boolean value) {
+    private static void saveBooleanPreference(Context context, String key, boolean value) {
         SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit();
         SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit();
         appPreferences.putBoolean(key, value).apply();
         appPreferences.putBoolean(key, value).apply();
     }
     }
@@ -301,17 +329,11 @@ public abstract class PreferenceManager {
         appPreferences.putInt(key, value).apply();
         appPreferences.putInt(key, value).apply();
     }
     }
 
 
-    public static void saveFloatPreference(Context context, String key, float value) {
+    private static void saveFloatPreference(Context context, String key, float value) {
         SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit();
         SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit();
         appPreferences.putFloat(key, value).apply();
         appPreferences.putFloat(key, value).apply();
     }
     }
 
 
-    private static void saveLongPreference(Context context, String key, long value) {
-        SharedPreferences.Editor appPreferences = getDefaultSharedPreferences(context.getApplicationContext()).edit();
-        appPreferences.putLong(key, value);
-        appPreferences.apply();
-    }
-
     public static SharedPreferences getDefaultSharedPreferences(Context context) {
     public static SharedPreferences getDefaultSharedPreferences(Context context) {
         return android.preference.PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
         return android.preference.PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
     }
     }

+ 17 - 1
src/main/java/com/owncloud/android/db/ProviderMeta.java

@@ -32,7 +32,7 @@ import com.owncloud.android.MainApp;
 public class ProviderMeta {
 public class ProviderMeta {
 
 
     public static final String DB_NAME = "filelist";
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 22;
+    public static final int DB_VERSION = 23;
 
 
     private ProviderMeta() {
     private ProviderMeta() {
     }
     }
@@ -46,6 +46,7 @@ public class ProviderMeta {
         public static final String EXTERNAL_LINKS_TABLE_NAME = "external_links";
         public static final String EXTERNAL_LINKS_TABLE_NAME = "external_links";
         public static final String ARBITRARY_DATA_TABLE_NAME = "arbitrary_data";
         public static final String ARBITRARY_DATA_TABLE_NAME = "arbitrary_data";
         public static final String VIRTUAL_TABLE_NAME = "virtual";
         public static final String VIRTUAL_TABLE_NAME = "virtual";
+        public static final String FILESYSTEM_TABLE_NAME = "filesystem";
 
 
         private static final String CONTENT_PREFIX = "content://";
         private static final String CONTENT_PREFIX = "content://";
 
 
@@ -68,6 +69,9 @@ public class ProviderMeta {
         public static final Uri CONTENT_URI_ARBITRARY_DATA = Uri.parse(CONTENT_PREFIX
         public static final Uri CONTENT_URI_ARBITRARY_DATA = Uri.parse(CONTENT_PREFIX
                 + MainApp.getAuthority() + "/arbitrary_data");
                 + MainApp.getAuthority() + "/arbitrary_data");
         public static final Uri CONTENT_URI_VIRTUAL = Uri.parse(CONTENT_PREFIX + MainApp.getAuthority() + "/virtual");
         public static final Uri CONTENT_URI_VIRTUAL = Uri.parse(CONTENT_PREFIX + MainApp.getAuthority() + "/virtual");
+        public static final Uri CONTENT_URI_FILESYSTEM = Uri.parse(CONTENT_PREFIX
+                + MainApp.getAuthority() + "/filesystem");
+
 
 
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file";
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file";
         public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file";
         public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file";
@@ -169,6 +173,8 @@ public class ProviderMeta {
         public static final String UPLOADS_LAST_RESULT = "last_result";
         public static final String UPLOADS_LAST_RESULT = "last_result";
         public static final String UPLOADS_CREATED_BY = "created_by";
         public static final String UPLOADS_CREATED_BY = "created_by";
         public static final String UPLOADS_DEFAULT_SORT_ORDER = ProviderTableMeta._ID + " collate nocase desc";
         public static final String UPLOADS_DEFAULT_SORT_ORDER = ProviderTableMeta._ID + " collate nocase desc";
+        public static final String UPLOADS_IS_WHILE_CHARGING_ONLY = "is_while_charging_only";
+        public static final String UPLOADS_IS_WIFI_ONLY = "is_wifi_only";
 
 
         // Columns of synced folder table
         // Columns of synced folder table
         public static final String SYNCED_FOLDER_LOCAL_PATH = "local_path";
         public static final String SYNCED_FOLDER_LOCAL_PATH = "local_path";
@@ -176,6 +182,7 @@ public class ProviderMeta {
         public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only";
         public static final String SYNCED_FOLDER_WIFI_ONLY = "wifi_only";
         public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only";
         public static final String SYNCED_FOLDER_CHARGING_ONLY = "charging_only";
         public static final String SYNCED_FOLDER_ENABLED = "enabled";
         public static final String SYNCED_FOLDER_ENABLED = "enabled";
+        public static final String SYNCED_FOLDER_TYPE = "type";
         public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date";
         public static final String SYNCED_FOLDER_SUBFOLDER_BY_DATE = "subfolder_by_date";
         public static final String SYNCED_FOLDER_ACCOUNT = "account";
         public static final String SYNCED_FOLDER_ACCOUNT = "account";
         public static final String SYNCED_FOLDER_UPLOAD_ACTION = "upload_option";
         public static final String SYNCED_FOLDER_UPLOAD_ACTION = "upload_option";
@@ -192,8 +199,17 @@ public class ProviderMeta {
         public static final String ARBITRARY_DATA_KEY = "key";
         public static final String ARBITRARY_DATA_KEY = "key";
         public static final String ARBITRARY_DATA_VALUE = "value";
         public static final String ARBITRARY_DATA_VALUE = "value";
 
 
+
         // Columns of virtual
         // Columns of virtual
         public static final String VIRTUAL_TYPE = "type";
         public static final String VIRTUAL_TYPE = "type";
         public static final String VIRTUAL_OCFILE_ID = "ocfile_id";
         public static final String VIRTUAL_OCFILE_ID = "ocfile_id";
+
+        // Columns of filesystem data table
+        public static final String FILESYSTEM_FILE_LOCAL_PATH = "local_path";
+        public static final String FILESYSTEM_FILE_MODIFIED = "modified_at";
+        public static final String FILESYSTEM_FILE_IS_FOLDER = "is_folder";
+        public static final String FILESYSTEM_FILE_FOUND_RECENTLY = "found_at";
+        public static final String FILESYSTEM_FILE_SENT_FOR_UPLOAD = "upload_triggered";
+        public static final String FILESYSTEM_SYNCED_FOLDER_ID = "syncedfolder_id";
     }
     }
 }
 }

+ 6 - 1
src/main/java/com/owncloud/android/db/UploadResult.java

@@ -35,7 +35,8 @@ public enum UploadResult {
     DELAYED_FOR_WIFI(9),
     DELAYED_FOR_WIFI(9),
     SERVICE_INTERRUPTED(10),
     SERVICE_INTERRUPTED(10),
     DELAYED_FOR_CHARGING(11),
     DELAYED_FOR_CHARGING(11),
-    MAINTENANCE_MODE(12);
+    MAINTENANCE_MODE(12),
+    LOCK_FAILED(13);
 
 
     private final int value;
     private final int value;
 
 
@@ -77,6 +78,8 @@ public enum UploadResult {
                 return DELAYED_FOR_CHARGING;
                 return DELAYED_FOR_CHARGING;
             case 12:
             case 12:
                 return MAINTENANCE_MODE;
                 return MAINTENANCE_MODE;
+            case 13:
+                return LOCK_FAILED;
         }
         }
         return null;
         return null;
     }
     }
@@ -120,6 +123,8 @@ public enum UploadResult {
                 return UNKNOWN;
                 return UNKNOWN;
             case MAINTENANCE_MODE:
             case MAINTENANCE_MODE:
                 return MAINTENANCE_MODE;
                 return MAINTENANCE_MODE;
+            case LOCK_FAILED:
+                return LOCK_FAILED;
             default:
             default:
                 return UNKNOWN;
                 return UNKNOWN;
         }
         }

+ 0 - 223
src/main/java/com/owncloud/android/files/InstantUploadBroadcastReceiver.java

@@ -1,223 +0,0 @@
-/**
- * ownCloud Android client application
- *
- * @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/>.
- */
-
-package com.owncloud.android.files;
-
-import android.Manifest;
-import android.accounts.Account;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.Build;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Video;
-import android.support.v4.content.ContextCompat;
-
-import com.owncloud.android.R;
-import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.db.PreferenceManager;
-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 {
-
-    private static final String TAG = InstantUploadBroadcastReceiver.class.getName();
-    // Image action
-    // Unofficial action, works for most devices but not HTC. See: https://github.com/owncloud/android/issues/6
-    private static final 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
-    private static final 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
-    private static final String NEW_VIDEO_ACTION = "android.hardware.action.NEW_VIDEO";
-
-    /**
-     * 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 = "";
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            Log_OC.d(TAG, "Received: " + intent.getAction());
-            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);
-                Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE");
-            } else if (intent.getAction().equals(NEW_VIDEO_ACTION)) {
-                handleNewVideoAction(context, intent);
-                Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO");
-            } else {
-                Log_OC.e(TAG, "Incorrect intent received: " + intent.getAction());
-            }
-        }
-    }
-
-    private void handleNewPictureAction(Context context, Intent intent) {
-        Cursor c = null;
-        String file_path = null;
-        String file_name = null;
-        String mime_type = null;
-        long date_taken = 0;
-
-        Log_OC.i(TAG, "New photo received");
-
-        if (!PreferenceManager.instantPictureUploadEnabled(context)) {
-            Log_OC.d(TAG, "Instant picture upload disabled, ignoring new picture");
-            return;
-        }
-
-        Account account = AccountUtils.getCurrentOwnCloudAccount(context);
-        if (account == null) {
-            Log_OC.w(TAG, "No account found for instant upload, aborting");
-            return;
-        }
-
-        String[] CONTENT_PROJECTION = {
-                Images.Media.DATA, Images.Media.DISPLAY_NAME, Images.Media.MIME_TYPE, Images.Media.SIZE};
-
-        // if < Jelly Bean permission must be accepted during installation
-        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
-            int permissionCheck = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE);
-
-            if (android.content.pm.PackageManager.PERMISSION_GRANTED != permissionCheck) {
-                Log_OC.w(TAG, "Read external storage permission isn't granted, aborting");
-                return;
-            }
-        }
-
-        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(Images.Media.DATA));
-        file_name = c.getString(c.getColumnIndex(Images.Media.DISPLAY_NAME));
-        mime_type = c.getString(c.getColumnIndex(Images.Media.MIME_TYPE));
-        date_taken = System.currentTimeMillis();
-        c.close();
-
-        if (file_path.equals(lastUploadedPhotoPath)) {
-            Log_OC.d(TAG, "Duplicate detected: " + file_path + ". Ignore.");
-            return;
-        }
-
-        lastUploadedPhotoPath = file_path;
-        Log_OC.d(TAG, "Path: " + file_path + "");
-
-        new FileUploader.UploadRequester();
-
-        int behaviour = getUploadBehaviour(context);
-        Boolean subfolderByDate = PreferenceManager.instantPictureUploadPathUseSubfolders(context);
-        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
-        String uploadPathdef = context.getString(R.string.instant_upload_path);
-        String uploadPath = pref.getString("instant_upload_path", uploadPathdef);
-
-        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadNewFile(
-                context,
-                account,
-                file_path,
-                FileStorageUtils.getInstantUploadFilePath(uploadPath, file_name, date_taken, subfolderByDate),
-                behaviour,
-                mime_type,
-                true,           // create parent folder if not existent
-                UploadFileOperation.CREATED_AS_INSTANT_PICTURE
-        );
-    }
-
-    private Integer getUploadBehaviour(Context context) {
-        SharedPreferences appPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
-        String behaviour = appPreferences.getString("prefs_instant_behaviour", "NOTHING");
-
-        if (behaviour.equalsIgnoreCase("NOTHING")) {
-            Log_OC.d(TAG, "upload file and do nothing");
-            return FileUploader.LOCAL_BEHAVIOUR_FORGET;
-        } else if (behaviour.equalsIgnoreCase("MOVE")) {
-            Log_OC.d(TAG, "upload file and move file to oc folder");
-            return FileUploader.LOCAL_BEHAVIOUR_MOVE;
-        } else if (behaviour.equalsIgnoreCase("DELETE")) {
-            Log_OC.d(TAG, "upload file and delete original file");
-            return FileUploader.LOCAL_BEHAVIOUR_DELETE;
-        }
-        return FileUploader.LOCAL_BEHAVIOUR_FORGET;
-    }
-
-    private void handleNewVideoAction(Context context, Intent intent) {
-        Cursor c = null;
-        String file_path = null;
-        String file_name = null;
-        String mime_type = null;
-        long date_taken = 0;
-
-        Log_OC.i(TAG, "New video received");
-
-        if (!PreferenceManager.instantVideoUploadEnabled(context)) {
-            Log_OC.d(TAG, "Instant video upload disabled, ignoring new video");
-            return;
-        }
-
-        Account account = AccountUtils.getCurrentOwnCloudAccount(context);
-        if (account == null) {
-            Log_OC.w(TAG, "No account found for instant upload, aborting");
-            return;
-        }
-
-        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();
-        date_taken = System.currentTimeMillis();
-        Log_OC.d(TAG, file_path + "");
-
-        int behaviour = getUploadBehaviour(context);
-        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadNewFile(
-                context,
-                account,
-                file_path,
-                FileStorageUtils.getInstantVideoUploadFilePath(context, file_name, date_taken),
-                behaviour,
-                mime_type,
-                true,           // create parent folder if not existent
-                UploadFileOperation.CREATED_AS_INSTANT_VIDEO
-        );
-    }
-
-}

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

@@ -1,223 +0,0 @@
-/**
- *   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.Context;
-import android.content.Intent;
-import android.net.NetworkInfo;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-
-import com.owncloud.android.db.PreferenceManager;
-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");
-        }
-
-        if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED) &&
-                (PreferenceManager.instantPictureUploadEnabled(context) &&
-                        PreferenceManager.instantPictureUploadWhenChargingOnly(context)) ||
-                (PreferenceManager.instantVideoUploadEnabled(context) &&
-                        PreferenceManager.instantVideoUploadWhenChargingOnly(context))
-                ) {
-            // for the moment, only recovery of instant uploads, similar to behaviour in release 1.9.1
-            Log_OC.d(TAG, "Requesting retry of instant uploads (& friends) due to charging");
-            FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-            requester.retryFailedUploads(
-                    context,
-                    null,
-                    UploadResult.DELAYED_FOR_CHARGING   // for the rest of enqueued when Wifi fell
-            );
-        }
-
-        /**
-         * 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.isAppConnectedViaUnmeteredWiFi(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 (
-                (PreferenceManager.instantPictureUploadEnabled(context) &&
-                        PreferenceManager.instantPictureUploadViaWiFiOnly(context)) ||
-                (PreferenceManager.instantVideoUploadEnabled(context) &&
-                        PreferenceManager.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() {
-        // 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);
-    }
-
-    */
-}

+ 123 - 50
src/main/java/com/owncloud/android/files/services/FileUploader.java

@@ -34,7 +34,6 @@ import android.app.Service;
 import android.content.Context;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Handler;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IBinder;
@@ -45,6 +44,9 @@ import android.os.Process;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.NotificationCompat;
 import android.util.Pair;
 import android.util.Pair;
 
 
+import com.evernote.android.job.JobRequest;
+import com.evernote.android.job.util.Device;
+import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.authentication.AuthenticatorActivity;
@@ -77,6 +79,8 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Map;
 import java.util.Vector;
 import java.util.Vector;
 
 
+import javax.annotation.Nullable;
+
 /**
 /**
  * Service for uploading files. Invoke using context.startService(...).
  * Service for uploading files. Invoke using context.startService(...).
  *
  *
@@ -144,6 +148,9 @@ public class FileUploader extends Service
      * Key to signal what is the origin of the upload request
      * Key to signal what is the origin of the upload request
      */
      */
     public static final String KEY_CREATED_BY = "CREATED_BY";
     public static final String KEY_CREATED_BY = "CREATED_BY";
+
+    public static final String KEY_WHILE_ON_WIFI_ONLY = "KEY_ON_WIFI_ONLY";
+
     /**
     /**
      * Set to true if upload is to performed only when phone is being charged.
      * Set to true if upload is to performed only when phone is being charged.
      */
      */
@@ -194,7 +201,6 @@ public class FileUploader extends Service
         sendBroadcastUploadStarted(mCurrentUpload);
         sendBroadcastUploadStarted(mCurrentUpload);
     }
     }
 
 
-
     /**
     /**
      * Helper class providing methods to ease requesting commands to {@link FileUploader} .
      * Helper class providing methods to ease requesting commands to {@link FileUploader} .
      *
      *
@@ -214,7 +220,37 @@ public class FileUploader extends Service
                 String[] mimeTypes,
                 String[] mimeTypes,
                 Integer behaviour,
                 Integer behaviour,
                 Boolean createRemoteFolder,
                 Boolean createRemoteFolder,
-                int createdBy
+                int createdBy,
+                boolean requiresWifi,
+                boolean requiresCharging
+        ) {
+            Intent intent = new Intent(context, FileUploader.class);
+
+            intent.putExtra(FileUploader.KEY_ACCOUNT, account);
+            intent.putExtra(FileUploader.KEY_LOCAL_FILE, localPaths);
+            intent.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths);
+            intent.putExtra(FileUploader.KEY_MIME_TYPE, mimeTypes);
+            intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
+            intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
+            intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
+            intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
+            intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
+
+            context.startService(intent);
+        }
+
+        public void uploadFileWithOverwrite(
+                Context context,
+                Account account,
+                String[] localPaths,
+                String[] remotePaths,
+                String[] mimeTypes,
+                Integer behaviour,
+                Boolean createRemoteFolder,
+                int createdBy,
+                boolean requiresWifi,
+                boolean requiresCharging,
+                boolean overwrite
         ) {
         ) {
             Intent intent = new Intent(context, FileUploader.class);
             Intent intent = new Intent(context, FileUploader.class);
 
 
@@ -225,15 +261,41 @@ public class FileUploader extends Service
             intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
             intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
             intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
             intent.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, createRemoteFolder);
             intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
             intent.putExtra(FileUploader.KEY_CREATED_BY, createdBy);
+            intent.putExtra(FileUploader.KEY_WHILE_ON_WIFI_ONLY, requiresWifi);
+            intent.putExtra(FileUploader.KEY_WHILE_CHARGING_ONLY, requiresCharging);
+            intent.putExtra(FileUploader.KEY_FORCE_OVERWRITE, overwrite);
 
 
             context.startService(intent);
             context.startService(intent);
         }
         }
 
 
+        /**
+         * Call to upload a file
+         */
+        public void uploadFileWithOverwrite(Context context, Account account, String localPath, String remotePath, int
+                behaviour, String mimeType, boolean createRemoteFile, int createdBy, boolean requiresWifi,
+                                  boolean requiresCharging, boolean overwrite) {
+
+            uploadFileWithOverwrite(
+                    context,
+                    account,
+                    new String[]{localPath},
+                    new String[]{remotePath},
+                    new String[]{mimeType},
+                    behaviour,
+                    createRemoteFile,
+                    createdBy,
+                    requiresWifi,
+                    requiresCharging,
+                    overwrite
+            );
+        }
+
         /**
         /**
          * Call to upload a new single file
          * Call to upload a new single file
          */
          */
         public void uploadNewFile(Context context, Account account, String localPath, String remotePath, int
         public void uploadNewFile(Context context, Account account, String localPath, String remotePath, int
-                behaviour, String mimeType, boolean createRemoteFile, int createdBy) {
+                behaviour, String mimeType, boolean createRemoteFile, int createdBy, boolean requiresWifi,
+                                  boolean requiresCharging) {
 
 
             uploadNewFile(
             uploadNewFile(
                 context,
                 context,
@@ -243,7 +305,9 @@ public class FileUploader extends Service
                 new String[]{mimeType},
                 new String[]{mimeType},
                 behaviour,
                 behaviour,
                 createRemoteFile,
                 createRemoteFile,
-                createdBy
+                createdBy,
+                requiresWifi,
+                requiresCharging
             );
             );
         }
         }
 
 
@@ -288,7 +352,6 @@ public class FileUploader extends Service
             }
             }
         }
         }
 
 
-
         /**
         /**
          * Retry a subset of all the stored failed uploads.
          * Retry a subset of all the stored failed uploads.
          *
          *
@@ -306,7 +369,7 @@ public class FileUploader extends Service
             boolean accountMatch;
             boolean accountMatch;
             for ( OCUpload failedUpload: failedUploads) {
             for ( OCUpload failedUpload: failedUploads) {
                 accountMatch = (account == null || account.name.equals(failedUpload.getAccountName()));
                 accountMatch = (account == null || account.name.equals(failedUpload.getAccountName()));
-                resultMatch = (uploadResult == null || uploadResult.equals(failedUpload.getLastResult()));
+                resultMatch = ((uploadResult == null || uploadResult.equals(failedUpload.getLastResult())));
                 if (accountMatch && resultMatch) {
                 if (accountMatch && resultMatch) {
                     if (currentAccount == null ||
                     if (currentAccount == null ||
                             !currentAccount.name.equals(failedUpload.getAccountName())) {
                             !currentAccount.name.equals(failedUpload.getAccountName())) {
@@ -330,13 +393,12 @@ public class FileUploader extends Service
                 i.putExtra(FileUploader.KEY_RETRY, true);
                 i.putExtra(FileUploader.KEY_RETRY, true);
                 i.putExtra(FileUploader.KEY_ACCOUNT, account);
                 i.putExtra(FileUploader.KEY_ACCOUNT, account);
                 i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
                 i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
+
                 context.startService(i);
                 context.startService(i);
             }
             }
         }
         }
-
     }
     }
 
 
-
     /**
     /**
      * Service initialization
      * Service initialization
      */
      */
@@ -370,7 +432,6 @@ public class FileUploader extends Service
         am.addOnAccountsUpdatedListener(this, null, false);
         am.addOnAccountsUpdatedListener(this, null, false);
     }
     }
 
 
-
     /**
     /**
      * Service clean-up when restarted after being killed
      * Service clean-up when restarted after being killed
      */
      */
@@ -379,7 +440,6 @@ public class FileUploader extends Service
         mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
         mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker);
     }
     }
 
 
-
     /**
     /**
      * Service clean up
      * Service clean up
      */
      */
@@ -399,7 +459,6 @@ public class FileUploader extends Service
         super.onDestroy();
         super.onDestroy();
     }
     }
 
 
-
     /**
     /**
      * Entry point to add one or several files to the queue of uploads.
      * Entry point to add one or several files to the queue of uploads.
      *
      *
@@ -426,9 +485,14 @@ public class FileUploader extends Service
             return Service.START_NOT_STICKY;
             return Service.START_NOT_STICKY;
         }
         }
         OwnCloudVersion ocv = AccountUtils.getServerVersion(account);
         OwnCloudVersion ocv = AccountUtils.getServerVersion(account);
+
         boolean chunked = ocv.isChunkedUploadSupported();
         boolean chunked = ocv.isChunkedUploadSupported();
 
 
+        boolean onWifiOnly = intent.getBooleanExtra(KEY_WHILE_ON_WIFI_ONLY, false);
+        boolean whileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, false);
+
         if (!retry) {
         if (!retry) {
+
             if (!(intent.hasExtra(KEY_LOCAL_FILE) ||
             if (!(intent.hasExtra(KEY_LOCAL_FILE) ||
                     intent.hasExtra(KEY_FILE))) {
                     intent.hasExtra(KEY_FILE))) {
                 Log_OC.e(TAG, "Not enough information provided in intent");
                 Log_OC.e(TAG, "Not enough information provided in intent");
@@ -451,7 +515,6 @@ public class FileUploader extends Service
                 mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
                 mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
             }
             }
 
 
-
             boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
             boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
             int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
             int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
 
 
@@ -503,10 +566,12 @@ public class FileUploader extends Service
                     ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
                     ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
                     ocUpload.setCreatedBy(createdBy);
                     ocUpload.setCreatedBy(createdBy);
                     ocUpload.setLocalAction(localAction);
                     ocUpload.setLocalAction(localAction);
-                    /*ocUpload.setUseWifiOnly(isUseWifiOnly);
-                    ocUpload.setWhileChargingOnly(isWhileChargingOnly);*/
+                    ocUpload.setUseWifiOnly(onWifiOnly);
+                    ocUpload.setWhileChargingOnly(whileChargingOnly);
+
                     ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
                     ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
 
 
+
                     newUpload = new UploadFileOperation(
                     newUpload = new UploadFileOperation(
                             account,
                             account,
                             files[i],
                             files[i],
@@ -514,7 +579,9 @@ public class FileUploader extends Service
                             chunked,
                             chunked,
                             forceOverwrite,
                             forceOverwrite,
                             localAction,
                             localAction,
-                            this
+                            this,
+                            onWifiOnly,
+                            whileChargingOnly
                     );
                     );
                     newUpload.setCreatedBy(createdBy);
                     newUpload.setCreatedBy(createdBy);
                     if (isCreateRemoteFolder) {
                     if (isCreateRemoteFolder) {
@@ -561,6 +628,9 @@ public class FileUploader extends Service
             }
             }
             OCUpload upload = intent.getParcelableExtra(KEY_RETRY_UPLOAD);
             OCUpload upload = intent.getParcelableExtra(KEY_RETRY_UPLOAD);
 
 
+            onWifiOnly = upload.isUseWifiOnly();
+            whileChargingOnly = upload.isWhileChargingOnly();
+
             UploadFileOperation newUpload = new UploadFileOperation(
             UploadFileOperation newUpload = new UploadFileOperation(
                     account,
                     account,
                     null,
                     null,
@@ -568,7 +638,9 @@ public class FileUploader extends Service
                     chunked,
                     chunked,
                     upload.isForceOverwrite(),  // TODO should be read from DB?
                     upload.isForceOverwrite(),  // TODO should be read from DB?
                     upload.getLocalAction(),    // TODO should be read from DB?
                     upload.getLocalAction(),    // TODO should be read from DB?
-                    this
+                    this,
+                    onWifiOnly,
+                    whileChargingOnly
             );
             );
 
 
             newUpload.addDatatransferProgressListener(this);
             newUpload.addDatatransferProgressListener(this);
@@ -634,9 +706,8 @@ public class FileUploader extends Service
     }
     }
 
 
     /**
     /**
-     * Binder to let client components to perform operations on the queue of
-     * uploads.
-     * <p/>
+     * Binder to let client components to perform operations on the queue of uploads.
+     *
      * It provides by itself the available operations.
      * It provides by itself the available operations.
      */
      */
     public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
     public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
@@ -645,9 +716,7 @@ public class FileUploader extends Service
          * Map of listeners that will be reported about progress of uploads from a
          * Map of listeners that will be reported about progress of uploads from a
          * {@link FileUploaderBinder} instance
          * {@link FileUploaderBinder} instance
          */
          */
-        private Map<String, OnDatatransferProgressListener> mBoundListeners =
-                new HashMap<String, OnDatatransferProgressListener>();
-
+        private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<>();
 
 
         /**
         /**
          * Cancels a pending or current upload of a remote file.
          * Cancels a pending or current upload of a remote file.
@@ -656,7 +725,7 @@ public class FileUploader extends Service
          * @param file    A file in the queue of pending uploads
          * @param file    A file in the queue of pending uploads
          */
          */
         public void cancel(Account account, OCFile file) {
         public void cancel(Account account, OCFile file) {
-            cancel(account.name, file.getRemotePath());
+            cancel(account.name, file.getRemotePath(), null);
         }
         }
 
 
         /**
         /**
@@ -665,7 +734,7 @@ public class FileUploader extends Service
          * @param storedUpload Upload operation persisted
          * @param storedUpload Upload operation persisted
          */
          */
         public void cancel(OCUpload storedUpload) {
         public void cancel(OCUpload storedUpload) {
-            cancel(storedUpload.getAccountName(), storedUpload.getRemotePath());
+            cancel(storedUpload.getAccountName(), storedUpload.getRemotePath(), null);
 
 
         }
         }
 
 
@@ -674,8 +743,10 @@ public class FileUploader extends Service
          *
          *
          * @param accountName Local name of an ownCloud account where the remote file will be stored.
          * @param accountName Local name of an ownCloud account where the remote file will be stored.
          * @param remotePath  Remote target of the upload
          * @param remotePath  Remote target of the upload
+         *
+         * Setting result code will pause rather than cancel the job
          */
          */
-        private void cancel(String accountName, String remotePath) {
+        private void cancel(String accountName, String remotePath, @Nullable ResultCode resultCode ) {
             Pair<UploadFileOperation, String> removeResult =
             Pair<UploadFileOperation, String> removeResult =
                     mPendingUploads.remove(accountName, remotePath);
                     mPendingUploads.remove(accountName, remotePath);
             UploadFileOperation upload = removeResult.first;
             UploadFileOperation upload = removeResult.first;
@@ -686,14 +757,17 @@ public class FileUploader extends Service
 
 
                 upload = mCurrentUpload;
                 upload = mCurrentUpload;
             }
             }
+
             if (upload != null) {
             if (upload != null) {
                 upload.cancel();
                 upload.cancel();
                 // need to update now table in mUploadsStorageManager,
                 // need to update now table in mUploadsStorageManager,
                 // since the operation will not get to be run by FileUploader#uploadFile
                 // since the operation will not get to be run by FileUploader#uploadFile
-                mUploadsStorageManager.removeUpload(accountName, remotePath);
-            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-                // try to cancel job in jobScheduler
-                mUploadsStorageManager.cancelPendingJob(accountName, remotePath);
+                if (resultCode != null) {
+                    mUploadsStorageManager.updateDatabaseUploadResult(new RemoteOperationResult(resultCode), upload);
+                    notifyUploadResult(upload, new RemoteOperationResult(resultCode));
+                } else {
+                    mUploadsStorageManager.removeUpload(accountName, remotePath);
+                }
             }
             }
         }
         }
 
 
@@ -740,7 +814,6 @@ public class FileUploader extends Service
             return (mPendingUploads.contains(account.name, file.getRemotePath()));
             return (mPendingUploads.contains(account.name, file.getRemotePath()));
         }
         }
 
 
-
         public boolean isUploadingNow(OCUpload upload) {
         public boolean isUploadingNow(OCUpload upload) {
             return (
             return (
                 upload != null  &&
                 upload != null  &&
@@ -751,7 +824,6 @@ public class FileUploader extends Service
             );
             );
         }
         }
 
 
-
         /**
         /**
          * Adds a listener interested in the progress of the upload for a concrete file.
          * Adds a listener interested in the progress of the upload for a concrete file.
          *
          *
@@ -771,7 +843,6 @@ public class FileUploader extends Service
             mBoundListeners.put(targetKey, listener);
             mBoundListeners.put(targetKey, listener);
         }
         }
 
 
-
         /**
         /**
          * Adds a listener interested in the progress of the upload for a concrete file.
          * Adds a listener interested in the progress of the upload for a concrete file.
          *
          *
@@ -789,7 +860,6 @@ public class FileUploader extends Service
             mBoundListeners.put(targetKey, listener);
             mBoundListeners.put(targetKey, listener);
         }
         }
 
 
-
         /**
         /**
          * Removes a listener interested in the progress of the upload for a concrete file.
          * Removes a listener interested in the progress of the upload for a concrete file.
          *
          *
@@ -811,7 +881,6 @@ public class FileUploader extends Service
             }
             }
         }
         }
 
 
-
         /**
         /**
          * Removes a listener interested in the progress of the upload for a concrete file.
          * Removes a listener interested in the progress of the upload for a concrete file.
          *
          *
@@ -831,7 +900,6 @@ public class FileUploader extends Service
             }
             }
         }
         }
 
 
-
         @Override
         @Override
         public void onTransferProgress(long progressRate, long totalTransferredSoFar,
         public void onTransferProgress(long progressRate, long totalTransferredSoFar,
                                        long totalToTransfer, String fileName) {
                                        long totalToTransfer, String fileName) {
@@ -840,12 +908,24 @@ public class FileUploader extends Service
             if (boundListener != null) {
             if (boundListener != null) {
                 boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
                 boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
                         totalToTransfer, fileName);
                         totalToTransfer, fileName);
+
+                if (MainApp.getAppContext() != null) {
+                    if (mCurrentUpload.getIsWifiRequired() && !Device.getNetworkType(MainApp.getAppContext()).
+                            equals(JobRequest.NetworkType.UNMETERED)) {
+                        cancel(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath()
+                                , ResultCode.DELAYED_FOR_WIFI);
+                    } else if (mCurrentUpload.getIsChargingRequired() &&
+                            !Device.isCharging(MainApp.getAppContext())) {
+                        cancel(mCurrentUpload.getAccount().name, mCurrentUpload.getFile().getRemotePath()
+                                , ResultCode.DELAYED_FOR_CHARGING);
+                    }
+                }
             }
             }
         }
         }
 
 
         /**
         /**
          * Builds a key for the map of listeners.
          * Builds a key for the map of listeners.
-         * <p/>
+         *
          * TODO use method in IndexedForest, or refactor both to a common place
          * TODO use method in IndexedForest, or refactor both to a common place
          * add to local database) to better policy (add to local database, then upload)
          * add to local database) to better policy (add to local database, then upload)
          *
          *
@@ -863,7 +943,7 @@ public class FileUploader extends Service
     /**
     /**
      * Upload worker. Performs the pending uploads in the order they were
      * Upload worker. Performs the pending uploads in the order they were
      * requested.
      * requested.
-     * <p/>
+     *
      * Created with the Looper of a new thread, started in
      * Created with the Looper of a new thread, started in
      * {@link FileUploader#onCreate()}.
      * {@link FileUploader#onCreate()}.
      */
      */
@@ -958,7 +1038,7 @@ public class FileUploader extends Service
                             mCurrentAccount.name,
                             mCurrentAccount.name,
                             mCurrentUpload.getOldFile().getRemotePath()
                             mCurrentUpload.getOldFile().getRemotePath()
                     );
                     );
-                    /** TODO: grant that name is also updated for mCurrentUpload.getOCUploadId */
+                    // TODO: grant that name is also updated for mCurrentUpload.getOCUploadId
 
 
                 } else {
                 } else {
                     removeResult = mPendingUploads.removePayload(
                     removeResult = mPendingUploads.removePayload(
@@ -973,7 +1053,6 @@ public class FileUploader extends Service
                 notifyUploadResult(mCurrentUpload, uploadResult);
                 notifyUploadResult(mCurrentUpload, uploadResult);
 
 
                 sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second);
                 sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second);
-
             }
             }
 
 
             // generate new Thumbnail
             // generate new Thumbnail
@@ -997,8 +1076,7 @@ public class FileUploader extends Service
     private void notifyUploadStart(UploadFileOperation upload) {
     private void notifyUploadStart(UploadFileOperation upload) {
         // / create status notification with a progress bar
         // / create status notification with a progress bar
         mLastPercent = 0;
         mLastPercent = 0;
-        mNotificationBuilder =
-                NotificationUtils.newNotificationBuilder(this);
+        mNotificationBuilder = NotificationUtils.newNotificationBuilder(this);
         mNotificationBuilder
         mNotificationBuilder
                 .setOngoing(true)
                 .setOngoing(true)
                 .setSmallIcon(R.drawable.notification_icon)
                 .setSmallIcon(R.drawable.notification_icon)
@@ -1022,7 +1100,6 @@ public class FileUploader extends Service
         }   // else wait until the upload really start (onTransferProgress is called), so that if it's discarded
         }   // else wait until the upload really start (onTransferProgress is called), so that if it's discarded
         // due to lack of Wifi, no notification is shown
         // due to lack of Wifi, no notification is shown
         // TODO generalize for automated uploads
         // TODO generalize for automated uploads
-
     }
     }
 
 
     /**
     /**
@@ -1058,7 +1135,8 @@ public class FileUploader extends Service
         if (!uploadResult.isCancelled() &&
         if (!uploadResult.isCancelled() &&
             !ResultCode.LOCAL_FILE_NOT_FOUND.equals(uploadResult.getCode()) &&
             !ResultCode.LOCAL_FILE_NOT_FOUND.equals(uploadResult.getCode()) &&
             !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_WIFI) &&
             !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_WIFI) &&
-            !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_CHARGING)) {
+            !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_CHARGING) &&
+            !uploadResult.getCode().equals(ResultCode.LOCK_FAILED)    ) {
 
 
             int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker :
             int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker :
                     R.string.uploader_upload_failed_ticker;
                     R.string.uploader_upload_failed_ticker;
@@ -1077,9 +1155,7 @@ public class FileUploader extends Service
                     .setOngoing(false)
                     .setOngoing(false)
                     .setProgress(0, 0, false);
                     .setProgress(0, 0, false);
 
 
-            content = ErrorMessageAdapter.getErrorCauseMessage(
-                    uploadResult, upload, getResources()
-            );
+            content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, upload, getResources());
 
 
             if (needsToUpdateCredentials) {
             if (needsToUpdateCredentials) {
                 // let the user update credentials with one click
                 // let the user update credentials with one click
@@ -1130,7 +1206,6 @@ public class FileUploader extends Service
         }
         }
     }
     }
 
 
-
     /**
     /**
      * Sends a broadcast in order to the interested activities can update their
      * Sends a broadcast in order to the interested activities can update their
      * view
      * view
@@ -1144,7 +1219,6 @@ public class FileUploader extends Service
         sendStickyBroadcast(start);
         sendStickyBroadcast(start);
     }
     }
 
 
-
     /**
     /**
      * Sends a broadcast in order to the interested activities can update their
      * Sends a broadcast in order to the interested activities can update their
      * view
      * view
@@ -1208,5 +1282,4 @@ public class FileUploader extends Service
         mPendingUploads.remove(account.name);
         mPendingUploads.remove(account.name);
         mUploadsStorageManager.removeUploads(account.name);
         mUploadsStorageManager.removeUploads(account.name);
     }
     }
-
 }
 }

+ 2 - 2
src/main/java/com/owncloud/android/services/AccountRemovalJob.java → src/main/java/com/owncloud/android/jobs/AccountRemovalJob.java

@@ -19,7 +19,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
-package com.owncloud.android.services;
+package com.owncloud.android.jobs;
 
 
 import android.accounts.Account;
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManager;
@@ -76,7 +76,7 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
 
 
             // remove pending account removal
             // remove pending account removal
             ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
             ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
-            arbitraryDataProvider.deleteKeyForAccount(account, PENDING_FOR_REMOVAL);
+            arbitraryDataProvider.deleteKeyForAccount(account.name, PENDING_FOR_REMOVAL);
 
 
             return Result.SUCCESS;
             return Result.SUCCESS;
         } else {
         } else {

+ 6 - 3
src/main/java/com/owncloud/android/services/ContactsBackupJob.java → src/main/java/com/owncloud/android/jobs/ContactsBackupJob.java

@@ -19,7 +19,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
-package com.owncloud.android.services;
+package com.owncloud.android.jobs;
 
 
 import android.accounts.Account;
 import android.accounts.Account;
 import android.content.ComponentName;
 import android.content.ComponentName;
@@ -44,6 +44,7 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
 
 
 import java.io.File;
 import java.io.File;
@@ -97,7 +98,7 @@ public class ContactsBackupJob extends Job {
                     OperationsService.BIND_AUTO_CREATE);
                     OperationsService.BIND_AUTO_CREATE);
 
 
             // store execution date
             // store execution date
-            arbitraryDataProvider.storeOrUpdateKeyValue(account,
+            arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
                     ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP,
                     ContactsPreferenceActivity.PREFERENCE_CONTACTS_LAST_BACKUP,
                     String.valueOf(Calendar.getInstance().getTimeInMillis()));
                     String.valueOf(Calendar.getInstance().getTimeInMillis()));
         } else {
         } else {
@@ -156,7 +157,9 @@ public class ContactsBackupJob extends Job {
                     FileUploader.LOCAL_BEHAVIOUR_MOVE,
                     FileUploader.LOCAL_BEHAVIOUR_MOVE,
                     null,
                     null,
                     true,
                     true,
-                    UploadFileOperation.CREATED_BY_USER
+                    UploadFileOperation.CREATED_BY_USER,
+                    false,
+                    false
             );
             );
 
 
         } catch (Exception e) {
         } catch (Exception e) {

+ 1 - 1
src/main/java/com/owncloud/android/services/ContactsImportJob.java → src/main/java/com/owncloud/android/jobs/ContactsImportJob.java

@@ -19,7 +19,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
-package com.owncloud.android.services;
+package com.owncloud.android.jobs;
 
 
 import android.database.Cursor;
 import android.database.Cursor;
 import android.net.Uri;
 import android.net.Uri;

+ 153 - 0
src/main/java/com/owncloud/android/jobs/FilesSyncJob.java

@@ -0,0 +1,153 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.jobs;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.PowerManager;
+import android.support.annotation.NonNull;
+import android.support.media.ExifInterface;
+import android.text.TextUtils;
+
+import com.evernote.android.job.Job;
+import com.evernote.android.job.util.support.PersistableBundleCompat;
+import com.owncloud.android.MainApp;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.FilesystemDataProvider;
+import com.owncloud.android.datamodel.MediaFolderType;
+import com.owncloud.android.datamodel.SyncedFolder;
+import com.owncloud.android.datamodel.SyncedFolderProvider;
+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;
+import com.owncloud.android.utils.FilesSyncHelper;
+import com.owncloud.android.utils.MimeTypeUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/*
+    Job that:
+        - restarts existing jobs if required
+        - finds new and modified files since we last run this
+        - creates upload tasks
+ */
+public class FilesSyncJob extends Job {
+    public static final String TAG = "FilesSyncJob";
+
+    public static final String SKIP_CUSTOM = "skipCustom";
+
+    @NonNull
+    @Override
+    protected Result onRunJob(Params params) {
+        final Context context = MainApp.getAppContext();
+        final ContentResolver contentResolver = context.getContentResolver();
+
+        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
+        PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                TAG);
+        wakeLock.acquire();
+
+        PersistableBundleCompat bundle = params.getExtras();
+        final boolean skipCustom = bundle.getBoolean(SKIP_CUSTOM, false);
+
+        FilesSyncHelper.restartJobsIfNeeded();
+        FilesSyncHelper.insertAllDBEntries(skipCustom);
+
+        // Create all the providers we'll need
+        final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
+        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver);
+
+        for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
+            if ((syncedFolder.isEnabled()) && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.getType())) {
+                for (String path : filesystemDataProvider.getFilesForUpload(syncedFolder.getLocalPath(),
+                        Long.toString(syncedFolder.getId()))) {
+                    File file = new File(path);
+
+                    Long lastModificationTime = file.lastModified();
+                    final Locale currentLocale = context.getResources().getConfiguration().locale;
+
+                    if (MediaFolderType.IMAGE == syncedFolder.getType()) {
+                        String mimetypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath());
+                        if ("image/jpeg".equalsIgnoreCase(mimetypeString) || "image/tiff".
+                                equalsIgnoreCase(mimetypeString)) {
+                            try {
+                                ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
+                                String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
+                                if (!TextUtils.isEmpty(exifDate)) {
+                                    ParsePosition pos = new ParsePosition(0);
+                                    SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss",
+                                            currentLocale);
+                                    sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
+                                    Date dateTime = sFormatter.parse(exifDate, pos);
+                                    lastModificationTime = dateTime.getTime();
+                                }
+
+                            } catch (IOException e) {
+                                Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage());
+                            }
+                        }
+                    }
+
+                    boolean needsCharging = syncedFolder.getChargingOnly();
+                    boolean needsWifi = syncedFolder.getWifiOnly();
+
+                    String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath());
+
+                    Account account = AccountUtils.getOwnCloudAccountByName(context, syncedFolder.getAccount());
+
+                    requester.uploadFileWithOverwrite(
+                            context,
+                            account,
+                            file.getAbsolutePath(),
+                            FileStorageUtils.getInstantUploadFilePath(
+                                    currentLocale,
+                                    syncedFolder.getRemotePath(), file.getName(),
+                                    lastModificationTime,
+                                    syncedFolder.getSubfolderByDate()),
+                            syncedFolder.getUploadAction(),
+                            mimeType,
+                            true,           // create parent folder if not existent
+                            UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
+                            needsWifi,
+                            needsCharging,
+                            true
+                    );
+
+                    filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
+                            Long.toString(syncedFolder.getId()));
+                }
+            }
+        }
+
+        wakeLock.release();
+        return Result.SUCCESS;
+    }
+}

+ 3 - 3
src/main/java/com/owncloud/android/services/NCJobCreator.java → src/main/java/com/owncloud/android/jobs/NCJobCreator.java

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
-package com.owncloud.android.services;
+package com.owncloud.android.jobs;
 
 
 import com.evernote.android.job.Job;
 import com.evernote.android.job.Job;
 import com.evernote.android.job.JobCreator;
 import com.evernote.android.job.JobCreator;
@@ -31,14 +31,14 @@ public class NCJobCreator implements JobCreator {
     @Override
     @Override
     public Job create(String tag) {
     public Job create(String tag) {
         switch (tag) {
         switch (tag) {
-            case AutoUploadJob.TAG:
-                return new AutoUploadJob();
             case ContactsBackupJob.TAG:
             case ContactsBackupJob.TAG:
                 return new ContactsBackupJob();
                 return new ContactsBackupJob();
             case ContactsImportJob.TAG:
             case ContactsImportJob.TAG:
                 return new ContactsImportJob();
                 return new ContactsImportJob();
             case AccountRemovalJob.TAG:
             case AccountRemovalJob.TAG:
                 return new AccountRemovalJob();
                 return new AccountRemovalJob();
+            case FilesSyncJob.TAG:
+                return new FilesSyncJob();
             default:
             default:
                 return null;
                 return null;
         }
         }

+ 69 - 0
src/main/java/com/owncloud/android/jobs/NContentObserverJob.java

@@ -0,0 +1,69 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.jobs;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+
+import com.evernote.android.job.JobRequest;
+import com.evernote.android.job.util.support.PersistableBundleCompat;
+import com.owncloud.android.utils.FilesSyncHelper;
+
+import java.util.concurrent.TimeUnit;
+
+/*
+    Job that triggers new FilesSyncJob in case new photo or video were detected
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class NContentObserverJob extends JobService {
+    @Override
+    public boolean onStartJob(JobParameters params) {
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            if (params.getJobId() == FilesSyncHelper.ContentSyncJobId && params.getTriggeredContentAuthorities()
+                    != null && params.getTriggeredContentUris() != null
+                    && params.getTriggeredContentUris().length > 0) {
+
+                PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat();
+                persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true);
+
+                new JobRequest.Builder(FilesSyncJob.TAG)
+                        .setExecutionWindow(1, TimeUnit.SECONDS.toMillis(2))
+                        .setBackoffCriteria(TimeUnit.SECONDS.toMillis(5), JobRequest.BackoffPolicy.LINEAR)
+                        .setUpdateCurrent(false)
+                        .build()
+                        .schedule();
+            }
+
+            FilesSyncHelper.scheduleNJobs(true);
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+}

+ 2 - 7
src/main/java/com/owncloud/android/operations/MoveFileOperation.java

@@ -20,8 +20,6 @@
 
 
 package com.owncloud.android.operations;
 package com.owncloud.android.operations;
 
 
-import android.accounts.Account;
-
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -41,17 +39,14 @@ public class MoveFileOperation extends SyncOperation {
     private String mTargetParentPath;
     private String mTargetParentPath;
     
     
     private OCFile mFile;
     private OCFile mFile;
-
-    
     
     
     /**
     /**
      * Constructor
      * Constructor
      * 
      * 
      * @param srcPath           Remote path of the {@link OCFile} to move.
      * @param srcPath           Remote path of the {@link OCFile} to move.
      * @param targetParentPath  Path to the folder where the file will be moved into.
      * @param targetParentPath  Path to the folder where the file will be moved into.
-     * @param account           OwnCloud account containing both the file and the target folder 
      */
      */
-    public MoveFileOperation(String srcPath, String targetParentPath, Account account) {
+    public MoveFileOperation(String srcPath, String targetParentPath) {
         mSrcPath = srcPath;
         mSrcPath = srcPath;
         mTargetParentPath = targetParentPath;
         mTargetParentPath = targetParentPath;
         if (!mTargetParentPath.endsWith(OCFile.PATH_SEPARATOR)) {
         if (!mTargetParentPath.endsWith(OCFile.PATH_SEPARATOR)) {
@@ -68,7 +63,7 @@ public class MoveFileOperation extends SyncOperation {
      */
      */
     @Override
     @Override
     protected RemoteOperationResult run(OwnCloudClient client) {
     protected RemoteOperationResult run(OwnCloudClient client) {
-        RemoteOperationResult result = null;
+        RemoteOperationResult result;
         
         
         /// 1. check move validity
         /// 1. check move validity
         if (mTargetParentPath.startsWith(mSrcPath)) {
         if (mTargetParentPath.startsWith(mSrcPath)) {

+ 4 - 7
src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java

@@ -203,7 +203,7 @@ public class RefreshFolderOperation extends RemoteOperation {
 
 
             if (result.isSuccess()) {
             if (result.isSuccess()) {
                 // request for the synchronization of KEPT-IN-SYNC file contents
                 // request for the synchronization of KEPT-IN-SYNC file contents
-                startContentSynchronizations(mFilesToSyncContents, client);
+                startContentSynchronizations(mFilesToSyncContents);
             }
             }
         }
         }
         
         
@@ -453,12 +453,9 @@ public class RefreshFolderOperation extends RemoteOperation {
      * on.
      * on.
      * 
      * 
      * @param filesToSyncContents       Synchronization operations to execute.
      * @param filesToSyncContents       Synchronization operations to execute.
-     * @param client                    Interface to the remote ownCloud server.
      */
      */
-    private void startContentSynchronizations(
-            List<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
-        ) {
-        RemoteOperationResult contentsResult = null;
+    private void startContentSynchronizations(List<SynchronizeFileOperation> filesToSyncContents) {
+        RemoteOperationResult contentsResult;
         for (SynchronizeFileOperation op: filesToSyncContents) {
         for (SynchronizeFileOperation op: filesToSyncContents) {
             contentsResult = op.execute(mStorageManager, mContext);   // async
             contentsResult = op.execute(mStorageManager, mContext);   // async
             if (!contentsResult.isSuccess()) {
             if (!contentsResult.isSuccess()) {
@@ -487,7 +484,7 @@ public class RefreshFolderOperation extends RemoteOperation {
      *                  the operation.
      *                  the operation.
      */
      */
     private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
     private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
-        RemoteOperationResult result = null;
+        RemoteOperationResult result;
         
         
         // remote request 
         // remote request 
         GetRemoteSharesForFileOperation operation = 
         GetRemoteSharesForFileOperation operation = 

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

@@ -469,19 +469,6 @@ 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
      * 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}
      * a 'lost' file with the same full name as the {@link com.owncloud.android.datamodel.OCFile}

+ 126 - 86
src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -1,21 +1,20 @@
 /**
 /**
- *   ownCloud Android client application
+ * ownCloud Android client application
  *
  *
- *   @author David A. Velasco
- *   Copyright (C) 2016 ownCloud GmbH.
+ * @author David A. Velasco
+ * Copyright (C) 2016 ownCloud GmbH.
  *
  *
- *   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;
 package com.owncloud.android.operations;
@@ -24,11 +23,12 @@ import android.accounts.Account;
 import android.content.Context;
 import android.content.Context;
 import android.net.Uri;
 import android.net.Uri;
 
 
+import com.evernote.android.job.JobRequest;
+import com.evernote.android.job.util.Device;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
@@ -44,7 +44,6 @@ import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
 import com.owncloud.android.lib.resources.files.RemoteFile;
 import com.owncloud.android.lib.resources.files.RemoteFile;
 import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
 import com.owncloud.android.lib.resources.files.UploadRemoteFileOperation;
 import com.owncloud.android.operations.common.SyncOperation;
 import com.owncloud.android.operations.common.SyncOperation;
-import com.owncloud.android.utils.ConnectivityUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.MimeType;
 import com.owncloud.android.utils.MimeType;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.MimeTypeUtil;
@@ -52,14 +51,20 @@ import com.owncloud.android.utils.UriUtils;
 
 
 import org.apache.commons.httpclient.HttpStatus;
 import org.apache.commons.httpclient.HttpStatus;
 import org.apache.commons.httpclient.methods.RequestEntity;
 import org.apache.commons.httpclient.methods.RequestEntity;
+import org.lukhnos.nnio.file.Files;
+import org.lukhnos.nnio.file.Paths;
 
 
 import java.io.File;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStream;
+import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
 import java.util.HashSet;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.Set;
@@ -94,6 +99,8 @@ public class UploadFileOperation extends SyncOperation {
     private boolean mForceOverwrite = false;
     private boolean mForceOverwrite = false;
     private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
     private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY;
     private int mCreatedBy = CREATED_BY_USER;
     private int mCreatedBy = CREATED_BY_USER;
+    private boolean mOnWifiOnly = false;
+    private boolean mWhileChargingOnly = false;
 
 
     private boolean mWasRenamed = false;
     private boolean mWasRenamed = false;
     private long mOCUploadId = -1;
     private long mOCUploadId = -1;
@@ -147,7 +154,9 @@ public class UploadFileOperation extends SyncOperation {
                                boolean chunked,
                                boolean chunked,
                                boolean forceOverwrite,
                                boolean forceOverwrite,
                                int localBehaviour,
                                int localBehaviour,
-                               Context context
+                               Context context,
+                               boolean onWifiOnly,
+                               boolean whileChargingOnly
     ) {
     ) {
         if (account == null) {
         if (account == null) {
             throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " + "creation");
             throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " + "creation");
@@ -171,6 +180,8 @@ public class UploadFileOperation extends SyncOperation {
         } else {
         } else {
             mFile = file;
             mFile = file;
         }
         }
+        mOnWifiOnly = onWifiOnly;
+        mWhileChargingOnly = whileChargingOnly;
         mRemotePath = upload.getRemotePath();
         mRemotePath = upload.getRemotePath();
         mChunked = chunked;
         mChunked = chunked;
         mForceOverwrite = forceOverwrite;
         mForceOverwrite = forceOverwrite;
@@ -182,6 +193,14 @@ public class UploadFileOperation extends SyncOperation {
         mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
         mRemoteFolderToBeCreated = upload.isCreateRemoteFolder();
     }
     }
 
 
+    public boolean getIsWifiRequired() {
+        return mOnWifiOnly;
+    }
+
+    public boolean getIsChargingRequired() {
+        return mWhileChargingOnly;
+    }
+
     public Account getAccount() {
     public Account getAccount() {
         return mAccount;
         return mAccount;
     }
     }
@@ -237,7 +256,7 @@ public class UploadFileOperation extends SyncOperation {
         }
         }
     }
     }
 
 
-    public int getCreatedBy () {
+    public int getCreatedBy() {
         return mCreatedBy;
         return mCreatedBy;
     }
     }
 
 
@@ -249,9 +268,10 @@ public class UploadFileOperation extends SyncOperation {
         return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
         return mCreatedBy == CREATED_AS_INSTANT_VIDEO;
     }
     }
 
 
-    public void setOCUploadId(long id){
+    public void setOCUploadId(long id) {
         mOCUploadId = id;
         mOCUploadId = id;
     }
     }
+
     public long getOCUploadId() {
     public long getOCUploadId() {
         return mOCUploadId;
         return mOCUploadId;
     }
     }
@@ -260,14 +280,14 @@ public class UploadFileOperation extends SyncOperation {
         return mDataTransferListeners;
         return mDataTransferListeners;
     }
     }
 
 
-    public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
+    public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
         synchronized (mDataTransferListeners) {
         synchronized (mDataTransferListeners) {
             mDataTransferListeners.add(listener);
             mDataTransferListeners.add(listener);
         }
         }
         if (mEntity != null) {
         if (mEntity != null) {
-            ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
+            ((ProgressiveDataTransferer) mEntity).addDatatransferProgressListener(listener);
         }
         }
-        if(mUploadOperation != null){
+        if (mUploadOperation != null) {
             mUploadOperation.addDatatransferProgressListener(listener);
             mUploadOperation.addDatatransferProgressListener(listener);
         }
         }
     }
     }
@@ -277,14 +297,14 @@ public class UploadFileOperation extends SyncOperation {
             mDataTransferListeners.remove(listener);
             mDataTransferListeners.remove(listener);
         }
         }
         if (mEntity != null) {
         if (mEntity != null) {
-            ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
+            ((ProgressiveDataTransferer) mEntity).removeDatatransferProgressListener(listener);
         }
         }
-        if(mUploadOperation != null){
+        if (mUploadOperation != null) {
             mUploadOperation.removeDatatransferProgressListener(listener);
             mUploadOperation.removeDatatransferProgressListener(listener);
         }
         }
     }
     }
 
 
-    public void addRenameUploadListener (OnRenameListener listener) {
+    public void addRenameUploadListener(OnRenameListener listener) {
         mRenameUploadListener = listener;
         mRenameUploadListener = listener;
     }
     }
 
 
@@ -297,17 +317,18 @@ public class UploadFileOperation extends SyncOperation {
         File temporalFile = null;
         File temporalFile = null;
         File originalFile = new File(mOriginalStoragePath);
         File originalFile = new File(mOriginalStoragePath);
         File expectedFile = null;
         File expectedFile = null;
+        FileLock fileLock = null;
 
 
         try {
         try {
 
 
             /// Check that connectivity conditions are met and delays the upload otherwise
             /// Check that connectivity conditions are met and delays the upload otherwise
-            if (delayForWifi()) {
+            if (mOnWifiOnly && !Device.getNetworkType(mContext).equals(JobRequest.NetworkType.UNMETERED)) {
                 Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
                 Log_OC.d(TAG, "Upload delayed until WiFi is available: " + getRemotePath());
                 return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
                 return new RemoteOperationResult(ResultCode.DELAYED_FOR_WIFI);
             }
             }
 
 
             // Check if charging conditions are met and delays the upload otherwise
             // Check if charging conditions are met and delays the upload otherwise
-            if (delayForCharging()){
+            if (mWhileChargingOnly && !Device.isCharging(mContext)) {
                 Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
                 Log_OC.d(TAG, "Upload delayed until the device is charging: " + getRemotePath());
                 return new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
                 return new RemoteOperationResult(ResultCode.DELAYED_FOR_CHARGING);
             }
             }
@@ -372,20 +393,21 @@ public class UploadFileOperation extends SyncOperation {
             }
             }
 
 
             // Get the last modification date of the file from the file system
             // Get the last modification date of the file from the file system
-            Long timeStampLong = originalFile.lastModified()/1000;
+            Long timeStampLong = originalFile.lastModified() / 1000;
             String timeStamp = timeStampLong.toString();
             String timeStamp = timeStampLong.toString();
 
 
             /// perform the upload
             /// perform the upload
-            if ( mChunked &&
+            if (mChunked &&
                     (new File(mFile.getStoragePath())).length() >
                     (new File(mFile.getStoragePath())).length() >
-                            ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) {
+                            ChunkedUploadRemoteFileOperation.CHUNK_SIZE) {
                 mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, mFile.getStoragePath(),
                 mUploadOperation = new ChunkedUploadRemoteFileOperation(mContext, mFile.getStoragePath(),
                         mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict(), timeStamp);
                         mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict(), timeStamp);
             } else {
             } else {
                 mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(),
                 mUploadOperation = new UploadRemoteFileOperation(mFile.getStoragePath(),
                         mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict(), timeStamp);
                         mFile.getRemotePath(), mFile.getMimetype(), mFile.getEtagInConflict(), timeStamp);
             }
             }
-            Iterator <OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
+
+            Iterator<OnDatatransferProgressListener> listener = mDataTransferListeners.iterator();
             while (listener.hasNext()) {
             while (listener.hasNext()) {
                 mUploadOperation.addDatatransferProgressListener(listener.next());
                 mUploadOperation.addDatatransferProgressListener(listener.next());
             }
             }
@@ -394,23 +416,77 @@ public class UploadFileOperation extends SyncOperation {
                 throw new OperationCancelledException();
                 throw new OperationCancelledException();
             }
             }
 
 
+            FileChannel channel = null;
+            try {
+                channel = new RandomAccessFile(mFile.getStoragePath(), "rw").getChannel();
+                fileLock = channel.tryLock();
+            } catch (FileNotFoundException e) {
+                if (temporalFile == null) {
+                    String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath();
+                    mFile.setStoragePath(temporalPath);
+                    temporalFile = new File(temporalPath);
+
+                    result = copy(originalFile, temporalFile);
+
+                    if (result != null) {
+                        return result;
+                    } else {
+                        if (temporalFile.length() == originalFile.length()) {
+                            channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
+                            fileLock = channel.tryLock();
+                        } else {
+                            while (temporalFile.length() != originalFile.length()) {
+                                Files.deleteIfExists(Paths.get(temporalPath));
+                                result = copy(originalFile, temporalFile);
+
+                                if (result != null) {
+                                    return result;
+                                } else {
+                                    channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").
+                                            getChannel();
+                                    fileLock = channel.tryLock();
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    channel = new RandomAccessFile(temporalFile.getAbsolutePath(), "rw").getChannel();
+                    fileLock = channel.tryLock();
+                }
+            }
+
             result = mUploadOperation.execute(client);
             result = mUploadOperation.execute(client);
 
 
             /// move local temporal file or original file to its corresponding
             /// move local temporal file or original file to its corresponding
             // location in the ownCloud local folder
             // location in the ownCloud local folder
-            if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED ) {
+            if (!result.isSuccess() && result.getHttpCode() == HttpStatus.SC_PRECONDITION_FAILED) {
                 result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
                 result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
             }
             }
 
 
+        } catch (FileNotFoundException e) {
+            Log_OC.d(TAG, mOriginalStoragePath + " not exists anymore");
+            result = new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND);
+        } catch (OverlappingFileLockException e) {
+            Log_OC.d(TAG, "Overlapping file lock exception");
+            result = new RemoteOperationResult(ResultCode.LOCK_FAILED);
         } catch (Exception e) {
         } catch (Exception e) {
             result = new RemoteOperationResult(e);
             result = new RemoteOperationResult(e);
 
 
         } finally {
         } finally {
             mUploadStarted.set(false);
             mUploadStarted.set(false);
+
+            if (fileLock != null) {
+                try {
+                    fileLock.release();
+                } catch (IOException e) {
+                    Log_OC.e(TAG, "Failed to unlock file with path " + mOriginalStoragePath);
+                }
+            }
+
             if (temporalFile != null && !originalFile.equals(temporalFile)) {
             if (temporalFile != null && !originalFile.equals(temporalFile)) {
                 temporalFile.delete();
                 temporalFile.delete();
             }
             }
-            if (result == null){
+            if (result == null) {
                 result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
                 result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR);
             }
             }
             if (result.isSuccess()) {
             if (result.isSuccess()) {
@@ -418,7 +494,7 @@ public class UploadFileOperation extends SyncOperation {
                         result.getLogMessage());
                         result.getLogMessage());
             } else {
             } else {
                 if (result.getException() != null) {
                 if (result.getException() != null) {
-                    if(result.isCancelled()){
+                    if (result.isCancelled()) {
                         Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
                         Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath +
                                 ": " + result.getLogMessage());
                                 ": " + result.getLogMessage());
                     } else {
                     } else {
@@ -478,41 +554,6 @@ public class UploadFileOperation extends SyncOperation {
         return result;
         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() &&  PreferenceManager.instantPictureUploadViaWiFiOnly(mContext)
-        );
-        boolean delayInstantVideo = (
-            isInstantVideo() && PreferenceManager.instantVideoUploadViaWiFiOnly(mContext)
-        );
-        return (
-            (delayInstantPicture || delayInstantVideo) &&
-            !ConnectivityUtils.isAppConnectedViaUnmeteredWiFi(mContext)
-        );
-    }
-
-    /**
-     * Check if upload should be delayed due to not charging
-     *
-     * @return      'True' if the upload was delayed until device is charging, 'false' otherwise.
-     */
-    private boolean delayForCharging() {
-        boolean delayInstantPicture = isInstantPicture() &&
-                PreferenceManager.instantPictureUploadWhenChargingOnly(mContext);
-
-        boolean delayInstantVideo = isInstantVideo() && PreferenceManager.instantVideoUploadWhenChargingOnly(mContext);
-
-        return ((delayInstantPicture || delayInstantVideo) && !ConnectivityUtils.isCharging(mContext));
-    }
-
-
     /**
     /**
      * Checks the existence of the folder where the current file will be uploaded both
      * Checks the existence of the folder where the current file will be uploaded both
      * in the remote server and in the local database.
      * in the remote server and in the local database.
@@ -566,7 +607,8 @@ public class UploadFileOperation extends SyncOperation {
 
 
     /**
     /**
      * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
      * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false.
-     * New file is stored as mFile, original as mOldFile. 
+     * New file is stored as mFile, original as mOldFile.
+     *
      * @param newRemotePath new remote path
      * @param newRemotePath new remote path
      */
      */
     private void createNewOCFile(String newRemotePath) {
     private void createNewOCFile(String newRemotePath) {
@@ -615,8 +657,7 @@ public class UploadFileOperation extends SyncOperation {
             suffix = " (" + count + ")";
             suffix = " (" + count + ")";
             if (pos >= 0) {
             if (pos >= 0) {
                 check = existsFile(wc, remotePath + suffix + "." + extension);
                 check = existsFile(wc, remotePath + suffix + "." + extension);
-            }
-            else {
+            } else {
                 check = existsFile(wc, remotePath + suffix);
                 check = existsFile(wc, remotePath + suffix);
             }
             }
             count++;
             count++;
@@ -629,7 +670,7 @@ public class UploadFileOperation extends SyncOperation {
         }
         }
     }
     }
 
 
-    private boolean existsFile(OwnCloudClient client, String remotePath){
+    private boolean existsFile(OwnCloudClient client, String remotePath) {
         ExistenceCheckRemoteOperation existsOperation =
         ExistenceCheckRemoteOperation existsOperation =
                 new ExistenceCheckRemoteOperation(remotePath, mContext, false);
                 new ExistenceCheckRemoteOperation(remotePath, mContext, false);
         RemoteOperationResult result = existsOperation.execute(client);
         RemoteOperationResult result = existsOperation.execute(client);
@@ -667,10 +708,10 @@ public class UploadFileOperation extends SyncOperation {
      * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
      * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult},
      * TODO     use Exceptions instead
      * TODO     use Exceptions instead
      *
      *
-     * @param   sourceFile      Source file to copy.
-     * @param   targetFile      Target location to copy the file.
-     * @return  {@link RemoteOperationResult}
-     * @throws  IOException
+     * @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 {
     private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException {
         Log_OC.d(TAG, "Copying local file");
         Log_OC.d(TAG, "Copying local file");
@@ -758,10 +799,10 @@ public class UploadFileOperation extends SyncOperation {
      *
      *
      * TODO refactor both this and 'copy' in a single method
      * 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
+     * @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 {
     private void move(File sourceFile, File targetFile) throws IOException {
 
 
@@ -769,8 +810,8 @@ public class UploadFileOperation extends SyncOperation {
             File expectedFolder = targetFile.getParentFile();
             File expectedFolder = targetFile.getParentFile();
             expectedFolder.mkdirs();
             expectedFolder.mkdirs();
 
 
-            if (expectedFolder.isDirectory()){
-                if (!sourceFile.renameTo(targetFile)){
+            if (expectedFolder.isDirectory()) {
+                if (!sourceFile.renameTo(targetFile)) {
                     // try to copy and then delete
                     // try to copy and then delete
                     targetFile.createNewFile();
                     targetFile.createNewFile();
                     FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
                     FileChannel inChannel = new FileInputStream(sourceFile).getChannel();
@@ -778,12 +819,11 @@ public class UploadFileOperation extends SyncOperation {
                     try {
                     try {
                         inChannel.transferTo(0, inChannel.size(), outChannel);
                         inChannel.transferTo(0, inChannel.size(), outChannel);
                         sourceFile.delete();
                         sourceFile.delete();
-                    } catch (Exception e){
+                    } catch (Exception e) {
                         mFile.setStoragePath(""); // forget the local file
                         mFile.setStoragePath(""); // forget the local file
                         // by now, treat this as a success; the file was uploaded
                         // by now, treat this as a success; the file was uploaded
                         // the best option could be show a warning message
                         // the best option could be show a warning message
-                    }
-                    finally {
+                    } finally {
                         if (inChannel != null) {
                         if (inChannel != null) {
                             inChannel.close();
                             inChannel.close();
                         }
                         }
@@ -847,7 +887,7 @@ public class UploadFileOperation extends SyncOperation {
 
 
         // generate new Thumbnail
         // generate new Thumbnail
         final ThumbnailsCacheManager.ThumbnailGenerationTask task =
         final ThumbnailsCacheManager.ThumbnailGenerationTask task =
-            new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount);
+                new ThumbnailsCacheManager.ThumbnailGenerationTask(getStorageManager(), mAccount);
         task.execute(file);
         task.execute(file);
     }
     }
 
 

+ 1 - 1
src/main/java/org/nextcloud/providers/DocumentsStorageProvider.java → src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -18,7 +18,7 @@
  *
  *
  */
  */
 
 
-package org.nextcloud.providers;
+package com.owncloud.android.providers;
 
 
 import android.accounts.Account;
 import android.accounts.Account;
 import android.annotation.TargetApi;
 import android.annotation.TargetApi;

+ 92 - 11
src/main/java/com/owncloud/android/providers/FileContentProvider.java

@@ -74,6 +74,7 @@ public class FileContentProvider extends ContentProvider {
     private static final int EXTERNAL_LINKS = 8;
     private static final int EXTERNAL_LINKS = 8;
     private static final int ARBITRARY_DATA = 9;
     private static final int ARBITRARY_DATA = 9;
     private static final int VIRTUAL = 10;
     private static final int VIRTUAL = 10;
+    private static final int FILESYSTEM = 11;
 
 
     private static final String TAG = FileContentProvider.class.getSimpleName();
     private static final String TAG = FileContentProvider.class.getSimpleName();
 
 
@@ -209,6 +210,9 @@ public class FileContentProvider extends ContentProvider {
             case VIRTUAL:
             case VIRTUAL:
                 count = db.delete(ProviderTableMeta.VIRTUAL_TABLE_NAME, where, whereArgs);
                 count = db.delete(ProviderTableMeta.VIRTUAL_TABLE_NAME, where, whereArgs);
                 break;
                 break;
+            case FILESYSTEM:
+                count = db.delete(ProviderTableMeta.FILESYSTEM_TABLE_NAME, where, whereArgs);
+                break;
             default:
             default:
                 //Log_OC.e(TAG, "Unknown uri " + uri);
                 //Log_OC.e(TAG, "Unknown uri " + uri);
                 throw new IllegalArgumentException("Unknown uri: " + uri.toString());
                 throw new IllegalArgumentException("Unknown uri: " + uri.toString());
@@ -312,7 +316,6 @@ public class FileContentProvider extends ContentProvider {
                 if (uploadId > 0) {
                 if (uploadId > 0) {
                     insertedUploadUri =
                     insertedUploadUri =
                             ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_UPLOADS, uploadId);
                             ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_UPLOADS, uploadId);
-                    trimSuccessfulUploads(db);
                 } else {
                 } else {
                     throw new SQLException(ERROR + uri);
                     throw new SQLException(ERROR + uri);
 
 
@@ -365,6 +368,16 @@ public class FileContentProvider extends ContentProvider {
                 }
                 }
 
 
                 return insertedVirtualUri;
                 return insertedVirtualUri;
+            case FILESYSTEM:
+                Uri insertedFilesystemUri = null;
+                long filesystedId = db.insert(ProviderTableMeta.FILESYSTEM_TABLE_NAME, null, values);
+                if (filesystedId > 0) {
+                    insertedFilesystemUri =
+                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILESYSTEM, filesystedId);
+                } else {
+                    throw new SQLException("ERROR " + uri);
+                }
+                return insertedFilesystemUri;
             default:
             default:
                 throw new IllegalArgumentException("Unknown uri id: " + uri);
                 throw new IllegalArgumentException("Unknown uri id: " + uri);
         }
         }
@@ -417,6 +430,7 @@ public class FileContentProvider extends ContentProvider {
         mUriMatcher.addURI(authority, "external_links", EXTERNAL_LINKS);
         mUriMatcher.addURI(authority, "external_links", EXTERNAL_LINKS);
         mUriMatcher.addURI(authority, "arbitrary_data", ARBITRARY_DATA);
         mUriMatcher.addURI(authority, "arbitrary_data", ARBITRARY_DATA);
         mUriMatcher.addURI(authority, "virtual", VIRTUAL);
         mUriMatcher.addURI(authority, "virtual", VIRTUAL);
+        mUriMatcher.addURI(authority, "filesystem", FILESYSTEM);
 
 
         return true;
         return true;
     }
     }
@@ -518,6 +532,13 @@ public class FileContentProvider extends ContentProvider {
                     sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1));
                     sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1));
                 }
                 }
                 break;
                 break;
+            case FILESYSTEM:
+                sqlQuery.setTables(ProviderTableMeta.FILESYSTEM_TABLE_NAME);
+                if (uri.getPathSegments().size() > 1) {
+                    sqlQuery.appendWhere(ProviderTableMeta._ID + "="
+                            + uri.getPathSegments().get(1));
+                }
+                break;
             default:
             default:
                 throw new IllegalArgumentException("Unknown uri id: " + uri);
                 throw new IllegalArgumentException("Unknown uri id: " + uri);
         }
         }
@@ -549,6 +570,9 @@ public class FileContentProvider extends ContentProvider {
                 default: // Files
                 default: // Files
                     order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
                     order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
                     break;
                     break;
+                case FILESYSTEM:
+                    order = ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH;
+                    break;
             }
             }
         } else {
         } else {
             order = sortOrder;
             order = sortOrder;
@@ -594,12 +618,13 @@ public class FileContentProvider extends ContentProvider {
                 return db.update(ProviderTableMeta.CAPABILITIES_TABLE_NAME, values, selection, selectionArgs);
                 return db.update(ProviderTableMeta.CAPABILITIES_TABLE_NAME, values, selection, selectionArgs);
             case UPLOADS:
             case UPLOADS:
                 int ret = db.update(ProviderTableMeta.UPLOADS_TABLE_NAME, values, selection, selectionArgs);
                 int ret = db.update(ProviderTableMeta.UPLOADS_TABLE_NAME, values, selection, selectionArgs);
-                trimSuccessfulUploads(db);
                 return ret;
                 return ret;
             case SYNCED_FOLDERS:
             case SYNCED_FOLDERS:
                 return db.update(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, values, selection, selectionArgs);
                 return db.update(ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME, values, selection, selectionArgs);
             case ARBITRARY_DATA:
             case ARBITRARY_DATA:
                 return db.update(ProviderTableMeta.ARBITRARY_DATA_TABLE_NAME, values, selection, selectionArgs);
                 return db.update(ProviderTableMeta.ARBITRARY_DATA_TABLE_NAME, values, selection, selectionArgs);
+            case FILESYSTEM:
+                return db.update(ProviderTableMeta.FILESYSTEM_TABLE_NAME, values, selection, selectionArgs);
             default:
             default:
                 return db.update(ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs);
                 return db.update(ProviderTableMeta.FILE_TABLE_NAME, values, selection, selectionArgs);
         }
         }
@@ -662,6 +687,9 @@ public class FileContentProvider extends ContentProvider {
 
 
             // Create virtual table
             // Create virtual table
             createVirtualTable(db);
             createVirtualTable(db);
+
+            // Create filesystem table
+            createFileSystemTable(db);
         }
         }
 
 
         @Override
         @Override
@@ -669,14 +697,14 @@ public class FileContentProvider extends ContentProvider {
             Log_OC.i(SQL, "Entering in onUpgrade");
             Log_OC.i(SQL, "Entering in onUpgrade");
             boolean upgraded = false;
             boolean upgraded = false;
             if (oldVersion == 1 && newVersion >= 2) {
             if (oldVersion == 1 && newVersion >= 2) {
-                Log_OC.i(SQL, "Entering in the #1 ADD in onUpgrade");
+                Log_OC.i(SQL, "Entering in the #2 ADD in onUpgrade");
                 db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
                 db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
                         ADD_COLUMN + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " +
                         ADD_COLUMN + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER " +
                         " DEFAULT 0");
                         " DEFAULT 0");
                 upgraded = true;
                 upgraded = true;
             }
             }
             if (oldVersion < 3 && newVersion >= 3) {
             if (oldVersion < 3 && newVersion >= 3) {
-                Log_OC.i(SQL, "Entering in the #2 ADD in onUpgrade");
+                Log_OC.i(SQL, "Entering in the #3 ADD in onUpgrade");
                 db.beginTransaction();
                 db.beginTransaction();
                 try {
                 try {
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
@@ -696,7 +724,7 @@ public class FileContentProvider extends ContentProvider {
                 }
                 }
             }
             }
             if (oldVersion < 4 && newVersion >= 4) {
             if (oldVersion < 4 && newVersion >= 4) {
-                Log_OC.i(SQL, "Entering in the #3 ADD in onUpgrade");
+                Log_OC.i(SQL, "Entering in the #4 ADD in onUpgrade");
                 db.beginTransaction();
                 db.beginTransaction();
                 try {
                 try {
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
@@ -720,7 +748,7 @@ public class FileContentProvider extends ContentProvider {
             }
             }
 
 
             if (oldVersion < 5 && newVersion >= 5) {
             if (oldVersion < 5 && newVersion >= 5) {
-                Log_OC.i(SQL, "Entering in the #4 ADD in onUpgrade");
+                Log_OC.i(SQL, "Entering in the #5 ADD in onUpgrade");
                 db.beginTransaction();
                 db.beginTransaction();
                 try {
                 try {
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
@@ -738,7 +766,7 @@ public class FileContentProvider extends ContentProvider {
             }
             }
 
 
             if (oldVersion < 6 && newVersion >= 6) {
             if (oldVersion < 6 && newVersion >= 6) {
-                Log_OC.i(SQL, "Entering in the #5 ADD in onUpgrade");
+                Log_OC.i(SQL, "Entering in the #6 ADD in onUpgrade");
                 db.beginTransaction();
                 db.beginTransaction();
                 try {
                 try {
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
@@ -843,6 +871,7 @@ public class FileContentProvider extends ContentProvider {
                     db.endTransaction();
                     db.endTransaction();
                 }
                 }
             }
             }
+
             if (!upgraded) {
             if (!upgraded) {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
             }
@@ -937,7 +966,6 @@ public class FileContentProvider extends ContentProvider {
                 } finally {
                 } finally {
                     db.endTransaction();
                     db.endTransaction();
                 }
                 }
-
             }
             }
 
 
             if (!upgraded) {
             if (!upgraded) {
@@ -1033,6 +1061,43 @@ public class FileContentProvider extends ContentProvider {
             if (!upgraded) {
             if (!upgraded) {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
             }
+
+            if (oldVersion < 23 && newVersion >= 23) {
+                Log_OC.i(SQL, "Entering in the #23 adding type column for synced folders, Create filesystem table");
+                db.beginTransaction();
+                try {
+                    // add type column default being CUSTOM (0)
+                    Log_OC.i(SQL, "Add type column and default value 0 (CUSTOM) to synced_folders table");
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.SYNCED_FOLDERS_TABLE_NAME +
+                            ADD_COLUMN + ProviderTableMeta.SYNCED_FOLDER_TYPE +
+                            " INTEGER " + " DEFAULT 0");
+
+                    Log_OC.i(SQL, "Add charging and wifi columns to uploads");
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.UPLOADS_TABLE_NAME +
+                            ADD_COLUMN + ProviderTableMeta.UPLOADS_IS_WIFI_ONLY +
+                            " INTEGER " + " DEFAULT 0");
+
+                    db.execSQL(ALTER_TABLE + ProviderTableMeta.UPLOADS_TABLE_NAME +
+                            ADD_COLUMN + ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY +
+                            " INTEGER " + " DEFAULT 0");
+
+                    // create Filesystem table
+                    Log_OC.i(SQL, "Create filesystem table");
+                    createFileSystemTable(db);
+
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+
+                } catch (Throwable t) {
+                    Log_OC.e(TAG, "ERROR!", t);
+                } finally {
+                    db.endTransaction();
+                }
+
+                if (!upgraded) {
+                    Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+                }
+            }
         }
         }
     }
     }
 
 
@@ -1135,6 +1200,8 @@ public class FileContentProvider extends ContentProvider {
                 + ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + INTEGER  // boolean
                 + ProviderTableMeta.UPLOADS_IS_CREATE_REMOTE_FOLDER + INTEGER  // boolean
                 + ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + INTEGER
                 + ProviderTableMeta.UPLOADS_UPLOAD_END_TIMESTAMP + INTEGER
                 + ProviderTableMeta.UPLOADS_LAST_RESULT + INTEGER     // Upload LastResult
                 + ProviderTableMeta.UPLOADS_LAST_RESULT + INTEGER     // Upload LastResult
+                + ProviderTableMeta.UPLOADS_IS_WHILE_CHARGING_ONLY + INTEGER  // boolean
+                + ProviderTableMeta.UPLOADS_IS_WIFI_ONLY + INTEGER // boolean
                 + ProviderTableMeta.UPLOADS_CREATED_BY + " INTEGER );"    // Upload createdBy
                 + ProviderTableMeta.UPLOADS_CREATED_BY + " INTEGER );"    // Upload createdBy
         );
         );
 
 
@@ -1159,7 +1226,8 @@ public class FileContentProvider extends ContentProvider {
                 + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, "            // enabled
                 + ProviderTableMeta.SYNCED_FOLDER_ENABLED + " INTEGER, "            // enabled
                 + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, "  // subfolder by date
                 + ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_BY_DATE + " INTEGER, "  // subfolder by date
                 + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + "  TEXT, "              // account
                 + ProviderTableMeta.SYNCED_FOLDER_ACCOUNT + "  TEXT, "              // account
-                + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER );"     // upload action
+                + ProviderTableMeta.SYNCED_FOLDER_UPLOAD_ACTION + " INTEGER, "     // upload action
+                + ProviderTableMeta.SYNCED_FOLDER_TYPE + " INTEGER );"               // type
         );
         );
     }
     }
 
 
@@ -1170,7 +1238,7 @@ public class FileContentProvider extends ContentProvider {
                 + ProviderTableMeta.EXTERNAL_LINKS_LANGUAGE + " TEXT, "     // language
                 + ProviderTableMeta.EXTERNAL_LINKS_LANGUAGE + " TEXT, "     // language
                 + ProviderTableMeta.EXTERNAL_LINKS_TYPE + " INTEGER, "      // type
                 + ProviderTableMeta.EXTERNAL_LINKS_TYPE + " INTEGER, "      // type
                 + ProviderTableMeta.EXTERNAL_LINKS_NAME + " TEXT, "         // name
                 + ProviderTableMeta.EXTERNAL_LINKS_NAME + " TEXT, "         // name
-                + ProviderTableMeta.EXTERNAL_LINKS_URL + " TEXT )"          // url
+                + ProviderTableMeta.EXTERNAL_LINKS_URL + " TEXT );"          // url
         );
         );
     }
     }
 
 
@@ -1179,7 +1247,7 @@ public class FileContentProvider extends ContentProvider {
                 + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "      // id
                 + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "      // id
                 + ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " TEXT, " // cloud id (account name + FQDN)
                 + ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID + " TEXT, " // cloud id (account name + FQDN)
                 + ProviderTableMeta.ARBITRARY_DATA_KEY + " TEXT, "      // key
                 + ProviderTableMeta.ARBITRARY_DATA_KEY + " TEXT, "      // key
-                + ProviderTableMeta.ARBITRARY_DATA_VALUE + " TEXT) "    // value
+                + ProviderTableMeta.ARBITRARY_DATA_VALUE + " TEXT );"    // value
         );
         );
     }
     }
 
 
@@ -1191,6 +1259,19 @@ public class FileContentProvider extends ContentProvider {
         );
         );
     }
     }
 
 
+    private void createFileSystemTable(SQLiteDatabase db) {
+        db.execSQL("CREATE TABLE " + ProviderTableMeta.FILESYSTEM_TABLE_NAME + "("
+                + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "      // id
+                + ProviderTableMeta.FILESYSTEM_FILE_LOCAL_PATH + " TEXT, "
+                + ProviderTableMeta.FILESYSTEM_FILE_IS_FOLDER + " INTEGER, "
+                + ProviderTableMeta.FILESYSTEM_FILE_FOUND_RECENTLY + " LONG, "
+                + ProviderTableMeta.FILESYSTEM_FILE_SENT_FOR_UPLOAD + " INTEGER, "
+                + ProviderTableMeta.FILESYSTEM_SYNCED_FOLDER_ID + " STRING, "
+                + ProviderTableMeta.FILESYSTEM_FILE_MODIFIED + " LONG );"
+        );
+    }
+
+
     /**
     /**
      * Version 10 of database does not modify its scheme. It coincides with the upgrade of the ownCloud account names
      * 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
      * structure to include in it the path to the server instance. Updating the account names and path to local files

+ 0 - 237
src/main/java/com/owncloud/android/services/AdvancedFileAlterationListener.java

@@ -1,237 +0,0 @@
-/**
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * Copyright (C) 2017 Mario Danic
- * <p>
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- * <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 Affero General Public License for more details.
- * <p>
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package com.owncloud.android.services;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.ExifInterface;
-import android.os.Handler;
-import android.text.TextUtils;
-
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.support.PersistableBundleCompat;
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.ArbitraryDataProvider;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.ui.activity.Preferences;
-import com.owncloud.android.utils.FileStorageUtils;
-
-import org.apache.commons.io.monitor.FileAlterationListener;
-import org.apache.commons.io.monitor.FileAlterationObserver;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TimeZone;
-
-/**
- * Magical file alteration listener
- */
-
-public class AdvancedFileAlterationListener implements FileAlterationListener {
-
-    public static final String TAG = "AdvancedFileAlterationListener";
-    public static final int DELAY_INVOCATION_MS = 2500;
-    private Context context;
-    private boolean lightVersion;
-
-    private SyncedFolder syncedFolder;
-
-    private Map<String, Runnable> uploadMap = new HashMap<>();
-    private Handler handler = new Handler();
-
-    public AdvancedFileAlterationListener(SyncedFolder syncedFolder, boolean lightVersion) {
-        super();
-
-        context = MainApp.getAppContext();
-        this.lightVersion = lightVersion;
-        this.syncedFolder = syncedFolder;
-    }
-
-    @Override
-    public void onStart(FileAlterationObserver observer) {
-        // This method is intentionally empty
-    }
-
-    @Override
-    public void onDirectoryCreate(File directory) {
-        // This method is intentionally empty
-    }
-
-    @Override
-    public void onDirectoryChange(File directory) {
-        // This method is intentionally empty
-    }
-
-    @Override
-    public void onDirectoryDelete(File directory) {
-        // This method is intentionally empty
-    }
-
-    @Override
-    public void onFileCreate(final File file) {
-        onFileCreate(file, DELAY_INVOCATION_MS);
-    }
-
-    public void onFileCreate(final File file, int delay) {
-        if (file != null) {
-            uploadMap.put(file.getAbsolutePath(), null);
-
-            String mimetypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath());
-            Long lastModificationTime = file.lastModified();
-            final Locale currentLocale = context.getResources().getConfiguration().locale;
-
-            if ("image/jpeg".equalsIgnoreCase(mimetypeString) || "image/tiff".equalsIgnoreCase(mimetypeString)) {
-                try {
-                    ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
-                    String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
-                    if (!TextUtils.isEmpty(exifDate)) {
-                        ParsePosition pos = new ParsePosition(0);
-                        SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale);
-                        sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
-                        Date dateTime = sFormatter.parse(exifDate, pos);
-                        lastModificationTime = dateTime.getTime();
-                    }
-
-                } catch (IOException e) {
-                    Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage());
-                }
-            }
-
-
-            final Long finalLastModificationTime = lastModificationTime;
-
-            Runnable runnable = new Runnable() {
-                @Override
-                public void run() {
-
-                    String remotePath;
-                    boolean subfolderByDate;
-                    boolean chargingOnly;
-                    boolean wifiOnly;
-                    Integer uploadAction;
-                    String accountName = syncedFolder.getAccount();
-
-                    if (lightVersion) {
-                        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context
-                                .getContentResolver());
-
-                        Resources resources = MainApp.getAppContext().getResources();
-
-                        remotePath = resources.getString(R.string.syncedFolder_remote_folder) + OCFile.PATH_SEPARATOR +
-                                new File(syncedFolder.getLocalPath()).getName() + OCFile.PATH_SEPARATOR;
-                        subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders);
-                        chargingOnly = resources.getBoolean(R.bool.syncedFolder_light_on_charging);
-                        wifiOnly = arbitraryDataProvider.getBooleanValue(accountName,
-                                Preferences.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI);
-                        String uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour);
-                        uploadAction = getUploadAction(uploadActionString);
-                    } else {
-                        remotePath = syncedFolder.getRemotePath();
-                        subfolderByDate = syncedFolder.getSubfolderByDate();
-                        chargingOnly = syncedFolder.getChargingOnly();
-                        wifiOnly = syncedFolder.getWifiOnly();
-                        uploadAction = syncedFolder.getUploadAction();
-                    }
-
-                    PersistableBundleCompat bundle = new PersistableBundleCompat();
-                    bundle.putString(AutoUploadJob.LOCAL_PATH, file.getAbsolutePath());
-                    bundle.putString(AutoUploadJob.REMOTE_PATH, FileStorageUtils.getInstantUploadFilePath(
-                            currentLocale,
-                            remotePath, file.getName(),
-                            finalLastModificationTime,
-                            subfolderByDate));
-                    bundle.putString(AutoUploadJob.ACCOUNT, accountName);
-                    bundle.putInt(AutoUploadJob.UPLOAD_BEHAVIOUR, uploadAction);
-
-                    new JobRequest.Builder(AutoUploadJob.TAG)
-                            .setExecutionWindow(30_000L, 80_000L)
-                            .setRequiresCharging(chargingOnly)
-                            .setRequiredNetworkType(wifiOnly ? JobRequest.NetworkType.UNMETERED :
-                                    JobRequest.NetworkType.ANY)
-                            .setExtras(bundle)
-                            .setPersisted(false)
-                            .setRequirementsEnforced(true)
-                            .setUpdateCurrent(false)
-                            .build()
-                            .schedule();
-
-                    uploadMap.remove(file.getAbsolutePath());
-                }
-            };
-
-            uploadMap.put(file.getAbsolutePath(), runnable);
-            handler.postDelayed(runnable, delay);
-        }
-    }
-
-    private Integer getUploadAction(String action) {
-        switch (action) {
-            case "LOCAL_BEHAVIOUR_FORGET":
-                return FileUploader.LOCAL_BEHAVIOUR_FORGET;
-            case "LOCAL_BEHAVIOUR_MOVE":
-                return FileUploader.LOCAL_BEHAVIOUR_MOVE;
-            case "LOCAL_BEHAVIOUR_DELETE":
-                return FileUploader.LOCAL_BEHAVIOUR_DELETE;
-            default:
-                return FileUploader.LOCAL_BEHAVIOUR_FORGET;
-        }
-    }
-
-    @Override
-    public void onFileChange(File file) {
-        onFileChange(file, 2500);
-    }
-
-    public void onFileChange(File file, int delay) {
-        Runnable runnable;
-        if ((runnable = uploadMap.get(file.getAbsolutePath())) != null) {
-            handler.removeCallbacks(runnable);
-            handler.postDelayed(runnable, delay);
-        }
-    }
-
-    @Override
-    public void onFileDelete(File file) {
-        Runnable runnable;
-        if ((runnable = uploadMap.get(file.getAbsolutePath())) != null) {
-            handler.removeCallbacks(runnable);
-            uploadMap.remove(file.getAbsolutePath());
-        }
-    }
-
-    @Override
-    public void onStop(FileAlterationObserver observer) {
-        // This method is intentionally empty
-    }
-
-    public int getActiveTasksCount() {
-        return uploadMap.size();
-    }
-}

+ 0 - 81
src/main/java/com/owncloud/android/services/AutoUploadJob.java

@@ -1,81 +0,0 @@
-/**
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2017 Mario Danic
- * Copyright (C) 2016 Tobias Kaminsky
- * Copyright (C) 2016 Nextcloud
- * <p>
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- * <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 AFFERO GENERAL PUBLIC LICENSE for more details.
- * <p>
- * You should have received a copy of the GNU Affero General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.services;
-
-import android.accounts.Account;
-import android.content.Context;
-import android.support.annotation.NonNull;
-
-import com.evernote.android.job.Job;
-import com.evernote.android.job.util.support.PersistableBundleCompat;
-import com.owncloud.android.MainApp;
-import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.operations.UploadFileOperation;
-import com.owncloud.android.utils.MimeTypeUtil;
-
-import java.io.File;
-
-public class AutoUploadJob extends Job {
-    public static final String TAG = "AutoUploadJob";
-
-    public static final String LOCAL_PATH = "filePath";
-    public static final String REMOTE_PATH = "remotePath";
-    public static final String ACCOUNT = "account";
-    public static final String UPLOAD_BEHAVIOUR = "uploadBehaviour";
-
-    @NonNull
-    @Override
-    protected Result onRunJob(Params params) {
-        final Context context = MainApp.getAppContext();
-        PersistableBundleCompat bundle = params.getExtras();
-        final String filePath = bundle.getString(LOCAL_PATH, "");
-        final String remotePath = bundle.getString(REMOTE_PATH, "");
-        final Account account = AccountUtils.getOwnCloudAccountByName(context, bundle.getString(ACCOUNT, ""));
-        final Integer uploadBehaviour = bundle.getInt(UPLOAD_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_FORGET);
-
-
-        File file = new File(filePath);
-
-
-        // File can be deleted between job generation and job execution. If file does not exist, just ignore it
-        if (file.exists()) {
-            final String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath());
-
-            final FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-            requester.uploadNewFile(
-                    context,
-                    account,
-                    filePath,
-                    remotePath,
-                    uploadBehaviour,
-                    mimeType,
-                    true,           // create parent folder if not existent
-                    UploadFileOperation.CREATED_AS_INSTANT_PICTURE
-            );
-        }
-
-
-        return Result.SUCCESS;
-    }
-}

+ 1 - 1
src/main/java/com/owncloud/android/services/OperationsService.java

@@ -678,7 +678,7 @@ public class OperationsService extends Service {
                     // Move file/folder
                     // Move file/folder
                     String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
                     String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
                     String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
                     String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
-                    operation = new MoveFileOperation(remotePath, newParentPath, account);
+                    operation = new MoveFileOperation(remotePath, newParentPath);
 
 
                 } else if (action.equals(ACTION_COPY_FILE)) {
                 } else if (action.equals(ACTION_COPY_FILE)) {
                     // Copy file/folder
                     // Copy file/folder

+ 0 - 402
src/main/java/com/owncloud/android/services/observer/AdvancedFileAlterationObserver.java

@@ -1,402 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Original source code:
- * https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/monitor/FileAlterationObserver.java
- *
- * Modified by Mario Danic
- * Changes are Copyright (C) 2017 Mario Danic
- * Copyright (C) 2017 Nextcloud GmbH
- *
- * All changes are under the same licence as the original.
- *
- */
-package com.owncloud.android.services.observer;
-
-import android.os.SystemClock;
-
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.services.AdvancedFileAlterationListener;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.comparator.NameFileComparator;
-import org.apache.commons.io.monitor.FileAlterationObserver;
-import org.apache.commons.io.monitor.FileEntry;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-public class AdvancedFileAlterationObserver extends FileAlterationObserver implements Serializable {
-
-    private static final long serialVersionUID = 1185122225658782848L;
-    private static final int DELAY_INVOCATION_MS = 2500;
-    private final List<AdvancedFileAlterationListener> listeners = new CopyOnWriteArrayList<>();
-    private FileEntry rootEntry;
-    private FileFilter fileFilter;
-    private Comparator<File> comparator;
-    private SyncedFolder syncedFolder;
-
-    private static final FileEntry[] EMPTY_ENTRIES = new FileEntry[0];
-    
-    public AdvancedFileAlterationObserver(SyncedFolder syncedFolder, FileFilter fileFilter) {
-        super(syncedFolder.getLocalPath(), fileFilter);
-
-        this.rootEntry = new FileEntry(new File(syncedFolder.getLocalPath()));
-        this.fileFilter = fileFilter;
-        this.syncedFolder = syncedFolder;
-        comparator = NameFileComparator.NAME_SYSTEM_COMPARATOR;
-    }
-
-    public long getSyncedFolderID() {
-        return syncedFolder.getId();
-    }
-
-    public SyncedFolder getSyncedFolder() {
-        return syncedFolder;
-    }
-
-    /**
-     * Return the directory being observed.
-     *
-     * @return the directory being observed
-     */
-    public File getDirectory() {
-        return rootEntry.getFile();
-    }
-
-    /**
-     * Return the fileFilter.
-     *
-     * @return the fileFilter
-     * @since 2.1
-     */
-    public FileFilter getFileFilter() {
-        return fileFilter;
-    }
-
-    public FileEntry getRootEntry() {
-        return rootEntry;
-    }
-
-    public void setRootEntry(FileEntry rootEntry) {
-        this.rootEntry = rootEntry;
-    }
-
-    /**
-     * Add a file system listener.
-     *
-     * @param listener The file system listener
-     */
-    public void addListener(final AdvancedFileAlterationListener listener) {
-        if (listener != null) {
-            listeners.add(listener);
-        }
-    }
-
-    /**
-     * Remove a file system listener.
-     *
-     * @param listener The file system listener
-     */
-    public void removeListener(final AdvancedFileAlterationListener listener) {
-        if (listener != null) {
-            while (listeners.remove(listener)) {
-            }
-        }
-    }
-
-    /**
-     * Returns the set of registered file system listeners.
-     *
-     * @return The file system listeners
-     */
-    public Iterable<AdvancedFileAlterationListener> getMagicListeners() {
-        return listeners;
-    }
-
-    /**
-     * Does nothing - hack for the monitor
-     *
-     *
-     */
-    public void initialize() {
-        // does nothing - hack the monitor
-    }
-
-
-    /**
-     * Initializes everything
-     *
-     * @throws Exception if an error occurs
-     */
-    public void init() throws Exception {
-        rootEntry.refresh(rootEntry.getFile());
-        final FileEntry[] children = doListFiles(rootEntry.getFile(), rootEntry);
-        rootEntry.setChildren(children);
-    }
-
-
-    /**
-     * Final processing.
-     *
-     * @throws Exception if an error occurs
-     */
-    public void destroy() throws Exception {
-        Iterator iterator = getMagicListeners().iterator();
-        while (iterator.hasNext()) {
-            AdvancedFileAlterationListener AdvancedFileAlterationListener = (AdvancedFileAlterationListener) iterator.next();
-            while (AdvancedFileAlterationListener.getActiveTasksCount() > 0) {
-                SystemClock.sleep(250);
-            }
-        }
-    }
-
-    public void checkAndNotifyNow() {
-                /* fire onStart() */
-        for (final AdvancedFileAlterationListener listener : listeners) {
-            listener.onStart(this);
-        }
-
-        /* fire directory/file events */
-        final File rootFile = rootEntry.getFile();
-        if (rootFile.exists()) {
-            checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile), 0);
-        } else if (rootEntry.isExists()) {
-            try {
-                // try to init once more
-                init();
-                if (rootEntry.getFile().exists()) {
-                    checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootEntry.getFile()), 0);
-                } else {
-                    checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, 0);
-                }
-            } catch (Exception e) {
-                Log_OC.d("AdvancedFileAlterationObserver", "Failed getting an observer to intialize " + e);
-                checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, 0);
-            }
-        } // else didn't exist and still doesn't
-
-        /* fire onStop() */
-        for (final AdvancedFileAlterationListener listener : listeners) {
-            listener.onStop(this);
-        }
-    }
-    
-    /**
-     * Check whether the file and its children have been created, modified or deleted.
-     */
-    public void checkAndNotify() {
-
-        /* fire onStart() */
-        for (final AdvancedFileAlterationListener listener : listeners) {
-            listener.onStart(this);
-        }
-
-        /* fire directory/file events */
-        final File rootFile = rootEntry.getFile();
-        if (rootFile.exists()) {
-            checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootFile), DELAY_INVOCATION_MS);
-        } else if (rootEntry.isExists()) {
-            try {
-                // try to init once more
-                init();
-                if (rootEntry.getFile().exists()) {
-                    checkAndNotify(rootEntry, rootEntry.getChildren(), listFiles(rootEntry.getFile()),
-                            DELAY_INVOCATION_MS);
-                } else {
-                    checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, DELAY_INVOCATION_MS);
-                }
-            } catch (Exception e) {
-                Log_OC.d("AdvancedFileAlterationObserver", "Failed getting an observer to intialize " + e);
-                checkAndNotify(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, DELAY_INVOCATION_MS);
-            }
-        } // else didn't exist and still doesn't
-
-        /* fire onStop() */
-        for (final AdvancedFileAlterationListener listener : listeners) {
-            listener.onStop(this);
-        }
-    }
-
-    /**
-     * Compare two file lists for files which have been created, modified or deleted.
-     *
-     * @param parent   The parent entry
-     * @param previous The original list of files
-     * @param files    The current list of files
-     */
-    private void checkAndNotify(final FileEntry parent, final FileEntry[] previous, final File[] files, int delay) {
-        if (files != null && files.length > 0) {
-            int c = 0;
-            final FileEntry[] current = files.length > 0 ? new FileEntry[files.length] : EMPTY_ENTRIES;
-            for (final FileEntry entry : previous) {
-                while (c < files.length && comparator.compare(entry.getFile(), files[c]) > 0) {
-                    current[c] = createFileEntry(parent, files[c]);
-                    doCreate(current[c], delay);
-                    c++;
-                }
-                if (c < files.length && comparator.compare(entry.getFile(), files[c]) == 0) {
-                    doMatch(entry, files[c], delay);
-                    checkAndNotify(entry, entry.getChildren(), listFiles(files[c]), delay);
-                    current[c] = entry;
-                    c++;
-                } else {
-                    checkAndNotify(entry, entry.getChildren(), FileUtils.EMPTY_FILE_ARRAY, delay);
-                    doDelete(entry);
-                }
-            }
-            for (; c < files.length; c++) {
-                current[c] = createFileEntry(parent, files[c]);
-                doCreate(current[c], delay);
-            }
-            parent.setChildren(current);
-        }
-    }
-
-    /**
-     * Create a new file entry for the specified file.
-     *
-     * @param parent The parent file entry
-     * @param file   The file to create an entry for
-     * @return A new file entry
-     */
-    private FileEntry createFileEntry(final FileEntry parent, final File file) {
-        final FileEntry entry = parent.newChildInstance(file);
-        entry.refresh(file);
-        final FileEntry[] children = doListFiles(file, entry);
-        entry.setChildren(children);
-        return entry;
-    }
-
-    /**
-     * List the files
-     *
-     * @param file  The file to list files for
-     * @param entry the parent entry
-     * @return The child files
-     */
-    private FileEntry[] doListFiles(File file, FileEntry entry) {
-        final File[] files = listFiles(file);
-        final FileEntry[] children = files.length > 0 ? new FileEntry[files.length] : EMPTY_ENTRIES;
-        for (int i = 0; i < files.length; i++) {
-            children[i] = createFileEntry(entry, files[i]);
-        }
-        return children;
-    }
-
-    /**
-     * Fire directory/file created events to the registered listeners.
-     *
-     * @param entry The file entry
-     */
-    private void doCreate(final FileEntry entry, int delay) {
-        for (final AdvancedFileAlterationListener listener : listeners) {
-            if (entry.isDirectory()) {
-                listener.onDirectoryCreate(entry.getFile());
-            } else {
-                listener.onFileCreate(entry.getFile(), delay);
-            }
-        }
-        final FileEntry[] children = entry.getChildren();
-        for (final FileEntry aChildren : children) {
-            doCreate(aChildren, delay);
-        }
-    }
-
-    /**
-     * Fire directory/file change events to the registered listeners.
-     *
-     * @param entry The previous file system entry
-     * @param file  The current file
-     */
-    private void doMatch(final FileEntry entry, final File file, int delay) {
-        if (entry.refresh(file)) {
-            for (final AdvancedFileAlterationListener listener : listeners) {
-                if (entry.isDirectory()) {
-                    listener.onDirectoryChange(file);
-                } else {
-                    listener.onFileChange(file, delay);
-                }
-            }
-        }
-    }
-
-    /**
-     * Fire directory/file delete events to the registered listeners.
-     *
-     * @param entry The file entry
-     */
-    private void doDelete(final FileEntry entry) {
-        for (final AdvancedFileAlterationListener listener : listeners) {
-            if (entry.isDirectory()) {
-                listener.onDirectoryDelete(entry.getFile());
-            } else {
-                listener.onFileDelete(entry.getFile());
-            }
-        }
-    }
-
-    /**
-     * List the contents of a directory
-     *
-     * @param file The file to list the contents of
-     * @return the directory contents or a zero length array if
-     * the empty or the file is not a directory
-     */
-    private File[] listFiles(final File file) {
-        File[] children = null;
-        if (file.isDirectory()) {
-            children = fileFilter == null ? file.listFiles() : file.listFiles(fileFilter);
-        }
-        if (children == null) {
-            children = FileUtils.EMPTY_FILE_ARRAY;
-        }
-        if (comparator != null && children.length > 1) {
-            Arrays.sort(children, comparator);
-        }
-        return children;
-    }
-
-    /**
-     * Provide a String representation of this observer.
-     *
-     * @return a String representation of this observer
-     */
-    @Override
-    public String toString() {
-        final StringBuilder builder = new StringBuilder();
-        builder.append(getClass().getSimpleName());
-        builder.append("[file='");
-        builder.append(getDirectory().getPath());
-        builder.append('\'');
-        if (fileFilter != null) {
-            builder.append(", ");
-            builder.append(fileFilter.toString());
-        }
-        builder.append(", listeners=");
-        builder.append(listeners.size());
-        builder.append("]");
-        return builder.toString();
-    }
-
-}

+ 0 - 180
src/main/java/com/owncloud/android/services/observer/SyncedFolderObserverService.java

@@ -1,180 +0,0 @@
-/**
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * @author Andy Scherzinger
- * @author Mario Danic
- * Copyright (C) 2016 Tobias Kaminsky, Andy Scherzinger
- * Copyright (C) 2017 Mario Danic
- * <p>
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * at your option) any later version.
- * <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 Affero General Public License for more details.
- * <p>
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package com.owncloud.android.services.observer;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.Binder;
-import android.os.IBinder;
-
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.services.AdvancedFileAlterationListener;
-
-import org.apache.commons.io.monitor.FileAlterationMonitor;
-import org.apache.commons.io.monitor.FileAlterationObserver;
-
-import java.io.File;
-import java.io.FileFilter;
-
-public class SyncedFolderObserverService extends Service {
-    private static final String TAG = "SyncedFolderObserverService";
-
-    private static final int MONITOR_SCAN_INTERVAL = 1000;
-
-    private final IBinder mBinder = new SyncedFolderObserverBinder();
-    private FileAlterationMonitor monitor;
-    private FileFilter fileFilter;
-    
-    @Override
-    public void onCreate() {
-        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(MainApp.getAppContext().
-                getContentResolver());
-        monitor = new FileAlterationMonitor(MONITOR_SCAN_INTERVAL);
-
-        fileFilter = new FileFilter() {
-            @Override
-            public boolean accept(File pathname) {
-                return !pathname.getName().startsWith(".") && !pathname.getName().endsWith(".tmp") &&
-                        !pathname.getName().endsWith(".temp") && !pathname.getName().endsWith(".thumbnail");
-            }
-        };
-
-
-        for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
-            if (syncedFolder.isEnabled()) {
-                AdvancedFileAlterationObserver observer = new AdvancedFileAlterationObserver(syncedFolder, fileFilter);
-
-                try {
-                    observer.init();
-                    observer.addListener(new AdvancedFileAlterationListener(syncedFolder,
-                            getResources().getBoolean(R.bool.syncedFolder_light)));
-                    monitor.addObserver(observer);
-                } catch (Exception e) {
-                    Log_OC.d(TAG, "Failed getting an observer to initialize " + e);
-                }
-
-            }
-        }
-
-
-        try {
-            monitor.start();
-        } catch (Exception e) {
-            Log_OC.d(TAG, "Something went very wrong at onStartCommand");
-        }
-
-    }
-
-    @Override
-    public void onDestroy() {
-
-        super.onDestroy();
-        for (FileAlterationObserver fileAlterationObserver : monitor.getObservers()) {
-            AdvancedFileAlterationObserver advancedFileAlterationObserver = (AdvancedFileAlterationObserver)
-                    fileAlterationObserver;
-            try {
-                monitor.removeObserver(advancedFileAlterationObserver);
-                advancedFileAlterationObserver.checkAndNotifyNow();
-                advancedFileAlterationObserver.destroy();
-            } catch (Exception e) {
-                Log_OC.d(TAG, "Something went very wrong on trying to destroy observers");
-            }
-        }
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        return Service.START_NOT_STICKY;
-    }
-
-    /**
-     * Restart oberver if it is enabled
-     * If syncedFolder exists already, use it, otherwise create new observer
-     *
-     * @param syncedFolder
-     */
-
-    public void restartObserver(SyncedFolder syncedFolder) {
-        boolean found = false;
-        AdvancedFileAlterationObserver advancedFileAlterationObserver;
-        for (FileAlterationObserver fileAlterationObserver : monitor.getObservers()) {
-            advancedFileAlterationObserver =
-                    (AdvancedFileAlterationObserver) fileAlterationObserver;
-            if (advancedFileAlterationObserver.getSyncedFolderID() == syncedFolder.getId()) {
-                monitor.removeObserver(fileAlterationObserver);
-                advancedFileAlterationObserver.checkAndNotifyNow();
-                try {
-                    advancedFileAlterationObserver.destroy();
-                } catch (Exception e) {
-                    Log_OC.d(TAG, "Failed to destroy the observer in restart");
-                }
-
-                if (syncedFolder.isEnabled()) {
-                    try {
-                        advancedFileAlterationObserver = new AdvancedFileAlterationObserver(syncedFolder, fileFilter);
-                        advancedFileAlterationObserver.init();
-                        advancedFileAlterationObserver.addListener(new AdvancedFileAlterationListener(syncedFolder,
-                                getResources().getBoolean(R.bool.syncedFolder_light)));
-                        monitor.addObserver(advancedFileAlterationObserver);
-                    } catch (Exception e) {
-                        Log_OC.d(TAG, "Failed getting an observer to initialize");
-                    }
-                } else {
-                    monitor.removeObserver(fileAlterationObserver);
-                }
-                found = true;
-                break;
-            }
-        }
-
-        if (!found && syncedFolder.isEnabled()) {
-            try {
-                advancedFileAlterationObserver = new AdvancedFileAlterationObserver(syncedFolder, fileFilter);
-                advancedFileAlterationObserver.init();
-                advancedFileAlterationObserver.addListener(new AdvancedFileAlterationListener(syncedFolder,
-                        getResources().getBoolean(R.bool.syncedFolder_light)));
-                monitor.addObserver(advancedFileAlterationObserver);
-            } catch (Exception e) {
-                Log_OC.d(TAG, "Failed getting an observer to initialize");
-            }
-
-        }
-
-    }
-
-    @Override
-    public IBinder onBind(Intent arg0) {
-        return mBinder;
-    }
-
-    public class SyncedFolderObserverBinder extends Binder {
-        public SyncedFolderObserverService getService() {
-            return SyncedFolderObserverService.this;
-        }
-    }
-
-}

+ 1 - 1
src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java

@@ -37,7 +37,7 @@ import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.services.ContactsBackupJob;
+import com.owncloud.android.jobs.ContactsBackupJob;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment;
 import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment;
 import com.owncloud.android.ui.fragment.contactsbackup.ContactsBackupFragment;
 import com.owncloud.android.ui.fragment.contactsbackup.ContactsBackupFragment;

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

@@ -211,12 +211,6 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
             setupDrawerMenu(mNavigationView);
             setupDrawerMenu(mNavigationView);
 
 
             setupQuotaElement();
             setupQuotaElement();
-
-            // show folder sync menu item only for Android 6+
-            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M &&
-                    mNavigationView.getMenu().findItem(R.id.nav_folder_sync) != null) {
-                mNavigationView.getMenu().removeItem(R.id.nav_folder_sync);
-            }
         }
         }
 
 
         setupDrawerToggle();
         setupDrawerToggle();
@@ -366,7 +360,7 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
         }
         }
 
 
         if (getResources().getBoolean(R.bool.syncedFolder_light)) {
         if (getResources().getBoolean(R.bool.syncedFolder_light)) {
-            navigationView.getMenu().removeItem(R.id.nav_folder_sync);
+            navigationView.getMenu().removeItem(R.id.nav_synced_folders);
         }
         }
 
 
         if (!getResources().getBoolean(R.bool.show_drawer_logout)) {
         if (!getResources().getBoolean(R.bool.show_drawer_logout)) {
@@ -450,9 +444,9 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
                 Intent notificationsIntent = new Intent(getApplicationContext(), NotificationsActivity.class);
                 Intent notificationsIntent = new Intent(getApplicationContext(), NotificationsActivity.class);
                 startActivity(notificationsIntent);
                 startActivity(notificationsIntent);
                 break;
                 break;
-            case R.id.nav_folder_sync:
-                Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class);
-                startActivity(folderSyncIntent);
+            case R.id.nav_synced_folders:
+                Intent syncedFoldersIntent = new Intent(getApplicationContext(), SyncedFoldersActivity.class);
+                startActivity(syncedFoldersIntent);
                 break;
                 break;
             case R.id.nav_contacts:
             case R.id.nav_contacts:
                 Intent contactsIntent = new Intent(getApplicationContext(), ContactsPreferenceActivity.class);
                 Intent contactsIntent = new Intent(getApplicationContext(), ContactsPreferenceActivity.class);

+ 2 - 3
src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java

@@ -21,7 +21,6 @@
 
 
 package com.owncloud.android.ui.activity;
 package com.owncloud.android.ui.activity;
 
 
-import android.app.Activity;
 import android.content.Intent;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v4.widget.DrawerLayout;
@@ -113,7 +112,6 @@ public class ExternalSiteWebView extends FileActivity {
         webSettings.setJavaScriptEnabled(true);
         webSettings.setJavaScriptEnabled(true);
         webSettings.setDomStorageEnabled(true);
         webSettings.setDomStorageEnabled(true);
 
 
-        final Activity activity = this;
         final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
         final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
 
 
         webview.setWebChromeClient(new WebChromeClient() {
         webview.setWebChromeClient(new WebChromeClient() {
@@ -124,7 +122,8 @@ public class ExternalSiteWebView extends FileActivity {
 
 
         webview.setWebViewClient(new WebViewClient() {
         webview.setWebViewClient(new WebViewClient() {
             public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
             public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
-                webview.loadData(DisplayUtils.getData(getResources().openRawResource(R.raw.custom_error)),"text/html; charset=UTF-8", null);
+                webview.loadData(DisplayUtils.getData(getResources().openRawResource(R.raw.custom_error)),
+                        "text/html; charset=UTF-8", null);
             }
             }
         });
         });
 
 

+ 1 - 1
src/main/java/com/owncloud/android/ui/activity/FileActivity.java

@@ -476,7 +476,7 @@ public abstract class FileActivity extends DrawerActivity
         Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG);
         Fragment frag = getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG);
         if (frag == null) {
         if (frag == null) {
             Log_OC.d(TAG, "show loading dialog");
             Log_OC.d(TAG, "show loading dialog");
-            LoadingDialog loading = new LoadingDialog(message);
+            LoadingDialog loading = LoadingDialog.newInstance(message);
             FragmentManager fm = getSupportFragmentManager();
             FragmentManager fm = getSupportFragmentManager();
             FragmentTransaction ft = fm.beginTransaction();
             FragmentTransaction ft = fm.beginTransaction();
             loading.show(ft, DIALOG_WAIT_TAG);
             loading.show(ft, DIALOG_WAIT_TAG);

+ 11 - 10
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -282,9 +282,8 @@ public class FileDisplayActivity extends HookActivity
      */
      */
     private void upgradeNotificationForInstantUpload() {
     private void upgradeNotificationForInstantUpload() {
         // check for Android 6+ if legacy instant upload is activated --> disable + show info
         // check for Android 6+ if legacy instant upload is activated --> disable + show info
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
-                (PreferenceManager.instantPictureUploadEnabled(this) ||
-                        PreferenceManager.instantPictureUploadEnabled(this))) {
+        if (PreferenceManager.instantPictureUploadEnabled(this) ||
+                        PreferenceManager.instantPictureUploadEnabled(this)) {
 
 
             // remove legacy shared preferences
             // remove legacy shared preferences
             SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
             SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this).edit();
@@ -303,14 +302,14 @@ public class FileDisplayActivity extends HookActivity
 
 
             // show info pop-up
             // show info pop-up
             new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog)
             new AlertDialog.Builder(this, R.style.Theme_ownCloud_Dialog)
-                    .setTitle(R.string.drawer_folder_sync)
-                    .setMessage(R.string.folder_sync_new_info)
+                    .setTitle(R.string.drawer_synced_folders)
+                    .setMessage(R.string.synced_folders_new_info)
                     .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() {
                     .setPositiveButton(R.string.drawer_open, new DialogInterface.OnClickListener() {
                         public void onClick(DialogInterface dialog, int which) {
                         public void onClick(DialogInterface dialog, int which) {
                             // show instant upload
                             // show instant upload
-                            Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class);
+                            Intent syncedFoldersIntent = new Intent(getApplicationContext(), SyncedFoldersActivity.class);
                             dialog.dismiss();
                             dialog.dismiss();
-                            startActivity(folderSyncIntent);
+                            startActivity(syncedFoldersIntent);
                         }
                         }
                     })
                     })
                     .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() {
                     .setNegativeButton(R.string.drawer_close, new DialogInterface.OnClickListener() {
@@ -318,7 +317,7 @@ public class FileDisplayActivity extends HookActivity
                             dialog.dismiss();
                             dialog.dismiss();
                         }
                         }
                     })
                     })
-                    .setIcon(R.drawable.nav_folder_sync)
+                    .setIcon(R.drawable.nav_synced_folders)
                     .show();
                     .show();
         }
         }
     }
     }
@@ -477,7 +476,7 @@ public class FileDisplayActivity extends HookActivity
         super.onNewIntent(intent);
         super.onNewIntent(intent);
         if(intent.getAction()!=null && intent.getAction().equalsIgnoreCase(ACTION_DETAILS)){
         if(intent.getAction()!=null && intent.getAction().equalsIgnoreCase(ACTION_DETAILS)){
             setIntent(intent);
             setIntent(intent);
-            setFile((OCFile)intent.getParcelableExtra(EXTRA_FILE));
+            setFile(intent.getParcelableExtra(EXTRA_FILE));
         }
         }
     }
     }
 
 
@@ -915,7 +914,9 @@ public class FileDisplayActivity extends HookActivity
                     null,           // MIME type will be detected from file name
                     null,           // MIME type will be detected from file name
                     behaviour,
                     behaviour,
                     false,          // do not create parent folder if not existent
                     false,          // do not create parent folder if not existent
-                    UploadFileOperation.CREATED_BY_USER
+                    UploadFileOperation.CREATED_BY_USER,
+                    false,
+                    false
             );
             );
 
 
         } else {
         } else {

+ 1 - 2
src/main/java/com/owncloud/android/ui/activity/FingerprintActivity.java

@@ -77,7 +77,6 @@ public class FingerprintActivity extends AppCompatActivity {
 
 
     public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT";
     public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT";
 
 
-    public final static String PREFERENCE_USE_FINGERPRINT = "use_fingerprint";
     public static final String ANDROID_KEY_STORE = "AndroidKeyStore";
     public static final String ANDROID_KEY_STORE = "AndroidKeyStore";
 
 
     private KeyStore keyStore;
     private KeyStore keyStore;
@@ -286,7 +285,7 @@ public class FingerprintActivity extends AppCompatActivity {
     }
     }
 }
 }
 
 
-@RequiresApi(Build.VERSION_CODES.M)
+@RequiresApi(api = Build.VERSION_CODES.M)
 class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
 class FingerprintHandler extends FingerprintManager.AuthenticationCallback {
 
 
     private TextView text;
     private TextView text;

+ 1 - 0
src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java

@@ -302,6 +302,7 @@ public class FolderPickerActivity extends FileActivity implements FileFragment.C
             retval = super.onOptionsItemSelected(item);
             retval = super.onOptionsItemSelected(item);
             break;
             break;
         }
         }
+
         return retval;
         return retval;
     }
     }
 
 

+ 1 - 3
src/main/java/com/owncloud/android/ui/activity/LogHistoryActivity.java

@@ -273,9 +273,7 @@ public class LogHistoryActivity extends ToolbarActivity {
      */
      */
     public void showLoadingDialog() {
     public void showLoadingDialog() {
         // Construct dialog
         // Construct dialog
-        LoadingDialog loading = new LoadingDialog(
-                getResources().getString(R.string.log_progress_dialog_text)
-        );
+        LoadingDialog loading = LoadingDialog.newInstance(getResources().getString(R.string.log_progress_dialog_text));
         FragmentManager fm = getSupportFragmentManager();
         FragmentManager fm = getSupportFragmentManager();
         FragmentTransaction ft = fm.beginTransaction();
         FragmentTransaction ft = fm.beginTransaction();
         loading.show(ft, DIALOG_WAIT_TAG);
         loading.show(ft, DIALOG_WAIT_TAG);

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

@@ -50,8 +50,7 @@ import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.services.AccountRemovalJob;
-import com.owncloud.android.services.AutoUploadJob;
+import com.owncloud.android.jobs.AccountRemovalJob;
 import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.ui.adapter.AccountListAdapter;
 import com.owncloud.android.ui.adapter.AccountListAdapter;
 import com.owncloud.android.ui.adapter.AccountListItem;
 import com.owncloud.android.ui.adapter.AccountListItem;
@@ -407,7 +406,7 @@ public class ManageAccountsActivity extends FileActivity
 
 
         // store pending account removal
         // store pending account removal
         ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
         ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
-        arbitraryDataProvider.storeOrUpdateKeyValue(account, PENDING_FOR_REMOVAL, String.valueOf(true));
+        arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PENDING_FOR_REMOVAL, String.valueOf(true));
 
 
         // Cancel transfers
         // Cancel transfers
         if (mUploaderBinder != null) {
         if (mUploaderBinder != null) {
@@ -419,7 +418,7 @@ public class ManageAccountsActivity extends FileActivity
 
 
         // schedule job
         // schedule job
         PersistableBundleCompat bundle = new PersistableBundleCompat();
         PersistableBundleCompat bundle = new PersistableBundleCompat();
-        bundle.putString(AutoUploadJob.ACCOUNT, account.name);
+        bundle.putString(AccountRemovalJob.ACCOUNT, account.name);
 
 
         new JobRequest.Builder(AccountRemovalJob.TAG)
         new JobRequest.Builder(AccountRemovalJob.TAG)
                 .setExecutionWindow(1_000L, 10_000L)
                 .setExecutionWindow(1_000L, 10_000L)

+ 54 - 150
src/main/java/com/owncloud/android/ui/activity/Preferences.java

@@ -85,9 +85,11 @@ import java.io.IOException;
  */
  */
 public class Preferences extends PreferenceActivity
 public class Preferences extends PreferenceActivity
         implements StorageMigration.StorageMigrationProgressListener {
         implements StorageMigration.StorageMigrationProgressListener {
-    
+
     private static final String TAG = Preferences.class.getSimpleName();
     private static final String TAG = Preferences.class.getSimpleName();
 
 
+    public final static String PREFERENCE_USE_FINGERPRINT = "use_fingerprint";
+
     private static final String SCREEN_NAME = "Settings";
     private static final String SCREEN_NAME = "Settings";
 
 
     private static final int ACTION_SELECT_UPLOAD_PATH = 1;
     private static final int ACTION_SELECT_UPLOAD_PATH = 1;
@@ -107,6 +109,7 @@ public class Preferences extends PreferenceActivity
     private SwitchPreference pCode;
     private SwitchPreference pCode;
     private SwitchPreference fPrint;
     private SwitchPreference fPrint;
     private SwitchPreference mShowHiddenFiles;
     private SwitchPreference mShowHiddenFiles;
+    private SwitchPreference mExpertMode;
     private Preference pAboutApp;
     private Preference pAboutApp;
     private AppCompatDelegate mDelegate;
     private AppCompatDelegate mDelegate;
 
 
@@ -190,13 +193,14 @@ public class Preferences extends PreferenceActivity
                 accentColor));
                 accentColor));
 
 
         // Synced folders
         // Synced folders
-        PreferenceCategory preferenceCategoryFolderSync = (PreferenceCategory) findPreference("folder_sync");
-        preferenceCategoryFolderSync.setTitle(ThemeUtils.getColoredTitle(getString(R.string.drawer_folder_sync),
+        PreferenceCategory preferenceCategorySyncedFolders =
+                (PreferenceCategory) findPreference("synced_folders_category");
+        preferenceCategorySyncedFolders.setTitle(ThemeUtils.getColoredTitle(getString(R.string.drawer_synced_folders),
                 accentColor));
                 accentColor));
         PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("preference_screen");
         PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("preference_screen");
 
 
         if (!getResources().getBoolean(R.bool.syncedFolder_light)) {
         if (!getResources().getBoolean(R.bool.syncedFolder_light)) {
-            preferenceScreen.removePreference(preferenceCategoryFolderSync);
+            preferenceScreen.removePreference(preferenceCategorySyncedFolders);
         } else {
         } else {
             // Upload on WiFi
             // Upload on WiFi
             final ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
             final ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
@@ -209,28 +213,28 @@ public class Preferences extends PreferenceActivity
             pUploadOnWifiCheckbox.setOnPreferenceClickListener(new OnPreferenceClickListener() {
             pUploadOnWifiCheckbox.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                 @Override
                 @Override
                 public boolean onPreferenceClick(Preference preference) {
                 public boolean onPreferenceClick(Preference preference) {
-                    arbitraryDataProvider.storeOrUpdateKeyValue(account, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI,
+                    arbitraryDataProvider.storeOrUpdateKeyValue(account.name, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI,
                             String.valueOf(pUploadOnWifiCheckbox.isChecked()));
                             String.valueOf(pUploadOnWifiCheckbox.isChecked()));
 
 
                     return true;
                     return true;
                 }
                 }
             });
             });
 
 
-            Preference pSyncedFolder = findPreference("folder_sync_folders");
+            Preference pSyncedFolder = findPreference("synced_folders_configure_folders");
             if (pSyncedFolder != null) {
             if (pSyncedFolder != null) {
                 if (getResources().getBoolean(R.bool.syncedFolder_light)
                 if (getResources().getBoolean(R.bool.syncedFolder_light)
                         && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                         && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                     pSyncedFolder.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                     pSyncedFolder.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                         @Override
                         @Override
                         public boolean onPreferenceClick(Preference preference) {
                         public boolean onPreferenceClick(Preference preference) {
-                            Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class);
-                            folderSyncIntent.putExtra(FolderSyncActivity.EXTRA_SHOW_SIDEBAR, false);
-                            startActivity(folderSyncIntent);
+                            Intent syncedFoldersIntent = new Intent(getApplicationContext(), SyncedFoldersActivity.class);
+                            syncedFoldersIntent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, false);
+                            startActivity(syncedFoldersIntent);
                             return true;
                             return true;
                         }
                         }
                     });
                     });
                 } else {
                 } else {
-                    preferenceCategoryFolderSync.removePreference(pSyncedFolder);
+                    preferenceCategorySyncedFolders.removePreference(pSyncedFolder);
                 }
                 }
             }
             }
         }
         }
@@ -265,7 +269,7 @@ public class Preferences extends PreferenceActivity
         }
         }
 
 
         boolean fPrintEnabled = getResources().getBoolean(R.bool.fingerprint_enabled);
         boolean fPrintEnabled = getResources().getBoolean(R.bool.fingerprint_enabled);
-        fPrint = (SwitchPreference) findPreference(FingerprintActivity.PREFERENCE_USE_FINGERPRINT);
+        fPrint = (SwitchPreference) findPreference(PREFERENCE_USE_FINGERPRINT);
         if (fPrint != null) {
         if (fPrint != null) {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                 if (FingerprintActivity.isFingerprintCapable(MainApp.getAppContext()) && fPrintEnabled) {
                 if (FingerprintActivity.isFingerprintCapable(MainApp.getAppContext()) && fPrintEnabled) {
@@ -332,9 +336,21 @@ public class Preferences extends PreferenceActivity
             });
             });
         } else {
         } else {
             preferenceCategoryDetails.removePreference(mShowHiddenFiles);
             preferenceCategoryDetails.removePreference(mShowHiddenFiles);
-
         }
         }
 
 
+        mExpertMode = (SwitchPreference) findPreference("expert_mode");
+        mExpertMode.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                SharedPreferences appPrefs =
+                        PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+                SharedPreferences.Editor editor = appPrefs.edit();
+                editor.putBoolean("expert_mode", mExpertMode.isChecked());
+                editor.apply();
+                return true;
+            }
+        });
+
         PreferenceCategory preferenceCategoryMore = (PreferenceCategory) findPreference("more");
         PreferenceCategory preferenceCategoryMore = (PreferenceCategory) findPreference("more");
         preferenceCategoryMore.setTitle(ThemeUtils.getColoredTitle(getString(R.string.prefs_category_more),
         preferenceCategoryMore.setTitle(ThemeUtils.getColoredTitle(getString(R.string.prefs_category_more),
                 accentColor));
                 accentColor));
@@ -468,7 +484,11 @@ public class Preferences extends PreferenceActivity
             }
             }
         }
         }
 
 
-        boolean loggerEnabled = getResources().getBoolean(R.bool.logger_enabled) || BuildConfig.DEBUG;
+        SharedPreferences appPrefs =
+                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+
+        boolean loggerEnabled = getResources().getBoolean(R.bool.logger_enabled) || BuildConfig.DEBUG ||
+                appPrefs.getBoolean("expert_mode", false);
         Preference pLogger = findPreference("logger");
         Preference pLogger = findPreference("logger");
         if (pLogger != null) {
         if (pLogger != null) {
             if (loggerEnabled) {
             if (loggerEnabled) {
@@ -521,12 +541,12 @@ public class Preferences extends PreferenceActivity
             mPrefStoragePath.setEntryValues(values);
             mPrefStoragePath.setEntryValues(values);
 
 
             mPrefStoragePath.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
             mPrefStoragePath.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-                    @Override
-                    public boolean onPreferenceChange(Preference preference, Object newValue) {
-                        String newPath = (String) newValue;
-                        if (mStoragePath.equals(newPath)) {
-                            return true;
-                        }
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newValue) {
+                    String newPath = (String) newValue;
+                    if (mStoragePath.equals(newPath)) {
+                        return true;
+                    }
 
 
                     StorageMigration storageMigration = new StorageMigration(Preferences.this, mStoragePath, newPath);
                     StorageMigration storageMigration = new StorageMigration(Preferences.this, mStoragePath, newPath);
 
 
@@ -540,89 +560,7 @@ public class Preferences extends PreferenceActivity
         }
         }
 
 
         mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category");
         mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category");
-
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            // Instant upload via preferences on pre Android Marshmallow
-            mPrefInstantUploadPath = findPreference("instant_upload_path");
-            if (mPrefInstantUploadPath != null) {
-
-                mPrefInstantUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-                    @Override
-                    public boolean onPreferenceClick(Preference preference) {
-                        if (!mUploadPath.endsWith(OCFile.PATH_SEPARATOR)) {
-                            mUploadPath += OCFile.PATH_SEPARATOR;
-                        }
-                        Intent intent = new Intent(Preferences.this, UploadPathActivity.class);
-                        intent.putExtra(UploadPathActivity.KEY_INSTANT_UPLOAD_PATH, mUploadPath);
-                        startActivityForResult(intent, ACTION_SELECT_UPLOAD_PATH);
-                        return true;
-                    }
-                });
-            }
-
-        mPrefInstantUploadCategory = (PreferenceCategory) findPreference("instant_uploading_category");
-
-            mPrefInstantUploadUseSubfolders = findPreference("instant_upload_path_use_subfolders");
-            mPrefInstantUploadPathWiFi = findPreference("instant_upload_on_wifi");
-            mPrefInstantPictureUploadOnlyOnCharging = findPreference("instant_upload_on_charging");
-            mPrefInstantUpload = (CheckBoxPreferenceWithLongTitle) findPreference("instant_uploading");
-
-            toggleInstantPictureOptions(mPrefInstantUpload.isChecked());
-
-            mPrefInstantUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-                @Override
-                public boolean onPreferenceChange(Preference preference, Object newValue) {
-                    toggleInstantPictureOptions((Boolean) newValue);
-                    toggleInstantUploadBehaviour(mPrefInstantVideoUpload.isChecked(), (Boolean) newValue);
-                    return true;
-                }
-            });
-
-        mPrefInstantVideoUploadPath = findPreference(PreferenceKeys.INSTANT_VIDEO_UPLOAD_PATH);
-            if (mPrefInstantVideoUploadPath != null) {
-
-                mPrefInstantVideoUploadPath.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-                    @Override
-                    public boolean onPreferenceClick(Preference preference) {
-                        if (!mUploadVideoPath.endsWith(OCFile.PATH_SEPARATOR)) {
-                            mUploadVideoPath += OCFile.PATH_SEPARATOR;
-                        }
-                        Intent intent = new Intent(Preferences.this, UploadPathActivity.class);
-                        intent.putExtra(UploadPathActivity.KEY_INSTANT_UPLOAD_PATH,
-                                mUploadVideoPath);
-                        startActivityForResult(intent, ACTION_SELECT_UPLOAD_VIDEO_PATH);
-                        return true;
-                    }
-                });
-            }
-
-            mPrefInstantVideoUploadUseSubfolders = findPreference("instant_video_upload_path_use_subfolders");
-            mPrefInstantVideoUploadPathWiFi = findPreference("instant_video_upload_on_wifi");
-            mPrefInstantVideoUpload = (CheckBoxPreferenceWithLongTitle) findPreference("instant_video_uploading");
-            mPrefInstantVideoUploadOnlyOnCharging = findPreference("instant_video_upload_on_charging");
-            toggleInstantVideoOptions(mPrefInstantVideoUpload.isChecked());
-            mPrefInstantVideoUpload.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
-                @Override
-                public boolean onPreferenceChange(Preference preference, Object newValue) {
-                    toggleInstantVideoOptions((Boolean) newValue);
-                    toggleInstantUploadBehaviour(
-                            (Boolean) newValue,
-                            mPrefInstantUpload.isChecked());
-                    return true;
-                }
-            });
-
-            mPrefInstantUploadBehaviour = findPreference("prefs_instant_behaviour");
-            toggleInstantUploadBehaviour(
-                    mPrefInstantVideoUpload.isChecked(),
-                    mPrefInstantUpload.isChecked());
-
-            loadInstantUploadPath();
-            loadInstantUploadVideoPath();
-        } else {
-            // Instant upload is handled via synced folders on Android Lollipop and up
-            getPreferenceScreen().removePreference(mPrefInstantUploadCategory);
-        }
+        getPreferenceScreen().removePreference(mPrefInstantUploadCategory);
 
 
         // About category
         // About category
         PreferenceCategory preferenceCategoryAbout = (PreferenceCategory) findPreference("about");
         PreferenceCategory preferenceCategoryAbout = (PreferenceCategory) findPreference("about");
@@ -716,48 +654,12 @@ public class Preferences extends PreferenceActivity
                     mUri = OwnCloudClientManagerFactory.getDefaultSingleton().
                     mUri = OwnCloudClientManagerFactory.getDefaultSingleton().
                             getClientFor(ocAccount, getApplicationContext()).getBaseUri();
                             getClientFor(ocAccount, getApplicationContext()).getBaseUri();
                 } catch (Throwable t) {
                 } catch (Throwable t) {
-                    Log_OC.e(TAG,"Error retrieving user's base URI", t);
+                    Log_OC.e(TAG, "Error retrieving user's base URI", t);
                 }
                 }
             }
             }
         });
         });
         t.start();
         t.start();
     }
     }
-    
-    private void toggleInstantPictureOptions(Boolean value){
-        if (value) {
-            mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPathWiFi);
-            mPrefInstantUploadCategory.addPreference(mPrefInstantUploadPath);
-            mPrefInstantUploadCategory.addPreference(mPrefInstantUploadUseSubfolders);
-            mPrefInstantUploadCategory.addPreference(mPrefInstantPictureUploadOnlyOnCharging);
-        } else {
-            mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPathWiFi);
-            mPrefInstantUploadCategory.removePreference(mPrefInstantUploadPath);
-            mPrefInstantUploadCategory.removePreference(mPrefInstantUploadUseSubfolders);
-            mPrefInstantUploadCategory.removePreference(mPrefInstantPictureUploadOnlyOnCharging);
-        }
-    }
-
-    private void toggleInstantVideoOptions(Boolean value){
-        if (value) {
-            mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPathWiFi);
-            mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadPath);
-            mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadUseSubfolders);
-            mPrefInstantUploadCategory.addPreference(mPrefInstantVideoUploadOnlyOnCharging);
-        } else {
-            mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPathWiFi);
-            mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadPath);
-            mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadUseSubfolders);
-            mPrefInstantUploadCategory.removePreference(mPrefInstantVideoUploadOnlyOnCharging);
-        }
-    }
-
-    private void toggleInstantUploadBehaviour(Boolean video, Boolean picture){
-        if (picture || video) {
-            mPrefInstantUploadCategory.addPreference(mPrefInstantUploadBehaviour);
-        } else {
-            mPrefInstantUploadCategory.removePreference(mPrefInstantUploadBehaviour);
-        }
-    }
 
 
     @Override
     @Override
     protected void onResume() {
     protected void onResume() {
@@ -782,14 +684,14 @@ public class Preferences extends PreferenceActivity
         Intent intent;
         Intent intent;
 
 
         switch (item.getItemId()) {
         switch (item.getItemId()) {
-        case android.R.id.home:
-            intent = new Intent(getBaseContext(), FileDisplayActivity.class);
-            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            startActivity(intent);
-            break;
-        default:
-            Log_OC.w(TAG, "Unknown menu item triggered");
-            return false;
+            case android.R.id.home:
+                intent = new Intent(getBaseContext(), FileDisplayActivity.class);
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                break;
+            default:
+                Log_OC.w(TAG, "Unknown menu item triggered");
+                return false;
         }
         }
         return true;
         return true;
     }
     }
@@ -800,7 +702,7 @@ public class Preferences extends PreferenceActivity
 
 
         if (requestCode == ACTION_SELECT_UPLOAD_PATH && resultCode == RESULT_OK) {
         if (requestCode == ACTION_SELECT_UPLOAD_PATH && resultCode == RESULT_OK) {
 
 
-            OCFile folderToUpload =  data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER);
+            OCFile folderToUpload = data.getParcelableExtra(UploadPathActivity.EXTRA_FOLDER);
 
 
             mUploadPath = folderToUpload.getRemotePath();
             mUploadPath = folderToUpload.getRemotePath();
 
 
@@ -830,7 +732,7 @@ public class Preferences extends PreferenceActivity
                         .getDefaultSharedPreferences(getApplicationContext()).edit();
                         .getDefaultSharedPreferences(getApplicationContext()).edit();
 
 
                 for (int i = 1; i <= 4; ++i) {
                 for (int i = 1; i <= 4; ++i) {
-                    appPrefs.putString(PassCodeActivity.PREFERENCE_PASSCODE_D + i, passcode.substring(i-1, i));
+                    appPrefs.putString(PassCodeActivity.PREFERENCE_PASSCODE_D + i, passcode.substring(i - 1, i));
                 }
                 }
                 appPrefs.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, true);
                 appPrefs.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, true);
                 appPrefs.apply();
                 appPrefs.apply();
@@ -868,10 +770,12 @@ public class Preferences extends PreferenceActivity
     public void setContentView(@LayoutRes int layoutResID) {
     public void setContentView(@LayoutRes int layoutResID) {
         getDelegate().setContentView(layoutResID);
         getDelegate().setContentView(layoutResID);
     }
     }
+
     @Override
     @Override
     public void setContentView(View view) {
     public void setContentView(View view) {
         getDelegate().setContentView(view);
         getDelegate().setContentView(view);
     }
     }
+
     @Override
     @Override
     public void setContentView(View view, ViewGroup.LayoutParams params) {
     public void setContentView(View view, ViewGroup.LayoutParams params) {
         getDelegate().setContentView(view, params);
         getDelegate().setContentView(view, params);
@@ -994,7 +898,7 @@ public class Preferences extends PreferenceActivity
         SharedPreferences appPrefs =
         SharedPreferences appPrefs =
                 PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
                 PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
         mStoragePath = appPrefs.getString(PreferenceKeys.STORAGE_PATH, Environment.getExternalStorageDirectory()
         mStoragePath = appPrefs.getString(PreferenceKeys.STORAGE_PATH, Environment.getExternalStorageDirectory()
-                                                         .getAbsolutePath());
+                .getAbsolutePath());
         String storageDescription = DataStorageProvider.getInstance().getStorageDescriptionByPath(mStoragePath);
         String storageDescription = DataStorageProvider.getInstance().getStorageDescriptionByPath(mStoragePath);
         mPrefStoragePath.setSummary(storageDescription);
         mPrefStoragePath.setSummary(storageDescription);
     }
     }

+ 3 - 1
src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -913,7 +913,9 @@ public class ReceiveExternalFilesActivity extends FileActivity
             FileUploader.LOCAL_BEHAVIOUR_COPY,
             FileUploader.LOCAL_BEHAVIOUR_COPY,
             null,
             null,
             true,
             true,
-            UploadFileOperation.CREATED_BY_USER
+            UploadFileOperation.CREATED_BY_USER,
+            false,
+            false
             );
             );
         finish();
         finish();
     }
     }

+ 4 - 16
src/main/java/com/owncloud/android/ui/activity/ShareActivity.java

@@ -55,11 +55,9 @@ import com.owncloud.android.utils.GetShareWithUsersAsyncTask;
 import java.util.ArrayList;
 import java.util.ArrayList;
 
 
 /**
 /**
- * Activity for sharing files
+ * Activity for sharing files.
  */
  */
-
-public class ShareActivity extends FileActivity
-        implements ShareFragmentListener {
+public class ShareActivity extends FileActivity implements ShareFragmentListener {
 
 
     private static final String TAG = ShareActivity.class.getSimpleName();
     private static final String TAG = ShareActivity.class.getSimpleName();
 
 
@@ -70,8 +68,6 @@ public class ShareActivity extends FileActivity
 
 
     /// Tags for dialog fragments
     /// Tags for dialog fragments
     private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
     private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
-    private static final String FTAG_SHARE_PASSWORD_DIALOG = "SHARE_PASSWORD_DIALOG";
-
 
 
     @Override
     @Override
     protected void onCreate(Bundle savedInstanceState) {
     protected void onCreate(Bundle savedInstanceState) {
@@ -87,7 +83,6 @@ public class ShareActivity extends FileActivity
             ft.replace(R.id.share_fragment_container, fragment, TAG_SHARE_FRAGMENT);
             ft.replace(R.id.share_fragment_container, fragment, TAG_SHARE_FRAGMENT);
             ft.commit();
             ft.commit();
         }
         }
-
     }
     }
 
 
     protected void onAccountSet(boolean stateWasRecovered) {
     protected void onAccountSet(boolean stateWasRecovered) {
@@ -144,10 +139,9 @@ public class ShareActivity extends FileActivity
         );
         );
     }
     }
 
 
-
     private int getAppropiatePermissions(ShareType shareType) {
     private int getAppropiatePermissions(ShareType shareType) {
 
 
-        // check if the Share is FERERATED
+        // check if the Share is FEDERATED
         boolean isFederated = ShareType.FEDERATED.equals(shareType);
         boolean isFederated = ShareType.FEDERATED.equals(shareType);
 
 
         if (getFile().isSharedWithMe()) {
         if (getFile().isSharedWithMe()) {
@@ -245,9 +239,8 @@ public class ShareActivity extends FileActivity
 
 
     }
     }
 
 
-
     /**
     /**
-     * Updates the view, reading data from {@link com.owncloud.android.datamodel.FileDataStorageManager}
+     * Updates the view, reading data from {@link com.owncloud.android.datamodel.FileDataStorageManager}.
      */
      */
     private void refreshSharesFromStorageManager() {
     private void refreshSharesFromStorageManager() {
 
 
@@ -270,7 +263,6 @@ public class ShareActivity extends FileActivity
                 editShareFragment.isAdded()) {
                 editShareFragment.isAdded()) {
             editShareFragment.refreshUiFromDB();
             editShareFragment.refreshUiFromDB();
         }
         }
-
     }
     }
 
 
     /**
     /**
@@ -300,7 +292,6 @@ public class ShareActivity extends FileActivity
         return (EditShareFragment) getSupportFragmentManager().findFragmentByTag(TAG_EDIT_SHARE_FRAGMENT);
         return (EditShareFragment) getSupportFragmentManager().findFragmentByTag(TAG_EDIT_SHARE_FRAGMENT);
     }
     }
 
 
-
     private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation,
     private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation,
                                                      RemoteOperationResult result) {
                                                      RemoteOperationResult result) {
         if (result.isSuccess()) {
         if (result.isSuccess()) {
@@ -369,8 +360,5 @@ public class ShareActivity extends FileActivity
                 t.show();
                 t.show();
             }
             }
         }
         }
-
     }
     }
-
-
 }
 }

+ 208 - 67
src/main/java/com/owncloud/android/ui/activity/FolderSyncActivity.java → src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java

@@ -23,56 +23,66 @@ package com.owncloud.android.ui.activity;
 
 
 import android.accounts.Account;
 import android.accounts.Account;
 import android.content.Intent;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Bundle;
-import android.os.Handler;
+import android.preference.PreferenceManager;
 import android.support.annotation.NonNull;
 import android.support.annotation.NonNull;
+import android.support.design.widget.AppBarLayout;
 import android.support.design.widget.BottomNavigationView;
 import android.support.design.widget.BottomNavigationView;
+import android.support.design.widget.CollapsingToolbarLayout;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v4.widget.DrawerLayout;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.ActionBar;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.RecyclerView;
+import android.util.Log;
 import android.view.MenuItem;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 import android.widget.TextView;
 
 
 import com.owncloud.android.MainApp;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.MediaFolder;
 import com.owncloud.android.datamodel.MediaFolder;
+import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.MediaProvider;
 import com.owncloud.android.datamodel.MediaProvider;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.ui.adapter.FolderSyncAdapter;
+import com.owncloud.android.ui.adapter.SyncedFolderAdapter;
 import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
 import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
 import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment;
 import com.owncloud.android.ui.dialog.SyncedFolderPreferencesDialogFragment;
 import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable;
 import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable;
 import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.ThemeUtils;
 import com.owncloud.android.utils.ThemeUtils;
 
 
 import java.io.File;
 import java.io.File;
+import java.io.FileFilter;
 import java.util.ArrayList;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
-import java.util.TimerTask;
 
 
+import static android.support.design.widget.AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS;
 import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID;
 import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID;
 
 
 /**
 /**
  * Activity displaying all auto-synced folders and/or instant upload media folders.
  * Activity displaying all auto-synced folders and/or instant upload media folders.
  */
  */
-public class FolderSyncActivity extends FileActivity implements FolderSyncAdapter.ClickListener,
+public class SyncedFoldersActivity extends FileActivity implements SyncedFolderAdapter.ClickListener,
         SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener {
         SyncedFolderPreferencesDialogFragment.OnSyncedFolderPreferenceListener {
 
 
     private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG";
     private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG";
@@ -81,16 +91,16 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
 
 
     private static final String SCREEN_NAME = "Auto upload";
     private static final String SCREEN_NAME = "Auto upload";
 
 
-    private static final String TAG = FolderSyncActivity.class.getSimpleName();
+    private static final String TAG = SyncedFoldersActivity.class.getSimpleName();
 
 
     private RecyclerView mRecyclerView;
     private RecyclerView mRecyclerView;
-    private FolderSyncAdapter mAdapter;
+    private SyncedFolderAdapter mAdapter;
     private LinearLayout mProgress;
     private LinearLayout mProgress;
     private TextView mEmpty;
     private TextView mEmpty;
     private SyncedFolderProvider mSyncedFolderProvider;
     private SyncedFolderProvider mSyncedFolderProvider;
-    private List<SyncedFolderDisplayItem> syncFolderItems;
     private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment;
     private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment;
     private boolean showSidebar = true;
     private boolean showSidebar = true;
+    private RelativeLayout mCustomFolderRelativeLayout;
 
 
     @Override
     @Override
     protected void onCreate(Bundle savedInstanceState) {
     protected void onCreate(Bundle savedInstanceState) {
@@ -100,13 +110,37 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
             showSidebar = getIntent().getExtras().getBoolean(EXTRA_SHOW_SIDEBAR);
             showSidebar = getIntent().getExtras().getBoolean(EXTRA_SHOW_SIDEBAR);
         }
         }
 
 
-        setContentView(R.layout.folder_sync_layout);
+        setContentView(R.layout.synced_folders_layout);
 
 
         // setup toolbar
         // setup toolbar
         setupToolbar();
         setupToolbar();
+        CollapsingToolbarLayout mCollapsingToolbarLayout = ((CollapsingToolbarLayout)
+                findViewById(R.id.collapsing_toolbar));
+        mCollapsingToolbarLayout.setTitle(this.getString(R.string.drawer_synced_folders));
+
+        mCustomFolderRelativeLayout = (RelativeLayout) findViewById(R.id.custom_folder_toolbar);
+
+        SharedPreferences appPrefs =
+                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+
+
+        findViewById(R.id.toolbar).post(() -> {
+            if (!appPrefs.getBoolean("expert_mode", false)) {
+                findViewById(R.id.app_bar).getLayoutParams().height = findViewById(R.id.toolbar).getHeight();
+
+                AppBarLayout.LayoutParams p = (AppBarLayout.LayoutParams) mCollapsingToolbarLayout.getLayoutParams();
+                p.setScrollFlags(SCROLL_FLAG_ENTER_ALWAYS);
+                mCollapsingToolbarLayout.setLayoutParams(p);
+                mCustomFolderRelativeLayout.setVisibility(View.GONE);
+            } else {
+                mCustomFolderRelativeLayout.setVisibility(View.VISIBLE);
+                findViewById(R.id.app_bar).setBackgroundColor(getResources().getColor(R.color.filelist_icon_backgorund));
+            }
+        });
+
 
 
         // setup drawer
         // setup drawer
-        setupDrawer(R.id.nav_folder_sync);
+        setupDrawer(R.id.nav_synced_folders);
 
 
         if (!showSidebar) {
         if (!showSidebar) {
             setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
             setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
@@ -117,7 +151,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
 
 
         ActionBar actionBar = getSupportActionBar();
         ActionBar actionBar = getSupportActionBar();
         if (actionBar != null) {
         if (actionBar != null) {
-            ThemeUtils.setColoredTitle(getSupportActionBar(), getString(R.string.drawer_folder_sync));
+            ThemeUtils.setColoredTitle(getSupportActionBar(), getString(R.string.drawer_synced_folders));
             actionBar.setDisplayHomeAsUpEnabled(true);
             actionBar.setDisplayHomeAsUpEnabled(true);
         }
         }
 
 
@@ -143,7 +177,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
 
 
         final int gridWidth = getResources().getInteger(R.integer.media_grid_width);
         final int gridWidth = getResources().getInteger(R.integer.media_grid_width);
         boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light);
         boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light);
-        mAdapter = new FolderSyncAdapter(this, gridWidth, this, lightVersion);
+        mAdapter = new SyncedFolderAdapter(this, gridWidth, this, lightVersion);
         mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver());
         mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver());
 
 
         final GridLayoutManager lm = new GridLayoutManager(this, gridWidth);
         final GridLayoutManager lm = new GridLayoutManager(this, gridWidth);
@@ -173,33 +207,25 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
             return;
             return;
         }
         }
         setListShown(false);
         setListShown(false);
-        final Handler mHandler = new Handler();
-        new Thread(new Runnable() {
-            @Override
-            public void run() {
-                final List<MediaFolder> mediaFolders = MediaProvider.getMediaFolders(getContentResolver(),
-                        perFolderMediaItemLimit, FolderSyncActivity.this);
-                List<SyncedFolder> syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders();
-                List<SyncedFolder> currentAccountSyncedFoldersList = new ArrayList<>();
-                Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(FolderSyncActivity.this);
-                for (SyncedFolder syncedFolder : syncedFolderArrayList) {
-                    if (syncedFolder.getAccount().equals(currentAccount.name)) {
-                        currentAccountSyncedFoldersList.add(syncedFolder);
-                    }
-                }
+        final List<MediaFolder> mediaFolders = MediaProvider.getImageFolders(getContentResolver(),
+                perFolderMediaItemLimit, SyncedFoldersActivity.this);
+        mediaFolders.addAll(MediaProvider.getVideoFolders(getContentResolver(), perFolderMediaItemLimit));
+
+        List<SyncedFolder> syncedFolderArrayList = mSyncedFolderProvider.getSyncedFolders();
+        List<SyncedFolder> currentAccountSyncedFoldersList = new ArrayList<>();
+        Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(SyncedFoldersActivity.this);
+        for (SyncedFolder syncedFolder : syncedFolderArrayList) {
+            if (syncedFolder.getAccount().equals(currentAccount.name)) {
+                currentAccountSyncedFoldersList.add(syncedFolder);
+            }
+        }
 
 
-                syncFolderItems = sortSyncedFolderItems(mergeFolderData(currentAccountSyncedFoldersList,
-                        mediaFolders));
+        List<SyncedFolderDisplayItem> syncFolderItems = sortSyncedFolderItems(
+                mergeFolderData(currentAccountSyncedFoldersList, mediaFolders));
 
 
-                mHandler.post(new TimerTask() {
-                    @Override
-                    public void run() {
-                        mAdapter.setSyncFolderItems(syncFolderItems);
-                        setListShown(true);
-                    }
-                });
-            }
-        }).start();
+        mAdapter.setSyncFolderItems(syncFolderItems);
+        mAdapter.notifyDataSetChanged();
+        setListShown(true);
     }
     }
 
 
     /**
     /**
@@ -215,20 +241,23 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
         Map<String, SyncedFolder> syncedFoldersMap = createSyncedFoldersMap(syncedFolders);
         Map<String, SyncedFolder> syncedFoldersMap = createSyncedFoldersMap(syncedFolders);
         List<SyncedFolderDisplayItem> result = new ArrayList<>();
         List<SyncedFolderDisplayItem> result = new ArrayList<>();
 
 
-
         for (MediaFolder mediaFolder : mediaFolders) {
         for (MediaFolder mediaFolder : mediaFolders) {
-            if (syncedFoldersMap.containsKey(mediaFolder.absolutePath)) {
-                SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath);
-                syncedFoldersMap.remove(mediaFolder.absolutePath);
-                result.add(createSyncedFolder(syncedFolder, mediaFolder));
+            if (syncedFoldersMap.containsKey(mediaFolder.absolutePath+"-"+mediaFolder.type)) {
+                SyncedFolder syncedFolder = syncedFoldersMap.get(mediaFolder.absolutePath+"-"+mediaFolder.type);
+                syncedFoldersMap.remove(mediaFolder.absolutePath+"-"+mediaFolder.type);
+
+                if (MediaFolderType.CUSTOM == syncedFolder.getType()) {
+                    result.add(createSyncedFolderWithoutMediaFolder(syncedFolder));
+                } else {
+                    result.add(createSyncedFolder(syncedFolder, mediaFolder));
+                }
             } else {
             } else {
                 result.add(createSyncedFolderFromMediaFolder(mediaFolder));
                 result.add(createSyncedFolderFromMediaFolder(mediaFolder));
             }
             }
         }
         }
 
 
         for (SyncedFolder syncedFolder : syncedFoldersMap.values()) {
         for (SyncedFolder syncedFolder : syncedFoldersMap.values()) {
-            SyncedFolderDisplayItem syncedFolderDisplayItem = createSyncedFolderWithoutMediaFolder(syncedFolder);
-            result.add(syncedFolderDisplayItem);
+            result.add(createSyncedFolderWithoutMediaFolder(syncedFolder));
         }
         }
 
 
         return result;
         return result;
@@ -277,6 +306,11 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
 
 
     @NonNull
     @NonNull
     private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) {
     private SyncedFolderDisplayItem createSyncedFolderWithoutMediaFolder(@NonNull SyncedFolder syncedFolder) {
+
+        File localFolder = new File(syncedFolder.getLocalPath());
+        File[] files = getFileList(localFolder);
+        List<String> filePaths = getDisplayFilePathList(files);
+
         return new SyncedFolderDisplayItem(
         return new SyncedFolderDisplayItem(
                 syncedFolder.getId(),
                 syncedFolder.getId(),
                 syncedFolder.getLocalPath(),
                 syncedFolder.getLocalPath(),
@@ -287,7 +321,10 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
                 syncedFolder.getAccount(),
                 syncedFolder.getAccount(),
                 syncedFolder.getUploadAction(),
                 syncedFolder.getUploadAction(),
                 syncedFolder.isEnabled(),
                 syncedFolder.isEnabled(),
-                new File(syncedFolder.getLocalPath()).getName());
+                filePaths,
+                localFolder.getName(),
+                files.length,
+                syncedFolder.getType());
     }
     }
 
 
     /**
     /**
@@ -311,7 +348,8 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
                 syncedFolder.isEnabled(),
                 syncedFolder.isEnabled(),
                 mediaFolder.filePaths,
                 mediaFolder.filePaths,
                 mediaFolder.folderName,
                 mediaFolder.folderName,
-                mediaFolder.numberOfFiles);
+                mediaFolder.numberOfFiles,
+                mediaFolder.type);
     }
     }
 
 
     /**
     /**
@@ -334,7 +372,42 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
                 false,
                 false,
                 mediaFolder.filePaths,
                 mediaFolder.filePaths,
                 mediaFolder.folderName,
                 mediaFolder.folderName,
-                mediaFolder.numberOfFiles);
+                mediaFolder.numberOfFiles,
+                mediaFolder.type);
+    }
+
+    private File[] getFileList(File localFolder) {
+        File[] files = localFolder.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File pathname) {
+                return !pathname.isDirectory();
+            }
+        });
+
+        if (files != null) {
+            Arrays.sort(files, new Comparator<File>() {
+                public int compare(File f1, File f2) {
+                    return Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
+                }
+            });
+        } else {
+            files = new File[]{};
+        }
+
+        return files;
+    }
+
+    private List<String> getDisplayFilePathList(File[] files) {
+        List<String> filePaths = null;
+
+        if (files != null && files.length > 0) {
+            filePaths = new ArrayList<>();
+            for (int i = 0; i < 7 && i < files.length; i++) {
+                filePaths.add(files[i].getAbsolutePath());
+            }
+        }
+
+        return filePaths;
     }
     }
 
 
     /**
     /**
@@ -348,7 +421,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
         Map<String, SyncedFolder> result = new HashMap<>();
         Map<String, SyncedFolder> result = new HashMap<>();
         if (syncFolders != null) {
         if (syncFolders != null) {
             for (SyncedFolder syncFolder : syncFolders) {
             for (SyncedFolder syncFolder : syncFolders) {
-                result.put(syncFolder.getLocalPath(), syncFolder);
+                result.put(syncFolder.getLocalPath()+"-"+syncFolder.getType(), syncFolder);
             }
             }
         }
         }
         return result;
         return result;
@@ -389,6 +462,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
                 result = super.onOptionsItemSelected(item);
                 result = super.onOptionsItemSelected(item);
                 break;
                 break;
         }
         }
+
         return result;
         return result;
     }
     }
 
 
@@ -409,15 +483,27 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
 
 
     @Override
     @Override
     public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
     public void onSyncStatusToggleClick(int section, SyncedFolderDisplayItem syncedFolderDisplayItem) {
+        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
+                getContentResolver());
+
         if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) {
         if (syncedFolderDisplayItem.getId() > UNPERSISTED_ID) {
-            mSyncedFolderProvider.updateFolderSyncEnabled(syncedFolderDisplayItem.getId(),
+            mSyncedFolderProvider.updateSyncedFolderEnabled(syncedFolderDisplayItem.getId(),
                     syncedFolderDisplayItem.isEnabled());
                     syncedFolderDisplayItem.isEnabled());
         } else {
         } else {
-            long storedId = mSyncedFolderProvider.storeFolderSync(syncedFolderDisplayItem);
+            long storedId = mSyncedFolderProvider.storeSyncedFolder(syncedFolderDisplayItem);
             if (storedId != -1) {
             if (storedId != -1) {
                 syncedFolderDisplayItem.setId(storedId);
                 syncedFolderDisplayItem.setId(storedId);
             }
             }
         }
         }
+
+        if (syncedFolderDisplayItem.isEnabled()) {
+            FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem);
+        } else {
+            String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + syncedFolderDisplayItem.getId();
+            arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
+        }
+        FilesSyncHelper.scheduleNJobs(false);
+
     }
     }
 
 
     @Override
     @Override
@@ -437,36 +523,76 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
                 && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
                 && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
             OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
             OCFile chosenFolder = data.getParcelableExtra(FolderPickerActivity.EXTRA_FOLDER);
             mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath());
             mSyncedFolderPreferencesDialogFragment.setRemoteFolderSummary(chosenFolder.getRemotePath());
-
-        } else {
+        } if (requestCode == SyncedFolderPreferencesDialogFragment.REQUEST_CODE__SELECT_LOCAL_FOLDER
+                && resultCode == RESULT_OK && mSyncedFolderPreferencesDialogFragment != null) {
+            String localPath = data.getStringExtra(UploadFilesActivity.EXTRA_CHOSEN_FILES);
+            mSyncedFolderPreferencesDialogFragment.setLocalFolderSummary(localPath);
+        }
+        else {
             super.onActivityResult(requestCode, resultCode, data);
             super.onActivityResult(requestCode, resultCode, data);
         }
         }
     }
     }
 
 
     @Override
     @Override
     public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
     public void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
-        SyncedFolderDisplayItem item = syncFolderItems.get(syncedFolder.getSection());
-        boolean dirty = item.isEnabled() != syncedFolder.getEnabled();
-        item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder
-                .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder
-                .getUploadAction(), syncedFolder.getEnabled());
-
-        if (syncedFolder.getId() == UNPERSISTED_ID) {
-            // newly set up folder sync config
-            long storedId = mSyncedFolderProvider.storeFolderSync(item);
+        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(MainApp.getAppContext().
+                getContentResolver());
+
+        // custom folders newly created aren't in the list already,
+        // so triggering a refresh
+        if (MediaFolderType.CUSTOM.equals(syncedFolder.getType()) && syncedFolder.getId() == UNPERSISTED_ID) {
+            SyncedFolderDisplayItem newCustomFolder = new SyncedFolderDisplayItem(
+                    SyncedFolder.UNPERSISTED_ID, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(),
+                    syncedFolder.getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(),
+                    syncedFolder.getAccount(), syncedFolder.getUploadAction(), syncedFolder.getEnabled(),
+                    new File(syncedFolder.getLocalPath()).getName(), syncedFolder.getType());
+            long storedId = mSyncedFolderProvider.storeSyncedFolder(newCustomFolder);
             if (storedId != -1) {
             if (storedId != -1) {
-                item.setId(storedId);
+                newCustomFolder.setId(storedId);
+                if (newCustomFolder.isEnabled()) {
+                    FilesSyncHelper.insertAllDBEntriesForSyncedFolder(newCustomFolder);
+                } else {
+                    String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + newCustomFolder.getId();
+                    arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
+                }
+                FilesSyncHelper.scheduleNJobs(false);
             }
             }
-
+            mAdapter.addSyncFolderItem(newCustomFolder);
         } else {
         } else {
-            // existing synced folder setup to be updated
-            mSyncedFolderProvider.updateSyncFolder(item);
-        }
-        mSyncedFolderPreferencesDialogFragment = null;
+            SyncedFolderDisplayItem item = mAdapter.get(syncedFolder.getSection());
+            item = updateSyncedFolderItem(item, syncedFolder.getLocalPath(), syncedFolder.getRemotePath(), syncedFolder
+                    .getWifiOnly(), syncedFolder.getChargingOnly(), syncedFolder.getSubfolderByDate(), syncedFolder
+                    .getUploadAction(), syncedFolder.getEnabled());
+
+            if (syncedFolder.getId() == UNPERSISTED_ID) {
+                // newly set up folder sync config
+                long storedId = mSyncedFolderProvider.storeSyncedFolder(item);
+                if (storedId != -1) {
+                    item.setId(storedId);
+                    if (item.isEnabled()) {
+                        FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
+                    } else {
+                        String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
+                        arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
+                    }
+                    FilesSyncHelper.scheduleNJobs(false);
+                }
+            } else {
+                // existing synced folder setup to be updated
+                mSyncedFolderProvider.updateSyncFolder(item);
+                if (item.isEnabled()) {
+                    FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item);
+                } else {
+                    String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
+                    arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);
+                }
+                FilesSyncHelper.scheduleNJobs(false);
+            }
 
 
-        if (dirty) {
             mAdapter.setSyncFolderItem(syncedFolder.getSection(), item);
             mAdapter.setSyncFolderItem(syncedFolder.getSection(), item);
         }
         }
+
+        mSyncedFolderPreferencesDialogFragment = null;
     }
     }
 
 
     @Override
     @Override
@@ -474,6 +600,12 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
         mSyncedFolderPreferencesDialogFragment = null;
         mSyncedFolderPreferencesDialogFragment = null;
     }
     }
 
 
+    @Override
+    public void onDeleteSyncedFolderPreference(SyncedFolderParcelable syncedFolder) {
+        mSyncedFolderProvider.deleteSyncedFolder(syncedFolder.getId());
+        mAdapter.removeItem(syncedFolder.getSection());
+    }
+
     /**
     /**
      * update given synced folder with the given values.
      * update given synced folder with the given values.
      *
      *
@@ -525,4 +657,13 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
                 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
                 super.onRequestPermissionsResult(requestCode, permissions, grantResults);
         }
         }
     }
     }
+
+    public void onAddCustomFolderClick(View view) {
+        Log.d(TAG, "Show custom folder dialog");
+        SyncedFolderDisplayItem emptyCustomFolder = new SyncedFolderDisplayItem(
+                SyncedFolder.UNPERSISTED_ID, null, null, true, false,
+                false, AccountUtils.getCurrentOwnCloudAccount(this).name,
+                FileUploader.LOCAL_BEHAVIOUR_FORGET, false, null, MediaFolderType.CUSTOM);
+        onSyncFolderSettingsClick(0, emptyCustomFolder);
+    }
 }
 }

+ 59 - 21
src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -27,6 +27,8 @@ import android.graphics.PorterDuff;
 import android.os.AsyncTask;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Environment;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.app.DialogFragment;
 import android.support.v4.app.DialogFragment;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentManager;
@@ -75,6 +77,7 @@ public class UploadFilesActivity extends FileActivity implements
     private ArrayAdapter<String> mDirectories;
     private ArrayAdapter<String> mDirectories;
     private File mCurrentDir = null;
     private File mCurrentDir = null;
     private boolean mSelectAll = false;
     private boolean mSelectAll = false;
+    private boolean mLocalFolderPickerMode = false;
     private LocalFileListFragment mFileListFragment;
     private LocalFileListFragment mFileListFragment;
     private Button mCancelBtn;
     private Button mCancelBtn;
     protected Button mUploadBtn;
     protected Button mUploadBtn;
@@ -88,6 +91,10 @@ public class UploadFilesActivity extends FileActivity implements
     public static final String EXTRA_CHOSEN_FILES =
     public static final String EXTRA_CHOSEN_FILES =
             UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES";
             UploadFilesActivity.class.getCanonicalName() + ".EXTRA_CHOSEN_FILES";
 
 
+    public static final String EXTRA_ACTION = UploadFilesActivity.class.getCanonicalName() + ".EXTRA_ACTION";
+    public final static String KEY_LOCAL_FOLDER_PICKER_MODE = UploadFilesActivity.class.getCanonicalName()
+            + ".LOCAL_FOLDER_PICKER_MODE";
+
     public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER;
     public static final int RESULT_OK_AND_MOVE = RESULT_FIRST_USER;
     public static final int RESULT_OK_AND_DO_NOTHING = 2;
     public static final int RESULT_OK_AND_DO_NOTHING = 2;
     public static final int RESULT_OK_AND_DELETE = 3;
     public static final int RESULT_OK_AND_DELETE = 3;
@@ -106,6 +113,11 @@ public class UploadFilesActivity extends FileActivity implements
         Log_OC.d(TAG, "onCreate() start");
         Log_OC.d(TAG, "onCreate() start");
         super.onCreate(savedInstanceState);
         super.onCreate(savedInstanceState);
 
 
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            mLocalFolderPickerMode = extras.getBoolean(KEY_LOCAL_FOLDER_PICKER_MODE, false);
+        }
+
         if(savedInstanceState != null) {
         if(savedInstanceState != null) {
             mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.KEY_DIRECTORY_PATH, Environment
             mCurrentDir = new File(savedInstanceState.getString(UploadFilesActivity.KEY_DIRECTORY_PATH, Environment
                     .getExternalStorageDirectory().getAbsolutePath()));
                     .getExternalStorageDirectory().getAbsolutePath()));
@@ -130,9 +142,14 @@ public class UploadFilesActivity extends FileActivity implements
         // Inflate and set the layout view
         // Inflate and set the layout view
         setContentView(R.layout.upload_files_layout);
         setContentView(R.layout.upload_files_layout);
 
 
+        if (mLocalFolderPickerMode) {
+            findViewById(R.id.upload_options).setVisibility(View.GONE);
+            ((AppCompatButton) findViewById(R.id.upload_files_btn_upload))
+                    .setText(R.string.uploader_btn_alternative_text);
+        }
+
         mFileListFragment = (LocalFileListFragment) getSupportFragmentManager().findFragmentById(R.id.local_files_list);
         mFileListFragment = (LocalFileListFragment) getSupportFragmentManager().findFragmentById(R.id.local_files_list);
         
         
-        
         // Set input controllers
         // Set input controllers
         mCancelBtn = (Button) findViewById(R.id.upload_files_btn_cancel);
         mCancelBtn = (Button) findViewById(R.id.upload_files_btn_cancel);
         mCancelBtn.setOnClickListener(this);
         mCancelBtn.setOnClickListener(this);
@@ -190,10 +207,15 @@ public class UploadFilesActivity extends FileActivity implements
     public boolean onCreateOptionsMenu(Menu menu) {
     public boolean onCreateOptionsMenu(Menu menu) {
         mOptionsMenu = menu;
         mOptionsMenu = menu;
         getMenuInflater().inflate(R.menu.upload_files_picker, menu);
         getMenuInflater().inflate(R.menu.upload_files_picker, menu);
-        MenuItem selectAll = menu.findItem(R.id.action_select_all);
-        setSelectAllMenuItem(selectAll, mSelectAll);
+
+        if(!mLocalFolderPickerMode) {
+            MenuItem selectAll = menu.findItem(R.id.action_select_all);
+            setSelectAllMenuItem(selectAll, mSelectAll);
+        }
+
         MenuItem switchView = menu.findItem(R.id.action_switch_view);
         MenuItem switchView = menu.findItem(R.id.action_switch_view);
         switchView.setTitle(isGridView() ? R.string.action_switch_list_view : R.string.action_switch_grid_view);
         switchView.setTitle(isGridView() ? R.string.action_switch_list_view : R.string.action_switch_grid_view);
+
         return super.onCreateOptionsMenu(menu);
         return super.onCreateOptionsMenu(menu);
     }
     }
 
 
@@ -215,9 +237,6 @@ public class UploadFilesActivity extends FileActivity implements
                 break;
                 break;
             }
             }
             case R.id.action_sort: {
             case R.id.action_sort: {
-                // Read sorting order, default to sort by name ascending
-                Integer sortOrder = PreferenceManager.getSortOrder(this);
-
                 FragmentManager fm = getSupportFragmentManager();
                 FragmentManager fm = getSupportFragmentManager();
                 FragmentTransaction ft = fm.beginTransaction();
                 FragmentTransaction ft = fm.beginTransaction();
                 ft.addToBackStack(null);
                 ft.addToBackStack(null);
@@ -290,7 +309,6 @@ public class UploadFilesActivity extends FileActivity implements
         }
         }
         return true;
         return true;
     }
     }
-
     
     
     @Override
     @Override
     public void onBackPressed() {
     public void onBackPressed() {
@@ -308,9 +326,10 @@ public class UploadFilesActivity extends FileActivity implements
         }
         }
 
 
         // invalidate checked state when navigating directories
         // invalidate checked state when navigating directories
-        setSelectAllMenuItem(mOptionsMenu.findItem(R.id.action_select_all), false);
+        if(!mLocalFolderPickerMode) {
+            setSelectAllMenuItem(mOptionsMenu.findItem(R.id.action_select_all), false);
+        }
     }
     }
-
     
     
     @Override
     @Override
     protected void onSaveInstanceState(Bundle outState) {
     protected void onSaveInstanceState(Bundle outState) {
@@ -361,15 +380,16 @@ public class UploadFilesActivity extends FileActivity implements
         }
         }
     }
     }
 
 
-
-    // Custom array adapter to override text colors
+    /**
+     * Custom array adapter to override text colors
+     */
     private class CustomArrayAdapter<T> extends ArrayAdapter<T> {
     private class CustomArrayAdapter<T> extends ArrayAdapter<T> {
     
     
         public CustomArrayAdapter(UploadFilesActivity ctx, int view) {
         public CustomArrayAdapter(UploadFilesActivity ctx, int view) {
             super(ctx, view);
             super(ctx, view);
         }
         }
-    
-        public View getView(int position, View convertView, ViewGroup parent) {
+
+        public @NonNull View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
             View v = super.getView(position, convertView, parent);
             View v = super.getView(position, convertView, parent);
     
     
             ((TextView) v).setTextColor(getResources().getColorStateList(
             ((TextView) v).setTextColor(getResources().getColorStateList(
@@ -377,8 +397,7 @@ public class UploadFilesActivity extends FileActivity implements
             return v;
             return v;
         }
         }
     
     
-        public View getDropDownView(int position, View convertView,
-                ViewGroup parent) {
+        public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) {
             View v = super.getDropDownView(position, convertView, parent);
             View v = super.getDropDownView(position, convertView, parent);
     
     
             ((TextView) v).setTextColor(getResources().getColorStateList(
             ((TextView) v).setTextColor(getResources().getColorStateList(
@@ -386,7 +405,6 @@ public class UploadFilesActivity extends FileActivity implements
     
     
             return v;
             return v;
         }
         }
-    
     }
     }
 
 
     /**
     /**
@@ -394,9 +412,11 @@ public class UploadFilesActivity extends FileActivity implements
      */
      */
     @Override
     @Override
     public void onDirectoryClick(File directory) {
     public void onDirectoryClick(File directory) {
-        // invalidate checked state when navigating directories
-        MenuItem selectAll = mOptionsMenu.findItem(R.id.action_select_all);
-        setSelectAllMenuItem(selectAll, false);
+        if(!mLocalFolderPickerMode) {
+            // invalidate checked state when navigating directories
+            MenuItem selectAll = mOptionsMenu.findItem(R.id.action_select_all);
+            setSelectAllMenuItem(selectAll, false);
+        }
 
 
         pushDirname(directory);
         pushDirname(directory);
         ActionBar actionBar = getSupportActionBar();
         ActionBar actionBar = getSupportActionBar();
@@ -419,6 +439,14 @@ public class UploadFilesActivity extends FileActivity implements
         return mCurrentDir;
         return mCurrentDir;
     }
     }
 
 
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isFolderPickerMode() {
+        return mLocalFolderPickerMode;
+    }
+
     /**
     /**
      * Performs corresponding action when user presses 'Cancel' or 'Upload' button
      * Performs corresponding action when user presses 'Cancel' or 'Upload' button
      * 
      * 
@@ -432,7 +460,17 @@ public class UploadFilesActivity extends FileActivity implements
             finish();
             finish();
 
 
         } else if (v.getId() == R.id.upload_files_btn_upload) {
         } else if (v.getId() == R.id.upload_files_btn_upload) {
-            new CheckAvailableSpaceTask().execute(mBehaviourSpinner.getSelectedItemPosition()==0);
+            if(mLocalFolderPickerMode) {
+                Intent data = new Intent();
+                if(mCurrentDir != null) {
+                    data.putExtra(EXTRA_CHOSEN_FILES, mCurrentDir.getAbsolutePath());
+                }
+                setResult(RESULT_OK, data);
+
+                finish();
+            } else {
+                new CheckAvailableSpaceTask().execute(mBehaviourSpinner.getSelectedItemPosition() == 0);
+            }
         }
         }
     }
     }
 
 
@@ -445,7 +483,7 @@ public class UploadFilesActivity extends FileActivity implements
     private class CheckAvailableSpaceTask extends AsyncTask<Boolean, Void, Boolean> {
     private class CheckAvailableSpaceTask extends AsyncTask<Boolean, Void, Boolean> {
 
 
         /**
         /**
-         * Updates the UI before trying the movement
+         * Updates the UI before trying the movement.
          */
          */
         @Override
         @Override
         protected void onPreExecute () {
         protected void onPreExecute () {

+ 38 - 19
src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -22,7 +22,6 @@
 package com.owncloud.android.ui.activity;
 package com.owncloud.android.ui.activity;
 
 
 import android.accounts.Account;
 import android.accounts.Account;
-import android.accounts.AccountManager;
 import android.content.ActivityNotFoundException;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ComponentName;
@@ -30,9 +29,11 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.ServiceConnection;
+import android.content.SharedPreferences;
 import android.net.Uri;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.IBinder;
+import android.preference.PreferenceManager;
 import android.support.design.widget.BottomNavigationView;
 import android.support.design.widget.BottomNavigationView;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v4.app.FragmentTransaction;
 import android.view.Menu;
 import android.view.Menu;
@@ -41,21 +42,22 @@ import android.view.MenuItem;
 import android.view.View;
 import android.view.View;
 import android.widget.Toast;
 import android.widget.Toast;
 
 
+import com.evernote.android.job.JobRequest;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
-import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
 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;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.jobs.FilesSyncJob;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
 import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
 import com.owncloud.android.ui.fragment.UploadListFragment;
 import com.owncloud.android.ui.fragment.UploadListFragment;
-import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.AnalyticsUtils;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.MimeTypeUtil;
 
 
 import java.io.File;
 import java.io.File;
@@ -74,8 +76,12 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
 
 
     private static final String SCREEN_NAME = "Uploads";
     private static final String SCREEN_NAME = "Uploads";
 
 
+    private static final String EXPERT_MODE = "expert_mode";
+
     private UploadMessagesReceiver mUploadMessagesReceiver;
     private UploadMessagesReceiver mUploadMessagesReceiver;
 
 
+    private Menu mMenu;
+
     @Override
     @Override
     public void showFiles(boolean onDeviceOnly) {
     public void showFiles(boolean onDeviceOnly) {
         super.showFiles(onDeviceOnly);
         super.showFiles(onDeviceOnly);
@@ -211,9 +217,14 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
                 } else {
                 } else {
                     openDrawer();
                     openDrawer();
                 }
                 }
+                break;
+
             case R.id.action_retry_uploads:
             case R.id.action_retry_uploads:
                 FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
                 FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
                 requester.retryFailedUploads(this, null, null);
                 requester.retryFailedUploads(this, null, null);
+                if (mMenu != null) {
+                    mMenu.removeItem(R.id.action_retry_uploads);
+                }
                 break;
                 break;
 
 
             case R.id.action_clear_failed_uploads:
             case R.id.action_clear_failed_uploads:
@@ -234,6 +245,19 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
                 uploadListFragment.updateUploads();
                 uploadListFragment.updateUploads();
                 break;
                 break;
 
 
+            case R.id.action_force_rescan:
+                new JobRequest.Builder(FilesSyncJob.TAG)
+                        .setExact(1_000L)
+                        .setUpdateCurrent(false)
+                        .build()
+                        .schedule();
+                
+                if (mMenu != null) {
+                    mMenu.removeItem(R.id.action_force_rescan);
+                }
+
+                break;
+
             default:
             default:
                 retval = super.onOptionsItemSelected(item);
                 retval = super.onOptionsItemSelected(item);
         }
         }
@@ -243,8 +267,14 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
 
 
     @Override
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
     public boolean onCreateOptionsMenu(Menu menu) {
-        MenuInflater inflater = getMenuInflater();
-        inflater.inflate(R.menu.upload_list_menu, menu);
+        SharedPreferences appPrefs =
+                PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+        if (appPrefs.getBoolean(EXPERT_MODE, false)) {
+            MenuInflater inflater = getMenuInflater();
+            inflater.inflate(R.menu.upload_list_menu, menu);
+            mMenu = menu;
+        }
+
         return true;
         return true;
     }
     }
 
 
@@ -252,17 +282,7 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
         super.onActivityResult(requestCode, resultCode, data);
         if (requestCode == FileActivity.REQUEST_CODE__UPDATE_CREDENTIALS && resultCode == RESULT_OK) {
         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
-            );
+            FilesSyncHelper.restartJobsIfNeeded();
         }
         }
     }
     }
 
 
@@ -283,8 +303,7 @@ public class UploadListActivity extends FileActivity implements UploadListFragme
 
 
             } else {
             } else {
                 // already updated -> just retry!
                 // already updated -> just retry!
-                FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-                requester.retryFailedUploads(this, account, UploadResult.CREDENTIAL_ERROR);
+                FilesSyncHelper.restartJobsIfNeeded();
             }
             }
 
 
         } else {
         } else {

+ 2 - 8
src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java

@@ -63,7 +63,6 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.PushConfigurationState;
 import com.owncloud.android.datamodel.PushConfigurationState;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.lib.common.UserInfo;
 import com.owncloud.android.lib.common.UserInfo;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -410,16 +409,11 @@ public class UserInfoActivity extends FileActivity {
                                             contentResolver);
                                             contentResolver);
                                     syncedFolderProvider.deleteSyncFoldersForAccount(account);
                                     syncedFolderProvider.deleteSyncFoldersForAccount(account);
 
 
-                                    UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(
-                                            contentResolver, getActivity());
-                                    uploadsStorageManager.cancelPendingAutoUploadJobsForAccount(account);
-                                    uploadsStorageManager.removeAccountUploads(account);
-
                                     // disable daily backup
                                     // disable daily backup
                                     ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
                                     ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
                                             contentResolver);
                                             contentResolver);
 
 
-                                    arbitraryDataProvider.storeOrUpdateKeyValue(account,
+                                    arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
                                             ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
                                             ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
                                             "false");
                                             "false");
 
 
@@ -433,7 +427,7 @@ public class UserInfoActivity extends FileActivity {
                                         PushConfigurationState pushArbitraryData = gson.fromJson(arbitraryDataPushString,
                                         PushConfigurationState pushArbitraryData = gson.fromJson(arbitraryDataPushString,
                                                 PushConfigurationState.class);
                                                 PushConfigurationState.class);
                                         pushArbitraryData.setShouldBeDeleted(true);
                                         pushArbitraryData.setShouldBeDeleted(true);
-                                        arbitraryDataProvider.storeOrUpdateKeyValue(account, PushUtils.KEY_PUSH,
+                                        arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PushUtils.KEY_PUSH,
                                                 gson.toJson(pushArbitraryData));
                                                 gson.toJson(pushArbitraryData));
                                         EventBus.getDefault().post(new TokenPushEvent());
                                         EventBus.getDefault().post(new TokenPushEvent());
                                     }
                                     }

+ 51 - 83
src/main/java/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java

@@ -1,21 +1,21 @@
 /**
 /**
- *  ownCloud Android client application
+ * ownCloud Android client application
  *
  *
- *  @author LukeOwncloud
- *  @author masensio
- *  Copyright (C) 2016 ownCloud Inc.
+ * @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 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.
+ * 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/>.
+ * 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;
 package com.owncloud.android.ui.adapter;
 
 
@@ -26,7 +26,6 @@ import android.graphics.Bitmap;
 import android.text.format.DateUtils;
 import android.text.format.DateUtils;
 import android.view.LayoutInflater;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewGroup;
 import android.widget.BaseExpandableListAdapter;
 import android.widget.BaseExpandableListAdapter;
 import android.widget.ExpandableListView;
 import android.widget.ExpandableListView;
@@ -34,7 +33,6 @@ import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.ImageView;
 import android.widget.ProgressBar;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.TextView;
-import android.widget.Toast;
 
 
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.authentication.AccountUtils;
@@ -43,7 +41,6 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
 import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
 import com.owncloud.android.db.OCUpload;
 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;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -102,10 +99,10 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
 
 
             @Override
             @Override
             public int compare(OCUpload upload1, OCUpload upload2) {
             public int compare(OCUpload upload1, OCUpload upload2) {
-                if (upload1 == null){
+                if (upload1 == null) {
                     return -1;
                     return -1;
                 }
                 }
-                if (upload2 == null){
+                if (upload2 == null) {
                     return 1;
                     return 1;
                 }
                 }
                 if (UploadStatus.UPLOAD_IN_PROGRESS.equals(upload1.getUploadStatus())) {
                 if (UploadStatus.UPLOAD_IN_PROGRESS.equals(upload1.getUploadStatus())) {
@@ -153,7 +150,7 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
         mUploadGroups[0] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_current_uploads)) {
         mUploadGroups[0] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_current_uploads)) {
             @Override
             @Override
             public void refresh() {
             public void refresh() {
-                items = mUploadsStorageManager.getCurrentAndPendingUploads();
+                items = mUploadsStorageManager.getCurrentAndPendingUploadsForCurrentAccount();
                 Arrays.sort(items, comparator);
                 Arrays.sort(items, comparator);
             }
             }
 
 
@@ -165,7 +162,7 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
         mUploadGroups[1] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_failed_uploads)) {
         mUploadGroups[1] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_failed_uploads)) {
             @Override
             @Override
             public void refresh() {
             public void refresh() {
-                items = mUploadsStorageManager.getFailedButNotDelayedUploads();
+                items = mUploadsStorageManager.getFailedButNotDelayedUploadsForCurrentAccount();
                 Arrays.sort(items, comparator);
                 Arrays.sort(items, comparator);
             }
             }
 
 
@@ -178,7 +175,7 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
         mUploadGroups[2] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_finished_uploads)) {
         mUploadGroups[2] = new UploadGroup(mParentActivity.getString(R.string.uploads_view_group_finished_uploads)) {
             @Override
             @Override
             public void refresh() {
             public void refresh() {
-                items = mUploadsStorageManager.getFinishedUploads();
+                items = mUploadsStorageManager.getFinishedUploadsForCurrentAccount();
                 Arrays.sort(items, comparator);
                 Arrays.sort(items, comparator);
             }
             }
 
 
@@ -292,33 +289,33 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
                             /// ... unbind the old progress bar, if any; ...
                             /// ... unbind the old progress bar, if any; ...
                             if (mProgressListener != null) {
                             if (mProgressListener != null) {
                                 binder.removeDatatransferProgressListener(
                                 binder.removeDatatransferProgressListener(
-                                    mProgressListener,
-                                    mProgressListener.getUpload()   // the one that was added
+                                        mProgressListener,
+                                        mProgressListener.getUpload()   // the one that was added
                                 );
                                 );
                             }
                             }
                             /// ... then, bind the current progress bar to listen for updates
                             /// ... then, bind the current progress bar to listen for updates
                             mProgressListener = new ProgressListener(upload, progressBar);
                             mProgressListener = new ProgressListener(upload, progressBar);
                             binder.addDatatransferProgressListener(
                             binder.addDatatransferProgressListener(
-                                mProgressListener,
-                                upload
+                                    mProgressListener,
+                                    upload
                             );
                             );
 
 
                         } else {
                         } else {
                             /// not really uploading; stop listening progress if view is reused!
                             /// not really uploading; stop listening progress if view is reused!
                             if (convertView != null &&
                             if (convertView != null &&
                                     mProgressListener != null &&
                                     mProgressListener != null &&
-                                    mProgressListener.isWrapping(progressBar))  {
+                                    mProgressListener.isWrapping(progressBar)) {
                                 binder.removeDatatransferProgressListener(
                                 binder.removeDatatransferProgressListener(
-                                    mProgressListener,
-                                    mProgressListener.getUpload()   // the one that was added
+                                        mProgressListener,
+                                        mProgressListener.getUpload()   // the one that was added
                                 );
                                 );
                                 mProgressListener = null;
                                 mProgressListener = null;
                             }
                             }
                         }
                         }
                     } else {
                     } else {
                         Log_OC.w(
                         Log_OC.w(
-                            TAG,
-                            "FileUploaderBinder not ready yet for upload " + upload.getRemotePath()
+                                TAG,
+                                "FileUploaderBinder not ready yet for upload " + upload.getRemotePath()
                         );
                         );
                     }
                     }
                     uploadDateTextView.setVisibility(View.GONE);
                     uploadDateTextView.setVisibility(View.GONE);
@@ -336,13 +333,14 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
             }
             }
             statusTextView.setText(status);
             statusTextView.setText(status);
 
 
+            /// bind listeners to perform actions
             /// bind listeners to perform actions
             /// bind listeners to perform actions
             ImageButton rightButton = (ImageButton) view.findViewById(R.id.upload_right_button);
             ImageButton rightButton = (ImageButton) view.findViewById(R.id.upload_right_button);
             if (upload.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS) {
             if (upload.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS) {
                 //Cancel
                 //Cancel
                 rightButton.setImageResource(R.drawable.ic_action_cancel_grey);
                 rightButton.setImageResource(R.drawable.ic_action_cancel_grey);
                 rightButton.setVisibility(View.VISIBLE);
                 rightButton.setVisibility(View.VISIBLE);
-                rightButton.setOnClickListener(new OnClickListener() {
+                rightButton.setOnClickListener(new View.OnClickListener() {
                     @Override
                     @Override
                     public void onClick(View v) {
                     public void onClick(View v) {
                         FileUploader.FileUploaderBinder uploaderBinder = mParentActivity.getFileUploaderBinder();
                         FileUploader.FileUploaderBinder uploaderBinder = mParentActivity.getFileUploaderBinder();
@@ -357,7 +355,7 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
                 //Delete
                 //Delete
                 rightButton.setImageResource(R.drawable.ic_action_delete_grey);
                 rightButton.setImageResource(R.drawable.ic_action_delete_grey);
                 rightButton.setVisibility(View.VISIBLE);
                 rightButton.setVisibility(View.VISIBLE);
-                rightButton.setOnClickListener(new OnClickListener() {
+                rightButton.setOnClickListener(new View.OnClickListener() {
                     @Override
                     @Override
                     public void onClick(View v) {
                     public void onClick(View v) {
                         mUploadsStorageManager.removeUpload(upload);
                         mUploadsStorageManager.removeUpload(upload);
@@ -368,41 +366,8 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
             } else {    // UploadStatus.UPLOAD_SUCCESS
             } else {    // UploadStatus.UPLOAD_SUCCESS
                 rightButton.setVisibility(View.INVISIBLE);
                 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);
-            }
+            
+            view.setOnClickListener(null);
 
 
             /// Set icon or thumbnail
             /// Set icon or thumbnail
             ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
             ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
@@ -509,7 +474,7 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
      * the given upload.
      * the given upload.
      *
      *
      * @param upload        Upload to describe.
      * @param upload        Upload to describe.
-     * @return              Text describing the status of the given upload.
+     * @return Text describing the status of the given upload.
      */
      */
     private String getStatusText(OCUpload upload) {
     private String getStatusText(OCUpload upload) {
 
 
@@ -533,37 +498,37 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
                 switch (upload.getLastResult()) {
                 switch (upload.getLastResult()) {
                     case CREDENTIAL_ERROR:
                     case CREDENTIAL_ERROR:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_failed_credentials_error
+                                R.string.uploads_view_upload_status_failed_credentials_error
                         );
                         );
                         break;
                         break;
                     case FOLDER_ERROR:
                     case FOLDER_ERROR:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_failed_folder_error
+                                R.string.uploads_view_upload_status_failed_folder_error
                         );
                         );
                         break;
                         break;
                     case FILE_NOT_FOUND:
                     case FILE_NOT_FOUND:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_failed_localfile_error
+                                R.string.uploads_view_upload_status_failed_localfile_error
                         );
                         );
                         break;
                         break;
                     case FILE_ERROR:
                     case FILE_ERROR:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_failed_file_error
+                                R.string.uploads_view_upload_status_failed_file_error
                         );
                         );
                         break;
                         break;
                     case PRIVILEDGES_ERROR:
                     case PRIVILEDGES_ERROR:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_failed_permission_error
+                                R.string.uploads_view_upload_status_failed_permission_error
                         );
                         );
                         break;
                         break;
                     case NETWORK_CONNECTION:
                     case NETWORK_CONNECTION:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_failed_connection_error
+                                R.string.uploads_view_upload_status_failed_connection_error
                         );
                         );
                         break;
                         break;
                     case DELAYED_FOR_WIFI:
                     case DELAYED_FOR_WIFI:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_waiting_for_wifi
+                                R.string.uploads_view_upload_status_waiting_for_wifi
                         );
                         );
                         break;
                         break;
                     case DELAYED_FOR_CHARGING:
                     case DELAYED_FOR_CHARGING:
@@ -572,32 +537,35 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
                         break;
                         break;
                     case CONFLICT_ERROR:
                     case CONFLICT_ERROR:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_conflict
+                                R.string.uploads_view_upload_status_conflict
                         );
                         );
                         break;
                         break;
                     case SERVICE_INTERRUPTED:
                     case SERVICE_INTERRUPTED:
-                        status =  mParentActivity.getString(
-                            R.string.uploads_view_upload_status_service_interrupted
+                        status = mParentActivity.getString(
+                                R.string.uploads_view_upload_status_service_interrupted
                         );
                         );
                         break;
                         break;
                     case UNKNOWN:
                     case UNKNOWN:
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_unknown_fail
+                                R.string.uploads_view_upload_status_unknown_fail
                         );
                         );
                         break;
                         break;
                     case CANCELLED:
                     case CANCELLED:
                         // should not get here ; cancelled uploads should be wiped out
                         // should not get here ; cancelled uploads should be wiped out
                         status = mParentActivity.getString(
                         status = mParentActivity.getString(
-                            R.string.uploads_view_upload_status_cancelled
+                                R.string.uploads_view_upload_status_cancelled
                         );
                         );
                         break;
                         break;
                     case UPLOADED:
                     case UPLOADED:
                         // should not get here ; status should be UPLOAD_SUCCESS
                         // should not get here ; status should be UPLOAD_SUCCESS
-                        status =  mParentActivity.getString(R.string.uploads_view_upload_status_succeeded);
+                        status = mParentActivity.getString(R.string.uploads_view_upload_status_succeeded);
                         break;
                         break;
                     case MAINTENANCE_MODE:
                     case MAINTENANCE_MODE:
                         status = mParentActivity.getString(R.string.maintenance_mode);
                         status = mParentActivity.getString(R.string.maintenance_mode);
                         break;
                         break;
+                    case LOCK_FAILED:
+                        status = mParentActivity.getString(R.string.lock_failed);
+                        break;
                     default:
                     default:
                         status = "Naughty devs added a new fail result but no description for the user";
                         status = "Naughty devs added a new fail result but no description for the user";
                         break;
                         break;
@@ -747,8 +715,8 @@ public class ExpandableUploadListAdapter extends BaseExpandableListAdapter imple
         public boolean isWrapping(ProgressBar progressBar) {
         public boolean isWrapping(ProgressBar progressBar) {
             ProgressBar wrappedProgressBar = mProgressBar.get();
             ProgressBar wrappedProgressBar = mProgressBar.get();
             return (
             return (
-                wrappedProgressBar != null &&
-                wrappedProgressBar == progressBar   // on purpose; don't replace with equals
+                    wrappedProgressBar != null &&
+                            wrappedProgressBar == progressBar   // on purpose; don't replace with equals
             );
             );
         }
         }
 
 

+ 19 - 4
src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java

@@ -41,6 +41,7 @@ import com.owncloud.android.utils.FileStorageUtils;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.MimeTypeUtil;
 
 
 import java.io.File;
 import java.io.File;
+import java.io.FileFilter;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Collections;
@@ -58,10 +59,12 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA
 
 
     private Context mContext;
     private Context mContext;
     private File[] mFiles = null;
     private File[] mFiles = null;
-    private Vector<File> mFilesAll = new Vector<File>();
+    private Vector<File> mFilesAll = new Vector<>();
+    private boolean mLocalFolderPicker;
 
 
-    public LocalFileListAdapter(File directory, Context context) {
+    public LocalFileListAdapter(boolean localFolderPickerMode, File directory, Context context) {
         mContext = context;
         mContext = context;
+        mLocalFolderPicker = localFolderPickerMode;
 
 
         // Read sorting order, default to sort by name ascending
         // Read sorting order, default to sort by name ascending
         FileStorageUtils.mSortOrder = PreferenceManager.getSortOrder(context);
         FileStorageUtils.mSortOrder = PreferenceManager.getSortOrder(context);
@@ -272,7 +275,11 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA
      * @param directory     New file to adapt. Can be NULL, meaning "no content to adapt".
      * @param directory     New file to adapt. Can be NULL, meaning "no content to adapt".
      */
      */
     public void swapDirectory(final File directory) {
     public void swapDirectory(final File directory) {
-        mFiles = (directory != null ? directory.listFiles() : null);
+        if(mLocalFolderPicker) {
+            mFiles = (directory != null ? getFolders(directory) : null);
+        } else {
+            mFiles = (directory != null ? directory.listFiles() : null);
+        }
         if (mFiles != null) {
         if (mFiles != null) {
             Arrays.sort(mFiles, new Comparator<File>() {
             Arrays.sort(mFiles, new Comparator<File>() {
                 @Override
                 @Override
@@ -288,7 +295,6 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA
                 private int compareNames(File lhs, File rhs) {
                 private int compareNames(File lhs, File rhs) {
                     return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase());                
                     return lhs.getName().toLowerCase().compareTo(rhs.getName().toLowerCase());                
                 }
                 }
-            
             });
             });
 
 
             mFiles = FileStorageUtils.sortLocalFolder(mFiles);
             mFiles = FileStorageUtils.sortLocalFolder(mFiles);
@@ -317,6 +323,15 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA
         notifyDataSetChanged();
         notifyDataSetChanged();
     }
     }
 
 
+    private File[] getFolders(final File directory) {
+        return directory.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(File file) {
+                return file.isDirectory();
+            }
+        });
+    }
+
     public void filter(String text){
     public void filter(String text){
         if(text.isEmpty()){
         if(text.isEmpty()){
             mFiles = mFilesAll.toArray(new File[1]);
             mFiles = mFilesAll.toArray(new File[1]);

+ 67 - 40
src/main/java/com/owncloud/android/ui/adapter/FolderSyncAdapter.java → src/main/java/com/owncloud/android/ui/adapter/SyncedFolderAdapter.java

@@ -1,22 +1,22 @@
 /**
 /**
  *   Nextcloud Android client application
  *   Nextcloud Android client application
  *
  *
- *   @author Andy Scherzinger
- *   Copyright (C) 2016 Andy Scherzinger
- *   Copyright (C) 2016 Nextcloud
+ * @author Andy Scherzinger
+ * Copyright (C) 2016 Andy Scherzinger
+ * Copyright (C) 2016 Nextcloud
  *
  *
- *   This program is free software; you can redistribute it and/or
- *   modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- *   License as published by the Free Software Foundation; either
- *   version 3 of the License, or any later version.
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
  *
  *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ * 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 AFFERO GENERAL PUBLIC LICENSE for more details.
  *
  *
- *   You should have received a copy of the GNU Affero General Public
- *   License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
 
 
 package com.owncloud.android.ui.adapter;
 package com.owncloud.android.ui.adapter;
@@ -29,10 +29,12 @@ import android.view.ViewGroup;
 import android.widget.ImageButton;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 import android.widget.TextView;
 
 
 import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter;
 import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.utils.ThemeUtils;
 import com.owncloud.android.utils.ThemeUtils;
@@ -44,7 +46,7 @@ import java.util.List;
 /**
 /**
  * Adapter to display all auto-synced folders and/or instant upload media folders.
  * Adapter to display all auto-synced folders and/or instant upload media folders.
  */
  */
-public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAdapter.MainViewHolder> {
+public class SyncedFolderAdapter extends SectionedRecyclerViewAdapter<SyncedFolderAdapter.MainViewHolder> {
 
 
     private final Context mContext;
     private final Context mContext;
     private final int mGridWidth;
     private final int mGridWidth;
@@ -53,19 +55,20 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
     private final List<SyncedFolderDisplayItem> mSyncFolderItems;
     private final List<SyncedFolderDisplayItem> mSyncFolderItems;
     private final boolean mLight;
     private final boolean mLight;
 
 
-    public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, boolean light) {
+    public SyncedFolderAdapter(Context context, int gridWidth, ClickListener listener, boolean light) {
         mContext = context;
         mContext = context;
         mGridWidth = gridWidth;
         mGridWidth = gridWidth;
         mGridTotal = gridWidth * 2;
         mGridTotal = gridWidth * 2;
         mListener = listener;
         mListener = listener;
         mSyncFolderItems = new ArrayList<>();
         mSyncFolderItems = new ArrayList<>();
         mLight = light;
         mLight = light;
+
+        shouldShowHeadersForEmptySections(true);
     }
     }
 
 
     public void setSyncFolderItems(List<SyncedFolderDisplayItem> syncFolderItems) {
     public void setSyncFolderItems(List<SyncedFolderDisplayItem> syncFolderItems) {
         mSyncFolderItems.clear();
         mSyncFolderItems.clear();
         mSyncFolderItems.addAll(syncFolderItems);
         mSyncFolderItems.addAll(syncFolderItems);
-        notifyDataSetChanged();
     }
     }
 
 
     public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderItem) {
     public void setSyncFolderItem(int location, SyncedFolderDisplayItem syncFolderItem) {
@@ -73,6 +76,16 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
         notifyDataSetChanged();
         notifyDataSetChanged();
     }
     }
 
 
+    public void addSyncFolderItem(SyncedFolderDisplayItem syncFolderItem) {
+        mSyncFolderItems.add(syncFolderItem);
+        notifyDataSetChanged();
+    }
+
+    public void removeItem(int section) {
+        mSyncFolderItems.remove(section);
+        notifyDataSetChanged();
+    }
+
     @Override
     @Override
     public int getSectionCount() {
     public int getSectionCount() {
         return mSyncFolderItems.size();
         return mSyncFolderItems.size();
@@ -87,18 +100,39 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
         }
         }
     }
     }
 
 
+    public SyncedFolderDisplayItem get(int section) {
+        return mSyncFolderItems.get(section);
+    }
+
     @Override
     @Override
     public void onBindHeaderViewHolder(final MainViewHolder holder, final int section) {
     public void onBindHeaderViewHolder(final MainViewHolder holder, final int section) {
+        holder.mainHeaderContainer.setVisibility(View.VISIBLE);
+
         holder.title.setText(mSyncFolderItems.get(section).getFolderName());
         holder.title.setText(mSyncFolderItems.get(section).getFolderName());
+
+        if (MediaFolderType.VIDEO == mSyncFolderItems.get(section).getType()) {
+            holder.type.setImageResource(R.drawable.ic_video_18dp);
+        } else if (MediaFolderType.IMAGE == mSyncFolderItems.get(section).getType()) {
+            holder.type.setImageResource(R.drawable.ic_image_18dp);
+        } else {
+            holder.type.setImageResource(R.drawable.ic_folder_star_18dp);
+        }
+
         holder.syncStatusButton.setVisibility(View.VISIBLE);
         holder.syncStatusButton.setVisibility(View.VISIBLE);
         holder.syncStatusButton.setTag(section);
         holder.syncStatusButton.setTag(section);
-        holder.syncStatusButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled());
-                setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
-                mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section));
-            }
+        holder.syncStatusButton.setOnClickListener(v -> {
+            mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled());
+            setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
+            mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section));
+        });
+        setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
+
+        holder.syncStatusButton.setVisibility(View.VISIBLE);
+        holder.syncStatusButton.setTag(section);
+        holder.syncStatusButton.setOnClickListener(v -> {
+            mSyncFolderItems.get(section).setEnabled(!mSyncFolderItems.get(section).isEnabled());
+            setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
+            mListener.onSyncStatusToggleClick(section, mSyncFolderItems.get(section));
         });
         });
         setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
         setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
 
 
@@ -107,18 +141,14 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
         } else {
         } else {
             holder.menuButton.setVisibility(View.VISIBLE);
             holder.menuButton.setVisibility(View.VISIBLE);
             holder.menuButton.setTag(section);
             holder.menuButton.setTag(section);
-            holder.menuButton.setOnClickListener(new View.OnClickListener() {
-                @Override
-                public void onClick(View v) {
-                    mListener.onSyncFolderSettingsClick(section, mSyncFolderItems.get(section));
-                }
-            });
+            holder.menuButton.setOnClickListener(v -> mListener.onSyncFolderSettingsClick(section,
+                    mSyncFolderItems.get(section)));
         }
         }
     }
     }
 
 
+
     @Override
     @Override
     public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) {
     public void onBindViewHolder(MainViewHolder holder, int section, int relativePosition, int absolutePosition) {
-
         if (mSyncFolderItems.get(section).getFilePaths() != null) {
         if (mSyncFolderItems.get(section).getFilePaths() != null) {
             File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition));
             File file = new File(mSyncFolderItems.get(section).getFilePaths().get(relativePosition));
 
 
@@ -148,14 +178,6 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
                 holder.counterBar.setVisibility(View.GONE);
                 holder.counterBar.setVisibility(View.GONE);
                 holder.thumbnailDarkener.setVisibility(View.GONE);
                 holder.thumbnailDarkener.setVisibility(View.GONE);
             }
             }
-
-            //holder.itemView.setTag(String.format(Locale.getDefault(), "%d:%d:%d", section, relativePos, absolutePos));
-            //holder.itemView.setOnClickListener(this);
-        } else {
-            holder.itemView.setTag(relativePosition % mGridWidth);
-            holder.counterValue.setText(Long.toString(0));
-            holder.counterBar.setVisibility(View.VISIBLE);
-            holder.thumbnailDarkener.setVisibility(View.VISIBLE);
         }
         }
     }
     }
 
 
@@ -163,7 +185,7 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
     public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
     public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         View v = LayoutInflater.from(parent.getContext()).inflate(
         View v = LayoutInflater.from(parent.getContext()).inflate(
                 viewType == VIEW_TYPE_HEADER ?
                 viewType == VIEW_TYPE_HEADER ?
-                        R.layout.folder_sync_item_header : R.layout.grid_sync_item, parent, false);
+                        R.layout.synced_folders_item_header : R.layout.grid_sync_item, parent, false);
         return new MainViewHolder(v);
         return new MainViewHolder(v);
     }
     }
 
 
@@ -175,16 +197,21 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
     static class MainViewHolder extends RecyclerView.ViewHolder {
     static class MainViewHolder extends RecyclerView.ViewHolder {
         private final ImageView image;
         private final ImageView image;
         private final TextView title;
         private final TextView title;
+        private final ImageView type;
         private final ImageButton menuButton;
         private final ImageButton menuButton;
         private final ImageButton syncStatusButton;
         private final ImageButton syncStatusButton;
         private final LinearLayout counterBar;
         private final LinearLayout counterBar;
         private final TextView counterValue;
         private final TextView counterValue;
         private final ImageView thumbnailDarkener;
         private final ImageView thumbnailDarkener;
 
 
+        private final RelativeLayout mainHeaderContainer;
+
         private MainViewHolder(View itemView) {
         private MainViewHolder(View itemView) {
             super(itemView);
             super(itemView);
+            mainHeaderContainer = (RelativeLayout) itemView.findViewById(R.id.header_container);
             image = (ImageView) itemView.findViewById(R.id.thumbnail);
             image = (ImageView) itemView.findViewById(R.id.thumbnail);
             title = (TextView) itemView.findViewById(R.id.title);
             title = (TextView) itemView.findViewById(R.id.title);
+            type = (ImageView) itemView.findViewById(R.id.type);
             menuButton = (ImageButton) itemView.findViewById(R.id.settingsButton);
             menuButton = (ImageButton) itemView.findViewById(R.id.settingsButton);
             syncStatusButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton);
             syncStatusButton = (ImageButton) itemView.findViewById(R.id.syncStatusButton);
             counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout);
             counterBar = (LinearLayout) itemView.findViewById(R.id.counterLayout);
@@ -194,7 +221,7 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
     }
     }
 
 
     private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) {
     private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) {
-        if(enabled) {
+        if (enabled) {
             syncStatusButton.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_cloud_sync_on,
             syncStatusButton.setImageDrawable(ThemeUtils.tintDrawable(R.drawable.ic_cloud_sync_on,
                     ThemeUtils.primaryColor()));
                     ThemeUtils.primaryColor()));
         } else {
         } else {

+ 3 - 1
src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java

@@ -228,7 +228,9 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
             behaviour,
             behaviour,
             mimeType,
             mimeType,
             false,      // do not create parent folder if not existent
             false,      // do not create parent folder if not existent
-            UploadFileOperation.CREATED_BY_USER
+            UploadFileOperation.CREATED_BY_USER,
+            false,
+             false
         );
         );
     }
     }
 
 

+ 4 - 2
src/main/java/com/owncloud/android/ui/dialog/LoadingDialog.java

@@ -47,8 +47,10 @@ public class LoadingDialog extends DialogFragment {
         setCancelable(false);
         setCancelable(false);
     }
     }
 
 
-    public LoadingDialog(String message) {
-        this.mMessage = message;
+    public static LoadingDialog newInstance(String message) {
+        LoadingDialog loadingDialog = new LoadingDialog();
+        loadingDialog.mMessage = message;
+        return loadingDialog;
     }
     }
 
 
     @Override
     @Override

+ 2 - 4
src/main/java/com/owncloud/android/ui/dialog/SortingOrderDialogFragment.java

@@ -105,7 +105,7 @@ public class SortingOrderDialogFragment extends DialogFragment {
         mView = inflater.inflate(R.layout.sorting_order_fragment, container, false);
         mView = inflater.inflate(R.layout.sorting_order_fragment, container, false);
 
 
         setupDialogElements(mView);
         setupDialogElements(mView);
-        setupListeners(mView);
+        setupListeners();
 
 
         return mView;
         return mView;
     }
     }
@@ -208,10 +208,8 @@ public class SortingOrderDialogFragment extends DialogFragment {
 
 
     /**
     /**
      * setup all listeners.
      * setup all listeners.
-     *
-     * @param view the parent view
      */
      */
-    private void setupListeners(View view) {
+    private void setupListeners() {
         mCancel.setOnClickListener(new View.OnClickListener() {
         mCancel.setOnClickListener(new View.OnClickListener() {
             @Override
             @Override
             public void onClick(View view) {
             public void onClick(View view) {

+ 125 - 25
src/main/java/com/owncloud/android/ui/dialog/SyncedFolderPreferencesDialogFragment.java

@@ -25,6 +25,7 @@ import android.app.Dialog;
 import android.content.DialogInterface;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.Intent;
 import android.graphics.Typeface;
 import android.graphics.Typeface;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.Nullable;
@@ -41,13 +42,19 @@ import android.view.ViewGroup;
 import android.widget.TextView;
 import android.widget.TextView;
 
 
 import com.owncloud.android.R;
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.activity.FolderPickerActivity;
 import com.owncloud.android.ui.activity.FolderPickerActivity;
+import com.owncloud.android.ui.activity.UploadFilesActivity;
 import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable;
 import com.owncloud.android.ui.dialog.parcel.SyncedFolderParcelable;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ThemeUtils;
 import com.owncloud.android.utils.ThemeUtils;
 
 
+import java.io.File;
+
+import static com.owncloud.android.datamodel.SyncedFolderDisplayItem.UNPERSISTED_ID;
+
 /**
 /**
  * Dialog to show the preferences/configuration of a synced folder allowing the user to change the different parameters.
  * Dialog to show the preferences/configuration of a synced folder allowing the user to change the different parameters.
  */
  */
@@ -57,6 +64,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     public static final String SYNCED_FOLDER_PARCELABLE = "SyncedFolderParcelable";
     public static final String SYNCED_FOLDER_PARCELABLE = "SyncedFolderParcelable";
     private static final String BEHAVIOUR_DIALOG_STATE = "BEHAVIOUR_DIALOG_STATE";
     private static final String BEHAVIOUR_DIALOG_STATE = "BEHAVIOUR_DIALOG_STATE";
     public static final int REQUEST_CODE__SELECT_REMOTE_FOLDER = 0;
     public static final int REQUEST_CODE__SELECT_REMOTE_FOLDER = 0;
+    public static final int REQUEST_CODE__SELECT_LOCAL_FOLDER = 1;
 
 
     private CharSequence[] mUploadBehaviorItemStrings;
     private CharSequence[] mUploadBehaviorItemStrings;
 
 
@@ -67,6 +75,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     private AppCompatCheckBox mUploadUseSubfoldersCheckbox;
     private AppCompatCheckBox mUploadUseSubfoldersCheckbox;
     private TextView mUploadBehaviorSummary;
     private TextView mUploadBehaviorSummary;
     private TextView mLocalFolderPath;
     private TextView mLocalFolderPath;
+    private TextView mLocalFolderSummary;
     private TextView mRemoteFolderSummary;
     private TextView mRemoteFolderSummary;
 
 
     private SyncedFolderParcelable mSyncedFolder;
     private SyncedFolderParcelable mSyncedFolder;
@@ -85,7 +94,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
         Bundle args = new Bundle();
         Bundle args = new Bundle();
         args.putParcelable(SYNCED_FOLDER_PARCELABLE, new SyncedFolderParcelable(syncedFolder, section));
         args.putParcelable(SYNCED_FOLDER_PARCELABLE, new SyncedFolderParcelable(syncedFolder, section));
         dialogFragment.setArguments(args);
         dialogFragment.setArguments(args);
-        dialogFragment.setStyle(STYLE_NORMAL,R.style.Theme_ownCloud_Dialog);
+        dialogFragment.setStyle(STYLE_NORMAL, R.style.Theme_ownCloud_Dialog);
 
 
         return dialogFragment;
         return dialogFragment;
     }
     }
@@ -116,7 +125,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         Log_OC.d(TAG, "onCreateView, savedInstanceState is " + savedInstanceState);
         Log_OC.d(TAG, "onCreateView, savedInstanceState is " + savedInstanceState);
 
 
-        mView = inflater.inflate(R.layout.folder_sync_settings_layout, container, false);
+        mView = inflater.inflate(R.layout.synced_folders_settings_layout, container, false);
 
 
         setupDialogElements(mView);
         setupDialogElements(mView);
         setupListeners(mView);
         setupListeners(mView);
@@ -132,20 +141,49 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     private void setupDialogElements(View view) {
     private void setupDialogElements(View view) {
         int accentColor = ThemeUtils.primaryAccentColor();
         int accentColor = ThemeUtils.primaryAccentColor();
 
 
+        if (mSyncedFolder.getType().getId() > MediaFolderType.CUSTOM.getId()) {
+            // hide local folder chooser and delete for non-custom folders
+            view.findViewById(R.id.local_folder_container).setVisibility(View.GONE);
+            view.findViewById(R.id.delete).setVisibility(View.GONE);
+        } else if (mSyncedFolder.getId() <= UNPERSISTED_ID) {
+            // Hide delete/enabled for unpersisted custom folders
+            view.findViewById(R.id.delete).setVisibility(View.GONE);
+            view.findViewById(R.id.sync_enabled).setVisibility(View.GONE);
+
+            // auto set custom folder to enabled
+            mSyncedFolder.setEnabled(true);
+
+            // switch text to create headline
+            ((TextView) view.findViewById(R.id.synced_folders_settings_title))
+                    .setText(R.string.autoupload_create_new_custom_folder);
+
+            // disable save button
+            view.findViewById(R.id.save).setEnabled(false);
+        } else {
+            view.findViewById(R.id.local_folder_container).setVisibility(View.GONE);
+        }
+
         // find/saves UI elements
         // find/saves UI elements
         mEnabledSwitch = (SwitchCompat) view.findViewById(R.id.sync_enabled);
         mEnabledSwitch = (SwitchCompat) view.findViewById(R.id.sync_enabled);
         ThemeUtils.tintSwitch(mEnabledSwitch, accentColor);
         ThemeUtils.tintSwitch(mEnabledSwitch, accentColor);
 
 
-        mLocalFolderPath = (TextView) view.findViewById(R.id.folder_sync_settings_local_folder_path);
+        mLocalFolderPath = (TextView) view.findViewById(R.id.synced_folders_settings_local_folder_path);
 
 
+        mLocalFolderSummary = (TextView) view.findViewById(R.id.local_folder_summary);
         mRemoteFolderSummary = (TextView) view.findViewById(R.id.remote_folder_summary);
         mRemoteFolderSummary = (TextView) view.findViewById(R.id.remote_folder_summary);
 
 
         mUploadOnWifiCheckbox = (AppCompatCheckBox) view.findViewById(R.id.setting_instant_upload_on_wifi_checkbox);
         mUploadOnWifiCheckbox = (AppCompatCheckBox) view.findViewById(R.id.setting_instant_upload_on_wifi_checkbox);
         ThemeUtils.tintCheckbox(mUploadOnWifiCheckbox, accentColor);
         ThemeUtils.tintCheckbox(mUploadOnWifiCheckbox, accentColor);
 
 
-        mUploadOnChargingCheckbox = (AppCompatCheckBox) view.findViewById(
-                R.id.setting_instant_upload_on_charging_checkbox);
-        ThemeUtils.tintCheckbox(mUploadOnChargingCheckbox, accentColor);
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            view.findViewById(R.id.setting_instant_upload_on_charging_container).setVisibility(View.GONE);
+        } else {
+            view.findViewById(R.id.setting_instant_upload_on_charging_container).setVisibility(View.VISIBLE);
+
+            mUploadOnChargingCheckbox = (AppCompatCheckBox) view.findViewById(
+                    R.id.setting_instant_upload_on_charging_checkbox);
+            ThemeUtils.tintCheckbox(mUploadOnChargingCheckbox, accentColor);
+        }
 
 
         mUploadUseSubfoldersCheckbox = (AppCompatCheckBox) view.findViewById(
         mUploadUseSubfoldersCheckbox = (AppCompatCheckBox) view.findViewById(
                 R.id.setting_instant_upload_path_use_subfolders_checkbox);
                 R.id.setting_instant_upload_path_use_subfolders_checkbox);
@@ -161,18 +199,30 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
 
 
         // Set values
         // Set values
         setEnabled(mSyncedFolder.getEnabled());
         setEnabled(mSyncedFolder.getEnabled());
-        mLocalFolderPath.setText(
-                DisplayUtils.createTextWithSpan(
-                        String.format(
-                                getString(R.string.folder_sync_preferences_folder_path),
-                                mSyncedFolder.getLocalPath()),
-                        mSyncedFolder.getFolderName(),
-                        new StyleSpan(Typeface.BOLD)));
 
 
-        mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath());
+        if (mSyncedFolder.getLocalPath() != null && mSyncedFolder.getLocalPath().length() > 0) {
+            mLocalFolderPath.setText(
+                    DisplayUtils.createTextWithSpan(
+                            String.format(
+                                    getString(R.string.synced_folders_preferences_folder_path),
+                                    mSyncedFolder.getLocalPath()),
+                            mSyncedFolder.getFolderName(),
+                            new StyleSpan(Typeface.BOLD)));
+            mLocalFolderSummary.setText(mSyncedFolder.getLocalPath());
+        } else {
+            mLocalFolderSummary.setText(R.string.choose_local_folder);
+        }
+
+        if (mSyncedFolder.getLocalPath() != null && mSyncedFolder.getLocalPath().length() > 0) {
+            mRemoteFolderSummary.setText(mSyncedFolder.getRemotePath());
+        } else {
+            mRemoteFolderSummary.setText(R.string.choose_remote_folder);
+        }
 
 
         mUploadOnWifiCheckbox.setChecked(mSyncedFolder.getWifiOnly());
         mUploadOnWifiCheckbox.setChecked(mSyncedFolder.getWifiOnly());
-        mUploadOnChargingCheckbox.setChecked(mSyncedFolder.getChargingOnly());
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            mUploadOnChargingCheckbox.setChecked(mSyncedFolder.getChargingOnly());
+        }
         mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.getSubfolderByDate());
         mUploadUseSubfoldersCheckbox.setChecked(mSyncedFolder.getSubfolderByDate());
 
 
         mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadActionInteger()]);
         mUploadBehaviorSummary.setText(mUploadBehaviorItemStrings[mSyncedFolder.getUploadActionInteger()]);
@@ -198,6 +248,35 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     public void setRemoteFolderSummary(String path) {
     public void setRemoteFolderSummary(String path) {
         mSyncedFolder.setRemotePath(path);
         mSyncedFolder.setRemotePath(path);
         mRemoteFolderSummary.setText(path);
         mRemoteFolderSummary.setText(path);
+        checkAndUpdateSaveButtonState();
+    }
+
+    /**
+     * set (new) local path on activity result of the folder picker activity. The result gets originally propagated
+     * to the underlying activity since the picker is an activity and the result can't get passed to the dialog
+     * fragment directly.
+     *
+     * @param path the remote path to be set
+     */
+    public void setLocalFolderSummary(String path) {
+        mSyncedFolder.setLocalPath(path);
+        mLocalFolderSummary.setText(path);
+        mLocalFolderPath.setText(
+                DisplayUtils.createTextWithSpan(
+                        String.format(
+                                getString(R.string.synced_folders_preferences_folder_path),
+                                mSyncedFolder.getLocalPath()),
+                        new File(mSyncedFolder.getLocalPath()).getName(),
+                        new StyleSpan(Typeface.BOLD)));
+        checkAndUpdateSaveButtonState();
+    }
+
+    private void checkAndUpdateSaveButtonState() {
+        if (mSyncedFolder.getLocalPath() != null && mSyncedFolder.getRemotePath() != null) {
+            mView.findViewById(R.id.save).setEnabled(true);
+        } else {
+            mView.findViewById(R.id.save).setEnabled(false);
+        }
     }
     }
 
 
     /**
     /**
@@ -208,6 +287,7 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
     private void setupListeners(View view) {
     private void setupListeners(View view) {
         mSave.setOnClickListener(new OnSyncedFolderSaveClickListener());
         mSave.setOnClickListener(new OnSyncedFolderSaveClickListener());
         mCancel.setOnClickListener(new OnSyncedFolderCancelClickListener());
         mCancel.setOnClickListener(new OnSyncedFolderCancelClickListener());
+        view.findViewById(R.id.delete).setOnClickListener(new OnSyncedFolderDeleteClickListener());
 
 
         view.findViewById(R.id.setting_instant_upload_on_wifi_container).setOnClickListener(
         view.findViewById(R.id.setting_instant_upload_on_wifi_container).setOnClickListener(
                 new OnClickListener() {
                 new OnClickListener() {
@@ -218,14 +298,17 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
                     }
                     }
                 });
                 });
 
 
-        view.findViewById(R.id.setting_instant_upload_on_charging_container).setOnClickListener(
-                new OnClickListener() {
-                    @Override
-                    public void onClick(View v) {
-                        mSyncedFolder.setChargingOnly(!mSyncedFolder.getChargingOnly());
-                        mUploadOnChargingCheckbox.toggle();
-                    }
-                });
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+
+            view.findViewById(R.id.setting_instant_upload_on_charging_container).setOnClickListener(
+                    new OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            mSyncedFolder.setChargingOnly(!mSyncedFolder.getChargingOnly());
+                            mUploadOnChargingCheckbox.toggle();
+                        }
+                    });
+        }
 
 
         view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setOnClickListener(
         view.findViewById(R.id.setting_instant_upload_path_use_subfolders_container).setOnClickListener(
                 new OnClickListener() {
                 new OnClickListener() {
@@ -240,12 +323,19 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
             @Override
             @Override
             public void onClick(View v) {
             public void onClick(View v) {
                 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
                 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
-                action.putExtra(
-                        FolderPickerActivity.EXTRA_ACTION, getResources().getText(R.string.choose_remote_folder));
                 getActivity().startActivityForResult(action, REQUEST_CODE__SELECT_REMOTE_FOLDER);
                 getActivity().startActivityForResult(action, REQUEST_CODE__SELECT_REMOTE_FOLDER);
             }
             }
         });
         });
 
 
+        view.findViewById(R.id.local_folder_container).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent action = new Intent(getActivity(), UploadFilesActivity.class);
+                action.putExtra(UploadFilesActivity.KEY_LOCAL_FOLDER_PICKER_MODE, true);
+                getActivity().startActivityForResult(action, REQUEST_CODE__SELECT_LOCAL_FOLDER);
+            }
+        });
+
         view.findViewById(R.id.sync_enabled).setOnClickListener(new OnClickListener() {
         view.findViewById(R.id.sync_enabled).setOnClickListener(new OnClickListener() {
             @Override
             @Override
             public void onClick(View v) {
             public void onClick(View v) {
@@ -330,10 +420,20 @@ public class SyncedFolderPreferencesDialogFragment extends DialogFragment {
         }
         }
     }
     }
 
 
+    private class OnSyncedFolderDeleteClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            dismiss();
+            ((OnSyncedFolderPreferenceListener) getActivity()).onDeleteSyncedFolderPreference(mSyncedFolder);
+        }
+    }
+
     public interface OnSyncedFolderPreferenceListener {
     public interface OnSyncedFolderPreferenceListener {
         void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder);
         void onSaveSyncedFolderPreference(SyncedFolderParcelable syncedFolder);
 
 
         void onCancelSyncedFolderPreference();
         void onCancelSyncedFolderPreference();
+
+        void onDeleteSyncedFolderPreference(SyncedFolderParcelable syncedFolder);
     }
     }
 
 
     @Override
     @Override

+ 13 - 0
src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java

@@ -23,6 +23,7 @@ package com.owncloud.android.ui.dialog.parcel;
 import android.os.Parcel;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Parcelable;
 
 
+import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader;
 
 
@@ -38,6 +39,7 @@ public class SyncedFolderParcelable implements Parcelable {
     private Boolean mEnabled = false;
     private Boolean mEnabled = false;
     private Boolean mSubfolderByDate = false;
     private Boolean mSubfolderByDate = false;
     private Integer mUploadAction;
     private Integer mUploadAction;
+    private MediaFolderType mType;
     private long mId;
     private long mId;
     private String mAccount;
     private String mAccount;
     private int mSection;
     private int mSection;
@@ -54,6 +56,7 @@ public class SyncedFolderParcelable implements Parcelable {
         mChargingOnly = syncedFolderDisplayItem.getChargingOnly();
         mChargingOnly = syncedFolderDisplayItem.getChargingOnly();
         mEnabled = syncedFolderDisplayItem.isEnabled();
         mEnabled = syncedFolderDisplayItem.isEnabled();
         mSubfolderByDate = syncedFolderDisplayItem.getSubfolderByDate();
         mSubfolderByDate = syncedFolderDisplayItem.getSubfolderByDate();
+        mType = syncedFolderDisplayItem.getType();
         mAccount = syncedFolderDisplayItem.getAccount();
         mAccount = syncedFolderDisplayItem.getAccount();
         mUploadAction = syncedFolderDisplayItem.getUploadAction();
         mUploadAction = syncedFolderDisplayItem.getUploadAction();
         mSection = section;
         mSection = section;
@@ -68,6 +71,7 @@ public class SyncedFolderParcelable implements Parcelable {
         mChargingOnly = read.readInt() != 0;
         mChargingOnly = read.readInt() != 0;
         mEnabled = read.readInt() != 0;
         mEnabled = read.readInt() != 0;
         mSubfolderByDate = read.readInt() != 0;
         mSubfolderByDate = read.readInt() != 0;
+        mType = MediaFolderType.getById(read.readInt());
         mAccount = read.readString();
         mAccount = read.readString();
         mUploadAction = read.readInt();
         mUploadAction = read.readInt();
         mSection = read.readInt();
         mSection = read.readInt();
@@ -83,6 +87,7 @@ public class SyncedFolderParcelable implements Parcelable {
         dest.writeInt(mChargingOnly ? 1 : 0);
         dest.writeInt(mChargingOnly ? 1 : 0);
         dest.writeInt(mEnabled ? 1 : 0);
         dest.writeInt(mEnabled ? 1 : 0);
         dest.writeInt(mSubfolderByDate ? 1 : 0);
         dest.writeInt(mSubfolderByDate ? 1 : 0);
+        dest.writeInt(mType.getId());
         dest.writeString(mAccount);
         dest.writeString(mAccount);
         dest.writeInt(mUploadAction);
         dest.writeInt(mUploadAction);
         dest.writeInt(mSection);
         dest.writeInt(mSection);
@@ -163,6 +168,14 @@ public class SyncedFolderParcelable implements Parcelable {
         this.mSubfolderByDate = mSubfolderByDate;
         this.mSubfolderByDate = mSubfolderByDate;
     }
     }
 
 
+    public MediaFolderType getType() {
+        return mType;
+    }
+
+    public void setType(MediaFolderType mType) {
+        this.mType = mType;
+    }
+
     public Integer getUploadAction() {
     public Integer getUploadAction() {
         return mUploadAction;
         return mUploadAction;
     }
     }

+ 10 - 15
src/main/java/com/owncloud/android/services/ShutdownReceiver.java → src/main/java/com/owncloud/android/ui/events/InitiateSyncedFolder.java

@@ -3,7 +3,6 @@
  *
  *
  * @author Mario Danic
  * @author Mario Danic
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2017 Mario Danic
- * Copyright (C) 2017 Nextcloud GmbH
  *
  *
  * This program is free software: you can redistribute it and/or modify
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * it under the terms of the GNU Affero General Public License as published by
@@ -18,23 +17,19 @@
  * You should have received a copy of the GNU Affero General Public License
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  */
-package com.owncloud.android.services;
+package com.owncloud.android.ui.events;
 
 
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
+import com.owncloud.android.datamodel.SyncedFolder;
 
 
-import com.owncloud.android.MainApp;
+public class InitiateSyncedFolder {
+    private final SyncedFolder syncedFolder;
 
 
-/**
- * Handles shutdown procedure - basically just waits a little bit for all jobs to finish
- */
 
 
-public class ShutdownReceiver extends BroadcastReceiver {
-    @Override
-    public void onReceive(final Context context, final Intent intent) {
-        if (MainApp.getSyncedFolderObserverService() != null) {
-            MainApp.getSyncedFolderObserverService().onDestroy();
-        }
+    public InitiateSyncedFolder(SyncedFolder syncedFolder) {
+        this.syncedFolder = syncedFolder;
+    }
+
+    public SyncedFolder getSyncedFolder() {
+        return syncedFolder;
     }
     }
 }
 }

+ 1 - 1
src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java

@@ -955,7 +955,7 @@ public class ExtendedListFragment extends Fragment
             maxColumnSize = maxColumnSizePortrait;
             maxColumnSize = maxColumnSizePortrait;
         }
         }
 
 
-        if (mGridView.getNumColumns() > maxColumnSize) {
+        if (mGridView != null && mGridView.getNumColumns() > maxColumnSize) {
             mGridView.setNumColumns(maxColumnSize);
             mGridView.setNumColumns(maxColumnSize);
             mGridView.invalidateViews();
             mGridView.invalidateViews();
         }
         }

+ 40 - 7
src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java

@@ -26,6 +26,8 @@ import android.os.Bundle;
 import android.os.Environment;
 import android.os.Environment;
 import android.util.SparseBooleanArray;
 import android.util.SparseBooleanArray;
 import android.view.LayoutInflater;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.View;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewGroup;
 import android.widget.AbsListView;
 import android.widget.AbsListView;
@@ -89,7 +91,6 @@ public class LocalFileListFragment extends ExtendedListFragment {
         }
         }
     }
     }
     
     
-    
     /**
     /**
      * {@inheritDoc}
      * {@inheritDoc}
      */
      */
@@ -97,11 +98,19 @@ public class LocalFileListFragment extends ExtendedListFragment {
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         Log_OC.i(TAG, "onCreateView() start");
         Log_OC.i(TAG, "onCreateView() start");
         View v = super.onCreateView(inflater, container, savedInstanceState);
         View v = super.onCreateView(inflater, container, savedInstanceState);
-        setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+
+        if(!mContainerActivity.isFolderPickerMode()) {
+            setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+            setMessageForEmptyList(R.string.file_list_empty_headline, R.string.local_file_list_empty,
+                    R.drawable.ic_list_empty_folder, true);
+        } else {
+            setMessageForEmptyList(R.string.folder_list_empty_headline, R.string.local_folder_list_empty,
+                    R.drawable.ic_list_empty_folder, true);
+        }
+
         setSwipeEnabled(false); // Disable pull-to-refresh
         setSwipeEnabled(false); // Disable pull-to-refresh
         setFabEnabled(false); // Disable FAB
         setFabEnabled(false); // Disable FAB
-        setMessageForEmptyList(R.string.file_list_empty_headline, R.string.local_file_list_empty,
-                R.drawable.ic_list_empty_folder, true);
+
         Log_OC.i(TAG, "onCreateView() end");
         Log_OC.i(TAG, "onCreateView() end");
         return v;
         return v;
     }
     }
@@ -115,11 +124,29 @@ public class LocalFileListFragment extends ExtendedListFragment {
         Log_OC.i(TAG, "onActivityCreated() start");
         Log_OC.i(TAG, "onActivityCreated() start");
         
         
         super.onActivityCreated(savedInstanceState);
         super.onActivityCreated(savedInstanceState);
-        mAdapter = new LocalFileListAdapter(mContainerActivity.getInitialDirectory(), getActivity());
+
+        mAdapter = new LocalFileListAdapter(
+                mContainerActivity.isFolderPickerMode(),
+                mContainerActivity.getInitialDirectory(),
+                getActivity()
+        );
         setListAdapter(mAdapter);
         setListAdapter(mAdapter);
         
         
         Log_OC.i(TAG, "onActivityCreated() stop");
         Log_OC.i(TAG, "onActivityCreated() stop");
     }
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        if (mContainerActivity.isFolderPickerMode()) {
+            menu.removeItem(R.id.action_select_all);
+            menu.removeItem(R.id.action_search);
+        } else {
+            super.onCreateOptionsMenu(menu, inflater);
+        }
+    }
     
     
     /**
     /**
      * Checks the file clicked over. Browses inside if it is a directory.
      * Checks the file clicked over. Browses inside if it is a directory.
@@ -303,8 +330,7 @@ public class LocalFileListFragment extends ExtendedListFragment {
          * @param file
          * @param file
          */
          */
         void onFileClick(File file);
         void onFileClick(File file);
-        
-        
+
         /**
         /**
          * Callback method invoked when the parent activity
          * Callback method invoked when the parent activity
          * is fully created to get the directory to list firstly.
          * is fully created to get the directory to list firstly.
@@ -313,6 +339,13 @@ public class LocalFileListFragment extends ExtendedListFragment {
          */
          */
         File getInitialDirectory();
         File getInitialDirectory();
 
 
+        /**
+         * config check if the list should behave in
+         * folder picker mode only displaying folders but no files.
+         *
+         * @return true if folder picker mode, else false
+         */
+        boolean isFolderPickerMode();
     }
     }
 
 
 
 

+ 1 - 1
src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java

@@ -67,7 +67,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.services.ContactsImportJob;
+import com.owncloud.android.jobs.ContactsImportJob;
 import com.owncloud.android.ui.TextDrawable;
 import com.owncloud.android.ui.TextDrawable;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
 import com.owncloud.android.ui.events.VCardToggleEvent;
 import com.owncloud.android.ui.events.VCardToggleEvent;

+ 2 - 2
src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java

@@ -49,9 +49,9 @@ import com.owncloud.android.R;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.jobs.ContactsBackupJob;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.RefreshFolderOperation;
-import com.owncloud.android.services.ContactsBackupJob;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
 import com.owncloud.android.ui.activity.Preferences;
 import com.owncloud.android.ui.activity.Preferences;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
@@ -338,7 +338,7 @@ public class ContactsBackupFragment extends FileFragment implements DatePickerDi
                     contactsPreferenceActivity.getAccount());
                     contactsPreferenceActivity.getAccount());
         }
         }
 
 
-        arbitraryDataProvider.storeOrUpdateKeyValue(account, PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
+        arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
                 String.valueOf(bool));
                 String.valueOf(bool));
     }
     }
 
 

+ 1 - 1
src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -176,7 +176,7 @@ public class FileOperationsHelper {
             openFileWithIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
             openFileWithIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
 
 
             List<ResolveInfo> launchables = mFileActivity.getPackageManager().
             List<ResolveInfo> launchables = mFileActivity.getPackageManager().
-                    queryIntentActivities(openFileWithIntent, PackageManager.GET_INTENT_FILTERS);
+                    queryIntentActivities(openFileWithIntent, PackageManager.GET_RESOLVED_FILTER);
 
 
             if (launchables != null && launchables.size() > 0) {
             if (launchables != null && launchables.size() > 0) {
                 try {
                 try {

+ 3 - 1
src/main/java/com/owncloud/android/ui/helpers/UriUploader.java

@@ -166,7 +166,9 @@ public class UriUploader {
                 mBehaviour,
                 mBehaviour,
                 null,       // MIME type will be detected from file name
                 null,       // MIME type will be detected from file name
                 false,      // do not create parent folder if not existent
                 false,      // do not create parent folder if not existent
-                UploadFileOperation.CREATED_BY_USER
+                UploadFileOperation.CREATED_BY_USER,
+                false,
+                false
         );
         );
     }
     }
 
 

+ 2 - 4
src/main/java/com/owncloud/android/ui/preview/PreviewTextFragment.java

@@ -183,7 +183,7 @@ public class PreviewTextFragment extends FileFragment {
     }
     }
 
 
     private void loadAndShowTextPreview() {
     private void loadAndShowTextPreview() {
-        mTextLoadTask = new TextLoadAsyncTask(new WeakReference<TextView>(mTextPreview));
+        mTextLoadTask = new TextLoadAsyncTask(new WeakReference<>(mTextPreview));
         mTextLoadTask.execute(getFile().getStoragePath());
         mTextLoadTask.execute(getFile().getStoragePath());
     }
     }
 
 
@@ -192,14 +192,12 @@ public class PreviewTextFragment extends FileFragment {
      * Reads the file to preview and shows its contents. Too critical to be anonymous.
      * Reads the file to preview and shows its contents. Too critical to be anonymous.
      */
      */
     private class TextLoadAsyncTask extends AsyncTask<Object, Void, StringWriter> {
     private class TextLoadAsyncTask extends AsyncTask<Object, Void, StringWriter> {
-        private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT";
         private final WeakReference<TextView> mTextViewReference;
         private final WeakReference<TextView> mTextViewReference;
 
 
         private TextLoadAsyncTask(WeakReference<TextView> textView) {
         private TextLoadAsyncTask(WeakReference<TextView> textView) {
             mTextViewReference = textView;
             mTextViewReference = textView;
         }
         }
 
 
-
         @Override
         @Override
         protected void onPreExecute() {
         protected void onPreExecute() {
             // not used at the moment
             // not used at the moment
@@ -454,7 +452,7 @@ public class PreviewTextFragment extends FileFragment {
      * @return 'True' if the file can be handled by the fragment.
      * @return 'True' if the file can be handled by the fragment.
      */
      */
     public static boolean canBePreviewed(OCFile file) {
     public static boolean canBePreviewed(OCFile file) {
-        final List<String> unsupportedTypes = new LinkedList<String>();
+        final List<String> unsupportedTypes = new LinkedList<>();
         unsupportedTypes.add("text/richtext");
         unsupportedTypes.add("text/richtext");
         unsupportedTypes.add("text/rtf");
         unsupportedTypes.add("text/rtf");
         unsupportedTypes.add("text/vnd.abc");
         unsupportedTypes.add("text/vnd.abc");

+ 22 - 5
src/main/java/com/owncloud/android/utils/BitmapUtils.java

@@ -1,4 +1,4 @@
-/**
+/*
  * ownCloud Android client application
  * ownCloud Android client application
  *
  *
  * @author David A. Velasco
  * @author David A. Velasco
@@ -23,7 +23,7 @@ import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory.Options;
 import android.graphics.BitmapFactory.Options;
 import android.graphics.Matrix;
 import android.graphics.Matrix;
-import android.media.ExifInterface;
+import android.support.media.ExifInterface;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
 
 
@@ -48,7 +48,7 @@ public class BitmapUtils {
      * @param srcPath       Absolute path to the file containing the image.
      * @param srcPath       Absolute path to the file containing the image.
      * @param reqWidth      Width of the surface where the Bitmap will be drawn on, in pixels.
      * @param reqWidth      Width of the surface where the Bitmap will be drawn on, in pixels.
      * @param reqHeight     Height of the surface where the Bitmap will be drawn on, in pixels.
      * @param reqHeight     Height of the surface where the Bitmap will be drawn on, in pixels.
-     * @return
+     * @return decoded bitmap
      */
      */
     public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) {
     public static Bitmap decodeSampledBitmapFromFile(String srcPath, int reqWidth, int reqHeight) {
 
 
@@ -106,6 +106,23 @@ public class BitmapUtils {
         return inSampleSize;
         return inSampleSize;
     }
     }
 
 
+    /**
+     * scales a given bitmap depending on the given size parameters.
+     *
+     * @param bitmap the bitmap to be scaled
+     * @param px     the target pixel size
+     * @param width  the width
+     * @param height the height
+     * @param max    the max(height, width)
+     * @return the scaled bitmap
+     */
+    public static Bitmap scaleBitmap(Bitmap bitmap, float px, int width, int height, int max) {
+        float scale = px / max;
+        int w = Math.round(scale * width);
+        int h = Math.round(scale * height);
+        return Bitmap.createScaledBitmap(bitmap, w, h, true);
+    }
+    
     /**
     /**
      * Rotate bitmap according to EXIF orientation. 
      * Rotate bitmap according to EXIF orientation. 
      * Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ 
      * Cf. http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ 
@@ -172,7 +189,7 @@ public class BitmapUtils {
      *  @param h Hue is specified as degrees in the range 0 - 360.
      *  @param h Hue is specified as degrees in the range 0 - 360.
      *  @param s Saturation is specified as a percentage in the range 1 - 100.
      *  @param s Saturation is specified as a percentage in the range 1 - 100.
      *  @param l Lumanance is specified as a percentage in the range 1 - 100.
      *  @param l Lumanance is specified as a percentage in the range 1 - 100.
-     *  @paran alpha  the alpha value between 0 - 1
+     *  @param alpha  the alpha value between 0 - 1
      *  adapted from https://svn.codehaus.org/griffon/builders/gfxbuilder/tags/GFXBUILDER_0.2/
      *  adapted from https://svn.codehaus.org/griffon/builders/gfxbuilder/tags/GFXBUILDER_0.2/
      *  gfxbuilder-core/src/main/com/camick/awt/HSLColor.java
      *  gfxbuilder-core/src/main/com/camick/awt/HSLColor.java
      */
      */
@@ -200,7 +217,7 @@ public class BitmapUtils {
         s /= 100f;
         s /= 100f;
         l /= 100f;
         l /= 100f;
 
 
-        float q = 0;
+        float q;
 
 
         if (l < 0.5) {
         if (l < 0.5) {
             q = l * (1 + s);
             q = l * (1 + s);

+ 5 - 0
src/main/java/com/owncloud/android/utils/DisplayUtils.java

@@ -389,6 +389,11 @@ public class DisplayUtils {
     public static SpannableStringBuilder createTextWithSpan(String text, String spanText, StyleSpan style) {
     public static SpannableStringBuilder createTextWithSpan(String text, String spanText, StyleSpan style) {
         SpannableStringBuilder sb = new SpannableStringBuilder(text);
         SpannableStringBuilder sb = new SpannableStringBuilder(text);
         int start = text.lastIndexOf(spanText);
         int start = text.lastIndexOf(spanText);
+
+        if (start < 0) {
+            start++;
+        }
+
         int end = start + spanText.length();
         int end = start + spanText.length();
         sb.setSpan(style, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
         sb.setSpan(style, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
         return sb;
         return sb;

+ 329 - 0
src/main/java/com/owncloud/android/utils/FilesSyncHelper.java

@@ -0,0 +1,329 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.utils;
+
+import android.accounts.Account;
+import android.app.job.JobInfo;
+import android.app.job.JobScheduler;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.MediaStore;
+import android.support.annotation.RequiresApi;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.evernote.android.job.JobRequest;
+import com.owncloud.android.MainApp;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
+import com.owncloud.android.datamodel.FilesystemDataProvider;
+import com.owncloud.android.datamodel.MediaFolderType;
+import com.owncloud.android.datamodel.SyncedFolder;
+import com.owncloud.android.datamodel.SyncedFolderProvider;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.db.OCUpload;
+import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.jobs.FilesSyncJob;
+import com.owncloud.android.jobs.NContentObserverJob;
+
+import org.lukhnos.nnio.file.FileVisitResult;
+import org.lukhnos.nnio.file.Files;
+import org.lukhnos.nnio.file.Path;
+import org.lukhnos.nnio.file.Paths;
+import org.lukhnos.nnio.file.SimpleFileVisitor;
+import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/*
+    Various utilities that make auto upload tick
+ */
+public class FilesSyncHelper {
+    public static final String TAG = "FileSyncHelper";
+
+    public static final String GLOBAL = "global";
+    public static final String SYNCEDFOLDERINITIATED = "syncedFolderIntitiated_";
+
+    public static int ContentSyncJobId = 315;
+
+    public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
+        final Context context = MainApp.getAppContext();
+        final ContentResolver contentResolver = context.getContentResolver();
+        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
+
+        Long currentTime = System.currentTimeMillis();
+        double currentTimeInSeconds = currentTime / 1000.0;
+        String currentTimeString = Long.toString((long) currentTimeInSeconds);
+
+        String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId();
+        boolean dryRun = TextUtils.isEmpty(arbitraryDataProvider.getValue
+                (GLOBAL, syncedFolderInitiatedKey));
+
+        if (MediaFolderType.IMAGE == syncedFolder.getType()) {
+            if (dryRun) {
+                arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey,
+                        currentTimeString);
+            } else {
+                FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI
+                        , syncedFolder);
+                FilesSyncHelper.insertContentIntoDB(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                        syncedFolder);
+            }
+
+        } else if (MediaFolderType.VIDEO == syncedFolder.getType()) {
+
+            if (dryRun) {
+                arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey,
+                        currentTimeString);
+            } else {
+                FilesSyncHelper.insertContentIntoDB(android.provider.MediaStore.Video.Media.INTERNAL_CONTENT_URI,
+                        syncedFolder);
+                FilesSyncHelper.insertContentIntoDB(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                        syncedFolder);
+            }
+
+        } else {
+            try {
+
+                if (dryRun) {
+                    arbitraryDataProvider.storeOrUpdateKeyValue(GLOBAL, syncedFolderInitiatedKey,
+                            currentTimeString);
+                } else {
+                    FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
+                    Path path = Paths.get(syncedFolder.getLocalPath());
+
+                    String dateInitiated = arbitraryDataProvider.getValue(GLOBAL,
+                            syncedFolderInitiatedKey);
+
+                    Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+                        @Override
+                        public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
+
+                            File file = path.toFile();
+                            if (attrs.lastModifiedTime().toMillis() >= Long.parseLong(dateInitiated) * 1000) {
+                                filesystemDataProvider.storeOrUpdateFileValue(path.toAbsolutePath().toString(),
+                                        attrs.lastModifiedTime().toMillis(), file.isDirectory(), syncedFolder);
+                            }
+
+                            return FileVisitResult.CONTINUE;
+                        }
+
+                        @Override
+                        public FileVisitResult visitFileFailed(Path file, IOException exc) {
+                            return FileVisitResult.CONTINUE;
+                        }
+                    });
+
+                }
+
+            } catch (IOException e) {
+                Log.e(TAG, "Something went wrong while indexing files for auto upload " + e.getLocalizedMessage());
+            }
+        }
+    }
+
+    public static void insertAllDBEntries(boolean skipCustom) {
+        final Context context = MainApp.getAppContext();
+        final ContentResolver contentResolver = context.getContentResolver();
+        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver);
+
+        for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
+            if ((syncedFolder.isEnabled()) && ((MediaFolderType.CUSTOM != syncedFolder.getType()) || !skipCustom)) {
+                    insertAllDBEntriesForSyncedFolder(syncedFolder);
+            }
+        }
+    }
+
+    private static void insertContentIntoDB(Uri uri, SyncedFolder syncedFolder) {
+        final Context context = MainApp.getAppContext();
+        final ContentResolver contentResolver = context.getContentResolver();
+        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
+
+        Cursor cursor;
+        int column_index_data;
+        int column_index_date_modified;
+
+        final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
+
+        String contentPath;
+        boolean isFolder;
+
+        String[] projection = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DATE_MODIFIED};
+
+        String path = syncedFolder.getLocalPath();
+        if (!path.endsWith("/")) {
+            path = path + "/%";
+        } else {
+            path = path + "%";
+        }
+
+        String syncedFolderInitiatedKey = SYNCEDFOLDERINITIATED + syncedFolder.getId();
+        String dateInitiated = arbitraryDataProvider.getValue(GLOBAL, syncedFolderInitiatedKey);
+
+        cursor = context.getContentResolver().query(uri, projection, MediaStore.MediaColumns.DATA + " LIKE ?",
+                new String[]{path}, null);
+
+        if (cursor != null) {
+            column_index_data = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
+            column_index_date_modified = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED);
+            while (cursor.moveToNext()) {
+                contentPath = cursor.getString(column_index_data);
+                isFolder = new File(contentPath).isDirectory();
+                if (cursor.getLong(column_index_date_modified) >= Long.parseLong(dateInitiated)) {
+                    filesystemDataProvider.storeOrUpdateFileValue(contentPath,
+                            cursor.getLong(column_index_date_modified), isFolder, syncedFolder);
+                }
+            }
+            cursor.close();
+        }
+    }
+
+    public static void restartJobsIfNeeded() {
+        final Context context = MainApp.getAppContext();
+
+        FileUploader.UploadRequester uploadRequester = new FileUploader.UploadRequester();
+
+        boolean accountExists;
+
+        UploadsStorageManager uploadsStorageManager = new UploadsStorageManager(context.getContentResolver(), context);
+        OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
+
+        for (OCUpload failedUpload : failedUploads) {
+            accountExists = false;
+
+            // check if accounts still exists
+            for (Account account : AccountUtils.getAccounts(context)) {
+                if (account.name.equals(failedUpload.getAccountName())) {
+                    accountExists = true;
+                    break;
+                }
+            }
+
+            if (!accountExists) {
+                uploadsStorageManager.removeUpload(failedUpload);
+            }
+        }
+
+        uploadRequester.retryFailedUploads(
+                context,
+                null,
+                null
+        );
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    public static boolean isContentObserverJobScheduled() {
+        JobScheduler js = MainApp.getAppContext().getSystemService(JobScheduler.class);
+        List<JobInfo> jobs = js.getAllPendingJobs();
+
+        if (jobs == null || jobs.size() == 0) {
+            return false;
+        }
+
+        for (int i = 0; i < jobs.size(); i++) {
+            if (jobs.get(i).getId() == ContentSyncJobId) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public static void scheduleNJobs(boolean force) {
+        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(MainApp.getAppContext().
+                getContentResolver());
+
+
+        boolean hasVideoFolders = false;
+        boolean hasImageFolders = false;
+
+        if (syncedFolderProvider.getSyncedFolders() != null) {
+            for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
+                if (MediaFolderType.VIDEO == syncedFolder.getType()) {
+                    hasVideoFolders = true;
+                } else if (MediaFolderType.IMAGE == syncedFolder.getType()) {
+                    hasImageFolders = true;
+                }
+            }
+        }
+
+            if (hasImageFolders || hasVideoFolders) {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                    scheduleJobOnN(hasImageFolders, hasVideoFolders, force);
+                }
+            } else {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+                    cancelJobOnN();
+                }
+            }
+    }
+
+    public static void scheduleFilesSyncIfNeeded() {
+        // always run this because it also allows us to perform retries of manual uploads
+        new JobRequest.Builder(FilesSyncJob.TAG)
+                .setPeriodic(900000L, 300000L)
+                .setUpdateCurrent(true)
+                .build()
+                .schedule();
+
+        scheduleNJobs(false);
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    private static void cancelJobOnN() {
+        JobScheduler jobScheduler = MainApp.getAppContext().getSystemService(JobScheduler.class);
+        if (isContentObserverJobScheduled()) {
+            jobScheduler.cancel(ContentSyncJobId);
+        }
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.N)
+    private static void scheduleJobOnN(boolean hasImageFolders, boolean hasVideoFolders,
+                                       boolean force) {
+        JobScheduler jobScheduler = MainApp.getAppContext().getSystemService(JobScheduler.class);
+
+        if ((hasImageFolders || hasVideoFolders) && (!isContentObserverJobScheduled() || force)) {
+            JobInfo.Builder builder = new JobInfo.Builder(ContentSyncJobId, new ComponentName(MainApp.getAppContext(),
+                    NContentObserverJob.class.getName()));
+            builder.addTriggerContentUri(new JobInfo.TriggerContentUri(android.provider.MediaStore.
+                    Images.Media.INTERNAL_CONTENT_URI,
+                    JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
+            builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MediaStore.
+                    Images.Media.EXTERNAL_CONTENT_URI,
+                    JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
+            builder.addTriggerContentUri(new JobInfo.TriggerContentUri(android.provider.MediaStore.
+                    Video.Media.INTERNAL_CONTENT_URI,
+                    JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
+            builder.addTriggerContentUri(new JobInfo.TriggerContentUri(MediaStore.
+                    Video.Media.EXTERNAL_CONTENT_URI,
+                    JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS));
+            builder.setTriggerContentMaxDelay(1500);
+            jobScheduler.schedule(builder.build());
+        }
+    }
+}
+

+ 3 - 3
src/main/java/com/owncloud/android/utils/MimeTypeUtil.java

@@ -147,7 +147,7 @@ public class MimeTypeUtil {
         } else if (isSharedViaUsers) {
         } else if (isSharedViaUsers) {
             drawableId = R.drawable.shared_with_me_folder;
             drawableId = R.drawable.shared_with_me_folder;
         } else {
         } else {
-            drawableId = R.drawable.ic_menu_archive;
+            drawableId = R.drawable.folder;
         }
         }
 
 
         return ThemeUtils.tintDrawable(drawableId, ThemeUtils.primaryColor(account));
         return ThemeUtils.tintDrawable(drawableId, ThemeUtils.primaryColor(account));
@@ -442,7 +442,7 @@ public class MimeTypeUtil {
         MIMETYPE_TO_ICON_MAPPING.put("application/yaml", R.drawable.file_code);
         MIMETYPE_TO_ICON_MAPPING.put("application/yaml", R.drawable.file_code);
         MIMETYPE_TO_ICON_MAPPING.put("application/zip", R.drawable.file_zip);
         MIMETYPE_TO_ICON_MAPPING.put("application/zip", R.drawable.file_zip);
         MIMETYPE_TO_ICON_MAPPING.put("database", R.drawable.file);
         MIMETYPE_TO_ICON_MAPPING.put("database", R.drawable.file);
-        MIMETYPE_TO_ICON_MAPPING.put("httpd/unix-directory", R.drawable.ic_menu_archive);
+        MIMETYPE_TO_ICON_MAPPING.put("httpd/unix-directory", R.drawable.folder);
         MIMETYPE_TO_ICON_MAPPING.put("image/svg+xml", R.drawable.file_image);
         MIMETYPE_TO_ICON_MAPPING.put("image/svg+xml", R.drawable.file_image);
         MIMETYPE_TO_ICON_MAPPING.put("image/vector", R.drawable.file_image);
         MIMETYPE_TO_ICON_MAPPING.put("image/vector", R.drawable.file_image);
         MIMETYPE_TO_ICON_MAPPING.put("text/calendar", R.drawable.file_calendar);
         MIMETYPE_TO_ICON_MAPPING.put("text/calendar", R.drawable.file_calendar);
@@ -456,7 +456,7 @@ public class MimeTypeUtil {
         MIMETYPE_TO_ICON_MAPPING.put("text/x-python", R.drawable.file_code);
         MIMETYPE_TO_ICON_MAPPING.put("text/x-python", R.drawable.file_code);
         MIMETYPE_TO_ICON_MAPPING.put("text/x-shellscript", R.drawable.file_code);
         MIMETYPE_TO_ICON_MAPPING.put("text/x-shellscript", R.drawable.file_code);
         MIMETYPE_TO_ICON_MAPPING.put("web", R.drawable.file_code);
         MIMETYPE_TO_ICON_MAPPING.put("web", R.drawable.file_code);
-        MIMETYPE_TO_ICON_MAPPING.put(MimeType.DIRECTORY, R.drawable.ic_menu_archive);
+        MIMETYPE_TO_ICON_MAPPING.put(MimeType.DIRECTORY, R.drawable.folder);
     }
     }
 
 
     /**
     /**

+ 74 - 0
src/main/java/com/owncloud/android/utils/ReceiversHelper.java

@@ -0,0 +1,74 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.utils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import com.evernote.android.job.JobRequest;
+import com.evernote.android.job.util.Device;
+import com.owncloud.android.MainApp;
+
+/*
+    Helper for setting up network and power receivers
+ */
+public class ReceiversHelper {
+
+    public static void registerNetworkChangeReceiver() {
+        Context context = MainApp.getAppContext();
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
+        intentFilter.addAction("android.net.wifi.STATE_CHANGE");
+
+        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
+                    FilesSyncHelper.restartJobsIfNeeded();
+                }
+            }
+        };
+
+        context.registerReceiver(broadcastReceiver, intentFilter);
+    }
+
+    public static void registerPowerChangeReceiver() {
+        Context context = MainApp.getAppContext();
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
+        intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+
+        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) {
+                    FilesSyncHelper.restartJobsIfNeeded();
+                }
+            }
+        };
+
+        context.registerReceiver(broadcastReceiver, intentFilter);
+    }
+}

+ 2 - 1
src/main/java/com/owncloud/android/utils/ThemeUtils.java

@@ -37,6 +37,7 @@ import android.support.v4.content.ContextCompat;
 import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.widget.CompoundButtonCompat;
 import android.support.v7.app.ActionBar;
 import android.support.v7.app.ActionBar;
 import android.support.v7.widget.AppCompatCheckBox;
 import android.support.v7.widget.AppCompatCheckBox;
 import android.support.v7.widget.SwitchCompat;
 import android.support.v7.widget.SwitchCompat;
@@ -254,7 +255,7 @@ public class ThemeUtils {
     }
     }
 
 
     public static void tintCheckbox(AppCompatCheckBox checkBox, int color) {
     public static void tintCheckbox(AppCompatCheckBox checkBox, int color) {
-        checkBox.setSupportButtonTintList(new ColorStateList(
+        CompoundButtonCompat.setButtonTintList(checkBox, new ColorStateList(
                 new int[][]{
                 new int[][]{
                         new int[]{-android.R.attr.state_checked},
                         new int[]{-android.R.attr.state_checked},
                         new int[]{android.R.attr.state_checked},
                         new int[]{android.R.attr.state_checked},

+ 3 - 1
src/main/java/third_parties/in/srain/cube/GridViewWithHeaderAndFooter.java

@@ -273,7 +273,9 @@ public class GridViewWithHeaderAndFooter extends GridView {
                 Field numColumns = getClass().getSuperclass().getDeclaredField("mColumnWidth");
                 Field numColumns = getClass().getSuperclass().getDeclaredField("mColumnWidth");
                 numColumns.setAccessible(true);
                 numColumns.setAccessible(true);
                 return numColumns.getInt(this);
                 return numColumns.getInt(this);
-            } catch (NoSuchFieldException | IllegalAccessException e) {
+            } catch (NoSuchFieldException e) {
+                throw new RuntimeException(e);
+            } catch (IllegalAccessException e) {
                 throw new RuntimeException(e);
                 throw new RuntimeException(e);
             }
             }
         }
         }

BIN
src/main/res/drawable-hdpi/common_error.png


+ 0 - 0
src/main/res/drawable-hdpi/ic_menu_archive.png → src/main/res/drawable-hdpi/folder.png


BIN
src/main/res/drawable-hdpi/ic_action_cancel_sync.png


BIN
src/main/res/drawable-hdpi/ic_action_cancel_white.png


BIN
src/main/res/drawable-hdpi/ic_action_copy.png


BIN
src/main/res/drawable-hdpi/ic_action_delete_white.png


BIN
src/main/res/drawable-hdpi/ic_action_download.png


BIN
src/main/res/drawable-hdpi/ic_action_move.png


BIN
src/main/res/drawable-hdpi/ic_action_set_available_offline.png


BIN
src/main/res/drawable-hdpi/ic_action_set_available_offline_white.png


BIN
src/main/res/drawable-hdpi/ic_action_share.png


BIN
src/main/res/drawable-hdpi/ic_action_unset_available_offline.png


BIN
src/main/res/drawable-hdpi/ic_action_unset_available_offline_white.png


BIN
src/main/res/drawable-hdpi/ic_cellphone.png


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است