Эх сурвалжийг харах

Merge branch 'master' of https://github.com/nextcloud/android into pinchZoom

# Conflicts:
#	src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
AndyScherzinger 7 жил өмнө
parent
commit
de8bb6a691
100 өөрчлөгдсөн 2084 нэмэгдсэн , 665 устгасан
  1. 1 1
      .drone.yml
  2. 0 0
      .drone.yml.sig
  3. 3 0
      CHANGELOG.md
  4. 3 4
      build.gradle
  5. 58 0
      drawable_resources/ic_checkbox_marked.svg
  6. 12 11
      drawable_resources/ic_list_empty_create_folder.svg
  7. 11 12
      drawable_resources/ic_list_empty_folder.svg
  8. 21 22
      drawable_resources/ic_list_empty_image.svg
  9. 11 10
      drawable_resources/ic_list_empty_upload.svg
  10. 25 26
      drawable_resources/ic_list_empty_video.svg
  11. 1 1
      scripts/lint/lint-results.txt
  12. 18 0
      scripts/lint/lint-up-wrapper.sh
  13. 2 2
      src/gplay/AndroidManifest.xml
  14. 2 2
      src/main/AndroidManifest.xml
  15. 4 1
      src/main/java/com/owncloud/android/authentication/AccountUtils.java
  16. 43 3
      src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java
  17. 257 113
      src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
  18. 5 4
      src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
  19. 30 0
      src/main/java/com/owncloud/android/datamodel/VirtualFolderType.java
  20. 7 1
      src/main/java/com/owncloud/android/db/ProviderMeta.java
  21. 7 7
      src/main/java/com/owncloud/android/files/InstantUploadBroadcastReceiver.java
  22. 2 1
      src/main/java/com/owncloud/android/files/services/IndexedForest.java
  23. 4 0
      src/main/java/com/owncloud/android/media/MediaControlView.java
  24. 0 1
      src/main/java/com/owncloud/android/media/MediaService.java
  25. 8 6
      src/main/java/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java
  26. 61 10
      src/main/java/com/owncloud/android/providers/FileContentProvider.java
  27. 58 7
      src/main/java/com/owncloud/android/services/AdvancedFileAlterationListener.java
  28. 7 5
      src/main/java/com/owncloud/android/services/ContactsBackupJob.java
  29. 55 2
      src/main/java/com/owncloud/android/services/ContactsImportJob.java
  30. 7 3
      src/main/java/com/owncloud/android/services/observer/SyncedFolderObserverService.java
  31. 2 1
      src/main/java/com/owncloud/android/ui/CustomPopup.java
  32. 31 13
      src/main/java/com/owncloud/android/ui/activity/ActivitiesListActivity.java
  33. 36 16
      src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
  34. 79 48
      src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  35. 27 6
      src/main/java/com/owncloud/android/ui/activity/FolderSyncActivity.java
  36. 48 0
      src/main/java/com/owncloud/android/ui/activity/Preferences.java
  37. 241 29
      src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java
  38. 96 39
      src/main/java/com/owncloud/android/ui/adapter/FileListListAdapter.java
  39. 17 10
      src/main/java/com/owncloud/android/ui/adapter/FolderSyncAdapter.java
  40. 1 1
      src/main/java/com/owncloud/android/ui/adapter/LocalFileListAdapter.java
  41. 2 3
      src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java
  42. 2 2
      src/main/java/com/owncloud/android/ui/dialog/RenameFileDialogFragment.java
  43. 24 21
      src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
  44. 5 6
      src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java
  45. 211 111
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  46. 172 51
      src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactListFragment.java
  47. 44 13
      src/main/java/com/owncloud/android/ui/fragment/contactsbackup/ContactsBackupFragment.java
  48. 13 0
      src/main/java/com/owncloud/android/ui/interfaces/ActivityListInterface.java
  49. 28 14
      src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java
  50. 40 2
      src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java
  51. 14 16
      src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java
  52. 20 0
      src/main/java/com/owncloud/android/utils/DisplayUtils.java
  53. 18 8
      src/main/java/com/owncloud/android/utils/MimeTypeUtil.java
  54. 36 0
      src/main/java/com/owncloud/android/utils/glide/CustomGlideStreamLoader.java
  55. 101 0
      src/main/java/com/owncloud/android/utils/glide/HttpStreamFetcher.java
  56. 53 0
      src/main/java/third_parties/ezvcard_android/ContactOperations.java
  57. BIN
      src/main/res/drawable-hdpi/ic_checkbox_marked.png
  58. BIN
      src/main/res/drawable-hdpi/ic_list_empty_create_folder.png
  59. BIN
      src/main/res/drawable-hdpi/ic_list_empty_folder.png
  60. BIN
      src/main/res/drawable-hdpi/ic_list_empty_upload.png
  61. BIN
      src/main/res/drawable-hdpi/nav_activity.png
  62. 0 0
      src/main/res/drawable-hdpi/nav_all_files.png
  63. BIN
      src/main/res/drawable-hdpi/nav_contacts.png
  64. 0 0
      src/main/res/drawable-hdpi/nav_favorites.png
  65. 0 0
      src/main/res/drawable-hdpi/nav_folder_sync.png
  66. 0 0
      src/main/res/drawable-hdpi/nav_logout.png
  67. BIN
      src/main/res/drawable-hdpi/nav_notifications.png
  68. 0 0
      src/main/res/drawable-hdpi/nav_on_device.png
  69. 0 0
      src/main/res/drawable-hdpi/nav_participate.png
  70. BIN
      src/main/res/drawable-hdpi/nav_photos.png
  71. 0 0
      src/main/res/drawable-hdpi/nav_recently.png
  72. BIN
      src/main/res/drawable-hdpi/nav_settings.png
  73. 0 0
      src/main/res/drawable-hdpi/nav_shared.png
  74. 0 0
      src/main/res/drawable-hdpi/nav_uploads.png
  75. BIN
      src/main/res/drawable-hdpi/nav_videos.png
  76. BIN
      src/main/res/drawable-mdpi/ic_checkbox_marked.png
  77. BIN
      src/main/res/drawable-mdpi/ic_list_empty_create_folder.png
  78. BIN
      src/main/res/drawable-mdpi/ic_list_empty_folder.png
  79. BIN
      src/main/res/drawable-mdpi/ic_list_empty_upload.png
  80. BIN
      src/main/res/drawable-mdpi/nav_activity.png
  81. BIN
      src/main/res/drawable-mdpi/nav_all_files.png
  82. BIN
      src/main/res/drawable-mdpi/nav_contacts.png
  83. 0 0
      src/main/res/drawable-mdpi/nav_favorites.png
  84. 0 0
      src/main/res/drawable-mdpi/nav_folder_sync.png
  85. 0 0
      src/main/res/drawable-mdpi/nav_logout.png
  86. BIN
      src/main/res/drawable-mdpi/nav_notifications.png
  87. 0 0
      src/main/res/drawable-mdpi/nav_on_device.png
  88. 0 0
      src/main/res/drawable-mdpi/nav_participate.png
  89. BIN
      src/main/res/drawable-mdpi/nav_photos.png
  90. 0 0
      src/main/res/drawable-mdpi/nav_recently.png
  91. BIN
      src/main/res/drawable-mdpi/nav_settings.png
  92. 0 0
      src/main/res/drawable-mdpi/nav_shared.png
  93. 0 0
      src/main/res/drawable-mdpi/nav_uploads.png
  94. BIN
      src/main/res/drawable-mdpi/nav_videos.png
  95. BIN
      src/main/res/drawable-xhdpi/ic_checkbox_marked.png
  96. BIN
      src/main/res/drawable-xhdpi/ic_list_empty_create_folder.png
  97. BIN
      src/main/res/drawable-xhdpi/ic_list_empty_folder.png
  98. BIN
      src/main/res/drawable-xhdpi/ic_list_empty_upload.png
  99. BIN
      src/main/res/drawable-xhdpi/nav_activity.png
  100. 0 0
      src/main/res/drawable-xhdpi/nav_all_files.png

+ 1 - 1
.drone.yml

@@ -34,7 +34,7 @@ pipeline:
         # needs gplay
         - sed -i '/com.google.*.gms/s/^.*\/\///g' build.gradle
         - export BRANCH=$(scripts/lint/getBranchName.sh ${GIT_USERNAME} ${GIT_TOKEN} ${DRONE_PULL_REQUEST})
-        - ruby scripts/lint/lint-up.rb ${GIT_USERNAME} ${GIT_TOKEN} $BRANCH
+        - scripts/lint/lint-up-wrapper.sh ${GIT_USERNAME} ${GIT_TOKEN} $BRANCH ${LOG_USERNAME} ${LOG_PASSWORD} ${DRONE_BUILD_NUMBER}
       when:
         matrix:
           FLAVOUR: Modified

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
.drone.yml.sig


+ 3 - 0
CHANGELOG.md

@@ -1,3 +1,6 @@
+## 1.4.3 (May 22, 2017)
+- Hotfix: ignore oauth header for now and use basic auth to allow new logins for nc12
+
 ## 1.4.2 (March 14, 2017)
 - Auto Upload for newly taken photos/images (Android 6+)
 - Auto Upload improvements and fixes

+ 3 - 4
build.gradle

@@ -13,7 +13,7 @@ buildscript {
         }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.3.1'
+        classpath 'com.android.tools.build:gradle:2.3.2'
         classpath 'com.google.gms:google-services:3.0.0'
     }
 }
@@ -52,7 +52,6 @@ repositories {
 android {
     lintOptions {
         abortOnError false
-        lintConfig file("${project.rootDir}/lint.xml")
         htmlReport true
         htmlOutput file("$project.buildDir/reports/lint/lint.html")
         disable 'MissingTranslation'
@@ -179,7 +178,7 @@ dependencies {
     compile name: 'touch-image-view'
     compile 'com.android.support:multidex:1.0.1'
 
-    compile 'com.github.nextcloud:android-library:1.0.18'
+    compile 'com.github.nextcloud:android-library:1.0.21'
     compile "com.android.support:support-v4:${supportLibraryVersion}"
     compile "com.android.support:design:${supportLibraryVersion}"
     compile 'com.jakewharton:disklrucache:2.0.2'
@@ -239,4 +238,4 @@ tasks.withType(Test) {
 }
 
 // uncomment for gplay, modified (must be at the bottom)
-//apply plugin: 'com.google.gms.google-services'
+//apply plugin: 'com.google.gms.google-services'

+ 58 - 0
drawable_resources/ic_checkbox_marked.svg

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    fill="#000000"
+    height="24"
+    viewBox="0 0 24 24"
+    width="24"
+    id="svg2"
+    version="1.1"
+    inkscape:version="0.91 r13725"
+    sodipodi:docname="ic_check_box_black_24px.svg">
+    <metadata
+        id="metadata12">
+        <rdf:RDF>
+            <cc:Work
+                rdf:about="">
+                <dc:format>image/svg+xml</dc:format>
+                <dc:type
+                    rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+            </cc:Work>
+        </rdf:RDF>
+    </metadata>
+    <defs
+        id="defs10"/>
+    <sodipodi:namedview
+        pagecolor="#ffffff"
+        bordercolor="#666666"
+        borderopacity="1"
+        objecttolerance="10"
+        gridtolerance="10"
+        guidetolerance="10"
+        inkscape:pageopacity="0"
+        inkscape:pageshadow="2"
+        inkscape:window-width="1600"
+        inkscape:window-height="835"
+        id="namedview8"
+        showgrid="false"
+        inkscape:zoom="19.666667"
+        inkscape:cx="12.44099"
+        inkscape:cy="9.9174774"
+        inkscape:window-x="1600"
+        inkscape:window-y="0"
+        inkscape:window-maximized="1"
+        inkscape:current-layer="svg2"/>
+    <path
+        d="M0 0h24v24H0z"
+        fill="none"
+        id="path4"/>
+    <path
+        d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
+        id="path6"
+        style="fill:#007cc2;fill-opacity:1"/>
+</svg>

+ 12 - 11
drawable_resources/ic_list_empty_create_folder.svg

@@ -7,15 +7,15 @@
    xmlns="http://www.w3.org/2000/svg"
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   height="16"
-   width="16"
+   height="17.066668"
+   width="17.066668"
    version="1"
    id="svg2"
-   inkscape:version="0.91 r13725"
+   inkscape:version="0.92.1 r15371"
    sodipodi:docname="ic_list_empty_create_folder.svg"
-   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\designTweaks\res\drawable-mdpi\ic_list_empty_create_folder.png"
-   inkscape:export-xdpi="405"
-   inkscape:export-ydpi="405">
+   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\colorFolderNew\src\main\res\drawable-xxxhdpi\ic_list_empty_create_folder.png"
+   inkscape:export-xdpi="1620"
+   inkscape:export-ydpi="1620">
   <metadata
      id="metadata10">
     <rdf:RDF>
@@ -44,14 +44,15 @@
      id="namedview6"
      showgrid="false"
      inkscape:zoom="29.5"
-     inkscape:cx="-12.171603"
-     inkscape:cy="13.899981"
+     inkscape:cx="4.4894139"
+     inkscape:cy="8.4762522"
      inkscape:window-x="-9"
      inkscape:window-y="-9"
      inkscape:window-maximized="1"
      inkscape:current-layer="svg2" />
   <path
-     style="fill:#99cde9;fill-opacity:1"
-     d="M 1.4570312 1.9960938 C 1.2070312 1.9960938 0.99609375 2.2070313 0.99609375 2.4570312 L 0.99609375 13.537109 C 0.99609375 13.797109 1.1990312 13.996094 1.4570312 13.996094 L 14.537109 13.996094 C 14.795109 13.996094 14.996094 13.797109 14.996094 13.537109 L 14.996094 4.4609375 C 14.996094 4.2109375 14.787109 3.9960937 14.537109 3.9960938 L 7.9960938 3.9960938 L 5.9960938 1.9960938 L 1.4570312 1.9960938 z M 9.9707031 6.2363281 L 11.306641 6.2363281 L 11.306641 8.25 L 13.314453 8.25 L 13.314453 9.5859375 L 11.306641 9.5859375 L 11.306641 11.589844 L 9.9707031 11.589844 L 9.9707031 9.5859375 L 7.9589844 9.5859375 L 7.9589844 8.25 L 9.9707031 8.25 L 9.9707031 6.2363281 z "
-     id="path4" />
+     style="fill:#0082c9;fill-opacity:1;stroke-width:1.06666672;opacity:0.5"
+     d="m 1.5541666,2.1291667 c -0.2666667,0 -0.4916666,0.225 -0.4916666,0.4916666 V 14.439583 c 0,0.277333 0.2164666,0.489584 0.4916666,0.489584 H 15.50625 c 0.2752,0 0.489584,-0.212251 0.489584,-0.489584 V 4.7583333 c 0,-0.2666666 -0.222918,-0.4958334 -0.489584,-0.4958332 H 8.5291667 L 6.3958334,2.1291667 Z m 9.0812504,4.5229166 h 1.425 V 8.8 h 2.141666 v 1.425 h -2.141666 v 2.1375 h -1.425 V 10.225 H 8.4895834 V 8.8 h 2.1458336 z"
+     id="path4"
+     inkscape:connector-curvature="0" />
 </svg>

+ 11 - 12
drawable_resources/ic_list_empty_folder.svg

@@ -7,15 +7,15 @@
    xmlns="http://www.w3.org/2000/svg"
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   height="16"
-   width="16"
+   height="17.066668"
+   width="17.066668"
    version="1"
    id="svg2"
-   inkscape:version="0.91 r13725"
-   sodipodi:docname="list_empty_folder_empty.svg"
-   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\designTweaks\res\drawable-mdpi\ic_folder_empty.png"
-   inkscape:export-xdpi="405"
-   inkscape:export-ydpi="405">
+   inkscape:version="0.92.1 r15371"
+   sodipodi:docname="ic_list_empty_folder.svg"
+   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\colorFolderNew\src\main\res\drawable-xxxhdpi\ic_list_empty_folder.png"
+   inkscape:export-xdpi="1620"
+   inkscape:export-ydpi="1620">
   <metadata
      id="metadata10">
     <rdf:RDF>
@@ -43,16 +43,15 @@
      id="namedview6"
      showgrid="false"
      inkscape:zoom="14.75"
-     inkscape:cx="-10.20339"
+     inkscape:cx="1.4237286"
      inkscape:cy="8"
      inkscape:window-x="-9"
      inkscape:window-y="-9"
      inkscape:window-maximized="1"
      inkscape:current-layer="svg2" />
   <path
-     d="M1.457 1.997c-.25 0-.46.21-.46.46v11.08c0 .26.202.46.46.46h13.08c.258 0 .46-.2.46-.46V4.46c0-.25-.21-.463-.46-.463h-6.54l-2-2z"
-     fill-rule="evenodd"
-     fill="#0082c9"
+     d="m 1.5541333,2.1301333 c -0.2666666,0 -0.4906666,0.224 -0.4906666,0.4906667 v 11.818667 c 0,0.277333 0.2154666,0.490666 0.4906666,0.490666 H 15.506133 c 0.2752,0 0.490667,-0.213333 0.490667,-0.490666 V 4.7573333 c 0,-0.2666666 -0.224,-0.4938666 -0.490667,-0.4938666 H 8.5301333 L 6.3968,2.1301333 Z"
      id="path4"
-     style="fill:#99cde9;fill-opacity:1" />
+     style="fill:#0082c9;fill-opacity:1;fill-rule:evenodd;stroke-width:1.06666672;opacity:0.5"
+     inkscape:connector-curvature="0" />
 </svg>

+ 21 - 22
drawable_resources/ic_list_empty_image.svg

@@ -1,21 +1,20 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   height="16"
-   width="16"
-   version="1"
-   id="svg4"
-   sodipodi:docname="ic_list_empty_image.svg"
-   inkscape:version="0.92.1 r15371"
-   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\favourites\src\main\res\drawable-mdpi\ic_list_empty_image.png"
-   inkscape:export-xdpi="432"
-   inkscape:export-ydpi="432">
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    height="16"
+    width="16"
+    version="1"
+    id="svg4"
+    sodipodi:docname="ic_list_empty_image.svg"
+    inkscape:version="0.91 r13725"
+    inkscape:export-filename="C:\DEV\src\Android\Nextcloud\favourites\src\main\res\drawable-mdpi\ic_list_empty_image.png"
+    inkscape:export-xdpi="432"
+    inkscape:export-ydpi="432">
   <metadata
      id="metadata10">
     <rdf:RDF>
@@ -38,19 +37,19 @@
      guidetolerance="10"
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
-     inkscape:window-width="1920"
-     inkscape:window-height="1005"
+     inkscape:window-width="1600"
+     inkscape:window-height="835"
      id="namedview6"
      showgrid="false"
      inkscape:zoom="14.75"
-     inkscape:cx="8"
+     inkscape:cx="17.694915"
      inkscape:cy="8"
-     inkscape:window-x="-9"
-     inkscape:window-y="-9"
+     inkscape:window-x="1600"
+     inkscape:window-y="0"
      inkscape:window-maximized="1"
      inkscape:current-layer="svg4" />
   <path
-     style="block-progression:tb;text-transform:none;text-indent:0;fill:#000000;fill-opacity:1;opacity:0.5"
+     style="block-progression:tb;text-transform:none;text-indent:0;fill:#757575;fill-opacity:1;opacity:1"
      d="M1.344 2A.445.445 0 0 0 1 2.438v11.124c0 .23.212.44.438.44h13.125a.457.457 0 0 0 .437-.44V2.61c0-.337-.264-.61-.515-.61zM2 3h12v5l-1-1-3 4-3-3-4 4H2zm2.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"
      fill="#969696"
      color="#000"

+ 11 - 10
drawable_resources/ic_list_empty_upload.svg

@@ -8,15 +8,15 @@
    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
    version="1.1"
-   width="24"
-   height="24"
+   width="25.6"
+   height="25.6"
    viewBox="0 0 24 24"
    id="svg2"
-   inkscape:version="0.91 r13725"
+   inkscape:version="0.92.1 r15371"
    sodipodi:docname="ic_list_empty_upload.svg"
-   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\designTweaks\res\drawable-xxxhdpi\ic_list_empty_upload.png"
-   inkscape:export-xdpi="1080"
-   inkscape:export-ydpi="1080">
+   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\colorFolderNew\src\main\res\drawable-mdpi\ic_list_empty_upload.png"
+   inkscape:export-xdpi="270"
+   inkscape:export-ydpi="270">
   <metadata
      id="metadata10">
     <rdf:RDF>
@@ -44,14 +44,15 @@
      id="namedview6"
      showgrid="false"
      inkscape:zoom="9.8333333"
-     inkscape:cx="-15.305085"
-     inkscape:cy="12"
+     inkscape:cx="14.338983"
+     inkscape:cy="16.067797"
      inkscape:window-x="-9"
      inkscape:window-y="-9"
      inkscape:window-maximized="1"
      inkscape:current-layer="svg2" />
   <path
-     d="M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z"
+     d="M 9,16 V 10 H 5 l 7,-7 7,7 h -4 v 6 H 9 m -4,4 v -2 h 14 v 2 z"
      id="path4"
-     style="fill:#99cde9;fill-opacity:1" />
+     style="fill:#0082c9;fill-opacity:1;opacity:0.5"
+     inkscape:connector-curvature="0" />
 </svg>

+ 25 - 26
drawable_resources/ic_list_empty_video.svg

@@ -1,21 +1,20 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   height="16"
-   width="16"
-   version="1"
-   id="svg4"
-   sodipodi:docname="ic_list_empty_video.svg"
-   inkscape:version="0.92.1 r15371"
-   inkscape:export-filename="C:\DEV\src\Android\Nextcloud\favourites\src\main\res\drawable-mdpi\ic_list_empty_video.png"
-   inkscape:export-xdpi="432"
-   inkscape:export-ydpi="432">
+    xmlns:dc="http://purl.org/dc/elements/1.1/"
+    xmlns:cc="http://creativecommons.org/ns#"
+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+    height="16"
+    width="16"
+    version="1"
+    id="svg4"
+    sodipodi:docname="ic_list_empty_video.svg"
+    inkscape:version="0.91 r13725"
+    inkscape:export-filename="C:\DEV\src\Android\Nextcloud\favourites\src\main\res\drawable-mdpi\ic_list_empty_video.png"
+    inkscape:export-xdpi="432"
+    inkscape:export-ydpi="432">
   <metadata
      id="metadata10">
     <rdf:RDF>
@@ -38,21 +37,21 @@
      guidetolerance="10"
      inkscape:pageopacity="0"
      inkscape:pageshadow="2"
-     inkscape:window-width="1920"
-     inkscape:window-height="1005"
+     inkscape:window-width="1600"
+     inkscape:window-height="835"
      id="namedview6"
      showgrid="false"
-     inkscape:zoom="14.75"
-     inkscape:cx="8"
-     inkscape:cy="8"
-     inkscape:window-x="-9"
-     inkscape:window-y="-9"
+     inkscape:zoom="20.85965"
+     inkscape:cx="-15.290906"
+     inkscape:cy="9.1100694"
+     inkscape:window-x="1600"
+     inkscape:window-y="0"
      inkscape:window-maximized="1"
      inkscape:current-layer="svg4" />
   <path
-     style="block-progression:tb;text-transform:none;text-indent:0;fill:#000000;fill-opacity:1;opacity:0.5"
+     style="block-progression:tb;text-transform:none;text-indent:0;fill:#757575;fill-opacity:1;opacity:1"
      d="M1.344 2A.445.445 0 0 0 1 2.438v11.124c0 .23.212.44.438.44h13.124a.457.457 0 0 0 .438-.44V2.61c0-.336-.265-.61-.516-.61zM2 3h12v10H2zm3 2v6l6-3z"
+     id="path2"
      fill="#969696"
-     color="#000"
-     id="path2" />
+     color="#000" />
 </svg>

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

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 75 errors and 852 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 74 errors and 848 warnings</span>

+ 18 - 0
scripts/lint/lint-up-wrapper.sh

@@ -0,0 +1,18 @@
+#!/bin/sh
+
+#1: GIT_USERNAME
+#2: GIT_TOKEN
+#3: BRANCH
+#4: LOG_USERNAME
+#5: LOG_PASSWORD
+#6: DRONE_BUILD_NUMBER
+
+ruby scripts/lint/lint-up.rb $1 $2 $3
+
+if [ $? -eq 0 ]; then
+    echo "New master at: https://nextcloud.kaminsky.me/index.php/s/tXwtChzyqMj6I8v"
+    curl -u $4:$5 -X PUT https://nextcloud.kaminsky.me/remote.php/webdav/droneLogs/master.html --upload-file build/reports/lint/lint.html
+else
+    echo "New results at https://nextcloud.kaminsky.me/index.php/s/tXwtChzyqMj6I8v ->" $6.html
+    curl -u $4:$5 -X PUT https://nextcloud.kaminsky.me/remote.php/webdav/droneLogs/$6.html --upload-file build/reports/lint/lint.html
+fi

+ 2 - 2
src/gplay/AndroidManifest.xml

@@ -19,8 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           xmlns:tools="http://schemas.android.com/tools"
           package="com.owncloud.android"
-          android:versionCode="10040299"
-          android:versionName="1.4.2">
+          android:versionCode="10040399"
+          android:versionName="1.4.3">
 
     <application
         android:name=".MainApp"

+ 2 - 2
src/main/AndroidManifest.xml

@@ -20,8 +20,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.owncloud.android"
-          android:versionCode="10040299"
-          android:versionName="1.4.2">
+          android:versionCode="10040399"
+          android:versionName="1.4.3">
 
     <uses-sdk
         android:minSdkVersion="14"

+ 4 - 1
src/main/java/com/owncloud/android/authentication/AccountUtils.java

@@ -208,7 +208,10 @@ public class AccountUtils {
             if (currentAccountVersion == null) {
                 Log_OC.i(TAG, "Upgrading accounts to account version #" + ACCOUNT_VERSION);
                 Account[] ocAccounts = accountMgr.getAccountsByType(MainApp.getAccountType());
-                String serverUrl, username, newAccountName, password;
+                String serverUrl;
+                String username;
+                String newAccountName;
+                String password;
                 Account newAccount;
                 for (Account account : ocAccounts) {
                     // build new account name

+ 43 - 3
src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.java

@@ -108,12 +108,44 @@ public class ArbitraryDataProvider {
         }
     }
 
-    public boolean getBooleanValue(Account account, String key) {
-        String value = getValue(account, key);
+    public boolean getBooleanValue(String accountName, String key) {
+        String value = getValue(accountName, key);
 
         return !value.isEmpty() && value.equalsIgnoreCase("true");
     }
 
+    public boolean getBooleanValue(Account account, String key) {
+        return getBooleanValue(account.name, key);
+    }
+
+    /**
+     * returns integer if found else -1
+     *
+     * @param accountName
+     * @param key
+     * @return
+     */
+    public Integer getIntegerValue(String accountName, String key) {
+        String value = getValue(accountName, key);
+
+        if (value.isEmpty()) {
+            return -1;
+        } else {
+            return Integer.valueOf(value);
+        }
+    }
+
+    /**
+     * returns integer if found else -1
+     *
+     * @param account
+     * @param key
+     * @return
+     */
+    public Integer getIntegerValue(Account account, String key) {
+        return getIntegerValue(account.name, key);
+    }
+
     private ArrayList<String> getValues(Account account, String key) {
         Cursor cursor = contentResolver.query(
                 ProviderMeta.ProviderTableMeta.CONTENT_URI_ARBITRARY_DATA,
@@ -146,13 +178,21 @@ public class ArbitraryDataProvider {
         return new ArrayList<>();
     }
 
+    /**
+     * Returns stored value as string or empty string
+     * @return string if value found or empty string
+     */
     public String getValue(Account account, String key) {
+        return getValue(account.name, key);
+    }
+
+    public String getValue(String accountName, 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},
+                new String[]{accountName, key},
                 null
         );
 

+ 257 - 113
src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -1,21 +1,20 @@
 /**
- *   ownCloud Android client application
- *
- *   Copyright (C) 2012  Bartek Przybylski
- *   Copyright (C) 2015 ownCloud Inc.
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License version 2,
- *   as published by the Free Software Foundation.
- *
- *   This program is 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/>.
- *
+ * ownCloud Android client application
+ * <p>
+ * Copyright (C) 2012  Bartek Przybylski
+ * Copyright (C) 2015 ownCloud Inc.
+ * <p>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ * <p>
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * <p>
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.datamodel;
@@ -27,6 +26,7 @@ import android.content.ContentProviderResult;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.Intent;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
@@ -36,7 +36,10 @@ import android.provider.MediaStore;
 
 import com.owncloud.android.MainApp;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
@@ -160,7 +163,7 @@ public class FileDataStorageManager {
             // TODO better implementation, filtering in the access to database instead of here
             Vector<OCFile> tmp = getFolderContent(folder, onlyOnDevice);
             OCFile current = null;
-            for (int i=0; i<tmp.size(); i++) {
+            for (int i = 0; i < tmp.size(); i++) {
                 current = tmp.get(i);
                 if (MimeTypeUtil.isImage(current)) {
                     ret.add(current);
@@ -247,6 +250,36 @@ public class FileDataStorageManager {
         return overriden;
     }
 
+    public OCFile saveFileWithParent(OCFile file, Context context) {
+        if (file.getParentId() == 0 && !file.getRemotePath().equals("/")) {
+            String remotePath = file.getRemotePath();
+            String parentPath = remotePath.substring(0, remotePath.lastIndexOf(file.getFileName()));
+
+            OCFile parentFile = getFileByPath(parentPath);
+
+            OCFile returnFile;
+            if (parentFile == null) {
+                // remote request
+                ReadRemoteFileOperation operation = new ReadRemoteFileOperation(parentPath);
+                RemoteOperationResult result = operation.execute(getAccount(), context);
+                if (result.isSuccess()) {
+                    OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+
+                    returnFile = saveFileWithParent(remoteFolder, context);
+                } else {
+                    returnFile = null;
+                    Log_OC.e(TAG, "Error during saving file with parents: " + file.getRemotePath());
+                }
+            } else {
+                returnFile = saveFileWithParent(parentFile, context);
+            }
+
+            file.setParentId(returnFile.getFileId());
+            saveFile(file);
+        }
+
+        return file;
+    }
 
     public void saveNewFile(OCFile newFile) {
         String remoteParentPath = new File(newFile.getRemotePath()).getParent();
@@ -276,52 +309,21 @@ public class FileDataStorageManager {
             OCFile folder, Collection<OCFile> updatedFiles, Collection<OCFile> filesToRemove
     ) {
 
-        Log_OC.d(TAG,  "Saving folder " + folder.getRemotePath() + " with " + updatedFiles.size()
+        Log_OC.d(TAG, "Saving folder " + folder.getRemotePath() + " with " + updatedFiles.size()
                 + " children and " + filesToRemove.size() + " files to remove");
 
-        ArrayList<ContentProviderOperation> operations =
-                new ArrayList<ContentProviderOperation>(updatedFiles.size());
+        ArrayList<ContentProviderOperation> operations = new ArrayList<>(updatedFiles.size());
 
         // prepare operations to insert or update files to save in the given folder
         for (OCFile file : updatedFiles) {
-            ContentValues cv = new ContentValues();
-            cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
-            cv.put(
-                    ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,
-                    file.getModificationTimestampAtLastSyncForData()
-            );
-            cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
-            cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
-            cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
-            cv.put(ProviderTableMeta.FILE_NAME, file.getFileName());
-            //cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
-            cv.put(ProviderTableMeta.FILE_PARENT, folder.getFileId());
-            cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
-            if (!file.isFolder()) {
-                cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
-            }
-            cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
-            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
-            cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
-            cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.isAvailableOffline() ? 1 : 0);
-            cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag());
-            cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, file.isSharedViaLink() ? 1 : 0);
-            cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, file.isSharedWithSharee() ? 1 : 0);
-            cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, file.getPublicLink());
-            cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
-            cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
-            cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
-            cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
-            cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, file.getEtagInConflict());
-            cv.put(ProviderTableMeta.FILE_FAVORITE, file.getIsFavorite());
+            ContentValues cv = createContentValueForFile(file, folder);
 
             boolean existsByPath = fileExists(file.getRemotePath());
             if (existsByPath || fileExists(file.getFileId())) {
                 // updating an existing file
-                operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
-                        withValues(cv).
-                        withSelection(ProviderTableMeta._ID + "=?",
-                                new String[]{String.valueOf(file.getFileId())})
+                operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI)
+                        .withValues(cv)
+                        .withSelection(ProviderTableMeta._ID + "=?", new String[]{String.valueOf(file.getFileId())})
                         .build());
 
             } else {
@@ -334,15 +336,13 @@ public class FileDataStorageManager {
         // prepare operations to remove files in the given folder
         String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + AND +
                 ProviderTableMeta.FILE_PATH + "=?";
-        String [] whereArgs = null;
+        String[] whereArgs = null;
         for (OCFile file : filesToRemove) {
             if (file.getParentId() == folder.getFileId()) {
                 whereArgs = new String[]{mAccount.name, file.getRemotePath()};
                 if (file.isFolder()) {
                     operations.add(ContentProviderOperation.newDelete(
-                            ContentUris.withAppendedId(
-                                    ProviderTableMeta.CONTENT_URI_DIR, file.getFileId()
-                            )
+                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, file.getFileId())
                     ).withSelection(where, whereArgs).build());
 
                     File localFolder = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
@@ -351,8 +351,7 @@ public class FileDataStorageManager {
                     }
                 } else {
                     operations.add(ContentProviderOperation.newDelete(
-                            ContentUris.withAppendedId(
-                                    ProviderTableMeta.CONTENT_URI_FILE, file.getFileId()
+                            ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, file.getFileId()
                             )
                     ).withSelection(where, whereArgs).build());
 
@@ -366,29 +365,7 @@ public class FileDataStorageManager {
         }
 
         // update metadata of folder
-        ContentValues cv = new ContentValues();
-        cv.put(ProviderTableMeta.FILE_MODIFIED, folder.getModificationTimestamp());
-        cv.put(
-                ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,
-                folder.getModificationTimestampAtLastSyncForData()
-        );
-        cv.put(ProviderTableMeta.FILE_CREATION, folder.getCreationTimestamp());
-        cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, 0);
-        cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, folder.getMimetype());
-        cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName());
-        cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId());
-        cv.put(ProviderTableMeta.FILE_PATH, folder.getRemotePath());
-        cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
-        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, folder.getLastSyncDateForProperties());
-        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, folder.getLastSyncDateForData());
-        cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, folder.isAvailableOffline() ? 1 : 0);
-        cv.put(ProviderTableMeta.FILE_ETAG, folder.getEtag());
-        cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, folder.isSharedViaLink() ? 1 : 0);
-        cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, folder.isSharedWithSharee() ? 1 : 0);
-        cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, folder.getPublicLink());
-        cv.put(ProviderTableMeta.FILE_PERMISSIONS, folder.getPermissions());
-        cv.put(ProviderTableMeta.FILE_REMOTE_ID, folder.getRemoteId());
-        cv.put(ProviderTableMeta.FILE_FAVORITE, folder.getIsFavorite());
+        ContentValues cv = createContentValueForFile(folder);
 
         operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
                 withValues(cv).
@@ -431,7 +408,67 @@ public class FileDataStorageManager {
                 }
             }
         }
+    }
 
+    private ContentValues createContentValueForFile(OCFile folder) {
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderTableMeta.FILE_MODIFIED, folder.getModificationTimestamp());
+        cv.put(
+                ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,
+                folder.getModificationTimestampAtLastSyncForData()
+        );
+        cv.put(ProviderTableMeta.FILE_CREATION, folder.getCreationTimestamp());
+        cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, 0);
+        cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, folder.getMimetype());
+        cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName());
+        cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId());
+        cv.put(ProviderTableMeta.FILE_PATH, folder.getRemotePath());
+        cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, folder.getLastSyncDateForProperties());
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, folder.getLastSyncDateForData());
+        cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, folder.isAvailableOffline() ? 1 : 0);
+        cv.put(ProviderTableMeta.FILE_ETAG, folder.getEtag());
+        cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, folder.isSharedViaLink() ? 1 : 0);
+        cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, folder.isSharedWithSharee() ? 1 : 0);
+        cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, folder.getPublicLink());
+        cv.put(ProviderTableMeta.FILE_PERMISSIONS, folder.getPermissions());
+        cv.put(ProviderTableMeta.FILE_REMOTE_ID, folder.getRemoteId());
+        cv.put(ProviderTableMeta.FILE_FAVORITE, folder.getIsFavorite());
+        return cv;
+    }
+
+    private ContentValues createContentValueForFile(OCFile file, OCFile folder) {
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
+        cv.put(
+                ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA,
+                file.getModificationTimestampAtLastSyncForData()
+        );
+        cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp());
+        cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
+        cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
+        cv.put(ProviderTableMeta.FILE_NAME, file.getFileName());
+        //cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
+        cv.put(ProviderTableMeta.FILE_PARENT, folder.getFileId());
+        cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
+        if (!file.isFolder()) {
+            cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
+        }
+        cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
+        cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.isAvailableOffline() ? 1 : 0);
+        cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag());
+        cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, file.isSharedViaLink() ? 1 : 0);
+        cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, file.isSharedWithSharee() ? 1 : 0);
+        cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, file.getPublicLink());
+        cv.put(ProviderTableMeta.FILE_PERMISSIONS, file.getPermissions());
+        cv.put(ProviderTableMeta.FILE_REMOTE_ID, file.getRemoteId());
+        cv.put(ProviderTableMeta.FILE_UPDATE_THUMBNAIL, file.needsUpdateThumbnail());
+        cv.put(ProviderTableMeta.FILE_IS_DOWNLOADING, file.isDownloading());
+        cv.put(ProviderTableMeta.FILE_ETAG_IN_CONFLICT, file.getEtagInConflict());
+        cv.put(ProviderTableMeta.FILE_FAVORITE, file.getIsFavorite());
+        return cv;
     }
 
 
@@ -499,7 +536,7 @@ public class FileDataStorageManager {
         Uri folder_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, "" + folder.getFileId()); // URI
         // for recursive deletion
         String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + "=?";
-        String [] whereArgs = new String[]{mAccount.name, folder.getRemotePath()};
+        String[] whereArgs = new String[]{mAccount.name, folder.getRemotePath()};
         int deleted = 0;
         if (getContentProviderClient() != null) {
             try {
@@ -793,9 +830,9 @@ public class FileDataStorageManager {
         if (c != null && c.moveToFirst()) {
             do {
                 OCFile child = createFileInstance(c);
-                 if (child.isFolder() || !onlyOnDevice || child.isDown()) {
-                     ret.add(child);
-                 }
+                if (child.isFolder() || !onlyOnDevice || child.isDown()) {
+                    ret.add(child);
+                }
             } while (c.moveToNext());
             c.close();
         }
@@ -868,6 +905,15 @@ public class FileDataStorageManager {
         return c;
     }
 
+    private OCFile createFileInstanceFromVirtual(Cursor c) {
+        OCFile file = null;
+        if (c != null) {
+            long fileId = c.getLong(c.getColumnIndex(ProviderTableMeta.VIRTUAL_OCFILE_ID));
+            file = getFileById(fileId);
+        }
+
+        return file;
+    }
 
     private OCFile createFileInstance(Cursor c) {
         OCFile file = null;
@@ -978,7 +1024,7 @@ public class FileDataStorageManager {
      * Retrieves an stored {@link OCShare} given its id.
      *
      * @param id    Identifier.
-     * @return      Stored {@link OCShare} given its id.
+     * @return Stored {@link OCShare} given its id.
      */
     public OCShare getShareById(long id) {
         OCShare share = null;
@@ -1036,7 +1082,7 @@ public class FileDataStorageManager {
                     .query(ProviderTableMeta.CONTENT_URI_SHARE,
                             null,
                             key + AND
-                                + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?",
+                                    + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?",
                             new String[]{value, mAccount.name},
                             null
                     );
@@ -1050,7 +1096,7 @@ public class FileDataStorageManager {
                         null
                 );
             } catch (RemoteException e) {
-                Log_OC.w(TAG, "Could not get details, assuming share does not exist: "+ e.getMessage());
+                Log_OC.w(TAG, "Could not get details, assuming share does not exist: " + e.getMessage());
                 c = null;
             }
         }
@@ -1058,14 +1104,13 @@ public class FileDataStorageManager {
     }
 
 
-
     /**
      * Get first share bound to a file with a known path and given {@link ShareType}.
      *
      * @param path          Path of the file.
      * @param type          Type of the share to get
      * @param shareWith     Target of the share. Ignored in type is {@link ShareType#PUBLIC_LINK}
-     * @return              First {@link OCShare} instance found in DB bound to the file in 'path'
+     * @return First {@link OCShare} instance found in DB bound to the file in 'path'
      */
     public OCShare getFirstShareByPathAndType(String path, ShareType type, String shareWith) {
         Cursor cursor = null;
@@ -1075,12 +1120,12 @@ public class FileDataStorageManager {
 
         String selection = ProviderTableMeta.OCSHARES_PATH + AND
                 + ProviderTableMeta.OCSHARES_SHARE_TYPE + AND
-                + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?" ;
+                + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?";
         if (!ShareType.PUBLIC_LINK.equals(type)) {
             selection += " AND " + ProviderTableMeta.OCSHARES_SHARE_WITH + "=?";
         }
 
-        String [] selectionArgs;
+        String[] selectionArgs;
         if (ShareType.PUBLIC_LINK.equals(type)) {
             selectionArgs = new String[]{
                     path,
@@ -1173,7 +1218,7 @@ public class FileDataStorageManager {
         cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, false);
         cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, "");
         String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PARENT + "=?";
-        String [] whereArgs = new String[] { mAccount.name , String.valueOf(folder.getFileId()) };
+        String[] whereArgs = new String[]{mAccount.name, String.valueOf(folder.getFileId())};
 
         if (getContentResolver() != null) {
             getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
@@ -1187,13 +1232,13 @@ public class FileDataStorageManager {
         }
     }
 
-    private void resetShareFlagInAFile(String filePath){
+    private void resetShareFlagInAFile(String filePath) {
         ContentValues cv = new ContentValues();
         cv.put(ProviderTableMeta.FILE_SHARED_VIA_LINK, false);
         cv.put(ProviderTableMeta.FILE_SHARED_WITH_SHAREE, false);
         cv.put(ProviderTableMeta.FILE_PUBLIC_LINK, "");
-        String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH+ "=?";
-        String [] whereArgs = new String[] { mAccount.name , filePath };
+        String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + AND + ProviderTableMeta.FILE_PATH + "=?";
+        String[] whereArgs = new String[]{mAccount.name, filePath};
 
         if (getContentResolver() != null) {
             getContentResolver().update(ProviderTableMeta.CONTENT_URI, cv, where, whereArgs);
@@ -1380,7 +1425,7 @@ public class FileDataStorageManager {
         Uri share_uri = ProviderTableMeta.CONTENT_URI_SHARE;
         String where = ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + AND +
                 ProviderTableMeta._ID + "=?";
-        String [] whereArgs = new String[]{mAccount.name, Long.toString(share.getId())};
+        String[] whereArgs = new String[]{mAccount.name, Long.toString(share.getId())};
         if (getContentProviderClient() != null) {
             try {
                 getContentProviderClient().delete(share_uri, where, whereArgs);
@@ -1397,16 +1442,16 @@ public class FileDataStorageManager {
 
         // Reset flags & Remove shares for this files
         String filePath = "";
-        for (OCShare share: shares) {
-            if (filePath != share.getPath()){
+        for (OCShare share : shares) {
+            if (filePath != share.getPath()) {
                 filePath = share.getPath();
                 resetShareFlagInAFile(filePath);
                 operations = prepareRemoveSharesInFile(filePath, operations);
             }
         }
 
-       // Add operations to insert shares
-       operations = prepareInsertShares(shares, operations);
+        // Add operations to insert shares
+        operations = prepareInsertShares(shares, operations);
 
         // apply operations in batch
         if (operations.size() > 0) {
@@ -1547,7 +1592,7 @@ public class FileDataStorageManager {
         if (folder != null) {
             String where = ProviderTableMeta.OCSHARES_PATH + AND
                     + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?";
-            String [] whereArgs = new String[]{ "", mAccount.name };
+            String[] whereArgs = new String[]{"", mAccount.name};
 
             Vector<OCFile> files = getFolderContent(folder, false);
 
@@ -1581,15 +1626,15 @@ public class FileDataStorageManager {
 
     }
 
-    public ArrayList<OCShare> getSharesWithForAFile(String filePath, String accountName){
+    public ArrayList<OCShare> getSharesWithForAFile(String filePath, String accountName) {
         // Condition
         String where = ProviderTableMeta.OCSHARES_PATH + AND
                 + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + AND
                 + " (" + ProviderTableMeta.OCSHARES_SHARE_TYPE + "=? OR "
-                + ProviderTableMeta.OCSHARES_SHARE_TYPE +  "=? OR "
-                + ProviderTableMeta.OCSHARES_SHARE_TYPE +  "=? OR "
+                + ProviderTableMeta.OCSHARES_SHARE_TYPE + "=? OR "
+                + ProviderTableMeta.OCSHARES_SHARE_TYPE + "=? OR "
                 + ProviderTableMeta.OCSHARES_SHARE_TYPE + "=? ) ";
-        String [] whereArgs = new String[]{ filePath, accountName ,
+        String[] whereArgs = new String[]{filePath, accountName,
                 Integer.toString(ShareType.USER.getValue()),
                 Integer.toString(ShareType.GROUP.getValue()),
                 Integer.toString(ShareType.EMAIL.getValue()),
@@ -1688,7 +1733,7 @@ public class FileDataStorageManager {
                     ProviderTableMeta.CONTENT_URI_FILE,
                     cv,
                     ProviderTableMeta._ID + "=?",
-                    new String[] { String.valueOf(file.getFileId())}
+                    new String[]{String.valueOf(file.getFileId())}
             );
         } else {
             try {
@@ -1825,7 +1870,7 @@ public class FileDataStorageManager {
 
     }
 
-    public OCCapability saveCapabilities(OCCapability capability){
+    public OCCapability saveCapabilities(OCCapability capability) {
 
         // Prepare capabilities data
         ContentValues cv = new ContentValues();
@@ -1912,7 +1957,7 @@ public class FileDataStorageManager {
         return exists;
     }
 
-    private Cursor getCapabilityCursorForAccount(String accountName){
+    private Cursor getCapabilityCursorForAccount(String accountName) {
         Cursor c = null;
         if (getContentResolver() != null) {
             c = getContentResolver()
@@ -1935,7 +1980,8 @@ public class FileDataStorageManager {
         return c;
 
     }
-    public OCCapability getCapability(String accountName){
+
+    public OCCapability getCapability(String accountName) {
         OCCapability capability = null;
         Cursor c = getCapabilityCursorForAccount(accountName);
 
@@ -2005,4 +2051,102 @@ public class FileDataStorageManager {
         return capability;
     }
 
+    public void deleteVirtuals(VirtualFolderType type) {
+        if (getContentResolver() != null) {
+            getContentResolver().delete(ProviderTableMeta.CONTENT_URI_VIRTUAL,
+                    ProviderTableMeta.VIRTUAL_TYPE + "=?", new String[]{String.valueOf(type)});
+        } else {
+            try {
+                getContentProviderClient().delete(ProviderTableMeta.CONTENT_URI_VIRTUAL,
+                        ProviderTableMeta.VIRTUAL_TYPE + "=?", new String[]{String.valueOf(type)});
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, FAILED_TO_INSERT_MSG + e.getMessage(), e);
+            }
+        }
+    }
+
+    public void saveVirtuals(VirtualFolderType type, List<ContentValues> values) {
+
+        if (getContentResolver() != null) {
+            getContentResolver().bulkInsert(ProviderTableMeta.CONTENT_URI_VIRTUAL, values.toArray(new ContentValues[values.size()]));
+        } else {
+            try {
+                getContentProviderClient().bulkInsert(ProviderTableMeta.CONTENT_URI_VIRTUAL, values.toArray(new ContentValues[values.size()]));
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, FAILED_TO_INSERT_MSG + e.getMessage(), e);
+            }
+        }
+    }
+
+    public void saveVirtual(VirtualFolderType type, OCFile file) {
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderTableMeta.VIRTUAL_TYPE, type.toString());
+        cv.put(ProviderTableMeta.VIRTUAL_OCFILE_ID, file.getFileId());
+
+        if (getContentResolver() != null) {
+            getContentResolver().insert(ProviderTableMeta.CONTENT_URI_VIRTUAL, cv);
+        } else {
+            try {
+                getContentProviderClient().insert(ProviderTableMeta.CONTENT_URI_VIRTUAL, cv);
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, FAILED_TO_INSERT_MSG + e.getMessage(), e);
+            }
+        }
+    }
+
+    public Vector<OCFile> getVirtualFolderContent(VirtualFolderType type, boolean onlyImages) {
+        Vector<OCFile> ocFiles = new Vector<>();
+        Uri req_uri = ProviderTableMeta.CONTENT_URI_VIRTUAL;
+        Cursor c;
+
+        if (getContentProviderClient() != null) {
+            try {
+                c = getContentProviderClient().query(
+                        req_uri,
+                        null,
+                        ProviderTableMeta.VIRTUAL_TYPE + "=?",
+                        new String[]{String.valueOf(type)},
+                        null
+                );
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, e.getMessage(), e);
+                return ocFiles;
+            }
+        } else {
+            c = getContentResolver().query(
+                    req_uri,
+                    null,
+                    ProviderTableMeta.VIRTUAL_TYPE + "=?",
+                    new String[]{String.valueOf(type)},
+                    null
+            );
+        }
+
+        if (c != null && c.moveToFirst()) {
+            do {
+                OCFile child = createFileInstanceFromVirtual(c);
+                ocFiles.add(child);
+            } while (c.moveToNext());
+            c.close();
+        }
+
+        if (onlyImages) {
+            OCFile current = null;
+            Vector<OCFile> temp = new Vector<>();
+            for (int i=0; i < ocFiles.size(); i++) {
+                current = ocFiles.get(i);
+                if (MimeTypeUtil.isImage(current)) {
+                    temp.add(current);
+                }
+            }
+            ocFiles = temp;
+        }
+
+        if (ocFiles.size() > 0) {
+            Collections.sort(ocFiles);
+        }
+
+        return ocFiles;
+    }
+
 }

+ 5 - 4
src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java

@@ -425,12 +425,13 @@ public class ThumbnailsCacheManager {
                         thumbnail = doFileInBackground(mFile);
                     }
                 }
+            } // the app should never break due to a problem with thumbnails
+            catch (OutOfMemoryError t) {
+                Log_OC.e(TAG, "Generation of thumbnail for " + mFile.getAbsolutePath() + " failed", t);
+                System.gc();
             } catch (Throwable t) {
                 // the app should never break due to a problem with thumbnails
                 Log_OC.e(TAG, "Generation of thumbnail for " + mFile.getAbsolutePath() + " failed", t);
-                if (t instanceof OutOfMemoryError) {
-                    System.gc();
-                }
             }
 
             return thumbnail;
@@ -451,7 +452,7 @@ public class ThumbnailsCacheManager {
                 } else {
                     if (mFile != null) {
                         if (mFile.isDirectory()) {
-                            imageView.setImageResource(R.drawable.ic_menu_archive);
+                            imageView.setImageDrawable(MimeTypeUtil.getDefaultFolderIcon());
                         } else {
                             if (MimeTypeUtil.isVideo(mFile)) {
                                 imageView.setImageBitmap(ThumbnailsCacheManager.mDefaultVideo);

+ 30 - 0
src/main/java/com/owncloud/android/datamodel/VirtualFolderType.java

@@ -0,0 +1,30 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2017 Tobias Kaminsky
+ * Copyright (C) 2017 Nextcloud GmbH.
+ * <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.datamodel;
+
+/**
+ * Type for virtual folders
+ */
+
+public enum VirtualFolderType {
+    FAVORITE, PHOTOS, NONE
+}

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

@@ -32,7 +32,7 @@ import com.owncloud.android.MainApp;
 public class ProviderMeta {
 
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 20;
+    public static final int DB_VERSION = 21;
 
     private ProviderMeta() {
     }
@@ -45,6 +45,7 @@ public class ProviderMeta {
         public static final String SYNCED_FOLDERS_TABLE_NAME = "synced_folders";
         public static final String EXTERNAL_LINKS_TABLE_NAME = "external_links";
         public static final String ARBITRARY_DATA_TABLE_NAME = "arbitrary_data";
+        public static final String VIRTUAL_TABLE_NAME = "virtual";
 
         private static final String CONTENT_PREFIX = "content://";
 
@@ -66,6 +67,7 @@ public class ProviderMeta {
                 + MainApp.getAuthority() + "/external_links");
         public static final Uri CONTENT_URI_ARBITRARY_DATA = Uri.parse(CONTENT_PREFIX
                 + MainApp.getAuthority() + "/arbitrary_data");
+        public static final Uri CONTENT_URI_VIRTUAL = Uri.parse(CONTENT_PREFIX + MainApp.getAuthority() + "/virtual");
 
         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";
@@ -185,5 +187,9 @@ public class ProviderMeta {
         public static final String ARBITRARY_DATA_CLOUD_ID = "cloud_id";
         public static final String ARBITRARY_DATA_KEY = "key";
         public static final String ARBITRARY_DATA_VALUE = "value";
+
+        // Columns of virtual
+        public static final String VIRTUAL_TYPE = "type";
+        public static final String VIRTUAL_OCFILE_ID = "ocfile_id";
     }
 }

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

@@ -56,6 +56,13 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
     // 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) {
@@ -75,13 +82,6 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver {
         }
     }
 
-    /**
-     * Because we support NEW_PHOTO_ACTION and NEW_PHOTO_ACTION_UNOFFICIAL it can happen that
-     * handleNewPictureAction is called twice for the same photo. Use this simple static variable to
-     * remember last uploaded photo to filter duplicates. Must not be null!
-     */
-    static String lastUploadedPhotoPath = "";
-
     private void handleNewPictureAction(Context context, Intent intent) {
         Cursor c = null;
         String file_path = null;

+ 2 - 1
src/main/java/com/owncloud/android/files/services/IndexedForest.java

@@ -115,7 +115,8 @@ public class IndexedForest<V> {
             String currentPath = remotePath;
             String parentPath = null;
             String parentKey = null;
-            Node<V> currentNode = valuedNode, parentNode = null;
+            Node<V> currentNode = valuedNode;
+            Node<V> parentNode = null;
             boolean linked = false;
             while (!OCFile.ROOT_PATH.equals(currentPath) && !linked) {
                 parentPath = new File(currentPath).getParent();

+ 4 - 0
src/main/java/com/owncloud/android/media/MediaControlView.java

@@ -197,6 +197,10 @@ public class MediaControlView extends FrameLayout /* implements OnLayoutChangeLi
         updatePausePlay();
     }
 
+    public void stopMediaPlayerMessages() {
+        mHandler.removeMessages(SHOW_PROGRESS);
+    }
+
 
     private void initControllerView(View v) {
         mPauseButton = (ImageButton) v.findViewById(R.id.playBtn);

+ 0 - 1
src/main/java/com/owncloud/android/media/MediaService.java

@@ -508,7 +508,6 @@ public class MediaService extends Service implements OnCompletionListener, OnPre
             // nobody is bound
             processStopRequest(true);
         }
-        return;
     }
 
 

+ 8 - 6
src/main/java/com/owncloud/android/operations/DetectAuthenticationMethodOperation.java

@@ -97,12 +97,14 @@ public class DetectAuthenticationMethodOperation extends RemoteOperation {
 
         // analyze response  
         if (result.getHttpCode() == HttpStatus.SC_UNAUTHORIZED) {
-            String authRequest = ((result.getAuthenticateHeader()).trim()).toLowerCase();
-            if (authRequest.startsWith("basic")) {
-                authMethod = AuthenticationMethod.BASIC_HTTP_AUTH;
-                
-            } else if (authRequest.startsWith("bearer")) {
-                authMethod = AuthenticationMethod.BEARER_TOKEN;
+            ArrayList<String> authHeaders = result.getAuthenticateHeaders();
+
+            for (String header : authHeaders) {
+                // currently we only support basic auth
+                if (header.toLowerCase().startsWith("basic")) {
+                    authMethod = AuthenticationMethod.BASIC_HTTP_AUTH;
+                    break;
+                }
             }
             // else - fall back to UNKNOWN
                     

+ 61 - 10
src/main/java/com/owncloud/android/providers/FileContentProvider.java

@@ -73,6 +73,7 @@ public class FileContentProvider extends ContentProvider {
     private static final int SYNCED_FOLDERS = 7;
     private static final int EXTERNAL_LINKS = 8;
     private static final int ARBITRARY_DATA = 9;
+    private static final int VIRTUAL = 10;
 
     private static final String TAG = FileContentProvider.class.getSimpleName();
 
@@ -205,6 +206,9 @@ public class FileContentProvider extends ContentProvider {
             case ARBITRARY_DATA:
                 count = db.delete(ProviderTableMeta.ARBITRARY_DATA_TABLE_NAME, where, whereArgs);
                 break;
+            case VIRTUAL:
+                count = db.delete(ProviderTableMeta.VIRTUAL_TABLE_NAME, where, whereArgs);
+                break;
             default:
                 //Log_OC.e(TAG, "Unknown uri " + uri);
                 throw new IllegalArgumentException("Unknown uri: " + uri.toString());
@@ -350,7 +354,17 @@ public class FileContentProvider extends ContentProvider {
 
                 }
                 return insertedArbitraryDataUri;
+            case VIRTUAL:
+                Uri insertedVirtualUri;
+                long virtualId = db.insert(ProviderTableMeta.VIRTUAL_TABLE_NAME, null, values);
 
+                if (virtualId > 0) {
+                    insertedVirtualUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_VIRTUAL, virtualId);
+                } else {
+                    throw new SQLException("ERROR " + uri);
+                }
+
+                return insertedVirtualUri;
             default:
                 throw new IllegalArgumentException("Unknown uri id: " + uri);
         }
@@ -402,6 +416,7 @@ public class FileContentProvider extends ContentProvider {
         mUriMatcher.addURI(authority, "synced_folders", SYNCED_FOLDERS);
         mUriMatcher.addURI(authority, "external_links", EXTERNAL_LINKS);
         mUriMatcher.addURI(authority, "arbitrary_data", ARBITRARY_DATA);
+        mUriMatcher.addURI(authority, "virtual", VIRTUAL);
 
         return true;
     }
@@ -497,6 +512,12 @@ public class FileContentProvider extends ContentProvider {
                             + uri.getPathSegments().get(1));
                 }
                 break;
+            case VIRTUAL:
+                sqlQuery.setTables(ProviderTableMeta.VIRTUAL_TABLE_NAME);
+                if (uri.getPathSegments().size() > 1) {
+                    sqlQuery.appendWhere(ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1));
+                }
+                break;
             default:
                 throw new IllegalArgumentException("Unknown uri id: " + uri);
         }
@@ -522,6 +543,9 @@ public class FileContentProvider extends ContentProvider {
                 case ARBITRARY_DATA:
                     order = ProviderTableMeta.ARBITRARY_DATA_CLOUD_ID;
                     break;
+                case VIRTUAL:
+                    order = ProviderTableMeta.VIRTUAL_TYPE;
+                    break;
                 default: // Files
                     order = ProviderTableMeta.FILE_DEFAULT_SORT_ORDER;
                     break;
@@ -635,6 +659,9 @@ public class FileContentProvider extends ContentProvider {
 
             // Create arbitrary data table
             createArbitraryData(db);
+
+            // Create virtual table
+            createVirtualTable(db);
         }
 
         @Override
@@ -881,7 +908,7 @@ public class FileContentProvider extends ContentProvider {
             }
 
             if (oldVersion < 16 && newVersion >= 16) {
-                Log_OC.i("SQL", "Entering in the #16 ADD synced folders table");
+                Log_OC.i(SQL, "Entering in the #16 ADD synced folders table");
                 db.beginTransaction();
                 try {
                     // Create synced folders table
@@ -898,7 +925,7 @@ public class FileContentProvider extends ContentProvider {
             }
 
             if (oldVersion < 17 && newVersion >= 17) {
-                Log_OC.i(SQL, "Entering in the #4 ADD in onUpgrade");
+                Log_OC.i(SQL, "Entering in the #17 ADD in onUpgrade");
                 db.beginTransaction();
                 try {
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.FILE_TABLE_NAME +
@@ -918,7 +945,7 @@ public class FileContentProvider extends ContentProvider {
             }
 
             if (oldVersion < 18 && newVersion >= 18) {
-                Log_OC.i(SQL, "Adding external link column to capabilities");
+                Log_OC.i(SQL, "Entering in the #18 Adding external link column to capabilities");
                 db.beginTransaction();
                 try {
                     db.execSQL(ALTER_TABLE + ProviderTableMeta.CAPABILITIES_TABLE_NAME +
@@ -937,7 +964,7 @@ public class FileContentProvider extends ContentProvider {
             }
 
             if (oldVersion < 19 && newVersion >= 19) {
-                Log_OC.i(SQL, "Adding external link column to capabilities");
+                Log_OC.i(SQL, "Entering in the #19 Adding external link column to capabilities");
                 db.beginTransaction();
                 try {
                     createExternalLinksTable(db);
@@ -953,7 +980,7 @@ public class FileContentProvider extends ContentProvider {
             }
 
             if (oldVersion < 20 && newVersion >= 20) {
-                Log_OC.i(SQL, "Adding arbitrary data table");
+                Log_OC.i(SQL, "Entering in the #20 Adding arbitrary data table");
                 db.beginTransaction();
                 try {
                     createArbitraryData(db);
@@ -967,6 +994,22 @@ public class FileContentProvider extends ContentProvider {
             if (!upgraded) {
                 Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
             }
+
+            if (oldVersion < 21 && newVersion >= 21) {
+                Log_OC.i(SQL, "Entering in the #21 Adding virtual table");
+                db.beginTransaction();
+                try {
+                    createVirtualTable(db);
+                    upgraded = true;
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+
+            if (!upgraded) {
+                Log_OC.i(SQL, String.format(Locale.ENGLISH, UPGRADE_VERSION_MSG, oldVersion, newVersion));
+            }
         }
     }
 
@@ -1047,7 +1090,7 @@ public class FileContentProvider extends ContentProvider {
                 + ProviderTableMeta.CAPABILITIES_FILES_UNDELETE + INTEGER  // boolean
                 + ProviderTableMeta.CAPABILITIES_FILES_VERSIONING + INTEGER   // boolean
                 + ProviderTableMeta.CAPABILITIES_FILES_DROP + INTEGER  // boolean
-                + ProviderTableMeta.CAPABILITIES_EXTERNAL_LINKS + " INTEGER );" );   // boolean
+                + ProviderTableMeta.CAPABILITIES_EXTERNAL_LINKS + " INTEGER );");   // boolean
     }
 
     private void createUploadsTable(SQLiteDatabase db) {
@@ -1113,6 +1156,14 @@ public class FileContentProvider extends ContentProvider {
         );
     }
 
+    private void createVirtualTable(SQLiteDatabase db) {
+        db.execSQL("CREATE TABLE " + ProviderTableMeta.VIRTUAL_TABLE_NAME + "("
+                + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, "          // id
+                + ProviderTableMeta.VIRTUAL_TYPE + " TEXT, "                // type
+                + ProviderTableMeta.VIRTUAL_OCFILE_ID + " INTEGER )"        // file id
+        );
+    }
+
     /**
      * 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
@@ -1120,7 +1171,7 @@ public class FileContentProvider extends ContentProvider {
      *
      * See {@link com.owncloud.android.authentication.AccountUtils#updateAccountVersion(android.content.Context)}
      *
-     * @param db        Database where table of files is included.
+     * @param db Database where table of files is included.
      */
     private void updateAccountName(SQLiteDatabase db) {
         Log_OC.d(SQL, "THREAD:  " + Thread.currentThread().getName());
@@ -1177,9 +1228,9 @@ public class FileContentProvider extends ContentProvider {
      * Rename the local ownCloud folder of one account to match the a rename of the account itself. Updates the
      * table of files in database so that the paths to the local files keep being the same.
      *
-     * @param db                    Database where table of files is included.
-     * @param newAccountName        New name for the target OC account.
-     * @param oldAccountName        Old name of the target OC account.
+     * @param db             Database where table of files is included.
+     * @param newAccountName New name for the target OC account.
+     * @param oldAccountName Old name of the target OC account.
      */
     private void updateDownloadedFiles(SQLiteDatabase db, String newAccountName,
                                        String oldAccountName) {

+ 58 - 7
src/main/java/com/owncloud/android/services/AdvancedFileAlterationListener.java

@@ -20,6 +20,7 @@
 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;
@@ -27,8 +28,13 @@ 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;
@@ -53,16 +59,18 @@ 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) {
+    public AdvancedFileAlterationListener(SyncedFolder syncedFolder, boolean lightVersion) {
         super();
 
         context = MainApp.getAppContext();
+        this.lightVersion = lightVersion;
         this.syncedFolder = syncedFolder;
     }
 
@@ -122,20 +130,50 @@ public class AdvancedFileAlterationListener implements FileAlterationListener {
             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,
-                            syncedFolder.getRemotePath(), file.getName(),
+                            remotePath, file.getName(),
                             finalLastModificationTime,
-                            syncedFolder.getSubfolderByDate()));
-                    bundle.putString(AutoUploadJob.ACCOUNT, syncedFolder.getAccount());
-                    bundle.putInt(AutoUploadJob.UPLOAD_BEHAVIOUR, syncedFolder.getUploadAction());
+                            subfolderByDate));
+                    bundle.putString(AutoUploadJob.ACCOUNT, accountName);
+                    bundle.putInt(AutoUploadJob.UPLOAD_BEHAVIOUR, uploadAction);
 
                     new JobRequest.Builder(AutoUploadJob.TAG)
                             .setExecutionWindow(30_000L, 80_000L)
-                            .setRequiresCharging(syncedFolder.getChargingOnly())
-                            .setRequiredNetworkType(syncedFolder.getWifiOnly() ? JobRequest.NetworkType.UNMETERED :
+                            .setRequiresCharging(chargingOnly)
+                            .setRequiredNetworkType(wifiOnly ? JobRequest.NetworkType.UNMETERED :
                                     JobRequest.NetworkType.ANY)
                             .setExtras(bundle)
                             .setPersisted(false)
@@ -153,6 +191,19 @@ public class AdvancedFileAlterationListener implements FileAlterationListener {
         }
     }
 
+    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);

+ 7 - 5
src/main/java/com/owncloud/android/services/ContactsBackupJob.java

@@ -1,20 +1,20 @@
-/**
+/*
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
  * Copyright (C) 2017 Tobias Kaminsky
  * Copyright (C) 2017 Nextcloud GmbH.
- * <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/>.
  */
@@ -173,7 +173,9 @@ public class ContactsBackupJob extends Job {
             cal.add(Calendar.DAY_OF_YEAR, -daysToExpire);
             Long timestampToExpire = cal.getTimeInMillis();
 
-            Log_OC.d(TAG, "expire: " + daysToExpire + " " + backupFolder.getFileName());
+            if (backupFolder != null) {
+                Log_OC.d(TAG, "expire: " + daysToExpire + " " + backupFolder.getFileName());
+            }
 
             Vector<OCFile> backups = storageManager.getFolderContent(backupFolder, false);
 

+ 55 - 2
src/main/java/com/owncloud/android/services/ContactsImportJob.java

@@ -21,14 +21,22 @@
 
 package com.owncloud.android.services;
 
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
 import android.support.annotation.NonNull;
 
 import com.evernote.android.job.Job;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment;
 
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.TreeMap;
 
 import ezvcard.Ezvcard;
 import ezvcard.VCard;
@@ -62,9 +70,34 @@ public class ContactsImportJob extends Job {
         try {
             ContactOperations operations = new ContactOperations(getContext(), accountName, accountType);
             vCards.addAll(Ezvcard.parse(file).all());
+            Collections.sort(vCards, new ContactListFragment.VCardComparator());
+            Cursor cursor = getContext().getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null,
+                    null, null, null);
 
-            for (int i = 0; i < intArray.length; i++ ){
-                operations.insertContact(vCards.get(intArray[i]));
+            TreeMap<VCard, Long> ownContactList = new TreeMap<>(new ContactListFragment.VCardComparator());
+            if (cursor != null && cursor.getCount() > 0) {
+                cursor.moveToFirst();
+                for (int i = 0; i < cursor.getCount(); i++) {
+                    VCard vCard = getContactFromCursor(cursor);
+                    if (vCard != null) {
+                        ownContactList.put(vCard, cursor.getLong(cursor.getColumnIndex("NAME_RAW_CONTACT_ID")));
+                    }
+                    cursor.moveToNext();
+                }
+            }
+
+
+            for (int i = 0; i < intArray.length; i++) {
+                VCard vCard = vCards.get(intArray[i]);
+                if (ContactListFragment.getDisplayName(vCard).length() != 0) {
+                    if (!ownContactList.containsKey(vCard)) {
+                        operations.insertContact(vCard);
+                    } else {
+                        operations.updateContact(vCard, ownContactList.get(vCard));
+                    }
+                } else {
+                    operations.insertContact(vCard); //Insert All the contacts without name
+                }
             }
         } catch (Exception e) {
             Log_OC.e(TAG, e.getMessage());
@@ -72,4 +105,24 @@ public class ContactsImportJob extends Job {
 
         return Result.SUCCESS;
     }
+
+    private VCard getContactFromCursor(Cursor cursor) {
+        String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
+        Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey);
+        VCard vCard = null;
+        try {
+            InputStream inputStream = getContext().getContentResolver().openInputStream(uri);
+            ArrayList<VCard> vCardList = new ArrayList<>();
+            vCardList.addAll(Ezvcard.parse(inputStream).all());
+            if (vCardList.size() > 0) {
+                vCard = vCardList.get(0);
+            }
+
+        } catch (IOException e) {
+            Log_OC.d(TAG, e.getMessage());
+        }
+        return vCard;
+    }
+
+
 }

+ 7 - 3
src/main/java/com/owncloud/android/services/observer/SyncedFolderObserverService.java

@@ -28,6 +28,7 @@ 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;
@@ -69,7 +70,8 @@ public class SyncedFolderObserverService extends Service {
 
                 try {
                     observer.init();
-                    observer.addListener(new AdvancedFileAlterationListener(syncedFolder));
+                    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);
@@ -135,7 +137,8 @@ public class SyncedFolderObserverService extends Service {
                     try {
                         advancedFileAlterationObserver = new AdvancedFileAlterationObserver(syncedFolder, fileFilter);
                         advancedFileAlterationObserver.init();
-                        advancedFileAlterationObserver.addListener(new AdvancedFileAlterationListener(syncedFolder));
+                        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");
@@ -152,7 +155,8 @@ public class SyncedFolderObserverService extends Service {
             try {
                 advancedFileAlterationObserver = new AdvancedFileAlterationObserver(syncedFolder, fileFilter);
                 advancedFileAlterationObserver.init();
-                advancedFileAlterationObserver.addListener(new AdvancedFileAlterationListener(syncedFolder));
+                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");

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

@@ -136,7 +136,8 @@ public class CustomPopup {
                 LayoutParams.WRAP_CONTENT));
         root.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
 
-        int rootW = root.getWidth(), rootH = root.getHeight();
+        int rootW = root.getWidth();
+        int rootH = root.getHeight();
         int screenW = mWManager.getDefaultDisplay().getWidth();
 
         int xpos = ((screenW - rootW) / 2) + x;

+ 31 - 13
src/main/java/com/owncloud/android/ui/activity/ActivitiesListActivity.java

@@ -19,7 +19,6 @@
  * 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.activity;
 
 import android.accounts.Account;
@@ -30,7 +29,6 @@ import android.content.Intent;
 import android.os.Bundle;
 import android.support.design.widget.BottomNavigationView;
 import android.support.v4.widget.SwipeRefreshLayout;
-import android.support.v7.widget.DividerItemDecoration;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.MenuItem;
@@ -43,6 +41,7 @@ import android.widget.TextView;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -50,7 +49,12 @@ import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.activities.GetRemoteActivitiesOperation;
+import com.owncloud.android.lib.resources.activities.models.RichObject;
+import com.owncloud.android.lib.resources.files.FileUtils;
 import com.owncloud.android.ui.adapter.ActivityListAdapter;
+import com.owncloud.android.ui.interfaces.ActivityListInterface;
+import com.owncloud.android.ui.preview.PreviewImageActivity;
+import com.owncloud.android.ui.preview.PreviewImageFragment;
 import com.owncloud.android.utils.AnalyticsUtils;
 import com.owncloud.android.utils.DisplayUtils;
 
@@ -66,7 +70,7 @@ import butterknife.Unbinder;
 /**
  * Activity displaying all server side stored activity items.
  */
-public class ActivitiesListActivity extends FileActivity {
+public class ActivitiesListActivity extends FileActivity implements ActivityListInterface {
 
     private static final String TAG = ActivitiesListActivity.class.getSimpleName();
     private static final String SCREEN_NAME = "Activities";
@@ -102,6 +106,7 @@ public class ActivitiesListActivity extends FileActivity {
     private ActivityListAdapter adapter;
     private Unbinder unbinder;
 
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         Log_OC.v(TAG, "onCreate() start");
@@ -133,9 +138,9 @@ public class ActivitiesListActivity extends FileActivity {
             public void onRefresh() {
                 setLoadingMessage();
                 fetchAndSetData();
-
             }
         });
+
         setupContent();
     }
 
@@ -156,18 +161,16 @@ public class ActivitiesListActivity extends FileActivity {
      * sets up the UI elements and loads all activity items.
      */
     private void setupContent() {
+
         emptyContentIcon.setImageResource(R.drawable.ic_activity_light_grey);
         setLoadingMessage();
 
-        adapter = new ActivityListAdapter(this);
+        adapter = new ActivityListAdapter(this, this);
         recyclerView.setAdapter(adapter);
 
         LinearLayoutManager layoutManager = new LinearLayoutManager(this);
-        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
-                layoutManager.getOrientation());
 
         recyclerView.setLayoutManager(layoutManager);
-        recyclerView.addItemDecoration(dividerItemDecoration);
 
         BottomNavigationView bottomNavigationView = (BottomNavigationView) findViewById(R.id.bottom_navigation_view);
 
@@ -192,8 +195,8 @@ public class ActivitiesListActivity extends FileActivity {
                     ocAccount = new OwnCloudAccount(
                             currentAccount,
                             context
-                            );
-                    OwnCloudClient mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                    );
+                    final OwnCloudClient mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
                             getClientFor(ocAccount, MainApp.getAppContext());
                     mClient.setOwnCloudVersion(AccountUtils.getServerVersion(currentAccount));
 
@@ -208,7 +211,7 @@ public class ActivitiesListActivity extends FileActivity {
                             @Override
                             public void run() {
                                 if (activities.size() > 0) {
-                                    populateList(activities);
+                                    populateList(activities, mClient);
                                     swipeEmptyListRefreshLayout.setVisibility(View.GONE);
                                     swipeListRefreshLayout.setVisibility(View.VISIBLE);
                                 } else {
@@ -258,9 +261,9 @@ public class ActivitiesListActivity extends FileActivity {
         });
     }
 
-    private void populateList(List<Object> activities) {
+    private void populateList(List<Object> activities, OwnCloudClient mClient) {
 
-        adapter.setActivityItems(activities);
+        adapter.setActivityItems(activities, mClient);
     }
 
     @Override
@@ -308,4 +311,19 @@ public class ActivitiesListActivity extends FileActivity {
 
         AnalyticsUtils.setCurrentScreenName(this, SCREEN_NAME, TAG);
     }
+
+
+    @Override
+    public void onActivityClicked(RichObject richObject) {
+        Intent showDetailsIntent;
+        OCFile ocFile = new OCFile(FileUtils.PATH_SEPARATOR + richObject.getPath());
+        if (PreviewImageFragment.canBePreviewed(ocFile)) {
+            showDetailsIntent = new Intent(this, PreviewImageActivity.class);
+        } else {
+            showDetailsIntent = new Intent(this, FileDisplayActivity.class);
+        }
+        showDetailsIntent.putExtra(EXTRA_FILE, ocFile);
+        showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
+        startActivity(showDetailsIntent);
+    }
 }

+ 36 - 16
src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java

@@ -274,10 +274,10 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
      * setup quota elements of the drawer.
      */
     private void setupQuotaElement() {
-        mQuotaView = (LinearLayout) findViewById(R.id.drawer_quota);
-        mQuotaProgressBar = (ProgressBar) findViewById(R.id.drawer_quota_ProgressBar);
-        mQuotaTextPercentage = (TextView) findViewById(R.id.drawer_quota_percentage);
-        mQuotaTextLink = (TextView) findViewById(R.id.drawer_quota_link);
+        mQuotaView = (LinearLayout) findQuotaViewById(R.id.drawer_quota);
+        mQuotaProgressBar = (ProgressBar) findQuotaViewById(R.id.drawer_quota_ProgressBar);
+        mQuotaTextPercentage = (TextView) findQuotaViewById(R.id.drawer_quota_percentage);
+        mQuotaTextLink = (TextView) findQuotaViewById(R.id.drawer_quota_link);
         DisplayUtils.colorPreLollipopHorizontalProgressBar(mQuotaProgressBar);
     }
 
@@ -317,21 +317,20 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
             navigationView.getMenu().setGroupVisible(R.id.drawer_menu_accounts, false);
         }
 
-        Account account = AccountUtils.
-                getCurrentOwnCloudAccount(MainApp.getAppContext());
+        Account account = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
         boolean searchSupported = AccountUtils.hasSearchSupport(account);
 
-        if ((getResources().getBoolean(R.bool.bottom_toolbar_enabled) || (!searchSupported)) &&
-                (account != null)){
+        if (getResources().getBoolean(R.bool.bottom_toolbar_enabled) && account != null) {
+            navigationView.getMenu().removeItem(R.id.nav_all_files);
+            navigationView.getMenu().removeItem(R.id.nav_settings);
+            navigationView.getMenu().removeItem(R.id.nav_favorites);
             navigationView.getMenu().removeItem(R.id.nav_photos);
-            if (getResources().getBoolean(R.bool.bottom_toolbar_enabled)) {
-                navigationView.getMenu().removeItem(R.id.nav_all_files);
-                navigationView.getMenu().removeItem(R.id.nav_settings);
-                navigationView.getMenu().removeItem(R.id.nav_favorites);
-            }
-            if (!searchSupported) {
-                navigationView.getMenu().removeItem(R.id.nav_videos);
-            }
+        }
+
+        if (!searchSupported && account != null) {
+            navigationView.getMenu().removeItem(R.id.nav_photos);
+            navigationView.getMenu().removeItem(R.id.nav_favorites);
+            navigationView.getMenu().removeItem(R.id.nav_videos);
         }
 
         if (getResources().getBoolean(R.bool.use_home) && navigationView.getMenu().findItem(R.id.nav_all_files) !=
@@ -353,6 +352,10 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
             navigationView.getMenu().removeItem(R.id.nav_contacts);
         }
 
+        if (getResources().getBoolean(R.bool.syncedFolder_light)) {
+            navigationView.getMenu().removeItem(R.id.nav_folder_sync);
+        }
+
         if (!getResources().getBoolean(R.bool.show_drawer_logout)) {
             navigationView.getMenu().removeItem(R.id.nav_logout);
         }
@@ -508,6 +511,7 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
             case Menu.NONE:
                 // account clicked
                 accountClicked(menuItem.getTitle().toString());
+                break;
             default:
                 Log_OC.i(TAG, "Unknown drawer menu item clicked: " + menuItem.getTitle());
         }
@@ -1100,6 +1104,22 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
         return ((NavigationView) findViewById(R.id.nav_view)).getHeaderView(0).findViewById(id);
     }
 
+    /**
+     * Quota view can be either at navigation bottom or header
+     *
+     * @param id the view's id
+     * @return The view if found or <code>null</code> otherwise.
+     */
+    private View findQuotaViewById(int id) {
+        View v = ((NavigationView) findViewById(R.id.nav_view)).getHeaderView(0).findViewById(id);
+
+        if (v != null) {
+            return v;
+        } else {
+            return findViewById(id);
+        }
+    }
+
     /**
      * restart helper method which is called after a changing the current account.
      */

+ 79 - 48
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -62,6 +62,7 @@ import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.VirtualFolderType;
 import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
@@ -231,6 +232,7 @@ public class FileDisplayActivity extends HookActivity
     protected void onPostCreate(Bundle savedInstanceState) {
         super.onPostCreate(savedInstanceState);
 
+
         if (!PermissionUtil.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
             // Check if we should show an explanation
             if (PermissionUtil.shouldShowRequestPermissionRationale(this,
@@ -255,22 +257,20 @@ public class FileDisplayActivity extends HookActivity
         }
 
         if (getIntent().getParcelableExtra(OCFileListFragment.SEARCH_EVENT) != null) {
-            switchToSearchFragment();
+            switchToSearchFragment(savedInstanceState);
 
             int menuId = getIntent().getIntExtra(DRAWER_MENU_ID, -1);
             if (menuId != -1) {
                 setupDrawer(menuId);
             }
-        } else if (savedInstanceState == null) {
-            createMinFragments();
+        } else {
+            createMinFragments(savedInstanceState);
             refreshList(true);
         }
 
         setIndeterminate(mSyncInProgress);
         // always AFTER setContentView(...) in onCreate(); to work around bug in its implementation
 
-        setBackgroundText();
-
         upgradeNotificationForInstantUpload();
     }
 
@@ -316,7 +316,7 @@ public class FileDisplayActivity extends HookActivity
                             dialog.dismiss();
                         }
                     })
-                    .setIcon(R.drawable.ic_cloud_upload)
+                    .setIcon(R.drawable.nav_folder_sync)
                     .show();
         }
     }
@@ -395,28 +395,36 @@ public class FileDisplayActivity extends HookActivity
         }
     }
 
-    private void switchToSearchFragment() {
-        OCFileListFragment listOfFiles = new OCFileListFragment();
-        Bundle args = new Bundle();
+    private void switchToSearchFragment(Bundle savedInstanceState) {
+        if (savedInstanceState == null) {
+            OCFileListFragment listOfFiles = new OCFileListFragment();
+            Bundle args = new Bundle();
 
-        args.putParcelable(OCFileListFragment.SEARCH_EVENT,
-                getIntent().getParcelableExtra(OCFileListFragment.SEARCH_EVENT));
-        args.putBoolean(OCFileListFragment.ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
+            args.putParcelable(OCFileListFragment.SEARCH_EVENT,
+                    getIntent().getParcelableExtra(OCFileListFragment.SEARCH_EVENT));
+            args.putBoolean(OCFileListFragment.ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
 
-        listOfFiles.setArguments(args);
-        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-        transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES);
-        transaction.commit();
+            listOfFiles.setArguments(args);
+            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+            transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES);
+            transaction.commit();
+        } else {
+            getSupportFragmentManager().findFragmentByTag(TAG_LIST_OF_FILES);
+        }
     }
 
-    private void createMinFragments() {
-        OCFileListFragment listOfFiles = new OCFileListFragment();
-        Bundle args = new Bundle();
-        args.putBoolean(OCFileListFragment.ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
-        listOfFiles.setArguments(args);
-        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-        transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES);
-        transaction.commit();
+    private void createMinFragments(Bundle savedInstanceState) {
+        if (savedInstanceState == null) {
+            OCFileListFragment listOfFiles = new OCFileListFragment();
+            Bundle args = new Bundle();
+            args.putBoolean(OCFileListFragment.ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
+            listOfFiles.setArguments(args);
+            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+            transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES);
+            transaction.commit();
+        } else {
+            getSupportFragmentManager().findFragmentByTag(TAG_LIST_OF_FILES);
+        }
     }
 
     private void initFragmentsWithFile() {
@@ -431,7 +439,12 @@ public class FileDisplayActivity extends HookActivity
 
             /// Second fragment
             OCFile file = getFile();
-            Fragment secondFragment = chooseInitialSecondFragment(file);
+
+            Fragment secondFragment = getSecondFragment();
+            if (secondFragment == null) {
+                secondFragment = chooseInitialSecondFragment(file);
+            }
+
             if (secondFragment != null) {
                 setSecondFragment(secondFragment);
                 updateFragmentsVisibility(true);
@@ -470,13 +483,9 @@ public class FileDisplayActivity extends HookActivity
         Fragment secondFragment = null;
         if (file != null && !file.isFolder()) {
             if (file.isDown() && PreviewMediaFragment.canBePreviewed(file)) {
-                int startPlaybackPosition =
-                        getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0);
-                boolean autoplay =
-                        getIntent().getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, true);
-                secondFragment = new PreviewMediaFragment(file, getAccount(),
-                        startPlaybackPosition, autoplay);
-
+                int startPlaybackPosition = getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0);
+                boolean autoplay = getIntent().getBooleanExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, true);
+                secondFragment = new PreviewMediaFragment(file, getAccount(), startPlaybackPosition, autoplay);
             } else if (file.isDown() && PreviewTextFragment.canBePreviewed(file)) {
                 secondFragment = null;
             } else {
@@ -550,8 +559,7 @@ public class FileDisplayActivity extends HookActivity
     }
 
     public FileFragment getSecondFragment() {
-        Fragment second = getSupportFragmentManager().findFragmentByTag(
-                FileDisplayActivity.TAG_SECOND_FRAGMENT);
+        Fragment second = getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_SECOND_FRAGMENT);
         if (second != null) {
             return (FileFragment) second;
         }
@@ -566,7 +574,7 @@ public class FileDisplayActivity extends HookActivity
             tr.commit();
         }
         updateFragmentsVisibility(false);
-        updateActionBarTitleAndHomeButton(null);
+        //updateActionBarTitleAndHomeButton(null);
     }
 
     public void refreshListOfFilesFragment(boolean fromSearch) {
@@ -576,6 +584,14 @@ public class FileDisplayActivity extends HookActivity
         }
     }
 
+    public void resetSearchView() {
+        OCFileListFragment fileListFragment = getListOfFilesFragment();
+
+        if (fileListFragment != null) {
+            fileListFragment.setSearchFragment(false);
+        }
+    }
+
     protected void refreshSecondFragment(String downloadEvent, String downloadedRemotePath,
                                          boolean success) {
         FileFragment secondFragment = getSecondFragment();
@@ -757,7 +773,7 @@ public class FileDisplayActivity extends HookActivity
                             R.drawable.ic_view_list));
                     getListOfFilesFragment().setGridAsPreferred();
                 }
-                return true;
+                break;
             }
             default:
                 retval = super.onOptionsItemSelected(item);
@@ -1053,20 +1069,22 @@ public class FileDisplayActivity extends HookActivity
 
         if (searchView != null && !TextUtils.isEmpty(searchQuery)) {
             searchView.setQuery(searchQuery, true);
-        } else {
+        } else if (getListOfFilesFragment() != null && !getListOfFilesFragment().getIsSearchFragment()) {
             refreshListOfFilesFragment(false);
         }
 
         // Listen for sync messages
-        IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
-        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
-        syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
-        syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
-        mSyncBroadcastReceiver = new SyncBroadcastReceiver();
-        registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
-        //LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver,
-        // syncIntentFilter);
+        if (getListOfFilesFragment() != null && !getListOfFilesFragment().getIsSearchFragment()) {
+            IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
+            syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
+            syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
+            syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+            syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+            mSyncBroadcastReceiver = new SyncBroadcastReceiver();
+            registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
+            //LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver,
+            // syncIntentFilter);
+        }
 
         // Listen for upload messages
         IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.getUploadFinishMessage());
@@ -1257,7 +1275,7 @@ public class FileDisplayActivity extends HookActivity
      * loading or folder is empty
      */
     private void setBackgroundText() {
-        OCFileListFragment ocFileListFragment = getListOfFilesFragment();
+        final OCFileListFragment ocFileListFragment = getListOfFilesFragment();
         if (ocFileListFragment != null) {
             if (!mSyncInProgress) {
                 ocFileListFragment.setEmptyListMessage(ExtendedListFragment.SearchType.NO_SEARCH);
@@ -1928,6 +1946,19 @@ public class FileDisplayActivity extends HookActivity
         showDetailsIntent.putExtra(EXTRA_FILE, file);
         showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
         startActivity(showDetailsIntent);
+    }
+
+    /**
+     * Opens the image gallery showing the image {@link OCFile} received as parameter.
+     *
+     * @param file Image {@link OCFile} to show.
+     */
+    public void startImagePreview(OCFile file, VirtualFolderType type) {
+        Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class);
+        showDetailsIntent.putExtra(PreviewImageActivity.EXTRA_FILE, file);
+        showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount());
+        showDetailsIntent.putExtra(PreviewImageActivity.EXTRA_VIRTUAL_TYPE, type);
+        startActivity(showDetailsIntent);
 
     }
 
@@ -2032,7 +2063,7 @@ public class FileDisplayActivity extends HookActivity
 
     private void refreshList(boolean ignoreETag) {
         OCFileListFragment listOfFiles = getListOfFilesFragment();
-        if (listOfFiles != null) {
+        if (listOfFiles != null && !listOfFiles.getIsSearchFragment()) {
             OCFile folder = listOfFiles.getCurrentFile();
             if (folder != null) {
                 /*mFile = mContainerActivity.getStorageManager().getFileById(mFile.getFileId());

+ 27 - 6
src/main/java/com/owncloud/android/ui/activity/FolderSyncActivity.java

@@ -29,6 +29,7 @@ import android.support.annotation.NonNull;
 import android.support.design.widget.BottomNavigationView;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
+import android.support.v4.widget.DrawerLayout;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.MenuItem;
@@ -72,6 +73,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
 
     private static final String SYNCED_FOLDER_PREFERENCES_DIALOG_TAG = "SYNCED_FOLDER_PREFERENCES_DIALOG";
     public static final String PRIORITIZED_FOLDER = "Camera";
+    public static final String EXTRA_SHOW_SIDEBAR = "SHOW_SIDEBAR";
 
     private static final String SCREEN_NAME = "Auto upload";
 
@@ -84,11 +86,16 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
     private SyncedFolderProvider mSyncedFolderProvider;
     private List<SyncedFolderDisplayItem> syncFolderItems;
     private SyncedFolderPreferencesDialogFragment mSyncedFolderPreferencesDialogFragment;
+    private boolean showSidebar = true;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        if (getIntent().getExtras() != null) {
+            showSidebar = getIntent().getExtras().getBoolean(EXTRA_SHOW_SIDEBAR);
+        }
+
         setContentView(R.layout.folder_sync_layout);
 
         // setup toolbar
@@ -96,9 +103,16 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
 
         // setup drawer
         setupDrawer(R.id.nav_folder_sync);
-        getSupportActionBar().setTitle(getString(R.string.drawer_folder_sync));
+
+        if (!showSidebar) {
+            setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+            mDrawerToggle.setDrawerIndicatorEnabled(false);
+        }
 
         setupContent();
+
+        getSupportActionBar().setTitle(getString(R.string.drawer_folder_sync));
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
     }
 
     @Override
@@ -117,7 +131,8 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
         mEmpty = (TextView) findViewById(android.R.id.empty);
 
         final int gridWidth = getResources().getInteger(R.integer.media_grid_width);
-        mAdapter = new FolderSyncAdapter(this, gridWidth, this);
+        boolean lightVersion = getResources().getBoolean(R.bool.syncedFolder_light);
+        mAdapter = new FolderSyncAdapter(this, gridWidth, this, lightVersion);
         mSyncedFolderProvider = new SyncedFolderProvider(getContentResolver());
 
         final GridLayoutManager lm = new GridLayoutManager(this, gridWidth);
@@ -327,6 +342,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
         }
         return result;
     }
+
     /**
      * show/hide recycler view list or the empty message / progress info.
      *
@@ -345,10 +361,15 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
         boolean result = true;
         switch (item.getItemId()) {
             case android.R.id.home: {
-                if (isDrawerOpen()) {
-                    closeDrawer();
+                if (showSidebar) {
+                    if (isDrawerOpen()) {
+                        closeDrawer();
+                    } else {
+                        openDrawer();
+                    }
                 } else {
-                    openDrawer();
+                    Intent settingsIntent = new Intent(getApplicationContext(), Preferences.class);
+                    startActivity(settingsIntent);
                 }
                 break;
             }
@@ -431,7 +452,7 @@ public class FolderSyncActivity extends FileActivity implements FolderSyncAdapte
         }
         mSyncedFolderPreferencesDialogFragment = null;
 
-        if(dirty) {
+        if (dirty) {
             mAdapter.setSyncFolderItem(syncedFolder.getSection(), item);
         }
     }

+ 48 - 0
src/main/java/com/owncloud/android/ui/activity/Preferences.java

@@ -42,6 +42,7 @@ import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
 import android.support.annotation.LayoutRes;
 import android.support.annotation.Nullable;
 import android.support.v7.app.ActionBar;
@@ -58,6 +59,7 @@ import com.owncloud.android.BuildConfig;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.ExternalLinksProvider;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datastorage.DataStorageProvider;
@@ -91,6 +93,8 @@ public class Preferences extends PreferenceActivity
 
     private static final int ACTION_REQUEST_CODE_DAVDROID_SETUP = 10;
 
+    public static final String SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI = "SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI";
+
     /**
      * The user's server base uri.
      */
@@ -161,6 +165,50 @@ public class Preferences extends PreferenceActivity
         // Register context menu for list of preferences.
         registerForContextMenu(getListView());
 
+        // Synced folders
+        PreferenceCategory preferenceCategoryFolderSync = (PreferenceCategory) findPreference("folder_sync");
+
+        if (!getResources().getBoolean(R.bool.syncedFolder_light)) {
+            PreferenceScreen preferenceScreen = (PreferenceScreen) findPreference("preference_screen");
+            preferenceScreen.removePreference(preferenceCategoryFolderSync);
+        } else {
+            // Upload on WiFi
+            final ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(getContentResolver());
+            final Account account = AccountUtils.getCurrentOwnCloudAccount(getApplicationContext());
+
+            final CheckBoxPreference pUploadOnWifiCheckbox = (CheckBoxPreference) findPreference("synced_folder_on_wifi");
+            pUploadOnWifiCheckbox.setChecked(
+                    arbitraryDataProvider.getBooleanValue(account, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI));
+
+            pUploadOnWifiCheckbox.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    arbitraryDataProvider.storeOrUpdateKeyValue(account, SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI,
+                            String.valueOf(pUploadOnWifiCheckbox.isChecked()));
+
+                    return true;
+                }
+            });
+
+            Preference pSyncedFolder = findPreference("folder_sync_folders");
+            if (pSyncedFolder != null) {
+                if (getResources().getBoolean(R.bool.syncedFolder_light)
+                        && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                    pSyncedFolder.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                        @Override
+                        public boolean onPreferenceClick(Preference preference) {
+                            Intent folderSyncIntent = new Intent(getApplicationContext(), FolderSyncActivity.class);
+                            folderSyncIntent.putExtra(FolderSyncActivity.EXTRA_SHOW_SIDEBAR, false);
+                            startActivity(folderSyncIntent);
+                            return true;
+                        }
+                    });
+                } else {
+                    preferenceCategoryFolderSync.removePreference(pSyncedFolder);
+                }
+            }
+        }
+
         PreferenceCategory preferenceCategoryDetails = (PreferenceCategory) findPreference("details");
 
 

+ 241 - 29
src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java

@@ -20,14 +20,21 @@
 package com.owncloud.android.ui.adapter;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.drawable.PictureDrawable;
 import android.net.Uri;
 import android.support.v7.widget.RecyclerView;
+import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
+import android.text.style.StyleSpan;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.GridLayout;
 import android.widget.ImageView;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.bumptech.glide.GenericRequestBuilder;
@@ -36,70 +43,203 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.model.StreamEncoder;
 import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
 import com.caverock.androidsvg.SVG;
+import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.resources.activities.models.Activity;
+import com.owncloud.android.lib.resources.activities.models.RichElement;
+import com.owncloud.android.lib.resources.activities.models.RichObject;
+import com.owncloud.android.ui.interfaces.ActivityListInterface;
 import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.MimeTypeUtil;
+import com.owncloud.android.utils.glide.CustomGlideStreamLoader;
 import com.owncloud.android.utils.svg.SvgDecoder;
 import com.owncloud.android.utils.svg.SvgDrawableTranscoder;
 import com.owncloud.android.utils.svg.SvgSoftwareLayerSetter;
 
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
 /**
  * Adapter for the activity view
  */
 
-public class ActivityListAdapter extends RecyclerView.Adapter<ActivityListAdapter.ActivityViewHolder> {
+public class ActivityListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+
+    public static final int HEADER_TYPE = 100;
+    public static final int ACTIVITY_TYPE = 101;
+    private final ActivityListInterface activityListInterface;
+    private final int px;
+    private OwnCloudClient mClient;
 
     private Context context;
     private List<Object> mValues;
 
-    public ActivityListAdapter(Context context) {
+    public ActivityListAdapter(Context context, ActivityListInterface activityListInterface) {
         this.mValues = new ArrayList<>();
         this.context = context;
+        this.activityListInterface = activityListInterface;
+        px = getThumbnailDimension();
     }
 
-    public void setActivityItems(List<Object> activityItems) {
+    public void setActivityItems(List<Object> activityItems, OwnCloudClient client) {
+        this.mClient = client;
         mValues.clear();
-        mValues.addAll(activityItems);
+        String sTime = "";
+        for (Object o : activityItems) {
+            Activity activity = (Activity) o;
+            String time = null;
+            if (activity.getDatetime() != null) {
+                time = DisplayUtils.getRelativeTimestamp(context,
+                        activity.getDatetime().getTime()).toString();
+            } else if (activity.getDate() != null) {
+                time = DisplayUtils.getRelativeTimestamp(context,
+                        activity.getDate().getTime()).toString();
+            } else {
+                time = "Unknown";
+            }
+
+            if (sTime.equalsIgnoreCase(time)) {
+                mValues.add(activity);
+            } else {
+
+                sTime = time;
+                mValues.add(sTime);
+                mValues.add(activity);
+            }
+        }
         notifyDataSetChanged();
-    }
 
-    @Override
-    public ActivityViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_list_item, parent, false);
-        return new ActivityViewHolder(v);
     }
 
     @Override
-    public void onBindViewHolder(ActivityViewHolder holder, int position) {
-        Activity activity = (Activity) mValues.get(position);
-        if (activity.getDatetime() != null) {
-            holder.dateTime.setText(DisplayUtils.getRelativeTimestamp(context,
-                    activity.getDatetime().getTime()));
+    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        if (viewType == ACTIVITY_TYPE) {
+            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_list_item, parent, false);
+            return new ActivityViewHolder(v);
         } else {
-            holder.dateTime.setText(DisplayUtils.getRelativeTimestamp(context,
-                    activity.getDate().getTime()));
+            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_list_item_header, parent, false);
+            return new ActivityViewHeaderHolder(v);
         }
 
-        if (!TextUtils.isEmpty(activity.getSubject())) {
-            holder.subject.setText(activity.getSubject());
-            holder.subject.setVisibility(View.VISIBLE);
-        } else {
-            holder.subject.setVisibility(View.GONE);
-        }
+    }
+
+    @Override
+    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+        if (holder instanceof ActivityViewHolder) {
+            final ActivityViewHolder activityViewHolder = (ActivityViewHolder) holder;
+            Activity activity = (Activity) mValues.get(position);
+            if (activity.getDatetime() != null) {
+                activityViewHolder.dateTime.setText(DisplayUtils.getRelativeTimestamp(context,
+                        activity.getDatetime().getTime()));
+            } else {
+                activityViewHolder.dateTime.setText(DisplayUtils.getRelativeTimestamp(context,
+                        new Date().getTime()));
+            }
+
+            if (activity.getRichSubjectElement() != null &&
+                    !TextUtils.isEmpty(activity.getRichSubjectElement().getRichSubject())) {
+                activityViewHolder.subject.setVisibility(View.VISIBLE);
+                activityViewHolder.subject.setMovementMethod(LinkMovementMethod.getInstance());
+                activityViewHolder.subject.setText(addClickablePart(activity.getRichSubjectElement()),
+                        TextView.BufferType.SPANNABLE);
+                activityViewHolder.subject.setVisibility(View.VISIBLE);
+            } else if (!TextUtils.isEmpty(activity.getSubject())) {
+                activityViewHolder.subject.setVisibility(View.VISIBLE);
+                activityViewHolder.subject.setText(activity.getSubject());
+            } else {
+                activityViewHolder.subject.setVisibility(View.GONE);
+            }
 
-        if (!TextUtils.isEmpty(activity.getMessage())) {
-            holder.message.setText(activity.getMessage());
-            holder.message.setVisibility(View.VISIBLE);
+            if (!TextUtils.isEmpty(activity.getMessage())) {
+                activityViewHolder.message.setText(activity.getMessage());
+                activityViewHolder.message.setVisibility(View.VISIBLE);
+            } else {
+                activityViewHolder.message.setVisibility(View.GONE);
+            }
+
+            if (!TextUtils.isEmpty(activity.getIcon())) {
+                downloadIcon(activity.getIcon(), activityViewHolder.activityIcon);
+            }
+
+            if (activity.getRichSubjectElement() != null &&
+                    activity.getRichSubjectElement().getRichObjectList().size() > 0) {
+
+                activityViewHolder.list.setVisibility(View.VISIBLE);
+                activityViewHolder.list.removeAllViews();
+
+                activityViewHolder.list.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        int w = activityViewHolder.list.getMeasuredWidth();
+
+                        int elPxSize = px + 20;
+                        int totalColumnCount = (int) Math.floor(w / elPxSize);
+                        activityViewHolder.list.setColumnCount(totalColumnCount);
+                    }
+                });
+
+
+                for (RichObject richObject : activity.getRichSubjectElement().getRichObjectList()) {
+                    ImageView imageView = createThumbnail(richObject);
+                    activityViewHolder.list.addView(imageView);
+                }
+
+            } else {
+                activityViewHolder.list.removeAllViews();
+                activityViewHolder.list.setVisibility(View.GONE);
+            }
         } else {
-            holder.message.setVisibility(View.GONE);
+            ActivityViewHeaderHolder activityViewHeaderHolder = (ActivityViewHeaderHolder) holder;
+            activityViewHeaderHolder.title.setText((String) mValues.get(position));
         }
+    }
+
+    private ImageView createThumbnail(final RichObject richObject) {
+        OCFile file = new OCFile("/" + richObject.getPath());
+        file.setRemoteId(richObject.getId());
+
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(px, px);
+        params.setMargins(10, 10, 10, 10);
+        ImageView imageView = new ImageView(context);
+        imageView.setLayoutParams(params);
+        imageView.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                activityListInterface.onActivityClicked(richObject);
+            }
+        });
+        setBitmap(file, imageView);
+
+        return imageView;
+    }
 
-        if (!TextUtils.isEmpty(activity.getIcon())) {
-            downloadIcon(activity.getIcon(), holder.activityIcon);
+    private void setBitmap(OCFile file, ImageView fileIcon) {
+        // No Folder
+        if (!file.isFolder()) {
+            if ((MimeTypeUtil.isImage(file) || MimeTypeUtil.isVideo(file))) {
+                String uri = mClient.getBaseUri() + "" +
+                        "/index.php/apps/files/api/v1/thumbnail/" +
+                        px + "/" + px + Uri.encode(file.getRemotePath(), "/");
+
+                Glide.with(context).using(new CustomGlideStreamLoader()).load(uri).into(fileIcon); //Using custom fetcher
+
+            } else {
+                fileIcon.setImageResource(MimeTypeUtil.getFileTypeIconId(file.getMimetype(),
+                        file.getFileName()));
+            }
+        } else {
+            // Folder
+            fileIcon.setImageDrawable(
+                    MimeTypeUtil.getFolderTypeIcon(
+                            file.isSharedWithMe() || file.isSharedWithSharee(),
+                            file.isSharedViaLink()
+                    )
+            );
         }
     }
 
@@ -125,17 +265,76 @@ public class ActivityListAdapter extends RecyclerView.Adapter<ActivityListAdapte
                 .into(itemViewType);
     }
 
+    private SpannableStringBuilder addClickablePart(RichElement richElement) {
+        String text = richElement.getRichSubject();
+        SpannableStringBuilder ssb = new SpannableStringBuilder(text);
+
+        int idx1 = text.indexOf("{");
+        int idx2;
+        while (idx1 != -1) {
+            idx2 = text.indexOf("}", idx1) + 1;
+            final String clickString = text.substring(idx1 + 1, idx2 - 1);
+            final RichObject richObject = searchObjectByName(richElement.getRichObjectList(), clickString);
+            if (richObject != null) {
+                String name = richObject.getName();
+                ssb.replace(idx1, idx2, name);
+                text = ssb.toString();
+                idx2 = idx1 + name.length();
+                ssb.setSpan(new ClickableSpan() {
+                    @Override
+                    public void onClick(View widget) {
+                        activityListInterface.onActivityClicked(richObject);
+                    }
+                }, idx1, idx2, 0);
+                ssb.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), idx1, idx2, 0);
+            }
+            idx1 = text.indexOf("{", idx2);
+        }
+
+        return ssb;
+    }
+
+    public RichObject searchObjectByName(ArrayList<RichObject> richObjectList, String name) {
+        for (RichObject richObject : richObjectList) {
+            if (richObject.getTag().equalsIgnoreCase(name))
+                return richObject;
+        }
+        return null;
+    }
+
+
+    @Override
+    public int getItemViewType(int position) {
+        if (mValues.get(position) instanceof Activity)
+            return ACTIVITY_TYPE;
+        else
+            return HEADER_TYPE;
+    }
+
     @Override
     public int getItemCount() {
         return mValues.size();
     }
 
-    class ActivityViewHolder extends RecyclerView.ViewHolder {
+    /**
+     * Converts size of file icon from dp to pixel
+     *
+     * @return int
+     */
+    private int getThumbnailDimension() {
+        // Converts dp to pixel
+        Resources r = MainApp.getAppContext().getResources();
+        Double d = Math.pow(2, Math.floor(Math.log(r.getDimension(R.dimen.file_icon_size_grid)) / Math.log(2))) / 2;
+        return d.intValue();
+    }
+
+    private class ActivityViewHolder extends RecyclerView.ViewHolder {
 
         private final ImageView activityIcon;
         private final TextView subject;
         private final TextView message;
         private final TextView dateTime;
+        private final GridLayout list;
 
         private ActivityViewHolder(View itemView) {
             super(itemView);
@@ -143,6 +342,19 @@ public class ActivityListAdapter extends RecyclerView.Adapter<ActivityListAdapte
             subject = (TextView) itemView.findViewById(R.id.activity_subject);
             message = (TextView) itemView.findViewById(R.id.activity_message);
             dateTime = (TextView) itemView.findViewById(R.id.activity_datetime);
+            list = (GridLayout) itemView.findViewById(R.id.list);
+        }
+    }
+
+    private class ActivityViewHeaderHolder extends RecyclerView.ViewHolder {
+
+        private final TextView title;
+
+        private ActivityViewHeaderHolder(View itemView) {
+            super(itemView);
+            title = (TextView) itemView.findViewById(R.id.title_header);
+
         }
     }
+
 }

+ 96 - 39
src/main/java/com/owncloud/android/ui/adapter/FileListListAdapter.java

@@ -24,6 +24,7 @@ package com.owncloud.android.ui.adapter;
 
 
 import android.accounts.Account;
+import android.content.ContentValues;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
@@ -46,7 +47,9 @@ import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+import com.owncloud.android.datamodel.VirtualFolderType;
 import com.owncloud.android.db.PreferenceManager;
+import com.owncloud.android.db.ProviderMeta;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -54,6 +57,7 @@ import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
 import com.owncloud.android.lib.resources.files.RemoteFile;
 import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 import com.owncloud.android.ui.fragment.ExtendedListFragment;
@@ -328,18 +332,17 @@ public class FileListListAdapter extends BaseAdapter {
             view.setBackgroundColor(Color.WHITE);
 
             AbsListView parentList = (AbsListView) parent;
+
             if (parentList.getChoiceMode() != AbsListView.CHOICE_MODE_NONE &&
                     parentList.getCheckedItemCount() > 0
                     ) {
                 if (parentList.isItemChecked(position)) {
-                    view.setBackgroundColor(mContext.getResources().getColor(
-                            R.color.selected_item_background));
-                    checkBoxV.setImageResource(
-                            R.drawable.ic_checkbox_marked);
+                    view.setBackgroundColor(mContext.getResources().getColor(R.color.selected_item_background));
+                    checkBoxV.setImageDrawable(DisplayUtils.tintDrawable(R.drawable.ic_checkbox_marked,
+                            R.color.primary));
                 } else {
                     view.setBackgroundColor(Color.WHITE);
-                    checkBoxV.setImageResource(
-                            R.drawable.ic_checkbox_blank_outline);
+                    checkBoxV.setImageResource(R.drawable.ic_checkbox_blank_outline);
                 }
                 checkBoxV.setVisibility(View.VISIBLE);
             }
@@ -410,8 +413,8 @@ public class FileListListAdapter extends BaseAdapter {
 
             } else {
                 // Folder
-                fileIcon.setImageResource(
-                        MimeTypeUtil.getFolderTypeIconId(
+                fileIcon.setImageDrawable(
+                        MimeTypeUtil.getFolderTypeIcon(
                                 file.isSharedWithMe() || file.isSharedWithSharee(),
                                 file.isSharedViaLink()
                         )
@@ -482,39 +485,18 @@ public class FileListListAdapter extends BaseAdapter {
         }
     }
 
-    public void setData(ArrayList<Object> objects, ExtendedListFragment.SearchType searchType) {
+    public void setData(ArrayList<Object> objects, ExtendedListFragment.SearchType searchType, FileDataStorageManager storageManager) {
+        if (storageManager != null && mStorageManager == null) {
+            mStorageManager = storageManager;
+        }
         mFiles = new Vector<>();
-        if (searchType.equals(ExtendedListFragment.SearchType.SHARED_FILTER)) {
-            ArrayList<OCShare> shares = new ArrayList<>();
-            for (int i = 0; i < objects.size(); i++) {
-                // check type before cast as of long running data fetch it is possible that old result is filled
-                if (objects.get(i) instanceof OCShare) {
-                    OCShare ocShare = (OCShare) objects.get(i);
-
-                    shares.add(ocShare);
-
-                    // get ocFile from Server to have an up-to-date copy
-                    ReadRemoteFileOperation operation = new ReadRemoteFileOperation(ocShare.getPath());
-                    RemoteOperationResult result = operation.execute(mAccount, mContext);
-                    if (result.isSuccess()) {
-                        OCFile file = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
-
-                        mStorageManager.saveFile(file);
 
-                        if (!mFiles.contains(file)) {
-                            mFiles.add(file);
-                        }
-                    } else {
-                        Log_OC.e(TAG, "Error in getting prop for file: " + ocShare.getPath());
-                    }
-                }
-            }
-            mStorageManager.saveShares(shares);
-        } else {
-            for (int i = 0; i < objects.size(); i++) {
-                OCFile ocFile = FileStorageUtils.fillOCFile((RemoteFile) objects.get(i));
-                searchForLocalFileInDefaultPath(ocFile);
-                mFiles.add(ocFile);
+        // early exit
+        if (objects.size() > 0 && mStorageManager != null) {
+            if (searchType.equals(ExtendedListFragment.SearchType.SHARED_FILTER)) {
+                parseShares(objects);
+            } else {
+                parseVirtuals(objects, searchType);
             }
         }
 
@@ -539,6 +521,81 @@ public class FileListListAdapter extends BaseAdapter {
         });
     }
 
+    private void parseShares(ArrayList<Object> objects) {
+        ArrayList<OCShare> shares = new ArrayList<>();
+        for (int i = 0; i < objects.size(); i++) {
+            // check type before cast as of long running data fetch it is possible that old result is filled
+            if (objects.get(i) instanceof OCShare) {
+                OCShare ocShare = (OCShare) objects.get(i);
+
+                shares.add(ocShare);
+
+                // get ocFile from Server to have an up-to-date copy
+                ReadRemoteFileOperation operation = new ReadRemoteFileOperation(ocShare.getPath());
+                RemoteOperationResult result = operation.execute(mAccount, mContext);
+                if (result.isSuccess()) {
+                    OCFile file = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+
+                    ShareType newShareType = ocShare.getShareType();
+                    if (newShareType == ShareType.PUBLIC_LINK) {
+                        file.setShareViaLink(true);
+                    } else if (newShareType == ShareType.USER || newShareType == ShareType.GROUP ||
+                                    newShareType == ShareType.EMAIL || newShareType == ShareType.FEDERATED) {
+                        file.setShareWithSharee(true);
+                    }
+
+                    mStorageManager.saveFile(file);
+
+                    if (!mFiles.contains(file)) {
+                        mFiles.add(file);
+                    }
+                } else {
+                    Log_OC.e(TAG, "Error in getting prop for file: " + ocShare.getPath());
+                }
+            }
+        }
+        mStorageManager.saveShares(shares);
+    }
+
+    private void parseVirtuals(ArrayList<Object> objects, ExtendedListFragment.SearchType searchType) {
+        VirtualFolderType type;
+        boolean onlyImages = false;
+        switch (searchType) {
+            case FAVORITE_SEARCH:
+                type = VirtualFolderType.FAVORITE;
+                break;
+            case PHOTO_SEARCH:
+                type = VirtualFolderType.PHOTOS;
+                onlyImages = true;
+                break;
+            default:
+                type = VirtualFolderType.NONE;
+                break;
+        }
+
+        mStorageManager.deleteVirtuals(type);
+
+        ArrayList<ContentValues> contentValues = new ArrayList<>();
+
+        for (int i = 0; i < objects.size(); i++) {
+            OCFile ocFile = FileStorageUtils.fillOCFile((RemoteFile) objects.get(i));
+            searchForLocalFileInDefaultPath(ocFile);
+            ocFile = mStorageManager.saveFileWithParent(ocFile, mContext);
+
+            if (!onlyImages || MimeTypeUtil.isImage(ocFile)) {
+                mFiles.add(ocFile);
+            }
+
+            ContentValues cv = new ContentValues();
+            cv.put(ProviderMeta.ProviderTableMeta.VIRTUAL_TYPE, type.toString());
+            cv.put(ProviderMeta.ProviderTableMeta.VIRTUAL_OCFILE_ID, ocFile.getFileId());
+
+            contentValues.add(cv);
+        }
+
+        mStorageManager.saveVirtuals(type, contentValues);
+    }
+
     /**
      * Filter for getting only the folders
      *

+ 17 - 10
src/main/java/com/owncloud/android/ui/adapter/FolderSyncAdapter.java

@@ -35,6 +35,7 @@ import com.afollestad.sectionedrecyclerview.SectionedRecyclerViewAdapter;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
+import com.owncloud.android.utils.DisplayUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -50,13 +51,15 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
     private final int mGridTotal;
     private final ClickListener mListener;
     private final List<SyncedFolderDisplayItem> mSyncFolderItems;
+    private final boolean mLight;
 
-    public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener) {
+    public FolderSyncAdapter(Context context, int gridWidth, ClickListener listener, boolean light) {
         mContext = context;
         mGridWidth = gridWidth;
         mGridTotal = gridWidth * 2;
         mListener = listener;
         mSyncFolderItems = new ArrayList<>();
+        mLight = light;
     }
 
     public void setSyncFolderItems(List<SyncedFolderDisplayItem> syncFolderItems) {
@@ -99,14 +102,18 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
         });
         setSyncButtonActiveIcon(holder.syncStatusButton, mSyncFolderItems.get(section).isEnabled());
 
-        holder.menuButton.setVisibility(View.VISIBLE);
-        holder.menuButton.setTag(section);
-        holder.menuButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mListener.onSyncFolderSettingsClick(section, mSyncFolderItems.get(section));
-            }
-        });
+        if (mLight) {
+            holder.menuButton.setVisibility(View.GONE);
+        } else {
+            holder.menuButton.setVisibility(View.VISIBLE);
+            holder.menuButton.setTag(section);
+            holder.menuButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mListener.onSyncFolderSettingsClick(section, mSyncFolderItems.get(section));
+                }
+            });
+        }
     }
 
     @Override
@@ -188,7 +195,7 @@ public class FolderSyncAdapter extends SectionedRecyclerViewAdapter<FolderSyncAd
 
     private void setSyncButtonActiveIcon(ImageButton syncStatusButton, boolean enabled) {
         if(enabled) {
-            syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_on);
+            syncStatusButton.setImageDrawable(DisplayUtils.tintDrawable(R.drawable.ic_cloud_sync_on, R.color.primary));
         } else {
             syncStatusButton.setImageResource(R.drawable.ic_cloud_sync_off);
         }

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

@@ -164,7 +164,7 @@ public class LocalFileListAdapter extends BaseAdapter implements FilterableListA
             if (!file.isDirectory()) {
                 fileIcon.setImageResource(R.drawable.file);
             } else {
-                fileIcon.setImageResource(R.drawable.ic_menu_archive);
+                fileIcon.setImageDrawable(MimeTypeUtil.getDefaultFolderIcon());
             }
             fileIcon.setTag(file.hashCode());
 

+ 2 - 3
src/main/java/com/owncloud/android/ui/dialog/CreateFolderDialogFragment.java

@@ -49,6 +49,8 @@ public class CreateFolderDialogFragment
     
     public static final String CREATE_FOLDER_FRAGMENT = "CREATE_FOLDER_FRAGMENT";
 
+    private OCFile mParentFolder;
+
     /**
      * Public factory method to create new CreateFolderDialogFragment instances.
      *
@@ -63,9 +65,6 @@ public class CreateFolderDialogFragment
         return frag;
         
     }
-
-    private OCFile mParentFolder;
-    
     
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {

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

@@ -54,6 +54,8 @@ public class RenameFileDialogFragment
 
     private static final String ARG_TARGET_FILE = "TARGET_FILE";
 
+    private OCFile mTargetFile;
+
     /**
      * Public factory method to create new RenameFileDialogFragment instances.
      * 
@@ -69,8 +71,6 @@ public class RenameFileDialogFragment
         
     }
 
-    private OCFile mTargetFile;
-    
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         mTargetFile = getArguments().getParcelable(ARG_TARGET_FILE);

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

@@ -5,16 +5,16 @@
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2012 Bartek Przybylski
  * Copyright (C) 2012-2016 ownCloud Inc.
- *
+ * <p>
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
  * as published by the Free Software Foundation.
- *
+ * <p>
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- *
+ * <p>
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
@@ -73,6 +73,7 @@ import com.owncloud.android.ui.activity.UploadFilesActivity;
 import com.owncloud.android.ui.adapter.FileListListAdapter;
 import com.owncloud.android.ui.adapter.LocalFileListAdapter;
 import com.owncloud.android.ui.events.SearchEvent;
+import com.owncloud.android.utils.DisplayUtils;
 
 import org.greenrobot.eventbus.EventBus;
 import org.parceler.Parcel;
@@ -104,7 +105,7 @@ public class ExtendedListFragment extends Fragment
 
     private ScaleGestureDetector mScaleGestureDetector = null;
     protected SwipeRefreshLayout mRefreshListLayout;
-    private SwipeRefreshLayout mRefreshGridLayout;
+    protected SwipeRefreshLayout mRefreshGridLayout;
     protected SwipeRefreshLayout mRefreshEmptyLayout;
     protected LinearLayout mEmptyListContainer;
     protected TextView mEmptyListMessage;
@@ -295,8 +296,8 @@ public class ExtendedListFragment extends Fragment
     }
 
     public boolean onQueryTextChange(final String query) {
-        if (getFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_SECOND_FRAGMENT)
-                instanceof ExtendedListFragment){
+        if (getFragmentManager() != null && getFragmentManager().
+                findFragmentByTag(FileDisplayActivity.TAG_SECOND_FRAGMENT) instanceof ExtendedListFragment) {
             performSearch(query, false);
             return true;
         } else {
@@ -306,13 +307,8 @@ public class ExtendedListFragment extends Fragment
 
     @Override
     public boolean onQueryTextSubmit(String query) {
-        if (getFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_SECOND_FRAGMENT)
-                instanceof ExtendedListFragment){
-            performSearch(query, true);
-            return true;
-        } else {
-            return false;
-        }
+        performSearch(query, true);
+        return true;
     }
 
     private void performSearch(final String query, boolean isSubmit) {
@@ -357,7 +353,9 @@ public class ExtendedListFragment extends Fragment
             Activity activity;
             if ((activity = getActivity()) != null) {
                 if (activity instanceof FileDisplayActivity) {
-                    ((FileDisplayActivity) activity).refreshListOfFilesFragment(true);
+                    FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) activity;
+                    fileDisplayActivity.resetSearchView();
+                    fileDisplayActivity.refreshListOfFilesFragment(true);
                 } else if (activity instanceof UploadFilesActivity) {
                     LocalFileListAdapter localFileListAdapter = (LocalFileListAdapter) mAdapter;
                     localFileListAdapter.filter(query);
@@ -465,6 +463,10 @@ public class ExtendedListFragment extends Fragment
         return v;
     }
 
+    public void setEmptyListVisible() {
+        mEmptyListContainer.setVisibility(View.VISIBLE);
+    }
+
     private class SingleTapConfirm extends GestureDetector.SimpleOnGestureListener {
         @Override
         public boolean onSingleTapUp(MotionEvent e) {
@@ -518,7 +520,6 @@ public class ExtendedListFragment extends Fragment
             mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
             mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
             setMessageForEmptyList(savedInstanceState.getString(KEY_EMPTY_LIST_MESSAGE));
-
         } else {
             mIndexes = new ArrayList<>();
             mFirstPositions = new ArrayList<>();
@@ -549,7 +550,7 @@ public class ExtendedListFragment extends Fragment
      * Calculates the position of the item that will be used as a reference to
      * reposition the visible items in the list when the device is turned to
      * other position.
-     *
+     * <p>
      * The current policy is take as a reference the visible item in the center
      * of the screen.
      *
@@ -658,9 +659,9 @@ public class ExtendedListFragment extends Fragment
 
     /**
      * Disables swipe gesture.
-     *
+     * <p>
      * Sets the 'enabled' state of the refresh layouts contained in the fragment.
-     *
+     * <p>
      * When 'false' is set, prevents user gestures but keeps the option to refresh programatically,
      *
      * @param enabled Desired state for capturing swipe gesture.
@@ -673,7 +674,7 @@ public class ExtendedListFragment extends Fragment
 
     /**
      * Sets the 'visibility' state of the FAB contained in the fragment.
-     *
+     * <p>
      * When 'false' is set, FAB visibility is set to View.GONE programmatically,
      *
      * @param enabled Desired visibility for the FAB.
@@ -717,10 +718,12 @@ public class ExtendedListFragment extends Fragment
                 if (mEmptyListContainer != null && mEmptyListMessage != null) {
                     mEmptyListHeadline.setText(headline);
                     mEmptyListMessage.setText(message);
-                    mEmptyListIcon.setImageResource(icon);
+
+                    mEmptyListIcon.setImageDrawable(DisplayUtils.tintDrawable(icon, R.color.primary));
 
                     mEmptyListIcon.setVisibility(View.VISIBLE);
                     mEmptyListProgress.setVisibility(View.GONE);
+                    mEmptyListMessage.setVisibility(View.VISIBLE);
                 }
             }
         });
@@ -731,7 +734,7 @@ public class ExtendedListFragment extends Fragment
             @Override
             public void run() {
 
-                if (searchType == SearchType.NO_SEARCH) {
+                if (searchType == SearchType.NO_SEARCH && mEmptyListProgress.getVisibility() == View.GONE) {
                     setMessageForEmptyList(
                             R.string.file_list_empty_headline,
                             R.string.file_list_empty,

+ 5 - 6
src/main/java/com/owncloud/android/ui/fragment/LocalFileListFragment.java

@@ -98,9 +98,8 @@ public class LocalFileListFragment extends ExtendedListFragment {
         setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
         setSwipeEnabled(false); // Disable pull-to-refresh
         setFabEnabled(false); // Disable FAB
-        setMessageForEmptyList(
-                R.string.file_list_empty_headline, R.string.local_file_list_empty, R.drawable.ic_list_empty_folder
-        );
+        setMessageForEmptyList(R.string.file_list_empty_headline, R.string.local_file_list_empty,
+                R.drawable.ic_list_empty_folder);
         Log_OC.i(TAG, "onCreateView() end");
         return v;
     }
@@ -289,7 +288,7 @@ public class LocalFileListFragment extends ExtendedListFragment {
          *  
          * @param directory
          */
-        public void onDirectoryClick(File directory);
+        void onDirectoryClick(File directory);
         
         /**
          * Callback method invoked when a file (non directory)
@@ -297,7 +296,7 @@ public class LocalFileListFragment extends ExtendedListFragment {
          *  
          * @param file
          */
-        public void onFileClick(File file);
+        void onFileClick(File file);
         
         
         /**
@@ -306,7 +305,7 @@ public class LocalFileListFragment extends ExtendedListFragment {
          * 
          * @return  Directory to list firstly. Can be NULL.
          */
-        public File getInitialDirectory();
+        File getInitialDirectory();
 
     }
 

+ 211 - 111
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -59,6 +59,7 @@ import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.VirtualFolderType;
 import com.owncloud.android.files.FileMenuFilter;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
@@ -178,7 +179,13 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
         mProgressBarActionModeColor = getResources().getColor(R.color.action_mode_background);
         mProgressBarColor = getResources().getColor(R.color.primary);
         mMultiChoiceModeListener = new MultiChoiceModeListener();
-        searchFragment = false;
+
+        if (savedInstanceState != null) {
+            currentSearchType = Parcels.unwrap(savedInstanceState.getParcelable(KEY_CURRENT_SEARCH_TYPE));
+            searchEvent = Parcels.unwrap(savedInstanceState.getParcelable(OCFileListFragment.SEARCH_EVENT));
+        }
+
+        searchFragment = currentSearchType != null;
     }
 
     /**
@@ -219,6 +226,7 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
             currentSearchType = SearchType.NO_SEARCH;
         }
 
+        searchFragment = savedInstanceState != null;
 
         if (getResources().getBoolean(R.bool.bottom_toolbar_enabled)) {
             bottomNavigationView.setVisibility(View.VISIBLE);
@@ -254,10 +262,6 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
     public void onResume() {
         super.onResume();
 
-        if (remoteOperationAsyncTask != null) {
-            remoteOperationAsyncTask.cancel(true);
-        }
-
         if (getActivity() != null) {
             AnalyticsUtils.setCurrentScreenName(getActivity(), SCREEN_NAME, TAG);
         }
@@ -282,6 +286,7 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
         super.onActivityCreated(savedInstanceState);
         Log_OC.i(TAG, "onActivityCreated() start");
 
+
         if (savedInstanceState != null) {
             mFile = savedInstanceState.getParcelable(KEY_FILE);
         }
@@ -325,9 +330,38 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
         }
 
         searchEvent = Parcels.unwrap(getArguments().getParcelable(OCFileListFragment.SEARCH_EVENT));
-        if (searchEvent != null) {
+        if (searchEvent != null && searchFragment && savedInstanceState == null) {
             onMessageEvent(searchEvent);
         }
+
+        prepareCurrentSearch(searchEvent);
+        setTitle();
+
+    }
+
+    private void prepareCurrentSearch(SearchEvent event) {
+        if (event != null) {
+            if (event.getSearchType().equals(SearchOperation.SearchType.FILE_SEARCH)) {
+                currentSearchType = SearchType.FILE_SEARCH;
+
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.CONTENT_TYPE_SEARCH)) {
+                if (event.getSearchQuery().equals("image/%")) {
+                    currentSearchType = SearchType.PHOTO_SEARCH;
+                } else if (event.getSearchQuery().equals("video/%")) {
+                    currentSearchType = SearchType.VIDEO_SEARCH;
+                }
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.FAVORITE_SEARCH)) {
+                currentSearchType = SearchType.FAVORITE_SEARCH;
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_ADDED_SEARCH)) {
+                currentSearchType = SearchType.RECENTLY_ADDED_SEARCH;
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_MODIFIED_SEARCH)) {
+                currentSearchType = SearchType.RECENTLY_MODIFIED_SEARCH;
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.SHARED_SEARCH)) {
+                currentSearchType = SearchType.SHARED_FILTER;
+            }
+
+            prepareActionBarItems(event);
+        }
     }
 
     /**
@@ -611,7 +645,7 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
             DisplayUtils.colorToolbarProgressBar(getActivity(), mProgressBarColor);
 
             // show FAB on multi selection mode exit
-            if (!mHideFab) {
+            if (!mHideFab && !searchFragment) {
                 setFabEnabled(true);
             }
         }
@@ -673,7 +707,10 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putParcelable(KEY_FILE, mFile);
-        outState.putParcelable(KEY_CURRENT_SEARCH_TYPE, Parcels.wrap(currentSearchType));
+        if (searchFragment) {
+            outState.putParcelable(KEY_CURRENT_SEARCH_TYPE, Parcels.wrap(currentSearchType));
+            outState.putParcelable(OCFileListFragment.SEARCH_EVENT, Parcels.wrap(searchEvent));
+        }
         mMultiChoiceModeListener.storeStateIn(outState);
     }
 
@@ -796,6 +833,7 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
         if (file != null) {
             if (file.isFolder()) {
                 // update state and view of this fragment
+                searchFragment = false;
                 listDirectory(file, MainApp.isOnlyOnDevice(), false);
                 // then, notify parent activity to let it update its state and view
                 mContainerActivity.onBrowsedDownTo(file);
@@ -805,8 +843,24 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
             } else { /// Click on a file
                 if (PreviewImageFragment.canBePreviewed(file)) {
                     // preview image - it handles the download, if needed
-                    ((FileDisplayActivity) mContainerActivity).startImagePreview(file);
-                } else if (file.isDown() && MimeTypeUtil.isVCard(file)){
+                    if (searchFragment) {
+                        VirtualFolderType type;
+                        switch (currentSearchType) {
+                            case FAVORITE_SEARCH:
+                                type = VirtualFolderType.FAVORITE;
+                                break;
+                            case PHOTO_SEARCH:
+                                type = VirtualFolderType.PHOTOS;
+                                break;
+                            default:
+                                type = VirtualFolderType.NONE;
+                                break;
+                        }
+                        ((FileDisplayActivity) mContainerActivity).startImagePreview(file, type);
+                    } else {
+                        ((FileDisplayActivity) mContainerActivity).startImagePreview(file);
+                    }
+                } else if (file.isDown() && MimeTypeUtil.isVCard(file)) {
                     ((FileDisplayActivity) mContainerActivity).startContactListFragment(file);
                 } else if (PreviewTextFragment.canBePreviewed(file)) {
                     ((FileDisplayActivity) mContainerActivity).startTextPreview(file);
@@ -959,10 +1013,6 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
     public void refreshDirectory() {
         searchFragment = false;
 
-        if (remoteOperationAsyncTask != null) {
-            remoteOperationAsyncTask.cancel(true);
-        }
-
         setFabEnabled(true);
         listDirectory(getCurrentFile(), MainApp.isOnlyOnDevice(), false);
     }
@@ -1236,6 +1286,92 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
 
     }
 
+    public void setTitleFromSearchEvent(SearchEvent event) {
+        prepareCurrentSearch(event);
+        setTitle();
+    }
+
+    private void setTitle() {
+        // set title
+
+        if (getActivity() instanceof FileDisplayActivity && currentSearchType != null) {
+            switch (currentSearchType) {
+                case FAVORITE_SEARCH:
+                    setTitle(R.string.drawer_item_favorites);
+                    break;
+                case PHOTO_SEARCH:
+                    setTitle(R.string.drawer_item_photos);
+                    break;
+                case VIDEO_SEARCH:
+                    setTitle(R.string.drawer_item_videos);
+                    break;
+                case RECENTLY_ADDED_SEARCH:
+                    setTitle(R.string.drawer_item_recently_added);
+                    break;
+                case RECENTLY_MODIFIED_SEARCH:
+                    setTitle(R.string.drawer_item_recently_modified);
+                    break;
+                case SHARED_FILTER:
+                    setTitle(R.string.drawer_item_shared);
+                    break;
+                default:
+                    setTitle(R.string.default_display_name_for_root_folder);
+                    break;
+            }
+        }
+
+    }
+
+    private void prepareActionBarItems(SearchEvent event) {
+        if (event != null) {
+            if (event.getSearchType().equals(SearchOperation.SearchType.CONTENT_TYPE_SEARCH)) {
+                if (event.getSearchQuery().equals("image/%")) {
+                    menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_GRID_AND_SORT;
+                } else if (event.getSearchQuery().equals("video/%")) {
+                    menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SEARCH;
+                }
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.FAVORITE_SEARCH)) {
+                menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SORT;
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_ADDED_SEARCH)) {
+                menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SORT;
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_MODIFIED_SEARCH)) {
+                menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SORT;
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.SHARED_SEARCH)) {
+                menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SEARCH;
+            }
+
+        }
+
+        if (currentSearchType != null && !currentSearchType.equals(SearchType.FILE_SEARCH) && getActivity() != null) {
+            getActivity().invalidateOptionsMenu();
+        }
+    }
+
+    private void setEmptyView(SearchEvent event) {
+
+        if (event != null) {
+            if (event.getSearchType().equals(SearchOperation.SearchType.FILE_SEARCH)) {
+                setEmptyListMessage(SearchType.FILE_SEARCH);
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.CONTENT_TYPE_SEARCH)) {
+                if (event.getSearchQuery().equals("image/%")) {
+                    setEmptyListMessage(SearchType.PHOTO_SEARCH);
+                } else if (event.getSearchQuery().equals("video/%")) {
+                    setEmptyListMessage(SearchType.VIDEO_SEARCH);
+                }
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.FAVORITE_SEARCH)) {
+                setEmptyListMessage(SearchType.FAVORITE_SEARCH);
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_ADDED_SEARCH)) {
+                setEmptyListMessage(SearchType.RECENTLY_ADDED_SEARCH);
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_MODIFIED_SEARCH)) {
+                setEmptyListMessage(SearchType.RECENTLY_MODIFIED_SEARCH);
+            } else if (event.getSearchType().equals(SearchOperation.SearchType.SHARED_SEARCH)) {
+                setEmptyListMessage(SearchType.SHARED_FILTER);
+            }
+
+        }
+
+    }
+
     @Subscribe(threadMode = ThreadMode.MAIN)
     public void onMessageEvent(ChangeMenuEvent changeMenuEvent) {
         menuItemAddRemoveValue = MenuItemAddRemove.ADD_GRID_AND_SORT_WITH_SEARCH;
@@ -1244,6 +1380,9 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
             setTitle(R.string.default_display_name_for_root_folder);
         }
 
+        getActivity().getIntent().removeExtra(OCFileListFragment.SEARCH_EVENT);
+        getArguments().putParcelable(OCFileListFragment.SEARCH_EVENT, null);
+
         setFabEnabled(true);
     }
 
@@ -1281,10 +1420,10 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
     }
 
     @Subscribe(threadMode = ThreadMode.BACKGROUND)
-    public void onMessageEvent(SearchEvent event) {
+    public void onMessageEvent(final SearchEvent event) {
         searchFragment = true;
         setEmptyListLoadingMessage();
-        mAdapter.setData(new ArrayList<>(), SearchType.NO_SEARCH);
+        mAdapter.setData(new ArrayList<>(), SearchType.NO_SEARCH, mContainerActivity.getStorageManager());
 
         setFabEnabled(false);
 
@@ -1294,51 +1433,6 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
             unsetAllMenuItems(true);
         }
 
-        if (event.getSearchType().equals(SearchOperation.SearchType.FILE_SEARCH)) {
-            currentSearchType = SearchType.FILE_SEARCH;
-
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.CONTENT_TYPE_SEARCH)) {
-            if (event.getSearchQuery().equals("image/%")) {
-                currentSearchType = SearchType.PHOTO_SEARCH;
-            } else if (event.getSearchQuery().equals("video/%")) {
-                currentSearchType = SearchType.VIDEO_SEARCH;
-            }
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.FAVORITE_SEARCH)) {
-            currentSearchType = SearchType.FAVORITE_SEARCH;
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_ADDED_SEARCH)) {
-            currentSearchType = SearchType.RECENTLY_ADDED_SEARCH;
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_MODIFIED_SEARCH)) {
-            currentSearchType = SearchType.RECENTLY_MODIFIED_SEARCH;
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.SHARED_SEARCH)) {
-            currentSearchType = SearchType.SHARED_FILTER;
-        }
-
-        // set title
-        if (getActivity() instanceof FileDisplayActivity) {
-            switch (currentSearchType) {
-                case FAVORITE_SEARCH:
-                    setTitle(R.string.drawer_item_favorites);
-                    break;
-                case PHOTO_SEARCH:
-                    setTitle(R.string.drawer_item_photos);
-                    break;
-                case VIDEO_SEARCH:
-                    setTitle(R.string.drawer_item_videos);
-                    break;
-                case RECENTLY_ADDED_SEARCH:
-                    setTitle(R.string.drawer_item_recently_added);
-                    break;
-                case RECENTLY_MODIFIED_SEARCH:
-                    setTitle(R.string.drawer_item_recently_modified);
-                    break;
-                case SHARED_FILTER:
-                    setTitle(R.string.drawer_item_shared);
-                    break;
-                default:
-                    setTitle(R.string.default_display_name_for_root_folder);
-                    break;
-            }
-        }
 
         if (bottomNavigationView != null && searchEvent != null) {
             switch (currentSearchType) {
@@ -1366,6 +1460,20 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
             }
         };
 
+        if (currentSearchType.equals(SearchType.PHOTO_SEARCH)) {
+            new Handler(Looper.getMainLooper()).post(new Runnable() {
+                @Override
+                public void run() {
+                    switchToGridView();
+                }
+            });
+        } else if (currentSearchType.equals(SearchType.NO_SEARCH) || currentSearchType.equals(
+                SearchType.REGULAR_FILTER)) {
+            new Handler(Looper.getMainLooper()).post(switchViewsRunnable);
+        } else {
+            new Handler(Looper.getMainLooper()).post(switchViewsRunnable);
+        }
+
         final RemoteOperation remoteOperation;
         if (!currentSearchType.equals(SearchType.SHARED_FILTER)) {
             remoteOperation = new SearchOperation(event.getSearchQuery(), event.getSearchType());
@@ -1381,9 +1489,26 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
                 if (getContext() != null && !isCancelled()) {
                     RemoteOperationResult remoteOperationResult = remoteOperation.execute(currentAccount, getContext());
 
+                    FileDataStorageManager storageManager = null;
+                    if (mContainerActivity != null && mContainerActivity.getStorageManager() != null) {
+                        storageManager = mContainerActivity.getStorageManager();
+                    }
+
                     if (remoteOperationResult.isSuccess() && remoteOperationResult.getData() != null
                             && !isCancelled() && searchFragment) {
-                        mAdapter.setData(remoteOperationResult.getData(), currentSearchType);
+                        if (remoteOperationResult.getData() == null || remoteOperationResult.getData().size() == 0) {
+                            setEmptyView(event);
+                        } else {
+                            mAdapter.setData(remoteOperationResult.getData(), currentSearchType, storageManager);
+                        }
+
+                        final FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) getActivity();
+                        fileDisplayActivity.runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                fileDisplayActivity.setIndeterminate(false);
+                            }
+                        });
                     }
 
                     return remoteOperationResult.isSuccess();
@@ -1402,54 +1527,17 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
 
         remoteOperationAsyncTask.execute(true);
 
-        if (event.getSearchType().equals(SearchOperation.SearchType.FILE_SEARCH)) {
-            setEmptyListMessage(SearchType.FILE_SEARCH);
-
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.CONTENT_TYPE_SEARCH)) {
-            if (event.getSearchQuery().equals("image/%")) {
-                setEmptyListMessage(SearchType.PHOTO_SEARCH);
-                menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_GRID_AND_SORT;
-            } else if (event.getSearchQuery().equals("video/%")) {
-                setEmptyListMessage(SearchType.VIDEO_SEARCH);
-                menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SEARCH;
-            }
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.FAVORITE_SEARCH)) {
-            setEmptyListMessage(SearchType.FAVORITE_SEARCH);
-            menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SORT;
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_ADDED_SEARCH)) {
-            setEmptyListMessage(SearchType.RECENTLY_ADDED_SEARCH);
-            menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SORT;
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.RECENTLY_MODIFIED_SEARCH)) {
-            setEmptyListMessage(SearchType.RECENTLY_MODIFIED_SEARCH);
-            menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SORT;
-        } else if (event.getSearchType().equals(SearchOperation.SearchType.SHARED_SEARCH)) {
-            setEmptyListMessage(SearchType.SHARED_FILTER);
-            menuItemAddRemoveValue = MenuItemAddRemove.REMOVE_SEARCH;
-        }
-
-        if (!currentSearchType.equals(SearchType.FILE_SEARCH) && getActivity() != null) {
-            getActivity().invalidateOptionsMenu();
-        }
-
-        if (currentSearchType.equals(SearchType.PHOTO_SEARCH)) {
-            new Handler(Looper.getMainLooper()).post(new Runnable() {
-                @Override
-                public void run() {
-                    switchToGridView();
-                }
-            });
-        } else if (currentSearchType.equals(SearchType.NO_SEARCH) || currentSearchType.equals(
-                SearchType.REGULAR_FILTER)) {
-            new Handler(Looper.getMainLooper()).post(switchViewsRunnable);
-        } else {
-            new Handler(Looper.getMainLooper()).post(switchViewsRunnable);
-        }
-
-        searchEvent = null;
     }
 
-    private void setTitle(@StringRes int title) {
-        ((FileDisplayActivity) getActivity()).getSupportActionBar().setTitle(title);
+    private void setTitle(@StringRes final int title) {
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (getActivity() != null && ((FileDisplayActivity) getActivity()).getSupportActionBar() != null) {
+                    ((FileDisplayActivity) getActivity()).getSupportActionBar().setTitle(title);
+                }
+            }
+        });
     }
 
     @Override
@@ -1467,10 +1555,22 @@ public class OCFileListFragment extends ExtendedListFragment implements OCFileLi
 
     @Override
     public void onRefresh() {
-        super.onRefresh();
-
-        if (searchEvent != null) {
+        if (searchEvent != null && searchFragment) {
             onMessageEvent(searchEvent);
+
+            mRefreshListLayout.setRefreshing(false);
+            mRefreshGridLayout.setRefreshing(false);
+            mRefreshEmptyLayout.setRefreshing(false);
+        } else {
+            super.onRefresh();
         }
     }
+
+    public void setSearchFragment(boolean searchFragment) {
+        this.searchFragment = searchFragment;
+    }
+
+    public boolean getIsSearchFragment() {
+        return searchFragment;
+    }
 }

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

@@ -1,4 +1,4 @@
-/**
+/*
  * Nextcloud Android client application
  *
  * @author Tobias Kaminsky
@@ -23,18 +23,22 @@ package com.owncloud.android.ui.fragment.contactsbackup;
 
 import android.Manifest;
 import android.accounts.Account;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.ContactsContract;
 import android.support.annotation.NonNull;
 import android.support.design.widget.Snackbar;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
+import android.support.v7.app.ActionBar;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
@@ -49,10 +53,15 @@ import android.widget.Button;
 import android.widget.CheckedTextView;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.Toast;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
 
 import com.evernote.android.job.JobRequest;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.owncloud.android.R;
+import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -71,6 +80,8 @@ import org.greenrobot.eventbus.ThreadMode;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -79,7 +90,8 @@ import butterknife.BindView;
 import butterknife.ButterKnife;
 import ezvcard.Ezvcard;
 import ezvcard.VCard;
-import ezvcard.property.StructuredName;
+
+import static com.owncloud.android.ui.fragment.contactsbackup.ContactListFragment.getDisplayName;
 
 /**
  * This fragment shows all contacts from a file and allows to import them.
@@ -101,7 +113,26 @@ public class ContactListFragment extends FileFragment {
     @BindView(R.id.contactlist_restore_selected)
     public Button restoreContacts;
 
+    @BindView(R.id.empty_list_view_text)
+    public TextView emptyContentMessage;
+
+    @BindView(R.id.empty_list_view_headline)
+    public TextView emptyContentHeadline;
+
+    @BindView(R.id.empty_list_icon)
+    public ImageView emptyContentIcon;
+
+    @BindView(R.id.empty_list_progress)
+    public ProgressBar emptyContentProgressBar;
+
+    @BindView(R.id.empty_list_container)
+    public RelativeLayout emptyListContainer;
+
+
     private ContactListAdapter contactListAdapter;
+    private Account account;
+    private ArrayList<VCard> vCards = new ArrayList<>();
+    private OCFile ocFile;
 
     public static ContactListFragment newInstance(OCFile file, Account account) {
         ContactListFragment frag = new ContactListFragment();
@@ -125,45 +156,19 @@ public class ContactListFragment extends FileFragment {
     @Override
     public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
 
-        View view = inflater.inflate(R.layout.contactlist_fragment, null);
+        View view = inflater.inflate(R.layout.contactlist_fragment, container, false);
         ButterKnife.bind(this, view);
 
         setHasOptionsMenu(true);
 
         ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
-        contactsPreferenceActivity.getSupportActionBar().setTitle(R.string.actionbar_contacts_restore);
-        contactsPreferenceActivity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-        contactsPreferenceActivity.setDrawerIndicatorEnabled(false);
 
-        ArrayList<VCard> vCards = new ArrayList<>();
-
-        try {
-            OCFile ocFile = getArguments().getParcelable(FILE_NAME);
-            setFile(ocFile);
-            Account account = getArguments().getParcelable(ACCOUNT);
-
-            if (!ocFile.isDown()) {
-                Intent i = new Intent(getContext(), FileDownloader.class);
-                i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
-                i.putExtra(FileDownloader.EXTRA_FILE, ocFile);
-                getContext().startService(i);
-            } else {
-                File file = new File(ocFile.getStoragePath());
-                vCards.addAll(Ezvcard.parse(file).all());
-            }
-        } catch (IOException e) {
-            Log_OC.e(TAG, "Error processing contacts file!", e);
+        ActionBar actionBar = contactsPreferenceActivity.getSupportActionBar();
+        if (actionBar != null) {
+            actionBar.setTitle(R.string.actionbar_contacts_restore);
+            actionBar.setDisplayHomeAsUpEnabled(true);
         }
-
-        restoreContacts.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-
-                if (checkAndAskForContactsWritePermission()) {
-                    getAccountForImport();
-                }
-            }
-        });
+        contactsPreferenceActivity.setDrawerIndicatorEnabled(false);
 
         recyclerView = (RecyclerView) view.findViewById(R.id.contactlist_recyclerview);
 
@@ -183,6 +188,35 @@ public class ContactListFragment extends FileFragment {
         recyclerView.setAdapter(contactListAdapter);
         recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
 
+        ocFile = getArguments().getParcelable(FILE_NAME);
+        setFile(ocFile);
+        account = getArguments().getParcelable(ACCOUNT);
+
+        if (!ocFile.isDown()) {
+            Intent i = new Intent(getContext(), FileDownloader.class);
+            i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
+            i.putExtra(FileDownloader.EXTRA_FILE, ocFile);
+            getContext().startService(i);
+
+            // Listen for download messages
+            IntentFilter downloadIntentFilter = new IntentFilter(FileDownloader.getDownloadAddedMessage());
+            downloadIntentFilter.addAction(FileDownloader.getDownloadFinishMessage());
+            DownloadFinishReceiver mDownloadFinishReceiver = new DownloadFinishReceiver();
+            getContext().registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
+        } else {
+            loadContactsTask.execute();
+        }
+
+        restoreContacts.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+
+                if (checkAndAskForContactsWritePermission()) {
+                    getAccountForImport();
+                }
+            }
+        });
+
         return view;
     }
 
@@ -223,6 +257,9 @@ public class ContactListFragment extends FileFragment {
     @Override
     public void onStop() {
         EventBus.getDefault().unregister(this);
+        if (loadContactsTask != null) {
+            loadContactsTask.cancel(true);
+        }
         super.onStop();
     }
 
@@ -249,9 +286,17 @@ public class ContactListFragment extends FileFragment {
         return retval;
     }
 
+    private void setLoadingMessage() {
+        emptyContentHeadline.setText(R.string.file_list_loading);
+        emptyContentMessage.setText("");
+
+        emptyContentIcon.setVisibility(View.GONE);
+        emptyContentProgressBar.setVisibility(View.VISIBLE);
+    }
+
     private void setSelectAllMenuItem(MenuItem selectAll, boolean checked) {
         selectAll.setChecked(checked);
-        if(checked) {
+        if (checked) {
             selectAll.setIcon(R.drawable.ic_select_none);
         } else {
             selectAll.setIcon(R.drawable.ic_select_all);
@@ -395,7 +440,12 @@ public class ContactListFragment extends FileFragment {
                     if (grantResults[index] >= 0) {
                         getAccountForImport();
                     } else {
-                        Snackbar.make(getView(), R.string.contactlist_no_permission, Snackbar.LENGTH_LONG).show();
+                        if (getView() != null) {
+                            Snackbar.make(getView(), R.string.contactlist_no_permission, Snackbar.LENGTH_LONG)
+                                    .show();
+                        } else {
+                            Toast.makeText(getContext(), R.string.contactlist_no_permission, Toast.LENGTH_LONG).show();
+                        }
                     }
                     break;
                 }
@@ -429,6 +479,77 @@ public class ContactListFragment extends FileFragment {
             return displayName;
         }
     }
+
+    private class DownloadFinishReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equalsIgnoreCase(FileDownloader.getDownloadFinishMessage())) {
+                String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH);
+
+                FileDataStorageManager storageManager = new FileDataStorageManager(account,
+                        getContext().getContentResolver());
+                ocFile = storageManager.getFileByPath(downloadedRemotePath);
+                loadContactsTask.execute();
+            }
+        }
+    }
+
+    public static class VCardComparator implements Comparator<VCard> {
+        @Override
+        public int compare(VCard o1, VCard o2) {
+            String contac1 = getDisplayName(o1);
+            String contac2 = getDisplayName(o2);
+
+            return contac1.compareToIgnoreCase(contac2);
+        }
+
+
+    }
+
+    private AsyncTask loadContactsTask = new AsyncTask() {
+
+        @Override
+        protected void onPreExecute() {
+            setLoadingMessage();
+        }
+
+        @Override
+        protected Object doInBackground(Object[] params) {
+            if (!isCancelled()) {
+                File file = new File(ocFile.getStoragePath());
+                try {
+                    vCards.addAll(Ezvcard.parse(file).all());
+                    Collections.sort(vCards, new VCardComparator());
+                } catch (IOException e) {
+                    Log_OC.e(TAG, "IO Exception: " + file.getAbsolutePath());
+                    return false;
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        protected void onPostExecute(Object o) {
+            if (!isCancelled()) {
+                emptyListContainer.setVisibility(View.GONE);
+                contactListAdapter.replaceVCards(vCards);
+            }
+        }
+    };
+
+    public static String getDisplayName(VCard vCard) {
+        if (vCard.getFormattedName() != null) {
+            return vCard.getFormattedName().getValue();
+        } else if (vCard.getTelephoneNumbers() != null && vCard.getTelephoneNumbers().size() > 0) {
+            return vCard.getTelephoneNumbers().get(0).getText();
+        } else if (vCard.getEmails() != null && vCard.getEmails().size() > 0) {
+            return vCard.getEmails().get(0).getValue();
+        }
+
+        return "";
+    }
 }
 
 class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.ContactItemViewHolder> {
@@ -458,12 +579,17 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
         }
     }
 
+    public void replaceVCards(List<VCard> vCards) {
+        this.vCards = vCards;
+        notifyDataSetChanged();
+    }
+
     public int[] getCheckedIntArray() {
         int[] intArray;
         if (checkedVCards != null && checkedVCards.size() > 0) {
             intArray = new int[checkedVCards.size()];
             int i = 0;
-            for (int position: checkedVCards) {
+            for (int position : checkedVCards) {
                 intArray[i] = position;
                 i++;
             }
@@ -483,7 +609,8 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
 
     @Override
     public void onBindViewHolder(final ContactListFragment.ContactItemViewHolder holder, final int position) {
-        final VCard vcard = vCards.get(position);
+        final int verifiedPosition = holder.getAdapterPosition();
+        final VCard vcard = vCards.get(verifiedPosition);
 
         if (vcard != null) {
 
@@ -492,15 +619,8 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
             } else {
                 holder.getName().setChecked(false);
             }
-            // name
-            StructuredName name = vcard.getStructuredName();
-            if (name != null) {
-                String first = (name.getGiven() == null) ? "" : name.getGiven() + " ";
-                String last = (name.getFamily() == null) ? "" : name.getFamily();
-                holder.getName().setText(first + last);
-            } else {
-                holder.getName().setText("");
-            }
+
+            holder.getName().setText(getDisplayName(vcard));
 
             // photo
             if (vcard.getPhotos().size() > 0) {
@@ -531,15 +651,15 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
                     holder.getName().setChecked(!holder.getName().isChecked());
 
                     if (holder.getName().isChecked()) {
-                        if (!checkedVCards.contains(position)) {
-                            checkedVCards.add(position);
+                        if (!checkedVCards.contains(verifiedPosition)) {
+                            checkedVCards.add(verifiedPosition);
                         }
                         if (checkedVCards.size() == 1) {
                             EventBus.getDefault().post(new VCardToggleEvent(true));
                         }
                     } else {
-                        if (checkedVCards.contains(position)) {
-                            checkedVCards.remove(position);
+                        if (checkedVCards.contains(verifiedPosition)) {
+                            checkedVCards.remove(verifiedPosition);
                         }
 
                         if (checkedVCards.size() == 0) {
@@ -572,4 +692,5 @@ class ContactListAdapter extends RecyclerView.Adapter<ContactListFragment.Contac
 
         notifyDataSetChanged();
     }
+
 }

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

@@ -1,4 +1,4 @@
-/**
+/*
  * Nextcloud Android client application
  *
  * @author Mario Danic
@@ -24,6 +24,7 @@ import android.Manifest;
 import android.accounts.Account;
 import android.app.DatePickerDialog;
 import android.content.DialogInterface;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -44,7 +45,10 @@ import com.evernote.android.job.JobRequest;
 import com.evernote.android.job.util.support.PersistableBundleCompat;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
+import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.services.ContactsBackupJob;
 import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
 import com.owncloud.android.ui.fragment.FileFragment;
@@ -167,21 +171,48 @@ public class ContactsBackupFragment extends FileFragment implements DatePickerDi
             }
         }
 
-        final ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
+        ContactsPreferenceActivity contactsPreferenceActivity = (ContactsPreferenceActivity) getActivity();
 
-        String backupFolderString = getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
-        OCFile backupFolder = contactsPreferenceActivity.getStorageManager().getFileByPath(backupFolderString);
+        String backupFolderPath = getResources().getString(R.string.contacts_backup_folder) + OCFile.PATH_SEPARATOR;
+        refreshBackupFolder(backupFolderPath, contactsPreferenceActivity);
+    }
 
-        Vector<OCFile> backupFiles = contactsPreferenceActivity.getStorageManager().getFolderContent(backupFolder,
-                false);
+    private void refreshBackupFolder(final String backupFolderPath,
+                                     final ContactsPreferenceActivity contactsPreferenceActivity) {
+        AsyncTask<String, Integer, Boolean> task = new AsyncTask<String, Integer, Boolean>() {
+            @Override
+            protected Boolean doInBackground(String... path) {
+                FileDataStorageManager storageManager = new FileDataStorageManager(account,
+                        getActivity().getContentResolver());
 
-        if (backupFiles == null || backupFiles.size() == 0) {
-            contactsRestoreHeader.setVisibility(View.GONE);
-            contactsDatePickerBtn.setVisibility(View.GONE);
-        } else {
-            contactsRestoreHeader.setVisibility(View.VISIBLE);
-            contactsDatePickerBtn.setVisibility(View.VISIBLE);
-        }
+                OCFile folder = storageManager.getFileByPath(path[0]);
+                RefreshFolderOperation operation = new RefreshFolderOperation(folder, System.currentTimeMillis(),
+                        false, false, false, storageManager, account, getContext());
+
+                RemoteOperationResult result = operation.execute(account, getContext());
+                return result.isSuccess();
+            }
+
+            @Override
+            protected void onPostExecute(Boolean result) {
+                if (result) {
+                    OCFile backupFolder = contactsPreferenceActivity.getStorageManager().getFileByPath(backupFolderPath);
+
+                    Vector<OCFile> backupFiles = contactsPreferenceActivity.getStorageManager()
+                            .getFolderContent(backupFolder, false);
+
+                    if (backupFiles == null || backupFiles.size() == 0) {
+                        contactsRestoreHeader.setVisibility(View.GONE);
+                        contactsDatePickerBtn.setVisibility(View.GONE);
+                    } else {
+                        contactsRestoreHeader.setVisibility(View.VISIBLE);
+                        contactsDatePickerBtn.setVisibility(View.VISIBLE);
+                    }
+                }
+            }
+        };
+
+        task.execute(backupFolderPath);
     }
 
 

+ 13 - 0
src/main/java/com/owncloud/android/ui/interfaces/ActivityListInterface.java

@@ -0,0 +1,13 @@
+package com.owncloud.android.ui.interfaces;
+
+import com.owncloud.android.lib.resources.activities.models.RichObject;
+
+/**
+ * Created by alejandro on 12/05/17.
+ */
+
+public interface ActivityListInterface {
+
+    void onActivityClicked(RichObject richObject);
+
+}

+ 28 - 14
src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java

@@ -40,6 +40,7 @@ import com.owncloud.android.R;
 import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.VirtualFolderType;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader;
@@ -72,6 +73,8 @@ public class PreviewImageActivity extends FileActivity implements
     private static final String KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER";
     private static final String KEY_SYSTEM_VISIBLE = "TRUE";
 
+    public static final String EXTRA_VIRTUAL_TYPE = "EXTRA_VIRTUAL_TYPE";
+
     private ExtendedViewPager mViewPager;
     private PreviewImagePagerAdapter mPreviewImagePagerAdapter;
     private int mSavedPosition = 0;
@@ -128,25 +131,36 @@ public class PreviewImageActivity extends FileActivity implements
     }
 
     private void initViewPager() {
-        // get parent from path
-        String parentPath = getFile().getRemotePath().substring(0,
-                getFile().getRemotePath().lastIndexOf(getFile().getFileName()));
-        OCFile parentFolder = getStorageManager().getFileByPath(parentPath);
-        if (parentFolder == null) {
-            // should not be necessary
-            parentFolder = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
-        }
+        // virtual folder
+        if (getIntent().getSerializableExtra(EXTRA_VIRTUAL_TYPE) != null) {
+            VirtualFolderType type = (VirtualFolderType) getIntent().getSerializableExtra(EXTRA_VIRTUAL_TYPE);
 
-        mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(),
-                parentFolder, getAccount(), getStorageManager(), MainApp.isOnlyOnDevice());
+            mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(),
+                    type, getAccount(), getStorageManager());
+        } else {
+            // get parent from path
+            String parentPath = getFile().getRemotePath().substring(0,
+                    getFile().getRemotePath().lastIndexOf(getFile().getFileName()));
+            OCFile parentFolder = getStorageManager().getFileByPath(parentPath);
+
+            if (parentFolder == null) {
+                // should not be necessary
+                parentFolder = getStorageManager().getFileByPath(OCFile.ROOT_PATH);
+            }
+
+            mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(),
+                    parentFolder, getAccount(), getStorageManager(), MainApp.isOnlyOnDevice());
+        }
 
         mViewPager = (ExtendedViewPager) findViewById(R.id.fragmentPager);
-        int position = mHasSavedPosition ? mSavedPosition :
-                mPreviewImagePagerAdapter.getFilePosition(getFile());
+
+        int position = mHasSavedPosition ? mSavedPosition : mPreviewImagePagerAdapter.getFilePosition(getFile());
         position = (position >= 0) ? position : 0;
-        mViewPager.setAdapter(mPreviewImagePagerAdapter); 
+
+        mViewPager.setAdapter(mPreviewImagePagerAdapter);
         mViewPager.setOnPageChangeListener(this);
         mViewPager.setCurrentItem(position);
+
         if (position == 0 && !getFile().isDown()) {
             // this is necessary because mViewPager.setCurrentItem(0) just after setting the
             // adapter does not result in a call to #onPageSelected(0)
@@ -446,7 +460,7 @@ public class PreviewImageActivity extends FileActivity implements
         super.onAccountSet(stateWasRecovered);
         if (getAccount() != null) {
             OCFile file = getFile();
-            /// Validate handled file  (first image to preview)
+            /// Validate handled file (first image to preview)
             if (file == null) {
                 throw new IllegalStateException("Instanced with a NULL OCFile");
             }

+ 40 - 2
src/main/java/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java

@@ -27,6 +27,7 @@ import android.view.ViewGroup;
 
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.VirtualFolderType;
 import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.utils.FileStorageUtils;
 
@@ -87,7 +88,44 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter {
         //mFragmentManager = fragmentManager;
         mCachedFragments = new HashMap<Integer, FileFragment>();
     }
-    
+
+    /**
+     * Constructor.
+     *
+     * @param fragmentManager {@link FragmentManager} instance that will handle
+     *                        the {@link Fragment}s provided by the adapter.
+     * @param type            Type of virtual folder, e.g. favorite or photos
+     * @param storageManager  Bridge to database.
+     */
+    public PreviewImagePagerAdapter(FragmentManager fragmentManager, VirtualFolderType type,
+                                    Account account, FileDataStorageManager storageManager) {
+        super(fragmentManager);
+
+        if (fragmentManager == null) {
+            throw new IllegalArgumentException("NULL FragmentManager instance");
+        }
+        if (type == null) {
+            throw new IllegalArgumentException("NULL parent folder");
+        }
+        if (storageManager == null) {
+            throw new IllegalArgumentException("NULL storage manager");
+        }
+
+        mAccount = account;
+        mStorageManager = storageManager;
+        mImageFiles = mStorageManager.getVirtualFolderContent(type, true);
+
+        if (type == VirtualFolderType.PHOTOS) {
+            mImageFiles = FileStorageUtils.sortOcFolderDescDateModified(mImageFiles);
+        }
+
+        mObsoleteFragments = new HashSet<Object>();
+        mObsoletePositions = new HashSet<Integer>();
+        mDownloadErrors = new HashSet<Integer>();
+        //mFragmentManager = fragmentManager;
+        mCachedFragments = new HashMap<Integer, FileFragment>();
+    }
+
     /**
      * Returns the image files handled by the adapter.
      * 
@@ -195,7 +233,7 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter {
     public void resetZoom() {
         Iterator<FileFragment> entries = mCachedFragments.values().iterator();
         while (entries.hasNext()) {
-        FileFragment fileFragment = (FileFragment) entries.next();
+        FileFragment fileFragment = entries.next();
             if (fileFragment instanceof PreviewImageFragment) {
                 ((PreviewImageFragment) fileFragment).getImageView().resetZoom();
             }

+ 14 - 16
src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -106,6 +106,7 @@ public class PreviewMediaFragment extends FileFragment implements
     private MediaServiceConnection mMediaServiceConnection = null;
     private VideoHelper mVideoHelper;
     private boolean mAutoplay;
+    private static boolean mOnResume = false;
     public boolean mPrepared;
 
     private static final String TAG = PreviewMediaFragment.class.getSimpleName();
@@ -220,6 +221,7 @@ public class PreviewMediaFragment extends FileFragment implements
      */
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
+        mOnResume = true;
         super.onActivityCreated(savedInstanceState);
         Log_OC.v(TAG, "onActivityCreated");
 
@@ -234,14 +236,11 @@ public class PreviewMediaFragment extends FileFragment implements
             if (!file.isDown()) {
                 throw new IllegalStateException("There is no local file to preview");
             }
-
-        }
-        else {
-            file = (OCFile) savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE);
+        } else {
+            file = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE);
             setFile(file);
             mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT);
-            mSavedPlaybackPosition =
-                    savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION);
+            mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION);
             mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING);
 
         }
@@ -251,15 +250,12 @@ public class PreviewMediaFragment extends FileFragment implements
                 mVideoPreview.setVisibility(View.VISIBLE);
                 mImagePreview.setVisibility(View.GONE);
                 prepareVideo();
-
-            }
-            else {
+            } else {
                 mVideoPreview.setVisibility(View.GONE);
                 mImagePreview.setVisibility(View.VISIBLE);
                 extractAndSetCoverArt(file);
             }
         }
-
     }
 
     /**
@@ -307,11 +303,8 @@ public class PreviewMediaFragment extends FileFragment implements
         }
         else {
             if (mMediaServiceBinder != null) {
-                outState.putInt(
-                        PreviewMediaFragment.EXTRA_PLAY_POSITION,
-                        mMediaServiceBinder.getCurrentPosition());
-                outState.putBoolean(
-                        PreviewMediaFragment.EXTRA_PLAYING, mMediaServiceBinder.isPlaying());
+                outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION, mMediaServiceBinder.getCurrentPosition());
+                outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING, mMediaServiceBinder.isPlaying());
             }
         }
     }
@@ -565,6 +558,8 @@ public class PreviewMediaFragment extends FileFragment implements
     @Override
     public void onResume() {
         super.onResume();
+        mOnResume = !mOnResume;
+
         if (getActivity() != null) {
             AnalyticsUtils.setCurrentScreenName(getActivity(), SCREEN_NAME, TAG);
         }
@@ -585,6 +580,7 @@ public class PreviewMediaFragment extends FileFragment implements
         if (mMediaServiceConnection != null) {
             Log_OC.d(TAG, "Unbinding from MediaService ...");
             if (mMediaServiceBinder != null && mMediaController != null) {
+                mMediaController.stopMediaPlayerMessages();
                 mMediaServiceBinder.unregisterMediaController(mMediaController);
             }
             getActivity().unbindService(mMediaServiceConnection);
@@ -638,7 +634,7 @@ public class PreviewMediaFragment extends FileFragment implements
 
     private void playAudio() {
         OCFile file = getFile();
-        if (!mMediaServiceBinder.isPlaying(file)) {
+        if (!mMediaServiceBinder.isPlaying(file) && !mOnResume) {
             Log_OC.d(TAG, "starting playback of " + file.getStoragePath());
             mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition);
 
@@ -649,6 +645,8 @@ public class PreviewMediaFragment extends FileFragment implements
                 mMediaController.updatePausePlay();
             }
         }
+
+        mOnResume = false;
     }
 
 

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

@@ -38,11 +38,15 @@ import android.graphics.drawable.PictureDrawable;
 import android.net.Uri;
 import android.os.Build;
 import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
+import android.support.annotation.DrawableRes;
 import android.support.annotation.NonNull;
 import android.support.design.widget.BottomNavigationView;
 import android.support.design.widget.Snackbar;
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.content.ContextCompat;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
 import android.text.Spannable;
 import android.text.SpannableStringBuilder;
 import android.text.format.DateUtils;
@@ -63,6 +67,7 @@ import com.bumptech.glide.request.target.SimpleTarget;
 import com.caverock.androidsvg.SVG;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
@@ -588,6 +593,14 @@ public class DisplayUtils {
 
         Menu menu = view.getMenu();
 
+        Account account = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
+        boolean searchSupported = AccountUtils.hasSearchSupport(account);
+
+        if (!searchSupported) {
+            menu.removeItem(R.id.nav_bar_favorites);
+            menu.removeItem(R.id.nav_bar_photos);
+        }
+
         if (resources.getBoolean(R.bool.use_home)) {
             menu.findItem(R.id.nav_bar_files).setTitle(resources.
                     getString(R.string.drawer_item_home));
@@ -677,4 +690,11 @@ public class DisplayUtils {
         return text.toString();
     }
 
+    public static Drawable tintDrawable(@DrawableRes int id, @ColorRes int color) {
+        Drawable drawable = ResourcesCompat.getDrawable(MainApp.getAppContext().getResources(), id, null);
+        drawable = DrawableCompat.wrap(drawable);
+        DrawableCompat.setTint(drawable, MainApp.getAppContext().getResources().getColor(color));
+        return drawable;
+    }
+
 }

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

@@ -1,23 +1,24 @@
 /**
  * ownCloud Android client application
- *
+ * <p>
  * Copyright (C) 2016 ownCloud Inc.
- *
+ * <p>
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2,
  * as published by the Free Software Foundation.
- *
+ * <p>
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- *
+ * <p>
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
 package com.owncloud.android.utils;
 
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.webkit.MimeTypeMap;
 
@@ -94,16 +95,25 @@ public class MimeTypeUtil {
      * @param isSharedViaLink flag if the folder is publicly shared via link
      * @return Identifier of an image resource.
      */
-    public static int getFolderTypeIconId(boolean isSharedViaUsers, boolean isSharedViaLink) {
+    public static Drawable getFolderTypeIcon(boolean isSharedViaUsers, boolean isSharedViaLink) {
+        int drawableId;
+
         if (isSharedViaLink) {
-            return R.drawable.folder_public;
+            drawableId = R.drawable.folder_public;
         } else if (isSharedViaUsers) {
-            return R.drawable.shared_with_me_folder;
+            drawableId = R.drawable.shared_with_me_folder;
+        } else {
+            drawableId = R.drawable.ic_menu_archive;
         }
 
-        return R.drawable.ic_menu_archive;
+        return DisplayUtils.tintDrawable(drawableId, R.color.primary);
+    }
+
+    public static Drawable getDefaultFolderIcon() {
+        return getFolderTypeIcon(false, false);
     }
 
+
     /**
      * Returns a single MIME type of all the possible, by inspection of the file extension, and taking
      * into account the MIME types known by ownCloud first.

+ 36 - 0
src/main/java/com/owncloud/android/utils/glide/CustomGlideStreamLoader.java

@@ -0,0 +1,36 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Alejandro Bautista
+ * Copyright (C) 2017 Alejandro Bautista
+ *
+ * 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.glide;
+
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.stream.StreamModelLoader;
+
+import java.io.InputStream;
+
+/**
+ * Custom Model for OwnCloudClient
+ */
+
+public class CustomGlideStreamLoader implements StreamModelLoader<String> {
+    @Override
+    public DataFetcher<InputStream> getResourceFetcher(String url, int width, int height) {
+        return new HttpStreamFetcher(url);
+    }
+}

+ 101 - 0
src/main/java/com/owncloud/android/utils/glide/HttpStreamFetcher.java

@@ -0,0 +1,101 @@
+/**
+ * Nextcloud Android client application
+ *
+ * @author Alejandro Bautista
+ * Copyright (C) 2017 Alejandro Bautista
+ *
+ * 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.glide;
+
+import android.accounts.Account;
+
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.owncloud.android.MainApp;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.status.OwnCloudVersion;
+
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+
+import java.io.InputStream;
+
+/**
+ * Fetcher with OwnCloudClient
+ */
+
+public class HttpStreamFetcher implements DataFetcher<InputStream> {
+
+    private static final String TAG = HttpStreamFetcher.class.getName();
+    private final String mURL;
+
+    public HttpStreamFetcher(String url) {
+        this.mURL = url;
+
+    }
+
+    @Override
+    public InputStream loadData(Priority priority) throws Exception {
+
+        Account mAccount = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
+        OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount,
+                MainApp.getAppContext());
+        OwnCloudClient mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+                getClientFor(ocAccount, MainApp.getAppContext());
+
+        OwnCloudVersion serverOCVersion = AccountUtils.getServerVersion(mAccount);
+        if (mClient != null && serverOCVersion != null) {
+            if (serverOCVersion.supportsRemoteThumbnails()) {
+                GetMethod get = null;
+                try {
+                    get = new GetMethod(mURL);
+                    get.setRequestHeader("Cookie",
+                            "nc_sameSiteCookielax=true;nc_sameSiteCookiestrict=true");
+                    int status = mClient.executeMethod(get);
+                    if (status == HttpStatus.SC_OK) {
+                        InputStream inputStream = get.getResponseBodyAsStream();
+                        return inputStream;
+                    } else {
+                        mClient.exhaustResponse(get.getResponseBodyAsStream());
+                    }
+                } catch (Exception e) {
+                    Log_OC.d(TAG, e.getMessage(), e);
+                }
+            } else {
+                Log_OC.d(TAG, "Server too old");
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void cleanup() {
+        Log_OC.i(TAG,"Cleanup");
+    }
+
+    @Override
+    public String getId() {
+        return mURL;
+    }
+
+    @Override
+    public void cancel() {
+        Log_OC.i(TAG,"Cancel");
+    }
+}

+ 53 - 0
src/main/java/third_parties/ezvcard_android/ContactOperations.java

@@ -141,6 +141,59 @@ public class ContactOperations {
         context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
     }
 
+    public void updateContact(VCard vcard, Long key) throws RemoteException, OperationApplicationException {
+
+        List<NonEmptyContentValues> contentValues = new ArrayList<NonEmptyContentValues>();
+        convertName(contentValues, vcard);
+        convertNickname(contentValues, vcard);
+        convertPhones(contentValues, vcard);
+        convertEmails(contentValues, vcard);
+        convertAddresses(contentValues, vcard);
+        convertIms(contentValues, vcard);
+
+        // handle Android Custom fields..This is only valid for Android generated Vcards. As the Android would
+        // generate NickName, ContactEvents other than Birthday and RelationShip with this "X-ANDROID-CUSTOM" name
+        convertCustomFields(contentValues, vcard);
+
+        // handle Iphone kinda of group properties. which are grouped together.
+        convertGroupedProperties(contentValues, vcard);
+
+        convertBirthdays(contentValues, vcard);
+
+        convertWebsites(contentValues, vcard);
+        convertNotes(contentValues, vcard);
+        convertPhotos(contentValues, vcard);
+        convertOrganization(contentValues, vcard);
+
+        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(contentValues.size());
+        ContentValues cv = account.getContentValues();
+        //ContactsContract.RawContact.CONTENT_URI needed to add account, backReference is also not needed
+        long contactID = key;
+        ContentProviderOperation operation;
+
+        for (NonEmptyContentValues values : contentValues) {
+            cv = values.getContentValues();
+            if (cv.size() == 0) {
+                continue;
+            }
+
+            String mimeType = cv.getAsString("mimetype");
+            cv.remove("mimetype");
+            //@formatter:off
+            operation =
+                    ContentProviderOperation.newUpdate(ContactsContract.Data.CONTENT_URI)
+                            .withSelection(ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ? ", new String[]{"" + contactID, "" + mimeType})
+                            .withValues(cv)
+                            .build();
+            //@formatter:on
+            operations.add(operation);
+        }
+
+        // Executing all the insert operations as a single database transaction
+        context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations);
+
+    }
+
     private void convertName(List<NonEmptyContentValues> contentValues, VCard vcard) {
         NonEmptyContentValues values = new NonEmptyContentValues(ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
 

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


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


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


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


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


+ 0 - 0
src/main/res/drawable-hdpi/ic_folder_open.png → src/main/res/drawable-hdpi/nav_all_files.png


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


+ 0 - 0
src/main/res/drawable-hdpi/ic_star.png → src/main/res/drawable-hdpi/nav_favorites.png


+ 0 - 0
src/main/res/drawable-hdpi/ic_cloud_upload.png → src/main/res/drawable-hdpi/nav_folder_sync.png


+ 0 - 0
src/main/res/drawable-hdpi/logout.png → src/main/res/drawable-hdpi/nav_logout.png


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


+ 0 - 0
src/main/res/drawable-hdpi/ic_action_available_offline.png → src/main/res/drawable-hdpi/nav_on_device.png


+ 0 - 0
src/main/res/drawable-hdpi/ic_participate.png → src/main/res/drawable-hdpi/nav_participate.png


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


+ 0 - 0
src/main/res/drawable-hdpi/ic_recent.png → src/main/res/drawable-hdpi/nav_recently.png


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


+ 0 - 0
src/main/res/drawable-hdpi/ic_shared.png → src/main/res/drawable-hdpi/nav_shared.png


+ 0 - 0
src/main/res/drawable-hdpi/ic_uploads.png → src/main/res/drawable-hdpi/nav_uploads.png


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


BIN
src/main/res/drawable-mdpi/ic_checkbox_marked.png


BIN
src/main/res/drawable-mdpi/ic_list_empty_create_folder.png


BIN
src/main/res/drawable-mdpi/ic_list_empty_folder.png


BIN
src/main/res/drawable-mdpi/ic_list_empty_upload.png


BIN
src/main/res/drawable-mdpi/nav_activity.png


BIN
src/main/res/drawable-mdpi/nav_all_files.png


BIN
src/main/res/drawable-mdpi/nav_contacts.png


+ 0 - 0
src/main/res/drawable-mdpi/ic_star.png → src/main/res/drawable-mdpi/nav_favorites.png


+ 0 - 0
src/main/res/drawable-mdpi/ic_cloud_upload.png → src/main/res/drawable-mdpi/nav_folder_sync.png


+ 0 - 0
src/main/res/drawable-mdpi/logout.png → src/main/res/drawable-mdpi/nav_logout.png


BIN
src/main/res/drawable-mdpi/nav_notifications.png


+ 0 - 0
src/main/res/drawable-mdpi/ic_action_available_offline.png → src/main/res/drawable-mdpi/nav_on_device.png


+ 0 - 0
src/main/res/drawable-mdpi/ic_participate.png → src/main/res/drawable-mdpi/nav_participate.png


BIN
src/main/res/drawable-mdpi/nav_photos.png


+ 0 - 0
src/main/res/drawable-mdpi/ic_recent.png → src/main/res/drawable-mdpi/nav_recently.png


BIN
src/main/res/drawable-mdpi/nav_settings.png


+ 0 - 0
src/main/res/drawable-mdpi/ic_shared.png → src/main/res/drawable-mdpi/nav_shared.png


+ 0 - 0
src/main/res/drawable-mdpi/ic_uploads.png → src/main/res/drawable-mdpi/nav_uploads.png


BIN
src/main/res/drawable-mdpi/nav_videos.png


BIN
src/main/res/drawable-xhdpi/ic_checkbox_marked.png


BIN
src/main/res/drawable-xhdpi/ic_list_empty_create_folder.png


BIN
src/main/res/drawable-xhdpi/ic_list_empty_folder.png


BIN
src/main/res/drawable-xhdpi/ic_list_empty_upload.png


BIN
src/main/res/drawable-xhdpi/nav_activity.png


+ 0 - 0
src/main/res/drawable-xhdpi/ic_folder_open.png → src/main/res/drawable-xhdpi/nav_all_files.png


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно