Browse Source

Merge remote-tracking branch 'remotes/origin/master' into sharingPart2

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 4 years ago
parent
commit
c8a49d99ba
43 changed files with 668 additions and 405 deletions
  1. 11 0
      CHANGELOG.md
  2. 12 11
      build.gradle
  3. 1 1
      lint.xml
  4. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.fragment.AvatarIT_showAvatars.png
  5. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showDetailsActivities.png
  6. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showDetailsActivitiesError.png
  7. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showDetailsActivitiesNone.png
  8. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showRichWorkspace.png
  9. BIN
      screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showSharedFiles.png
  10. 2 0
      scripts/uploadReport.sh
  11. 62 0
      src/androidTest/java/com/owncloud/android/ui/fragment/AvatarIT.kt
  12. 59 0
      src/androidTest/java/com/owncloud/android/ui/fragment/AvatarTestFragment.kt
  13. 107 3
      src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt
  14. 0 231
      src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentIT.kt
  15. 128 2
      src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt
  16. 1 9
      src/debug/java/com/nextcloud/client/TestActivity.kt
  17. 27 0
      src/debug/res/layout/avatar_fragment.xml
  18. 1 1
      src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
  19. 37 11
      src/main/java/com/owncloud/android/files/services/FileUploader.java
  20. 31 8
      src/main/java/com/owncloud/android/operations/UploadFileOperation.java
  21. 17 51
      src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java
  22. 2 1
      src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.java
  23. 77 52
      src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  24. 6 0
      src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  25. 17 12
      src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
  26. 7 2
      src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  27. 9 3
      src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  28. 1 0
      src/main/res/values-ca/strings.xml
  29. 1 0
      src/main/res/values-el/strings.xml
  30. 1 0
      src/main/res/values-es-rPY/strings.xml
  31. 1 0
      src/main/res/values-eu/strings.xml
  32. 2 1
      src/main/res/values-fr/strings.xml
  33. 1 1
      src/main/res/values-gl/strings.xml
  34. 25 0
      src/main/res/values-land/bools.xml
  35. 6 0
      src/main/res/values-nb-rNO/strings.xml
  36. 1 0
      src/main/res/values-nl/strings.xml
  37. 1 1
      src/main/res/values-pl/strings.xml
  38. 1 1
      src/main/res/values-pt-rBR/strings.xml
  39. 2 0
      src/main/res/values-ru/strings.xml
  40. 1 0
      src/main/res/values-sk-rSK/strings.xml
  41. 1 0
      src/main/res/values-sv/strings.xml
  42. 7 0
      src/main/res/values-zh-rTW/strings.xml
  43. 2 3
      src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java

+ 11 - 0
CHANGELOG.md

@@ -1,3 +1,14 @@
+## 3.13.1 (September, 15, 2020)
+
+- bugfix release
+- auto upload obey metered network
+- fix adding account via qrCode
+- fix deleting password on share
+- fix conflict handling on auto upload
+- lots more
+
+For a full list, please see https://github.com/nextcloud/android/milestone/51
+
 ## 3.13.0 (August, 18, 2020)
 
 - new UI overhaul @Shagequi @JorisBodin

+ 12 - 11
build.gradle

@@ -8,7 +8,7 @@ import com.github.spotbugs.snom.SpotBugsTask
 // its root folder, another one for the app module in subfolder of root.
 
 buildscript {
-    ext.kotlin_version = '1.4.0'
+    ext.kotlin_version = '1.4.10'
     repositories {
         google()
         jcenter()
@@ -25,7 +25,7 @@ buildscript {
         classpath('com.hiya:jacoco-android:0.2')
         classpath 'gradle.plugin.com.github.spotbugs.snom:spotbugs-gradle-plugin:4.5.0'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-        classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.12.0"
+        classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.13.1"
         classpath "commons-httpclient:commons-httpclient:3.1@jar" // remove after entire switch to lib v2
         classpath 'com.karumi:shot:4.4.0'
     }
@@ -59,12 +59,13 @@ configurations {
 
 ext {
     jacocoVersion = "0.8.2"
-    daggerVersion = "2.28.3"
-    markwonVersion =  "4.5.1"
+    daggerVersion = "2.29.1"
+    markwonVersion =  "4.6.0"
     prismVersion = "2.0.0"
     butterknifeVersion = "10.2.3"
     androidLibraryVersion = "sharingPart2-SNAPSHOT"
-    mockitoVersion = "3.5.10"
+    mockitoVersion = "3.5.11"
+    byteBuddyVersion = "1.10.15"
     espressoVersion = "3.3.0"
 
     travisBuild = System.getenv("TRAVIS") == "true"
@@ -294,14 +295,14 @@ dependencies {
     implementation 'com.jakewharton:disklrucache:2.0.2'
     implementation 'androidx.appcompat:appcompat:1.2.0-rc01'
     implementation 'androidx.cardview:cardview:1.0.0'
-    implementation 'androidx.exifinterface:exifinterface:1.2.0'
+    implementation 'androidx.exifinterface:exifinterface:1.3.0'
     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
     implementation "androidx.work:work-runtime:2.4.0"
     implementation "androidx.work:work-runtime-ktx:2.4.0"
     implementation "androidx.fragment:fragment:1.2.5"
     implementation 'com.github.albfernandez:juniversalchardet:2.0.3' // need this version for Android <7
     compileOnly 'com.google.code.findbugs:annotations:2.0.1'
-    implementation 'commons-io:commons-io:2.7'
+    implementation 'commons-io:commons-io:2.8.0'
     implementation "com.jakewharton:butterknife:$butterknifeVersion"
     kapt "com.jakewharton:butterknife-compiler:$butterknifeVersion"
     implementation 'org.greenrobot:eventbus:3.2.0'
@@ -335,8 +336,8 @@ dependencies {
     kapt "com.google.dagger:dagger-compiler:$daggerVersion"
     kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
 
-    ktlint "com.pinterest:ktlint:0.38.1"
-    implementation 'org.conscrypt:conscrypt-android:2.5.0'
+    ktlint "com.pinterest:ktlint:0.39.0"
+    implementation 'org.conscrypt:conscrypt-android:2.5.1'
 
     // Shimmer animation
     implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0'
@@ -385,8 +386,8 @@ dependencies {
     androidTestImplementation("org.mockito:mockito-android:$mockitoVersion") {
         exclude group: "net.bytebuddy", module: "byte-buddy-android"
     }
-    androidTestImplementation 'net.bytebuddy:byte-buddy:1.10.14'
-    androidTestImplementation "net.bytebuddy:byte-buddy-android:1.10.14"
+    androidTestImplementation "net.bytebuddy:byte-buddy:$byteBuddyVersion"
+    androidTestImplementation "net.bytebuddy:byte-buddy-android:$byteBuddyVersion"
     androidTestImplementation "io.mockk:mockk-android:1.10.0"
     androidTestImplementation 'androidx.arch.core:core-testing:2.0.1'
 

+ 1 - 1
lint.xml

@@ -60,7 +60,7 @@
         <ignore path="**/fragment-1.2.5/**/lint.jar" />
         <ignore path="**/work-runtime-2.**/**/lint.jar" />
         <ignore path="**/jetified-butterknife-runtime-10.**/**/lint.jar" />
-        <ignore path="**/jetified-dagger-lint-aar-2.28.**/**/lint.jar" />
+        <ignore path="**/jetified-dagger-lint-aar-2.29.**/**/lint.jar" />
         <ignore path="**/jetified-annotation-experimental-1.**/**/lint.jar" />
     </issue>
 </lint>

BIN
screenshots/gplay/debug/com.owncloud.android.ui.fragment.AvatarIT_showAvatars.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showDetailsActivities.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showDetailsActivitiesError.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.fragment.FileDetailFragmentStaticServerIT_showDetailsActivitiesNone.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showRichWorkspace.png


BIN
screenshots/gplay/debug/com.owncloud.android.ui.fragment.OCFileListFragmentStaticServerIT_showSharedFiles.png


+ 2 - 0
scripts/uploadReport.sh

@@ -87,4 +87,6 @@ else
         BRANCH_TYPE=$BRANCH-$TYPE
         upload "build/reports/shot/verification"
     fi
+
+    exit 1 # always fail
 fi

+ 62 - 0
src/androidTest/java/com/owncloud/android/ui/fragment/AvatarIT.kt

@@ -0,0 +1,62 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.ui.fragment
+
+import androidx.test.espresso.intent.rule.IntentsTestRule
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import com.nextcloud.client.TestActivity
+import com.owncloud.android.AbstractIT
+import com.owncloud.android.R
+import com.owncloud.android.utils.DisplayUtils
+import com.owncloud.android.utils.ScreenshotTest
+import org.junit.Rule
+import org.junit.Test
+
+class AvatarIT : AbstractIT() {
+    @get:Rule
+    val testActivityRule = IntentsTestRule(TestActivity::class.java, true, false)
+
+    @Test
+    @ScreenshotTest
+    fun showAvatars() {
+        val avatarRadius = targetContext.resources.getDimension(R.dimen.list_item_avatar_icon_radius)
+        val width = DisplayUtils.convertDpToPixel(40f, targetContext)
+        val sut = testActivityRule.launchActivity(null)
+        val fragment = AvatarTestFragment()
+
+        sut.addFragment(fragment)
+
+        runOnUiThread {
+            fragment.addAvatar("Admin", avatarRadius, width, targetContext)
+            fragment.addAvatar("Test Server Admin", avatarRadius, width, targetContext)
+            fragment.addAvatar("Cormier Paulette", avatarRadius, width, targetContext)
+            fragment.addAvatar("winston brent", avatarRadius, width, targetContext)
+            fragment.addAvatar("Baker James Lorena", avatarRadius, width, targetContext)
+            fragment.addAvatar("Baker  James   Lorena", avatarRadius, width, targetContext)
+            fragment.addAvatar("email@server.com", avatarRadius, width, targetContext)
+        }
+
+        waitForIdleSync()
+        screenshot(sut)
+    }
+}

+ 59 - 0
src/androidTest/java/com/owncloud/android/ui/fragment/AvatarTestFragment.kt

@@ -0,0 +1,59 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2020 Tobias Kaminsky
+ * Copyright (C) 2020 Nextcloud GmbH
+ *
+ * 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.
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.ui.fragment
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.RelativeLayout
+import androidx.fragment.app.Fragment
+import com.owncloud.android.R
+import com.owncloud.android.ui.TextDrawable
+
+internal class AvatarTestFragment : Fragment() {
+    lateinit var list: LinearLayout
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        val view: View = inflater.inflate(R.layout.avatar_fragment, null)
+
+        list = view.findViewById(R.id.avatar_list)
+
+        return view
+    }
+
+    fun addAvatar(name: String, avatarRadius: Float, width: Int, targetContext: Context) {
+        val margin = 10
+        val imageView = ImageView(targetContext)
+        imageView.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadius))
+
+        val layoutParams: RelativeLayout.LayoutParams = RelativeLayout.LayoutParams(width, width)
+        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT)
+        layoutParams.setMargins(margin, margin, margin, margin)
+        imageView.layoutParams = layoutParams
+
+        list.addView(imageView)
+    }
+}

+ 107 - 3
src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt

@@ -25,10 +25,16 @@ package com.owncloud.android.ui.fragment
 import androidx.test.espresso.intent.rule.IntentsTestRule
 import com.nextcloud.client.TestActivity
 import com.owncloud.android.AbstractIT
+import com.owncloud.android.R
 import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.resources.activities.model.Activity
+import com.owncloud.android.lib.resources.activities.model.RichElement
+import com.owncloud.android.lib.resources.activities.model.RichObject
+import com.owncloud.android.lib.resources.activities.models.PreviewObject
 import com.owncloud.android.utils.ScreenshotTest
 import org.junit.Rule
 import org.junit.Test
+import java.util.GregorianCalendar
 
 class FileDetailFragmentStaticServerIT : AbstractIT() {
     @get:Rule
@@ -58,12 +64,110 @@ class FileDetailFragmentStaticServerIT : AbstractIT() {
 
     @Test
     @ScreenshotTest
+    @Suppress("MagicNumber")
     fun showDetailsActivities() {
-        val sut = testActivityRule.launchActivity(null)
-        sut.addFragment(FileDetailFragment.newInstance(file, user, 0))
+        val activity = testActivityRule.launchActivity(null)
+        val sut = FileDetailFragment.newInstance(file, user, 0)
+        activity.addFragment(sut)
 
         waitForIdleSync()
-        screenshot(sut)
+
+        val date = GregorianCalendar()
+        date.set(2005, 4, 17, 10, 35, 30) // random date
+
+        val richObjectList: ArrayList<RichObject> = ArrayList()
+        richObjectList.add(RichObject("file", "abc", "text.txt", "/text.txt", "link", "tag"))
+        richObjectList.add(RichObject("file", "1", "text.txt", "/text.txt", "link", "tag"))
+
+        val previewObjectList1: ArrayList<PreviewObject> = ArrayList()
+        previewObjectList1.add(PreviewObject(1, "source", "link", true, "text/plain", "view"))
+
+        val richObjectList2: ArrayList<RichObject> = ArrayList()
+        richObjectList2.add(RichObject("user", "admin", "Admin", "", "", ""))
+
+        val activities = mutableListOf(
+            Activity(
+                1,
+                date.time,
+                date.time,
+                "files",
+                "file_changed",
+                "user1",
+                "user1",
+                "You changed text.txt",
+                "",
+                "icon",
+                "link",
+                "files",
+                "1",
+                "/text.txt",
+                previewObjectList1,
+                RichElement("", richObjectList)
+            ),
+            Activity(
+                2,
+                date.time,
+                date.time,
+                "comments",
+                "comments",
+                "user1",
+                "user1",
+                "admin commented",
+                "test2",
+                "icon",
+                "link",
+                "files",
+                "1",
+                "/text.txt",
+                null,
+                null
+            )
+        )
+
+        activity.runOnUiThread {
+            sut.fileDetailActivitiesFragment.populateList(activities as List<Any>?, true)
+        }
+
+        screenshot(activity)
+    }
+
+    @Test
+    @ScreenshotTest
+    fun showDetailsActivitiesNone() {
+        val activity = testActivityRule.launchActivity(null)
+        val sut = FileDetailFragment.newInstance(file, user, 0)
+        activity.addFragment(sut)
+
+        waitForIdleSync()
+
+        activity.runOnUiThread {
+            sut.fileDetailActivitiesFragment.populateList(emptyList(), true)
+        }
+
+        shortSleep()
+
+        screenshot(activity)
+    }
+
+    @Test
+    @ScreenshotTest
+    fun showDetailsActivitiesError() {
+        val activity = testActivityRule.launchActivity(null)
+        val sut = FileDetailFragment.newInstance(file, user, 0)
+        activity.addFragment(sut)
+
+        waitForIdleSync()
+
+        activity.runOnUiThread {
+            sut
+                .fileDetailActivitiesFragment
+                .setErrorContent(targetContext.resources.getString(R.string.file_detail_activity_error))
+        }
+
+        shortSleep()
+        shortSleep()
+
+        screenshot(activity)
     }
 
     @Test

+ 0 - 231
src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentIT.kt

@@ -23,32 +23,15 @@
 package com.owncloud.android.ui.fragment
 
 import android.Manifest
-import androidx.test.core.app.ActivityScenario
 import androidx.test.espresso.intent.rule.IntentsTestRule
-import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
-import com.nextcloud.client.account.UserAccountManagerImpl
 import com.nextcloud.client.device.BatteryStatus
 import com.nextcloud.client.device.PowerManagementService
 import com.nextcloud.client.network.Connectivity
 import com.nextcloud.client.network.ConnectivityService
-import com.nextcloud.client.preferences.AppPreferences
-import com.nextcloud.client.preferences.AppPreferencesImpl
-import com.nextcloud.client.preferences.DarkMode
 import com.owncloud.android.AbstractOnServerIT
-import com.owncloud.android.MainApp
 import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.datamodel.UploadsStorageManager
-import com.owncloud.android.db.OCUpload
-import com.owncloud.android.files.services.FileUploader
-import com.owncloud.android.lib.resources.shares.CreateShareRemoteOperation
-import com.owncloud.android.lib.resources.shares.OCShare
-import com.owncloud.android.lib.resources.shares.ShareType
-import com.owncloud.android.operations.CreateFolderOperation
-import com.owncloud.android.operations.RefreshFolderOperation
-import com.owncloud.android.operations.UploadFileOperation
 import com.owncloud.android.ui.activity.FileDisplayActivity
-import junit.framework.TestCase
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Rule
@@ -88,220 +71,6 @@ class OCFileListFragmentIT : AbstractOnServerIT() {
             get() = BatteryStatus()
     }
 
-    @Test
-    // @ScreenshotTest // todo run without real server
-    fun showRichWorkspace() {
-        assertTrue(CreateFolderOperation("/test/", user, targetContext).execute(client, storageManager).isSuccess)
-
-        val ocUpload = OCUpload(
-            getDummyFile("/nonEmpty.txt").absolutePath,
-            "/test/Readme.md",
-            account.name
-        )
-        val newUpload = UploadFileOperation(
-            UploadsStorageManager(UserAccountManagerImpl.fromContext(targetContext), targetContext.contentResolver),
-            connectivityServiceMock,
-            powerManagementServiceMock,
-            user,
-            null,
-            ocUpload,
-            FileUploader.NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
-            targetContext,
-            false,
-            false
-        )
-
-        newUpload.addRenameUploadListener {}
-        newUpload.setRemoteFolderToBeCreated()
-
-        assertTrue(newUpload.execute(client, storageManager).isSuccess)
-
-        assertTrue(
-            RefreshFolderOperation(
-                storageManager.getFileByPath("/test/"),
-                System.currentTimeMillis() / SECOND_IN_MILLIS,
-                false,
-                true,
-                storageManager,
-                account,
-                targetContext
-            )
-                .execute(client)
-                .isSuccess
-        )
-
-        val sut = ActivityScenario.launch(FileDisplayActivity::class.java)
-        shortSleep()
-        sut.onActivity { activity -> activity.onBrowsedDownTo(storageManager.getFileByPath("/test/")) }
-
-        shortSleep()
-        shortSleep()
-
-//        sut.onActivity { activity ->
-//            Screenshot.snapActivity(activity).setName("richworkspaces_light").record()
-//        }
-
-        val preferences: AppPreferences = AppPreferencesImpl.fromContext(targetContext)
-        preferences.darkThemeMode = DarkMode.DARK
-
-        sut.onActivity { activity ->
-            MainApp.setAppTheme(DarkMode.DARK)
-        }
-
-        shortSleep()
-        sut.onActivity { activity -> activity.onBackPressed() }
-
-        shortSleep()
-
-        sut.recreate()
-
-        sut.onActivity { activity -> activity.onBrowsedDownTo(storageManager.getFileByPath("/test/")) }
-
-        shortSleep()
-        shortSleep()
-
-//        sut.onActivity { activity ->
-//            Screenshot.snapActivity(activity).setName("richworkspaces_dark").record()
-//        }
-
-        // switch back to light mode
-        preferences.darkThemeMode = DarkMode.LIGHT
-        sut.onActivity { MainApp.setAppTheme(DarkMode.LIGHT) }
-
-        shortSleep()
-        sut.onActivity { activity -> activity.onBackPressed() }
-
-        sut.recreate()
-    }
-
-    @Test
-    // @ScreenshotTest // todo run without real server
-    fun createAndShowShareToUser() {
-        val path = "/shareToAdmin/"
-        TestCase.assertTrue(
-            CreateFolderOperation(path, user, targetContext)
-                .execute(client, storageManager)
-                .isSuccess
-        )
-
-        // share folder to user "admin"
-        TestCase.assertTrue(
-            CreateShareRemoteOperation(
-                path,
-                ShareType.USER,
-                "admin",
-                false,
-                "",
-                OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER
-            )
-                .execute(client).isSuccess
-        )
-
-        val sut: FileDisplayActivity = activityRule.launchActivity(null)
-        sut.startSyncFolderOperation(storageManager.getFileByPath("/"), true)
-
-        shortSleep()
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-//        Screenshot.snapActivity(sut).record()
-    }
-
-    @Test
-    // @ScreenshotTest // todo run without real server
-    fun createAndShowShareToGroup() {
-        val path = "/shareToGroup/"
-        TestCase.assertTrue(
-            CreateFolderOperation(path, user, targetContext)
-                .execute(client, storageManager)
-                .isSuccess
-        )
-
-        // share folder to group
-        assertTrue(
-            CreateShareRemoteOperation(
-                "/shareToGroup/",
-                ShareType.GROUP,
-                "users",
-                false,
-                "",
-                OCShare.NO_PERMISSION
-            )
-                .execute(client)
-                .isSuccess
-        )
-
-        val sut: FileDisplayActivity = activityRule.launchActivity(null)
-        sut.startSyncFolderOperation(storageManager.getFileByPath("/"), true)
-
-        shortSleep()
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-//        Screenshot.snapActivity(sut).record()
-    }
-
-//    @Test
-//    @ScreenshotTest
-//    fun createAndShowShareToCircle() {
-//        val path = "/shareToCircle/"
-//        TestCase.assertTrue(CreateFolderOperation(path, account, targetContext)
-//            .execute(client, storageManager)
-//            .isSuccess)
-//
-//        // share folder to circle
-//        // get circleId
-//        val searchResult = GetShareesRemoteOperation("publicCircle", 1, RESULT_PER_PAGE).execute(client)
-//        assertTrue(searchResult.logMessage, searchResult.isSuccess)
-//
-//        val resultJson: JSONObject = searchResult.data[0] as JSONObject
-//        val circleId: String = resultJson.getJSONObject("value").getString("shareWith")
-//
-//        assertTrue(CreateShareRemoteOperation("/shareToCircle/",
-//            ShareType.CIRCLE,
-//            circleId,
-//            false,
-//            "",
-//            OCShare.DEFAULT_PERMISSION)
-//            .execute(client).isSuccess)
-//
-//        val sut: FileDisplayActivity = activityRule.launchActivity(null)
-//        sut.startSyncFolderOperation(storageManager.getFileByPath("/"), true)
-//
-//        shortSleep()
-//        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-//        Screenshot.snapActivity(sut).record()
-//    }
-
-    @Test
-    // @ScreenshotTest // todo run without real server
-    fun createAndShowShareViaLink() {
-        val path = "/shareViaLink/"
-        TestCase.assertTrue(
-            CreateFolderOperation(path, user, targetContext)
-                .execute(client, storageManager)
-                .isSuccess
-        )
-
-        // share folder via public link
-        TestCase.assertTrue(
-            CreateShareRemoteOperation(
-                "/shareViaLink/",
-                ShareType.PUBLIC_LINK,
-                "",
-                true,
-                "",
-                OCShare.READ_PERMISSION_FLAG
-            )
-                .execute(client)
-                .isSuccess
-        )
-
-        val sut: FileDisplayActivity = activityRule.launchActivity(null)
-        sut.startSyncFolderOperation(storageManager.getFileByPath("/"), true)
-
-        shortSleep()
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
-//        Screenshot.snapActivity(sut).record()
-    }
-
     @Test
     @SuppressWarnings("MagicNumber")
     fun testEnoughSpaceWithoutLocalFile() {

+ 128 - 2
src/androidTest/java/com/owncloud/android/ui/fragment/OCFileListFragmentStaticServerIT.kt

@@ -25,11 +25,13 @@ package com.owncloud.android.ui.fragment
 import android.Manifest
 import androidx.test.espresso.intent.rule.IntentsTestRule
 import androidx.test.rule.GrantPermissionRule
-import com.facebook.testing.screenshot.Screenshot
 import com.nextcloud.client.TestActivity
 import com.owncloud.android.AbstractIT
 import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.resources.shares.ShareType
+import com.owncloud.android.lib.resources.shares.ShareeUser
 import com.owncloud.android.utils.ScreenshotTest
+import org.junit.After
 import org.junit.Rule
 import org.junit.Test
 
@@ -72,6 +74,130 @@ class OCFileListFragmentStaticServerIT : AbstractIT() {
 
         waitForIdleSync()
 
-        Screenshot.snapActivity(sut).record()
+        screenshot(sut)
+    }
+
+    @Test
+    @ScreenshotTest
+    fun showSharedFiles() {
+        val sut = testActivityRule.launchActivity(null)
+        val fragment = OCFileListFragment()
+
+        val groupShare = OCFile("/sharedToGroup.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedWithSharee = true
+            sharees = listOf(ShareeUser("group", "Group", ShareType.GROUP))
+        }
+        sut.storageManager.saveFile(groupShare)
+
+        val roomShare = OCFile("/sharedToRoom.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedWithSharee = true
+            sharees = listOf(ShareeUser("Conversation", "Meeting", ShareType.ROOM))
+        }
+        sut.storageManager.saveFile(roomShare)
+
+        val circleShare = OCFile("/sharedToCircle.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedWithSharee = true
+            sharees = listOf(ShareeUser("circle", "Circle (Public circle)", ShareType.CIRCLE))
+        }
+        sut.storageManager.saveFile(circleShare)
+
+        val userShare = OCFile("/sharedToUser.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedWithSharee = true
+            sharees = listOf(ShareeUser("Admin", "Server Admin", ShareType.USER))
+        }
+        sut.storageManager.saveFile(userShare)
+
+        val federatedUserShare = OCFile("/sharedToFederatedUser.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedWithSharee = true
+            sharees = listOf(
+                ShareeUser("admin@remote.nextcloud.com", "admin@remote.nextcloud.com (remote)", ShareType.FEDERATED)
+            )
+        }
+        sut.storageManager.saveFile(federatedUserShare)
+
+        val emailShare = OCFile("/sharedToEmail.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedWithSharee = true
+            sharees = listOf(ShareeUser("test@remote.nextcloud.com", "test@remote.nextcloud.com", ShareType.EMAIL))
+        }
+        sut.storageManager.saveFile(emailShare)
+
+        val publicLink = OCFile("/publicLink.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedViaLink = true
+        }
+        sut.storageManager.saveFile(publicLink)
+
+        val noShare = OCFile("/notShared.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+        }
+        sut.storageManager.saveFile(noShare)
+
+        val usersShare = OCFile("/sharedToUser.jpg").apply {
+            parentId = sut.storageManager.getFileByEncryptedRemotePath("/").fileId
+            isSharedWithSharee = true
+            sharees = listOf(
+                ShareeUser("Admin", "Server Admin", ShareType.USER),
+                ShareeUser("User", "User", ShareType.USER),
+                ShareeUser("Christine", "Christine Scott", ShareType.USER)
+            )
+        }
+        sut.storageManager.saveFile(usersShare)
+
+        sut.addFragment(fragment)
+
+        shortSleep()
+
+        val root = sut.storageManager.getFileByEncryptedRemotePath("/")
+
+        sut.runOnUiThread {
+            fragment.listDirectory(root, false, false)
+            fragment.adapter.setShowShareAvatar(true)
+        }
+
+        waitForIdleSync()
+        shortSleep()
+        shortSleep()
+        shortSleep()
+
+        screenshot(sut)
+    }
+
+    @After
+    fun after() {
+        fileDataStorageManager.deleteAllFiles()
+    }
+
+    @Test
+    @ScreenshotTest
+    @Suppress("MagicNumber")
+    fun showRichWorkspace() {
+        val sut = testActivityRule.launchActivity(null)
+        val fragment = OCFileListFragment()
+
+        val folder = OCFile("/test/")
+        folder.setFolder()
+        sut.storageManager.saveFile(folder)
+
+        val textFile = OCFile("/test/Readme.md", "00000001")
+        textFile.mimeType = "text/markdown"
+        textFile.fileLength = 1024000
+        textFile.modificationTimestamp = 1188206955000
+        textFile.parentId = sut.storageManager.getFileByEncryptedRemotePath("/test/").fileId
+        textFile.storagePath = getFile("java.md").absolutePath
+        sut.storageManager.saveFile(textFile)
+
+        sut.addFragment(fragment)
+        val testFolder: OCFile = sut.storageManager.getFileByEncryptedRemotePath("/test/")
+        testFolder.richWorkspace = getFile("java.md").readText()
+
+        sut.runOnUiThread { fragment.listDirectory(testFolder, false, false) }
+
+        screenshot(sut)
     }
 }

+ 1 - 9
src/debug/java/com/nextcloud/TestActivity.kt → src/debug/java/com/nextcloud/client/TestActivity.kt

@@ -32,8 +32,6 @@ import com.owncloud.android.datamodel.FileDataStorageManager
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.files.services.FileDownloader
 import com.owncloud.android.files.services.FileUploader
-import com.owncloud.android.lib.resources.status.OCCapability
-import com.owncloud.android.lib.resources.status.OwnCloudVersion
 import com.owncloud.android.services.OperationsService
 import com.owncloud.android.ui.activity.FileActivity
 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener
@@ -81,7 +79,7 @@ class TestActivity :
     }
 
     override fun showSortListGroup(show: Boolean) {
-        TODO("Not yet implemented")
+        // not needed
     }
 
     override fun showDetails(file: OCFile?) {
@@ -103,12 +101,6 @@ class TestActivity :
     override fun getStorageManager(): FileDataStorageManager {
         if (!this::storage.isInitialized) {
             storage = FileDataStorageManager(account, contentResolver)
-
-            val capability = OCCapability().apply {
-                versionMayor = OwnCloudVersion.nextcloud_15.getMajorVersionNumber()
-            }
-
-            storage.saveCapabilities(capability)
         }
 
         return storage

+ 27 - 0
src/debug/res/layout/avatar_fragment.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Nextcloud Android client application
+
+  @author Tobias Kaminsky
+  Copyright (C) 2020 Tobias Kaminsky
+  Copyright (C) 2020 Nextcloud GmbH
+
+  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.
+
+  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 <https://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/avatar_list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+</LinearLayout>

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

@@ -1712,7 +1712,7 @@ public class FileDataStorageManager {
         if (path != null) {
             ContentValues values = new ContentValues();
             ContentResolver contentResolver = MainApp.getAppContext().getContentResolver();
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && !TextUtils.isEmpty(path)) {
                 if (file != null) {
                     values.put(MediaStore.Images.Media.MIME_TYPE, file.getMimeType());
                     values.put(MediaStore.Images.Media.TITLE, file.getFileName());

+ 37 - 11
src/main/java/com/owncloud/android/files/services/FileUploader.java

@@ -167,6 +167,11 @@ public class FileUploader extends Service
 
     public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
 
+    /**
+     * Set to true if the HTTP library should disable automatic retries of uploads.
+     */
+    public static final String KEY_DISABLE_RETRIES = "DISABLE_RETRIES";
+
     public static final int LOCAL_BEHAVIOUR_COPY = 0;
     public static final int LOCAL_BEHAVIOUR_MOVE = 1;
     public static final int LOCAL_BEHAVIOUR_FORGET = 2;
@@ -407,6 +412,7 @@ public class FileUploader extends Service
         int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
         boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false);
         int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER);
+        boolean disableRetries = intent.getBooleanExtra(KEY_DISABLE_RETRIES, true);
         try {
             for (OCFile file : files) {
                 startNewUpload(
@@ -418,8 +424,9 @@ public class FileUploader extends Service
                     localAction,
                     isCreateRemoteFolder,
                     createdBy,
-                    file
-                );
+                    file,
+                    disableRetries
+                              );
             }
         } catch (IllegalArgumentException e) {
             Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
@@ -446,8 +453,9 @@ public class FileUploader extends Service
         int localAction,
         boolean isCreateRemoteFolder,
         int createdBy,
-        OCFile file
-    ) {
+        OCFile file,
+        boolean disableRetries
+                               ) {
         OCUpload ocUpload = new OCUpload(file, user.toPlatformAccount());
         ocUpload.setFileSize(file.getFileLength());
         ocUpload.setNameCollisionPolicy(nameCollisionPolicy);
@@ -469,7 +477,8 @@ public class FileUploader extends Service
             localAction,
             this,
             onWifiOnly,
-            whileChargingOnly
+            whileChargingOnly,
+            disableRetries
         );
         newUpload.setCreatedBy(createdBy);
         if (isCreateRemoteFolder) {
@@ -517,7 +526,8 @@ public class FileUploader extends Service
             upload.getLocalAction(),
             this,
             onWifiOnly,
-            whileChargingOnly
+            whileChargingOnly,
+            true
         );
 
         newUpload.addDataTransferProgressListener(this);
@@ -957,7 +967,7 @@ public class FileUploader extends Service
     }
 
     /**
-     * Upload and overwrite an already uploaded file
+     * Upload and overwrite an already uploaded file with disabled retries
      */
     public static void uploadUpdateFile(
         Context context,
@@ -965,8 +975,22 @@ public class FileUploader extends Service
         OCFile existingFile,
         Integer behaviour,
         NameCollisionPolicy nameCollisionPolicy
-    ) {
-        uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy);
+                                       ) {
+        uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy, true);
+    }
+
+    /**
+     * Upload and overwrite an already uploaded file
+     */
+    public static void uploadUpdateFile(
+        Context context,
+        Account account,
+        OCFile existingFile,
+        Integer behaviour,
+        NameCollisionPolicy nameCollisionPolicy,
+        boolean disableRetries
+                                       ) {
+        uploadUpdateFile(context, account, new OCFile[]{existingFile}, behaviour, nameCollisionPolicy, disableRetries);
     }
 
     /**
@@ -977,14 +1001,16 @@ public class FileUploader extends Service
         Account account,
         OCFile[] existingFiles,
         Integer behaviour,
-        NameCollisionPolicy nameCollisionPolicy
-    ) {
+        NameCollisionPolicy nameCollisionPolicy,
+        boolean disableRetries
+                                       ) {
         Intent intent = new Intent(context, FileUploader.class);
 
         intent.putExtra(FileUploader.KEY_ACCOUNT, account);
         intent.putExtra(FileUploader.KEY_FILE, existingFiles);
         intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
         intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
+        intent.putExtra(FileUploader.KEY_DISABLE_RETRIES, disableRetries);
 
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             context.startForegroundService(intent);

+ 31 - 8
src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -121,6 +121,7 @@ public class UploadFileOperation extends SyncOperation {
     private boolean mOnWifiOnly;
     private boolean mWhileChargingOnly;
     private boolean mIgnoringPowerSaveMode;
+    private final boolean mDisableRetries;
 
     private boolean mWasRenamed;
     private long mOCUploadId;
@@ -184,15 +185,30 @@ public class UploadFileOperation extends SyncOperation {
                                int localBehaviour,
                                Context context,
                                boolean onWifiOnly,
-                               boolean whileChargingOnly
-    ) {
+                               boolean whileChargingOnly) {
+        this(uploadsStorageManager, connectivityService, powerManagementService, user, file, upload,
+             nameCollisionPolicy, localBehaviour, context, onWifiOnly, whileChargingOnly, true);
+    }
+
+    public UploadFileOperation(UploadsStorageManager uploadsStorageManager,
+                               ConnectivityService connectivityService,
+                               PowerManagementService powerManagementService,
+                               User user,
+                               OCFile file,
+                               OCUpload upload,
+                               FileUploader.NameCollisionPolicy nameCollisionPolicy,
+                               int localBehaviour,
+                               Context context,
+                               boolean onWifiOnly,
+                               boolean whileChargingOnly,
+                               boolean disableRetries) {
         if (upload == null) {
             throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation");
         }
         if (TextUtils.isEmpty(upload.getLocalPath())) {
             throw new IllegalArgumentException(
-                    "Illegal file in UploadFileOperation; storage path invalid: "
-                            + upload.getLocalPath());
+                "Illegal file in UploadFileOperation; storage path invalid: "
+                    + upload.getLocalPath());
         }
 
         this.uploadsStorageManager = uploadsStorageManager;
@@ -222,6 +238,7 @@ public class UploadFileOperation extends SyncOperation {
         // Ignore power save mode only if user explicitly created this upload
         mIgnoringPowerSaveMode = mCreatedBy == CREATED_BY_USER;
         mFolderUnlockToken = upload.getFolderUnlockToken();
+        mDisableRetries = disableRetries;
     }
 
     public boolean isWifiRequired() {
@@ -561,14 +578,18 @@ public class UploadFileOperation extends SyncOperation {
                                                                         mFile.getEtagInConflict(),
                                                                         timeStamp,
                                                                         onWifiConnection,
-                                                                        token);
+                                                                        token,
+                                                                        mDisableRetries
+                );
             } else {
                 mUploadOperation = new UploadFileRemoteOperation(encryptedTempFile.getAbsolutePath(),
                                                                  mFile.getParentRemotePath() + encryptedFileName,
                                                                  mFile.getMimeType(),
                                                                  mFile.getEtagInConflict(),
                                                                  timeStamp,
-                                                                 token);
+                                                                 token,
+                                                                 mDisableRetries
+                );
             }
 
             for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {
@@ -796,13 +817,15 @@ public class UploadFileOperation extends SyncOperation {
                                                                         mFile.getMimeType(),
                                                                         mFile.getEtagInConflict(),
                                                                         timeStamp,
-                                                                        onWifiConnection);
+                                                                        onWifiConnection,
+                                                                        mDisableRetries);
             } else {
                 mUploadOperation = new UploadFileRemoteOperation(mFile.getStoragePath(),
                                                                  mFile.getRemotePath(),
                                                                  mFile.getMimeType(),
                                                                  mFile.getEtagInConflict(),
-                                                                 timeStamp);
+                                                                 timeStamp,
+                                                                 mDisableRetries);
             }
 
             for (OnDatatransferProgressListener mDataTransferListener : mDataTransferListeners) {

+ 17 - 51
src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -153,15 +153,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         throws FileNotFoundException {
         Log.d(TAG, "queryChildDocuments(), id=" + parentDocumentId);
 
-        Context context = getContext();
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null");
-        }
-
+        Context context = getNonNullContext();
         Document parentFolder = toDocument(parentDocumentId);
-
         FileDataStorageManager storageManager = parentFolder.getStorageManager();
-
         final FileCursor resultCursor = new FileCursor(projection);
 
         for (OCFile file : storageManager.getFolderContent(parentFolder.getFile(), false)) {
@@ -171,7 +165,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         boolean isLoading = false;
         if (parentFolder.isExpired()) {
             final ReloadFolderDocumentTask task = new ReloadFolderDocumentTask(parentFolder, result ->
-                getContext().getContentResolver().notifyChange(toNotifyUri(parentFolder), null, false));
+                context.getContentResolver().notifyChange(toNotifyUri(parentFolder), null, false));
             task.executeOnExecutor(executor);
             resultCursor.setLoadingTask(task);
             isLoading = true;
@@ -180,7 +174,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         final Bundle extra = new Bundle();
         extra.putBoolean(DocumentsContract.EXTRA_LOADING, isLoading);
         resultCursor.setExtras(extra);
-        resultCursor.setNotificationUri(getContext().getContentResolver(), toNotifyUri(parentFolder));
+        resultCursor.setNotificationUri(context.getContentResolver(), toNotifyUri(parentFolder));
         return resultCursor;
     }
 
@@ -191,7 +185,6 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         Log.d(TAG, "openDocument(), id=" + documentId);
 
         Document document = toDocument(documentId);
-
         Context context = getNonNullContext();
 
         OCFile ocFile = document.getFile();
@@ -249,8 +242,8 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                             account,
                             ocFile,
                             LOCAL_BEHAVIOUR_MOVE,
-                            NameCollisionPolicy.OVERWRITE
-                                                     );
+                            NameCollisionPolicy.OVERWRITE,
+                            false);
                     } else { // error, no upload needed
                         Log_OC.e(TAG, "File was closed with an error: " + ocFile.getFileName(), error);
                     }
@@ -306,16 +299,10 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             throws FileNotFoundException {
         Log.d(TAG, "openDocumentThumbnail(), id=" + documentId);
 
-        Context context = getContext();
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
-
         OCFile file = toDocument(documentId).getFile();
 
         boolean exists = ThumbnailsCacheManager.containsBitmap(ThumbnailsCacheManager.PREFIX_THUMBNAIL
                                                                    + file.getRemoteId());
-
         if (!exists) {
             ThumbnailsCacheManager.generateThumbnailFromOCFile(file);
         }
@@ -329,11 +316,6 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
         Log.d(TAG, "renameDocument(), id=" + documentId);
 
-        Context context = getContext();
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
-
         Document document = toDocument(documentId);
 
         RemoteOperationResult result = new RenameFileOperation(document.getRemotePath(), displayName)
@@ -345,6 +327,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                                                 result.getException());
         }
 
+        Context context = getNonNullContext();
         context.getContentResolver().notifyChange(toNotifyUri(document.getParent()), null, false);
 
         return null;
@@ -354,11 +337,6 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     public String copyDocument(String sourceDocumentId, String targetParentDocumentId) throws FileNotFoundException {
         Log.d(TAG, "copyDocument(), id=" + sourceDocumentId);
 
-        Context context = getContext();
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
-
         Document document = toDocument(sourceDocumentId);
 
         FileDataStorageManager storageManager = document.getStorageManager();
@@ -373,6 +351,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                                                 + " to " + targetParentDocumentId);
         }
 
+        Context context = getNonNullContext();
         Account account = document.getAccount();
 
         RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(), System.currentTimeMillis(),
@@ -403,11 +382,6 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         throws FileNotFoundException {
         Log.d(TAG, "moveDocument(), id=" + sourceDocumentId);
 
-        Context context = getContext();
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
-
         Document document = toDocument(sourceDocumentId);
         Document targetFolder = toDocument(targetParentDocumentId);
 
@@ -422,8 +396,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
         Document sourceFolder = toDocument(sourceParentDocumentId);
 
-        getContext().getContentResolver().notifyChange(toNotifyUri(sourceFolder), null, false);
-        getContext().getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
+        Context context = getNonNullContext();
+        context.getContentResolver().notifyChange(toNotifyUri(sourceFolder), null, false);
+        context.getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
 
         return sourceDocumentId;
     }
@@ -461,18 +436,13 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
     private String createFolder(Document targetFolder, String displayName) throws FileNotFoundException {
 
-        Context context = getContext();
-
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
-
+        Context context = getNonNullContext();
         String newDirPath = targetFolder.getRemotePath() + displayName + PATH_SEPARATOR;
         FileDataStorageManager storageManager = targetFolder.getStorageManager();
 
         RemoteOperationResult result = new CreateFolderOperation(newDirPath,
                                                                  accountManager.getUser(),
-                                                                 getContext())
+                                                                 context)
             .execute(targetFolder.getClient(), storageManager);
 
         if (!result.isSuccess()) {
@@ -499,10 +469,6 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     }
 
     private String createFile(Document targetFolder, String displayName, String mimeType) throws FileNotFoundException {
-        Context context = getContext();
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
 
         Account account = targetFolder.getAccount();
 
@@ -537,7 +503,8 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                                                                      newFilePath,
                                                                      mimeType,
                                                                      "",
-                                                                     String.valueOf(System.currentTimeMillis() / 1000))
+                                                                     String.valueOf(System.currentTimeMillis() / 1000),
+                                                                     false)
             .execute(client);
 
         if (!result.isSuccess()) {
@@ -545,6 +512,8 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             throw new FileNotFoundException("Failed to upload document with path " + newFilePath);
         }
 
+        Context context = getNonNullContext();
+
         RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(),
                                                                         System.currentTimeMillis(),
                                                                         false,
@@ -576,10 +545,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     public void deleteDocument(String documentId) throws FileNotFoundException {
         Log.d(TAG, "deleteDocument(), id=" + documentId);
 
-        Context context = getContext();
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
+        Context context = getNonNullContext();
 
         Document document = toDocument(documentId);
         // get parent here, because it is not available anymore after the document was deleted

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

@@ -100,7 +100,8 @@ public class ExternalSiteWebView extends FileActivity {
         // allow debugging (when building the debug version); see details in
         // https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
-            (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+            ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 ||
+                getResources().getBoolean(R.bool.is_beta))) {
             Log_OC.d(this, "Enable debug for webView");
             WebView.setWebContentsDebuggingEnabled(true);
         }

+ 77 - 52
src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -542,15 +542,31 @@ public class FileDisplayActivity extends FileActivity
         return secondFragment;
     }
 
-
     /**
-     * Replaces the second fragment managed by the activity with the received as
-     * a parameter.
+     * Replaces the first fragment managed by the activity with the received as a parameter.
      *
+     * @param fragment New Fragment to set.
+     */
+    private void setLeftFragment(Fragment fragment) {
+        if (searchView != null) {
+            searchView.post(() -> searchView.setQuery(searchQuery, true));
+        }
+        setDrawerIndicatorEnabled(false);
+        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+        transaction.addToBackStack(null);
+        transaction.replace(R.id.left_fragment_container, fragment, TAG_LIST_OF_FILES);
+        transaction.commit();
+    }
+
+
+    /**
+     * Replaces the second fragment managed by the activity with the received as a parameter.
+     * <p>
      * Assumes never will be more than two fragments managed at the same time.
      *
      * @param fragment New second Fragment to set.
      */
+    @Deprecated // in future no dual pane
     private void setSecondFragment(Fragment fragment) {
         if (searchView != null) {
             searchView.post(new Runnable() {
@@ -594,11 +610,16 @@ public class FileDisplayActivity extends FileActivity
         }
     }
 
+    public @androidx.annotation.Nullable
+    Fragment getLeftFragment() {
+        return getSupportFragmentManager().findFragmentByTag(FileDisplayActivity.TAG_LIST_OF_FILES);
+    }
 
     public @androidx.annotation.Nullable
+    @Deprecated
     OCFileListFragment getListOfFilesFragment() {
         Fragment listOfFiles = getSupportFragmentManager().findFragmentByTag(
-                FileDisplayActivity.TAG_LIST_OF_FILES);
+            FileDisplayActivity.TAG_LIST_OF_FILES);
         if (listOfFiles != null) {
             return (OCFileListFragment) listOfFiles;
         }
@@ -785,10 +806,13 @@ public class FileDisplayActivity extends FileActivity
                 OCFile currentDir = getCurrentDir();
                 if (isDrawerOpen()) {
                     closeDrawer();
-                } else if ((currentDir != null && currentDir.getParentId() != 0) ||
-                        (second != null && second.getFile() != null) || isSearchOpen()) {
+                } else if (
+                    currentDir != null && currentDir.getParentId() != 0 ||
+                        second != null && second.getFile() != null ||
+                        isSearchOpen() ||
+                        second == null
+                ) {
                     onBackPressed();
-
                 } else {
                     openDrawer();
                 }
@@ -1022,45 +1046,45 @@ public class FileDisplayActivity extends FileActivity
         boolean isDrawerOpen = isDrawerOpen();
         boolean isSearchOpen = isSearchOpen();
 
-        OCFileListFragment listOfFiles = getListOfFilesFragment();
+        Fragment leftFragment = getLeftFragment();
 
-        if (isSearchOpen && searchView != null) {
-            searchView.setQuery("", true);
-            searchView.onActionViewCollapsed();
-            searchView.clearFocus();
+        if (leftFragment instanceof OCFileListFragment) {
+            OCFileListFragment listOfFiles = (OCFileListFragment) leftFragment;
 
-            // Remove the list to the original state
-            if (listOfFiles != null) {
-                listOfFiles.performSearch("", true);
-            }
+            if (isSearchOpen && searchView != null) {
+                searchView.setQuery("", true);
+                searchView.onActionViewCollapsed();
+                searchView.clearFocus();
 
-            hideSearchView(getCurrentDir());
+                // Remove the list to the original state
+                listOfFiles.performSearch("", true);
 
-            setDrawerIndicatorEnabled(isDrawerIndicatorAvailable());
-        } else if (isDrawerOpen) {
-            // close drawer first
-            super.onBackPressed();
-        } else {
-            // all closed
+                hideSearchView(getCurrentDir());
 
-            listOfFiles = getListOfFilesFragment();
-            if (mDualPane || getSecondFragment() == null) {
-                OCFile currentDir = getCurrentDir();
-                if (currentDir == null || currentDir.getParentId() == FileDataStorageManager.ROOT_PARENT_ID) {
-                    finish();
-                    return;
-                }
-                if (listOfFiles != null) {  // should never be null, indeed
+                setDrawerIndicatorEnabled(isDrawerIndicatorAvailable());
+            } else if (isDrawerOpen) {
+                // close drawer first
+                super.onBackPressed();
+            } else {
+                // all closed
+                if (mDualPane || getSecondFragment() == null) {
+                    OCFile currentDir = getCurrentDir();
+                    if (currentDir == null || currentDir.getParentId() == FileDataStorageManager.ROOT_PARENT_ID) {
+                        finish();
+                        return;
+                    }
                     listOfFiles.onBrowseUp();
                 }
-            }
-            if (listOfFiles != null) {  // should never be null, indeed
                 setFile(listOfFiles.getCurrentFile());
                 listOfFiles.setFabVisible(true);
                 listOfFiles.registerFabListener();
                 showSortListGroup(true);
+                cleanSecondFragment();
             }
-            cleanSecondFragment();
+        } else {
+            // pop back
+            hideSearchView(getCurrentDir());
+            super.onBackPressed();
         }
     }
 
@@ -1088,10 +1112,15 @@ public class FileDisplayActivity extends FileActivity
         Log_OC.v(TAG, "onResume() start");
         super.onResume();
         // Instead of onPostCreate, starting the loading in onResume for children fragments
-        OCFileListFragment ocFileListFragment = getListOfFilesFragment();
-        if (ocFileListFragment != null) {
-            ocFileListFragment.setLoading(mSyncInProgress);
+        Fragment leftFragment = getLeftFragment();
+
+        if (!(leftFragment instanceof OCFileListFragment)) {
+            return;
         }
+
+        OCFileListFragment ocFileListFragment = (OCFileListFragment) leftFragment;
+
+        ocFileListFragment.setLoading(mSyncInProgress);
         syncAndUpdateFolder(false);
 
         OCFile startFile = null;
@@ -1103,18 +1132,16 @@ public class FileDisplayActivity extends FileActivity
         // refresh list of files
         if (searchView != null && !TextUtils.isEmpty(searchQuery)) {
             searchView.setQuery(searchQuery, false);
-        } else if (ocFileListFragment != null && !ocFileListFragment.isSearchFragment() && startFile == null) {
+        } else if (!ocFileListFragment.isSearchFragment() && startFile == null) {
             updateListOfFilesFragment(false);
             ocFileListFragment.registerFabListener();
         } else {
-            if (ocFileListFragment != null) {
-                ocFileListFragment.listDirectory(startFile, false, false);
-            }
+            ocFileListFragment.listDirectory(startFile, false, false);
             updateActionBarTitleAndHomeButton(startFile);
         }
 
         // Listen for sync messages
-        if (ocFileListFragment != null && !ocFileListFragment.isSearchFragment()) {
+        if (!ocFileListFragment.isSearchFragment()) {
             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);
@@ -1565,6 +1592,7 @@ public class FileDisplayActivity extends FileActivity
     }
 
     @Override
+    @Deprecated // in future no dual pane
     public void updateActionBarTitleAndHomeButton(OCFile chosenFile) {
         if (chosenFile == null) {
             chosenFile = getFile();     // if no file is passed, current file decides
@@ -1576,7 +1604,6 @@ public class FileDisplayActivity extends FileActivity
         } else {
             super.updateActionBarTitleAndHomeButton(chosenFile);
         }
-
     }
 
     @Override
@@ -2181,10 +2208,9 @@ public class FileDisplayActivity extends FileActivity
             args.putString(EXTRA_SEARCH_QUERY, searchQuery);
             Fragment textPreviewFragment = Fragment.instantiate(getApplicationContext(),
                                                                 PreviewTextFileFragment.class.getName(), args);
-            setSecondFragment(textPreviewFragment);
-            updateFragmentsVisibility(true);
-            updateActionBarTitleAndHomeButton(file);
-            setFile(file);
+            setLeftFragment(textPreviewFragment);
+            binding.rightFragmentContainer.setVisibility(View.GONE);
+            super.updateActionBarTitleAndHomeButton(file);
         } else {
             Intent previewIntent = new Intent();
             previewIntent.putExtra(EXTRA_FILE, file);
@@ -2197,7 +2223,7 @@ public class FileDisplayActivity extends FileActivity
     }
 
     /**
-     * Stars rich workspace preview for a folder.
+     * Starts rich workspace preview for a folder.
      *
      * @param folder {@link OCFile} to preview its rich workspace.
      */
@@ -2208,10 +2234,9 @@ public class FileDisplayActivity extends FileActivity
         Fragment textPreviewFragment = Fragment.instantiate(getApplicationContext(),
                                                             PreviewTextStringFragment.class.getName(),
                                                             args);
-        setSecondFragment(textPreviewFragment);
-        updateFragmentsVisibility(true);
-        updateActionBarTitleAndHomeButton(folder);
-        setFile(folder);
+        setLeftFragment(textPreviewFragment);
+        binding.rightFragmentContainer.setVisibility(View.GONE);
+        super.updateActionBarTitleAndHomeButton(folder);
     }
 
     public void startContactListFragment(OCFile file) {

+ 6 - 0
src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -96,6 +96,7 @@ import java.util.Vector;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -1282,6 +1283,11 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
         gridView = bool;
     }
 
+    @VisibleForTesting
+    public void setShowShareAvatar(boolean bool) {
+        showShareAvatar = bool;
+    }
+
     static class OCFileListItemViewHolder extends OCFileListGridItemViewHolder {
         @BindView(R.id.file_size)
         public TextView fileSize;

+ 17 - 12
src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java

@@ -74,6 +74,7 @@ import java.util.List;
 import javax.inject.Inject;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
@@ -340,7 +341,6 @@ public class FileDetailActivitiesFragment extends Fragment implements
 
                 Log_OC.d(TAG, "BEFORE getRemoteActivitiesOperation.execute");
                 RemoteOperationResult result = nextcloudClient.execute(getRemoteNotificationOperation);
-                result = new RemoteOperationResult(RemoteOperationResult.ResultCode.UNHANDLED_HTTP_CODE);
 
                 ArrayList<Object> versions = null;
                 if (restoreFileVersionSupported) {
@@ -368,15 +368,6 @@ public class FileDetailActivitiesFragment extends Fragment implements
 
                     activity.runOnUiThread(() -> {
                         populateList(activitiesAndVersions, lastGiven == -1);
-                        if (adapter.getItemCount() == 0) {
-                            setEmptyContent(noResultsHeadline, noResultsMessage);
-                            list.setVisibility(View.GONE);
-                            empty.setVisibility(View.VISIBLE);
-                        } else {
-                            empty.setVisibility(View.GONE);
-                            list.setVisibility(View.VISIBLE);
-                        }
-                        isLoadingActivities = false;
                     });
                 } else {
                     Log_OC.d(TAG, result.getLogMessage());
@@ -415,8 +406,19 @@ public class FileDetailActivitiesFragment extends Fragment implements
         }).start();
     }
 
-    private void populateList(List<Object> activities, boolean clear) {
+    @VisibleForTesting
+    public void populateList(List<Object> activities, boolean clear) {
         adapter.setActivityAndVersionItems(activities, nextcloudClient, clear);
+
+        if (adapter.getItemCount() == 0) {
+            setEmptyContent(noResultsHeadline, noResultsMessage);
+            swipeListRefreshLayout.setVisibility(View.GONE);
+            swipeEmptyListRefreshLayout.setVisibility(View.VISIBLE);
+        } else {
+            swipeListRefreshLayout.setVisibility(View.VISIBLE);
+            swipeEmptyListRefreshLayout.setVisibility(View.GONE);
+        }
+        isLoadingActivities = false;
     }
 
     private void setEmptyContent(String headline, String message) {
@@ -433,7 +435,8 @@ public class FileDetailActivitiesFragment extends Fragment implements
         }
     }
 
-    private void setErrorContent(String message) {
+    @VisibleForTesting
+    public void setErrorContent(String message) {
         if (emptyContentContainer != null && emptyContentMessage != null) {
             emptyContentHeadline.setText(R.string.common_error);
             emptyContentIcon.setImageDrawable(ResourcesCompat.getDrawable(requireContext().getResources(),
@@ -444,6 +447,8 @@ public class FileDetailActivitiesFragment extends Fragment implements
             emptyContentMessage.setVisibility(View.VISIBLE);
             emptyContentProgressBar.setVisibility(View.GONE);
             emptyContentIcon.setVisibility(View.VISIBLE);
+            swipeListRefreshLayout.setVisibility(View.GONE);
+            swipeEmptyListRefreshLayout.setVisibility(View.VISIBLE);
         }
     }
 

+ 7 - 2
src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -348,9 +348,14 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
     public void onResume() {
         super.onResume();
 
-        if (toolbarActivity != null && previewLoaded) {
-            toolbarActivity.setPreviewImageVisibility(true);
+        if (toolbarActivity != null) {
+            toolbarActivity.showSortListGroup(false);
+
+            if (previewLoaded) {
+                toolbarActivity.setPreviewImageVisibility(true);
+            }
         }
+
     }
 
     @Override

+ 9 - 3
src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -329,7 +329,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
         super.onActivityCreated(savedInstanceState);
         Log_OC.i(TAG, "onActivityCreated() start");
 
-
         if (savedInstanceState != null) {
             mFile = savedInstanceState.getParcelable(KEY_FILE);
         }
@@ -389,6 +388,13 @@ public class OCFileListFragment extends ExtendedListFragment implements
         if (searchEvent != null) {
             onMessageEvent(searchEvent);
         }
+
+        FragmentActivity fragmentActivity;
+        if ((fragmentActivity = getActivity()) != null && fragmentActivity instanceof FileDisplayActivity) {
+            FileDisplayActivity fileDisplayActivity = (FileDisplayActivity) fragmentActivity;
+            fileDisplayActivity.updateActionBarTitleAndHomeButton(fileDisplayActivity.getCurrentDir());
+        }
+        listDirectory(false, false);
     }
 
     protected void prepareCurrentSearch(SearchEvent event) {
@@ -771,6 +777,8 @@ public class OCFileListFragment extends ExtendedListFragment implements
      */
     @Override
     public void onSaveInstanceState(@NonNull Bundle outState) {
+        super.onSaveInstanceState(outState);
+
         outState.putParcelable(KEY_FILE, mFile);
         if (searchFragment) {
             outState.putParcelable(KEY_CURRENT_SEARCH_TYPE, Parcels.wrap(currentSearchType));
@@ -779,8 +787,6 @@ public class OCFileListFragment extends ExtendedListFragment implements
             }
         }
         mMultiChoiceModeListener.storeStateIn(outState);
-
-        super.onSaveInstanceState(outState);
     }
 
     @Override

+ 1 - 0
src/main/res/values-ca/strings.xml

@@ -27,6 +27,7 @@
     <string name="actionbar_sort">Ordena</string>
     <string name="active_user">Usuari actiu</string>
     <string name="activities_no_results_headline">Encara no hi ha activitat</string>
+    <string name="activities_no_results_message">Encara no hi ha esdeveniments com ara addicions, canvis i comparticions.</string>
     <string name="activity_chooser_send_file_title">Envia</string>
     <string name="activity_chooser_title">Envia l\'enllaç a…</string>
     <string name="activity_icon">Activitat</string>

+ 1 - 0
src/main/res/values-el/strings.xml

@@ -787,6 +787,7 @@
     <string name="uploads_view_upload_status_virus_detected">Ανιχνεύθηκε ιός. Η μεταφόρτωση δεν μπορεί να ολοκληρωθεί!</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Αναμονή για έξοδο από τη λειτουργία εξοικονόμησης ενέργειας</string>
     <string name="uploads_view_upload_status_waiting_for_charging">Αναμονή για φόρτιση συσκευής</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Αναμονή για ασύρματο δίκτυο.</string>
     <string name="user_icon">Χρήστης</string>
     <string name="user_info_address">Διεύθυνση</string>
     <string name="user_info_email">E-mail</string>

+ 1 - 0
src/main/res/values-es-rPY/strings.xml

@@ -150,6 +150,7 @@
     <string name="drawer_item_uploads_list">Cargas</string>
     <string name="drawer_item_videos">Videos</string>
     <string name="drawer_logout">Cerrar sesión</string>
+    <string name="drawer_open">Abrir barra lateral</string>
     <string name="drawer_quota">%1$s de %2$s usados</string>
     <string name="drawer_synced_folders">Carga automática</string>
     <string name="encrypted">Establecer como encriptado</string>

+ 1 - 0
src/main/res/values-eu/strings.xml

@@ -787,6 +787,7 @@
     <string name="uploads_view_upload_status_virus_detected">Birusa detektatuta. Igoera ezin da osatu!</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Itxaron irteteko energia aurrezteko modutik</string>
     <string name="uploads_view_upload_status_waiting_for_charging">Karga itxoiten</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Mugarik ez duen Wi-Fiaren zain</string>
     <string name="user_icon">Erabiltzailea</string>
     <string name="user_info_address">Helbidea</string>
     <string name="user_info_email">E-posta</string>

+ 2 - 1
src/main/res/values-fr/strings.xml

@@ -72,7 +72,7 @@
     <string name="auto_upload_path">/EnvoiAuto</string>
     <string name="autoupload_configure">Configurer</string>
     <string name="autoupload_create_new_custom_folder">Créer une nouvelle configuration de dossier personnalisé</string>
-    <string name="autoupload_custom_folder">Configurer un dossier personnalisé</string>
+    <string name="autoupload_custom_folder">Définir un dossier personnalisé</string>
     <string name="autoupload_disable_power_save_check">Désactiver l\'économie de batterie</string>
     <string name="autoupload_hide_folder">Masquer le dossier</string>
     <string name="avatar">Avatar</string>
@@ -789,6 +789,7 @@ Attention la suppression est irréversible.</string>
     <string name="uploads_view_upload_status_virus_detected">Un virus a été détecté. L\'envoi ne peut donc pas être réalisé !</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">En attente de sortie du mode d’économie d’énergie</string>
     <string name="uploads_view_upload_status_waiting_for_charging">En attente de recharge de l\'appareil</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">En attente d\'une connexion Wifi non limitée</string>
     <string name="user_icon">Utilisateur</string>
     <string name="user_info_address">Adresse</string>
     <string name="user_info_email">Adresse e-mail</string>

+ 1 - 1
src/main/res/values-gl/strings.xml

@@ -304,7 +304,7 @@
     <string name="file_list_folder">cartafol</string>
     <string name="file_list_loading">Cargando…</string>
     <string name="file_list_no_app_for_file_type">Non foi estabelecida unha aplicación para manexar este tipo de ficheiro.</string>
-    <string name="file_list_seconds_ago">segundos atrás</string>
+    <string name="file_list_seconds_ago">hai uns segundos</string>
     <string name="file_migration_checking_destination">Comprobando o destino…</string>
     <string name="file_migration_cleaning">Limpando…</string>
     <string name="file_migration_dialog_title">Actualizando a ruta do almacenamento</string>

+ 25 - 0
src/main/res/values-land/bools.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~
+  ~ Nextcloud Android client application
+  ~
+  ~ @author Tobias Kaminsky
+  ~ Copyright (C) 2020 Tobias Kaminsky
+  ~ Copyright (C) 2020 Nextcloud GmbH
+  ~
+  ~ 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.
+  ~
+  ~ 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 <https://www.gnu.org/licenses/>.
+  -->
+
+<resources>
+    <bool name="large_land_layout">true</bool>
+</resources>

+ 6 - 0
src/main/res/values-nb-rNO/strings.xml

@@ -553,6 +553,7 @@
     <string name="screenshot_01_gridView_heading">Beskytter din data</string>
     <string name="screenshot_01_gridView_subline">produktivitet på egen tjener</string>
     <string name="screenshot_02_listView_heading">Bla gjennom og del</string>
+    <string name="screenshot_02_listView_subline">alle handlinger tilgjengelig ved fingertuppene</string>
     <string name="screenshot_03_drawer_heading">Aktivitet, delinger...</string>
     <string name="screenshot_03_drawer_subline">Alt så hurtig som mulig</string>
     <string name="screenshot_04_accounts_heading">Alle kontoene dine</string>
@@ -652,6 +653,10 @@
     <string name="storage_movies">Filmer</string>
     <string name="storage_music">Musikk</string>
     <string name="storage_pictures">Bilder</string>
+    <string name="store_full_desc">Den selvstyrte produktivitetsplattformen som du har kontroll over.\n\nFunksjoner:\n* Last opp filer til din Nextcloud server\n* Del dem med andre\n* Hold favorittfilene og mappene dine synkroniserte\n* Søk gjennom alle mappene dine på serveren\n* Automatisk opplasting av bilder og videoer tatt med enheten din\n* Hold deg oppdatert via notifikasjoner\n* Støtte for flere kontoer\n* Sikker tilgang til dataene dine med fingeravtrykk eller PIN\n* Integrasjon med DAVx5 (tidligere kjent som DAVdroid) for enkel oppsett av synkronisering av kalendre og kontakter\n* Vennligst rapporter alle problemer på https://github.com/nextcloud/android/issues og diskuter appen på https://help.nextcloud.com/c/clients/android\n\nEr Nextcloud nytt for deg? Nextcloud er en privat filsynkroniserings, -deling og kommunikasjonsserver. Det er gratis programvare, og du kan ha den på din egen server, eller betale et selskap for å gjøre det for deg. På den måten har du kontroll over bildene dine, kalenderen og kontaktdata, dokumentene og alt annet.\n\nSjekk ut Nextcloud på https://nextcloud.com</string>
+    <string name="store_full_dev_desc">Den selvstyrte produktivitetsplattformen som du har kontroll over.\nDette er den offisielle utviklingsversjonen, med et daglig utvalg av ny uprøvd funksjonalitet, som kan forårsake ustabilitet og tap av data. Appen er for brukere som er villige til å teste, og rapporterer feil hvis de skulle oppstå. Ikke bruk den til ditt produktive arbeid!\n\nBåde den offisielle utvikler versjonen og den vanlige versjonen er tilgjengelig på F-droid, og kan installeres samtidig.</string>
+    <string name="store_short_desc">Den selvstyrte produktivitetsplattformen som du har kontroll over</string>
+    <string name="store_short_dev_desc">Den selvstyrte produktivitetsplattformen som du har kontroll over (dev forhåndsvisningsversjon)</string>
     <string name="stream">Strøm med…</string>
     <string name="stream_not_possible_headline">Intern strøming ikke mulig</string>
     <string name="stream_not_possible_message">Vennligst last ned media i stedet, eller bruk ekstern app.</string>
@@ -782,6 +787,7 @@
     <string name="uploads_view_upload_status_virus_detected">Virus oppdaget. Opplasting kan ikke fullføres.</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Venter på å gå ut av strømsparingsmodus</string>
     <string name="uploads_view_upload_status_waiting_for_charging">Venter på lading av enhet.</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Venter på ubegrenset trådløs tilkobling</string>
     <string name="user_icon">Bruker</string>
     <string name="user_info_address">Adresse</string>
     <string name="user_info_email">E-post</string>

+ 1 - 0
src/main/res/values-nl/strings.xml

@@ -787,6 +787,7 @@
     <string name="uploads_view_upload_status_virus_detected">Virus gevonden. Upload kan niet vervolledigd worden!</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Wachten om de energiemodus te verlaten</string>
     <string name="uploads_view_upload_status_waiting_for_charging">Wachten to apparaat wordt opgeladen</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Wachten op niet-gemeten Wi-Fi</string>
     <string name="user_icon">Gebruiker</string>
     <string name="user_info_address">Adres</string>
     <string name="user_info_email">Email</string>

+ 1 - 1
src/main/res/values-pl/strings.xml

@@ -113,7 +113,7 @@
     <string name="common_unknown">nieznane</string>
     <string name="common_yes">Tak</string>
     <string name="community_beta_headline">Sprawdź wersję deweloperską</string>
-    <string name="community_beta_text">Zawiera w sobie wszystkie nadchodzące funkcje i może być mało stabilna. Przy występowaniu błędów, należy zgłosić je do nas.</string>
+    <string name="community_beta_text">Zawiera w sobie wszystkie nadchodzące funkcje i może być mało stabilna. Przy wystąpieniu błędów, należy zgłosić je do nas.</string>
     <string name="community_contribute_forum_forum">forum</string>
     <string name="community_contribute_forum_text">Pomóż innym na</string>
     <string name="community_contribute_github_text">Przejrzyj, zmodyfikuj i napisz kod. Szczegóły znajdziesz w %1$s.</string>

+ 1 - 1
src/main/res/values-pt-rBR/strings.xml

@@ -29,7 +29,7 @@
     <string name="activities_no_results_headline">Nenhuma atividade</string>
     <string name="activities_no_results_message">Ainda nenhum evento como adições, alterações e compartilhamentos.</string>
     <string name="activity_chooser_send_file_title">Enviar</string>
-    <string name="activity_chooser_title">Enviar o link para…</string>
+    <string name="activity_chooser_title">Enviar link para…</string>
     <string name="activity_icon">Atividade</string>
     <string name="add_another_public_share_link">Adicionar um novo link</string>
     <string name="add_new_public_share">Adicionar um novo link público de compartilhamento</string>

+ 2 - 0
src/main/res/values-ru/strings.xml

@@ -27,6 +27,7 @@
     <string name="actionbar_sort">Сортировка</string>
     <string name="active_user">Активный пользователь</string>
     <string name="activities_no_results_headline">Событий ещё нет</string>
+    <string name="activities_no_results_message">Отсутствуют события добавления, изменения и предоставления общего доступа.</string>
     <string name="activity_chooser_send_file_title">Отправить</string>
     <string name="activity_chooser_title">Отправить ссылку…</string>
     <string name="activity_icon">События</string>
@@ -786,6 +787,7 @@
     <string name="uploads_view_upload_status_virus_detected">Обнаружен вирус. Передача не может быть завершена!</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Ожидание выхода из режима энергосбережения</string>
     <string name="uploads_view_upload_status_waiting_for_charging">Ожидание зарядки устройства</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Ожидание безлимитного Wi-Fi</string>
     <string name="user_icon">Пользователь</string>
     <string name="user_info_address">Адрес</string>
     <string name="user_info_email">Email</string>

+ 1 - 0
src/main/res/values-sk-rSK/strings.xml

@@ -787,6 +787,7 @@
     <string name="uploads_view_upload_status_virus_detected">Bol zistený víru.  Nahrávanie nebude ukončené!</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Čaká sa na ukončenie úsporného režimu</string>
     <string name="uploads_view_upload_status_waiting_for_charging">Čakám na nabitie</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Čakanie na nemerané Wi-Fi</string>
     <string name="user_icon">Používateľ</string>
     <string name="user_info_address">Adresa</string>
     <string name="user_info_email">E-mail</string>

+ 1 - 0
src/main/res/values-sv/strings.xml

@@ -788,6 +788,7 @@
     <string name="uploads_view_upload_status_virus_detected">Virus upptäckt. Uppladdningen kan inte slutföras!</string>
     <string name="uploads_view_upload_status_waiting_exit_power_save_mode">Väntar på att avsluta energisparläge</string>
     <string name="uploads_view_upload_status_waiting_for_charging">Väntar på laddning</string>
+    <string name="uploads_view_upload_status_waiting_for_wifi">Väntar på icke-uppmätt Wi-Fi</string>
     <string name="user_icon">Användare</string>
     <string name="user_info_address">Adress</string>
     <string name="user_info_email">E-post</string>

+ 7 - 0
src/main/res/values-zh-rTW/strings.xml

@@ -27,10 +27,12 @@
     <string name="actionbar_sort">排序</string>
     <string name="active_user">活躍使用者</string>
     <string name="activities_no_results_headline">目前沒有任何活動</string>
+    <string name="activities_no_results_message">尚未有任何新增、更動或分享事件。</string>
     <string name="activity_chooser_send_file_title">寄出</string>
     <string name="activity_chooser_title">傳送連結到…</string>
     <string name="activity_icon">活動</string>
     <string name="add_another_public_share_link">加入另一個連結</string>
+    <string name="add_new_public_share">增加一個新的公開連結</string>
     <string name="add_to_cloud">新增到 %1$s </string>
     <string name="allow_editing">允許編輯</string>
     <string name="appbar_search_in">%s內搜尋</string>
@@ -216,6 +218,7 @@
     <string name="drawer_quota">在  %2$s中使用了%1$s </string>
     <string name="drawer_quota_unlimited">%1$s已使用</string>
     <string name="drawer_synced_folders">自動上傳</string>
+    <string name="edit_label">更改名稱</string>
     <string name="edit_permission_label">編輯</string>
     <string name="edit_rich_workspace">編輯資料夾資訊</string>
     <string name="encrypted">設為已加密的</string>
@@ -257,6 +260,7 @@
     <string name="etm_background_jobs_schedule_test_job">排程測試作業</string>
     <string name="etm_background_jobs_start_test_job">開始測試作業</string>
     <string name="etm_background_jobs_stop_test_job">停止測試作業</string>
+    <string name="etm_download_path">遠端路徑</string>
     <string name="etm_preferences">設定</string>
     <string name="etm_title">工程測試模式</string>
     <string name="fab_label">新增或上傳</string>
@@ -365,6 +369,7 @@
     <string name="list_layout">清單顯示</string>
     <string name="local_file_list_empty">這個資料夾中沒有任何檔案</string>
     <string name="local_file_not_found_message">本地檔案系統中找不到檔案</string>
+    <string name="local_folder_friendly_path">%1$s/%2$s</string>
     <string name="local_folder_list_empty">沒有其他資料夾了</string>
     <string name="log_send_mail_subject">%1$s Android 應用程式記錄</string>
     <string name="log_send_no_mail_app">無應用程式送出的紀錄,請安裝 Email 客戶端。</string>
@@ -512,9 +517,11 @@
     <string name="preview_image_error_unknown_format">無法顯示圖像</string>
     <string name="preview_sorry">很抱歉</string>
     <string name="privacy">隱私權</string>
+    <string name="public_share_name">新名稱</string>
     <string name="push_notifications_not_implemented">由於私人Google Play服務的相依性,推送通知已停用</string>
     <string name="push_notifications_old_login">由於過期的登入工作階段,沒有推送通知。請考慮重新登入您的帳號</string>
     <string name="push_notifications_temp_error">推送通知目前無法使用</string>
+    <string name="qr_could_not_be_read">QR 碼無法讀取!</string>
     <string name="recommend_subject">在您的裝置上試用 %1$s!</string>
     <string name="recommend_text">我想邀請您在您的裝置也使用%1$s
 請從這裡下載:%2$s</string>

+ 2 - 3
src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java

@@ -28,7 +28,6 @@ import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
-import org.mockito.internal.util.reflection.FieldSetter;
 
 import java.util.ArrayList;
 
@@ -39,10 +38,10 @@ public final class ActivityListAdapterTest {
     private ActivityListAdapter activityListAdapter;
 
     @Before
-    public void setUp() throws NoSuchFieldException {
+    public void setUp() {
         MockitoAnnotations.initMocks(this);
         MockitoAnnotations.initMocks(activityListAdapter);
-        FieldSetter.setField(activityListAdapter, activityListAdapter.getClass().getDeclaredField("values"), new ArrayList<>());
+        activityListAdapter.values = new ArrayList<>();
     }
 
     @Test