Răsfoiți Sursa

Merge branch 'master' into feature/ui-revamp/fonts

Signed-off-by: Joris Bodin <joris.bodin@infomaniak.com>

# Conflicts:
#	src/main/res/values-night/colors.xml
Joris Bodin 5 ani în urmă
părinte
comite
8008688fec
77 a modificat fișierele cu 1581 adăugiri și 1284 ștergeri
  1. 11 0
      CHANGELOG.md
  2. 2 2
      build.gradle
  3. 5 1
      lint.xml
  4. 1 1
      scripts/analysis/findbugs-results.txt
  5. 2 2
      src/androidTest/java/com/nextcloud/client/jobs/BackgroundJobManagerTest.kt
  6. 18 15
      src/generic/fastlane/metadata/android/ko-KR/full_description.txt
  7. 1 1
      src/main/AndroidManifest.xml
  8. 51 34
      src/main/java/com/nextcloud/android/sso/InputStreamBinder.java
  9. 11 3
      src/main/java/com/nextcloud/android/sso/Response.java
  10. 31 32
      src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java
  11. 7 2
      src/main/java/com/nextcloud/client/etm/EtmViewModel.kt
  12. 4 1
      src/main/java/com/nextcloud/client/etm/pages/EtmBackgroundJobsFragment.kt
  13. 52 5
      src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
  14. 9 0
      src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
  15. 85 8
      src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
  16. 1 0
      src/main/java/com/nextcloud/client/jobs/ContactsBackupWork.kt
  17. 1 0
      src/main/java/com/nextcloud/client/jobs/ContactsImportWork.kt
  18. 2 22
      src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt
  19. 234 0
      src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt
  20. 244 0
      src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt
  21. 143 0
      src/main/java/com/nextcloud/client/jobs/OfflineSyncWork.kt
  22. 13 43
      src/main/java/com/owncloud/android/MainApp.java
  23. 123 122
      src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java
  24. 4 1
      src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java
  25. 0 273
      src/main/java/com/owncloud/android/jobs/FilesSyncJob.java
  26. 0 278
      src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java
  27. 0 23
      src/main/java/com/owncloud/android/jobs/NCJobCreator.java
  28. 0 185
      src/main/java/com/owncloud/android/jobs/OfflineSyncJob.java
  29. 1 10
      src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java
  30. 7 7
      src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
  31. 14 0
      src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
  32. 2 0
      src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java
  33. 2 2
      src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java
  34. 9 8
      src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java
  35. 3 3
      src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
  36. 6 14
      src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
  37. 16 0
      src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
  38. 1 4
      src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java
  39. 4 0
      src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  40. 6 3
      src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java
  41. 2 12
      src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java
  42. 2 11
      src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
  43. 1 0
      src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java
  44. 19 19
      src/main/java/com/owncloud/android/utils/CsrHelper.java
  45. 57 51
      src/main/java/com/owncloud/android/utils/DisplayUtils.java
  46. 6 35
      src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
  47. 28 3
      src/main/java/com/owncloud/android/utils/ThemeUtils.java
  48. 13 0
      src/main/res/drawable-night/favorite.xml
  49. 2 2
      src/main/res/drawable/uploader_list_separator.xml
  50. 8 8
      src/main/res/layout-land/account_setup.xml
  51. 8 8
      src/main/res/layout/account_setup.xml
  52. 2 2
      src/main/res/layout/conflict_resolve_dialog.xml
  53. 8 8
      src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml
  54. 1 1
      src/main/res/layout/fragment_etm_background_jobs.xml
  55. 6 0
      src/main/res/menu/fragment_etm_background_jobs.xml
  56. 13 0
      src/main/res/values-bg-rBG/strings.xml
  57. 44 0
      src/main/res/values-ca/strings.xml
  58. 5 0
      src/main/res/values-da/strings.xml
  59. 4 0
      src/main/res/values-fa/strings.xml
  60. 1 1
      src/main/res/values-gl/strings.xml
  61. 5 0
      src/main/res/values-hu-rHU/strings.xml
  62. 45 0
      src/main/res/values-iw/strings.xml
  63. 55 0
      src/main/res/values-ko/strings.xml
  64. 1 1
      src/main/res/values-lt-rLT/strings.xml
  65. 30 0
      src/main/res/values-lv/strings.xml
  66. 8 0
      src/main/res/values-nb-rNO/strings.xml
  67. 3 3
      src/main/res/values-night/colors.xml
  68. 16 11
      src/main/res/values-nl/strings.xml
  69. 14 0
      src/main/res/values-pt-rPT/strings.xml
  70. 5 0
      src/main/res/values-ru/strings.xml
  71. 5 0
      src/main/res/values-sk-rSK/strings.xml
  72. 30 0
      src/main/res/values-uk/strings.xml
  73. 1 0
      src/main/res/values-zh-rCN/strings.xml
  74. 3 0
      src/main/res/values-zh-rTW/strings.xml
  75. 2 3
      src/main/res/values/colors.xml
  76. 1 0
      src/main/res/values/dims.xml
  77. 1 0
      src/main/res/values/strings.xml

+ 11 - 0
CHANGELOG.md

@@ -1,3 +1,14 @@
+## 3.11.1 (April, 23, 2020)
+
+- Crash while browsing files
+- auto upload:
+ - fix wrong conflict detection on custom folder
+ - allow to choose default conflict strategy @fodinabor
+ - fix hanging UI after saving
+- open office files online if no local app installed
+
+For a full list, please see https://github.com/nextcloud/android/milestone/47
+
 ## 3.11.0 (March, 26, 2020)
 
 - not enough space dialog @Shagequi

+ 2 - 2
build.gradle

@@ -84,7 +84,7 @@ repositories {
 
 // semantic versioning for version code
 def versionMajor = 3
-def versionMinor = 12
+def versionMinor = 13
 def versionPatch = 0
 def versionBuild = 0 // 0-50=Alpha / 51-98=RC / 90-99=stable
 
@@ -311,7 +311,7 @@ dependencies {
     implementation 'org.greenrobot:eventbus:3.2.0'
     implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.6'
     implementation 'org.lukhnos:nnio:0.2'
-    implementation 'com.madgag.spongycastle:pkix:1.54.0.0'
+    implementation 'org.bouncycastle:bcpkix-jdk15on:1.65'
     implementation 'com.google.code.gson:gson:2.8.6'
     implementation 'com.afollestad:sectioned-recyclerview:0.5.0'
     implementation 'com.github.chrisbanes:PhotoView:2.3.0'

+ 5 - 1
lint.xml

@@ -7,7 +7,7 @@
     <issue id="InvalidPackage">
         <ignore path="**/freemarker-2.*.*.jar" />
         <ignore path="**/nnio-0.2.jar"/>
-        <ignore path="**/pkix-1.54.0.0.jar"/>
+        <ignore path="**/bcpkix-jdk15on-1.65.jar" />
     </issue>
 
     <issue id="UnusedResources">
@@ -44,4 +44,8 @@
         <ignore path="**/values-**/strings.xml" />
     </issue>
 
+    <issue id="TrustAllX509TrustManager">
+        <ignore path="**/bouncycastle/est/jcajce/*.class" />
+    </issue>
+
 </lint>

+ 1 - 1
scripts/analysis/findbugs-results.txt

@@ -1 +1 @@
-384
+379

+ 2 - 2
src/androidTest/java/com/nextcloud/client/jobs/BackgroundJobManagerTest.kt

@@ -210,10 +210,10 @@ class BackgroundJobManagerTest {
         }
 
         @Test
-        fun job_is_unique() {
+        fun job_is_unique_and_replaces_previous_job() {
             verify(workManager).enqueueUniqueWork(
                 eq(BackgroundJobManagerImpl.JOB_CONTENT_OBSERVER),
-                eq(ExistingWorkPolicy.KEEP),
+                eq(ExistingWorkPolicy.REPLACE),
                 argThat(IsOneTimeWorkRequest())
             )
         }

+ 18 - 15
src/generic/fastlane/metadata/android/ko-KR/full_description.txt

@@ -1,19 +1,22 @@
-자유 소프트웨어 Nextcloud Android 앱으로 내 Nextcloud에 있는 파일에 접근할 수 있습니다.
+당신이 관리할 수 있는 자체 운영되는 생산성 플랫폼.
 
-기능:
-* 쉽고 간결한 인터페이스, 서버의 테마를 따라가는 테마
-* 내 Nextcloud 서버에 파일 업로드
-* 다른 사람과 파일 공유하기
-* 즐겨찾는 파일과 폴더 동기화
-* 서버에 있는 모든 폴더 검색
-* 내 장치로 찍은 사진과 동영상 자동 업로드
-* 최신 알림 유지
-* 다중 계정 지원
-* 지문이나 PIN으로 데이터 접근 보호
-* DAVDroid 통합을 통한 간편한 캘린더 및 연락처 동기화
+특징:
+당신의 Nextcloud 서버 테마에 맞는 쉽고 현대적인 인터페이스를 지니고 있음
+파일을 Nextcloud 서버에 올리고 다른 이들과 공유할 수 있음
+당신의 중요 파일과 폴더를 동기화 된 상태로 유지 할 수 있음
+당신의 폴더들 구석구석을 검색할 수 있음
+당신의 장치로 찍힌 사진과 동영상을 자동 업로드 할 수 있음
+Nextcloud의 알림으로 최신 상태로 유지할 수 있음
+다-계정 지원 가능함
+지문이나 PIN을 통한 안전한 잠금을 지원함
+DAVx5(DAVdroid) 와의 통합으로 캘린더와 주소록을 쉽게 동기화할 수 있음
 
-https://github.com/nextcloud/android/issues 사이트에 버그를 보고해 주십시오. https://help.nextcloud.com/c/clients/android 사이트에서 앱에 대해 토론할 수 있습니다.
+문제가 있다면 https://github.com/nextcloud/android/issues 여기서 토론을
+앱에 문제가 있다면 https://help.nextcloud.com/c/clients/android 여기서 토론을 부탁드립니다.
 
-Nextcloud를 처음 들어 보셨나요? Nextcloud는 개인 파일 동기화 및 공유 서버입니다. 직접 서버에 설치하거나 호스팅 업체에 설치할 수 있는 자유 오픈 소스 프로젝트입니다. 내 사진, 캘린더, 연락처, 문서 및 모든 것의 통제권을 가질 수 있습니다.
+Nextcloud가 처음이신가요?
+Nextcloud는 개인적인 파일 동기화, 공유 그리고 의사소통을 위한 서버입니다.
+Nextcloud는 자유 소프트웨어이고, 당신이 직접 운영하거나 돈을 내고 다른 서버의 계정를 빌릴 수 있습니다.
+그럼으로써, 당신은 당신의 사진, 캘린더와 주소록, 당신의 문서 등을 관리할 수 있습니다.
 
-Nextcloud에 대해 더 알아보려면 https://nextcloud.com 사이트를 방문하십시오
+Nextcloud를 여기서 확인하십시오: https://nextcloud.com

+ 1 - 1
src/main/AndroidManifest.xml

@@ -140,7 +140,7 @@
         <activity android:name=".ui.activities.ActivitiesActivity"
             android:configChanges="orientation|screenSize|keyboardHidden" />
         <activity android:name=".ui.activity.SyncedFoldersActivity"/>
-        <receiver android:name="com.owncloud.android.jobs.MediaFoldersDetectionJob$NotificationReceiver" />
+        <receiver android:name="com.nextcloud.client.jobs.MediaFoldersDetectionWork$NotificationReceiver" />
         <receiver android:name="com.owncloud.android.jobs.NotificationJob$NotificationReceiver" />
         <activity android:name=".ui.activity.UploadFilesActivity" />
         <activity android:name=".ui.activity.ExternalSiteWebView"

+ 51 - 34
src/main/java/com/nextcloud/android/sso/InputStreamBinder.java

@@ -104,16 +104,6 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         this.accountManager = accountManager;
     }
 
-    private NameValuePair[] convertMapToNVP(Map<String, String> map) {
-        NameValuePair[] nvp = new NameValuePair[map.size()];
-        int i = 0;
-        for (String key : map.keySet()) {
-            nvp[i] = new NameValuePair(key, map.get(key));
-            i++;
-        }
-        return nvp;
-    }
-
     public ParcelFileDescriptor performNextcloudRequestV2(ParcelFileDescriptor input) {
         return performNextcloudRequestAndBodyStreamV2(input, null);
     }
@@ -143,7 +133,9 @@ public class InputStreamBinder extends IInputStreamService.Stub {
             InputStream exceptionStream = serializeObjectToInputStreamV2(exception, response.getPlainHeadersString());
             InputStream resultStream = new java.io.SequenceInputStream(exceptionStream, response.getBody());
 
-            return ParcelFileDescriptorUtil.pipeFrom(resultStream, thread -> Log.d(TAG, "Done sending result"));
+            return ParcelFileDescriptorUtil.pipeFrom(resultStream,
+                                                     thread -> Log.d(TAG, "Done sending result"),
+                                                     response.getMethod());
         } catch (IOException e) {
             Log_OC.e(TAG, "Error while sending response back to client app", e);
         }
@@ -163,6 +155,7 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         final InputStream requestBodyInputStream = requestBodyParcelFileDescriptor != null ?
             new ParcelFileDescriptor.AutoCloseInputStream(requestBodyParcelFileDescriptor) : null;
         Exception exception = null;
+        HttpMethodBase httpMethod = null;
         InputStream httpStream = new InputStream() {
             @Override
             public int read() {
@@ -173,7 +166,8 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         try {
             // Start request and catch exceptions
             NextcloudRequest request = deserializeObjectAndCloseStream(is);
-            httpStream = processRequest(request, requestBodyInputStream);
+            httpMethod = processRequest(request, requestBodyInputStream);
+            httpStream = httpMethod.getResponseBodyAsStream();
         } catch (Exception e) {
             Log_OC.e(TAG, "Error during Nextcloud request", e);
             exception = e;
@@ -188,7 +182,9 @@ public class InputStreamBinder extends IInputStreamService.Stub {
             } else {
                 resultStream = exceptionStream;
             }
-            return ParcelFileDescriptorUtil.pipeFrom(resultStream, thread -> Log.d(TAG, "Done sending result"));
+            return ParcelFileDescriptorUtil.pipeFrom(resultStream,
+                                                     thread -> Log.d(TAG, "Done sending result"),
+                                                     httpMethod);
         } catch (IOException e) {
             Log_OC.e(TAG, "Error while sending response back to client app", e);
         }
@@ -309,7 +305,7 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         return method;
     }
 
-    private InputStream processRequest(final NextcloudRequest request, final InputStream requestBodyInputStream)
+    private HttpMethodBase processRequest(final NextcloudRequest request, final InputStream requestBodyInputStream)
         throws UnsupportedOperationException,
         com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException,
         OperationCanceledException, AuthenticatorException, IOException {
@@ -354,23 +350,21 @@ public class InputStreamBinder extends IInputStreamService.Stub {
 
         // Check if status code is 2xx --> https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success
         if (status >= HTTP_STATUS_CODE_OK && status < HTTP_STATUS_CODE_MULTIPLE_CHOICES) {
-            return method.getResponseBodyAsStream();
+            return method;
         } else {
-            StringBuilder total = new StringBuilder();
             InputStream inputStream = method.getResponseBodyAsStream();
+            String total = "No response body";
+
             // If response body is available
             if (inputStream != null) {
-                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-                String line = reader.readLine();
-                while (line != null) {
-                    total.append(line).append('\n');
-                    line = reader.readLine();
-                }
-                Log_OC.e(TAG, total.toString());
+                total = inputStreamToString(inputStream);
+                Log_OC.e(TAG, total);
             }
+
+            method.releaseConnection();
             throw new IllegalStateException(EXCEPTION_HTTP_REQUEST_FAILED,
                                             new IllegalStateException(String.valueOf(status),
-                                                                      new IllegalStateException(total.toString())));
+                                                                      new IllegalStateException(total)));
         }
     }
 
@@ -419,23 +413,21 @@ public class InputStreamBinder extends IInputStreamService.Stub {
 
         // Check if status code is 2xx --> https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success
         if (status >= HTTP_STATUS_CODE_OK && status < HTTP_STATUS_CODE_MULTIPLE_CHOICES) {
-            return new Response(method.getResponseBodyAsStream(), method.getResponseHeaders());
+            return new Response(method);
         } else {
-            StringBuilder total = new StringBuilder();
             InputStream inputStream = method.getResponseBodyAsStream();
+            String total = "No response body";
+
             // If response body is available
             if (inputStream != null) {
-                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
-                String line = reader.readLine();
-                while (line != null) {
-                    total.append(line).append('\n');
-                    line = reader.readLine();
-                }
-                Log_OC.e(TAG, total.toString());
+                total = inputStreamToString(inputStream);
+                Log_OC.e(TAG, total);
             }
+
+            method.releaseConnection();
             throw new IllegalStateException(EXCEPTION_HTTP_REQUEST_FAILED,
                                             new IllegalStateException(String.valueOf(status),
-                                                                      new IllegalStateException(total.toString())));
+                                                                      new IllegalStateException(total)));
         }
     }
 
@@ -474,4 +466,29 @@ public class InputStreamBinder extends IInputStreamService.Stub {
         }
         return result == 0;
     }
+
+    private static String inputStreamToString(InputStream inputStream) {
+        try {
+            StringBuilder total = new StringBuilder();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+            String line = reader.readLine();
+            while (line != null) {
+                total.append(line).append('\n');
+                line = reader.readLine();
+            }
+            return total.toString();
+        } catch (Exception e) {
+            return e.getMessage();
+        }
+    }
+
+    private static NameValuePair[] convertMapToNVP(Map<String, String> map) {
+        NameValuePair[] nvp = new NameValuePair[map.size()];
+        int i = 0;
+        for (String key : map.keySet()) {
+            nvp[i] = new NameValuePair(key, map.get(key));
+            i++;
+        }
+        return nvp;
+    }
 }

+ 11 - 3
src/main/java/com/nextcloud/android/sso/Response.java

@@ -25,7 +25,9 @@ package com.nextcloud.android.sso;
 import com.google.gson.Gson;
 
 import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpMethodBase;
 
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
@@ -33,6 +35,7 @@ import java.util.List;
 public class Response {
     private InputStream body;
     private Header[] headers;
+    private HttpMethodBase method;
 
     public Response() {
         headers = new Header[0];
@@ -44,9 +47,10 @@ public class Response {
         };
     }
 
-    public Response(InputStream inputStream, Header[] headers) {
-        this.body = inputStream;
-        this.headers = headers;
+    public Response(HttpMethodBase methodBase) throws IOException {
+        this.method = methodBase;
+        this.body = methodBase.getResponseBodyAsStream();
+        this.headers = methodBase.getResponseHeaders();
     }
 
     public String getPlainHeadersString() {
@@ -63,4 +67,8 @@ public class Response {
     public InputStream getBody() {
         return this.body;
     }
+
+    public HttpMethodBase getMethod() {
+        return method;
+    }
 }

+ 31 - 32
src/main/java/com/nextcloud/android/sso/aidl/ParcelFileDescriptorUtil.java

@@ -24,6 +24,8 @@ import android.util.Log;
 
 import com.owncloud.android.lib.common.utils.Log_OC;
 
+import org.apache.commons.httpclient.HttpMethodBase;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -32,45 +34,37 @@ public final class ParcelFileDescriptorUtil {
 
     private ParcelFileDescriptorUtil() { }
 
-    public static ParcelFileDescriptor pipeFrom(InputStream inputStream, IThreadListener listener)
+    public static ParcelFileDescriptor pipeFrom(InputStream inputStream,
+                                                IThreadListener listener,
+                                                HttpMethodBase method)
             throws IOException {
         ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
         ParcelFileDescriptor readSide = pipe[0];
         ParcelFileDescriptor writeSide = pipe[1];
 
         // start the transfer thread
-        new TransferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(writeSide),
-                listener)
+        new TransferThread(inputStream,
+                           new ParcelFileDescriptor.AutoCloseOutputStream(writeSide),
+                           listener,
+                           method)
                 .start();
 
         return readSide;
     }
 
-    public static ParcelFileDescriptor pipeTo(OutputStream outputStream, IThreadListener listener)
-            throws IOException {
-        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
-        ParcelFileDescriptor readSide = pipe[0];
-        ParcelFileDescriptor writeSide = pipe[1];
-
-        // start the transfer thread
-        new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(readSide), outputStream,
-                listener)
-                .start();
-
-        return writeSide;
-    }
-
     public static class TransferThread extends Thread {
         private static final String TAG = TransferThread.class.getCanonicalName();
-        private final InputStream mIn;
-        private final OutputStream mOut;
-        private final IThreadListener mListener;
+        private final InputStream inputStream;
+        private final OutputStream outputStream;
+        private final IThreadListener threadListener;
+        private final HttpMethodBase httpMethod;
 
-        TransferThread(InputStream in, OutputStream out, IThreadListener listener) {
+        TransferThread(InputStream in, OutputStream out, IThreadListener listener, HttpMethodBase method) {
             super("ParcelFileDescriptor Transfer Thread");
-            mIn = in;
-            mOut = out;
-            mListener = listener;
+            inputStream = in;
+            outputStream = out;
+            threadListener = listener;
+            httpMethod = method;
             setDaemon(true);
         }
 
@@ -80,26 +74,31 @@ public final class ParcelFileDescriptorUtil {
             int len;
 
             try {
-                while ((len = mIn.read(buf)) > 0) {
-                    mOut.write(buf, 0, len);
+                while ((len = inputStream.read(buf)) > 0) {
+                    outputStream.write(buf, 0, len);
                 }
-                mOut.flush(); // just to be safe
+                outputStream.flush(); // just to be safe
             } catch (IOException e) {
-                Log.e("TransferThread", "writing failed");
+                Log_OC.e(TAG, "writing failed: " + e.getMessage());
             } finally {
                 try {
-                    mIn.close();
+                    inputStream.close();
                 } catch (IOException e) {
                     Log_OC.e(TAG, e.getMessage());
                 }
                 try {
-                    mOut.close();
+                    outputStream.close();
                 } catch (IOException e) {
                     Log_OC.e(TAG, e.getMessage());
                 }
             }
-            if (mListener != null) {
-                mListener.onThreadFinished(this);
+            if (threadListener != null) {
+                threadListener.onThreadFinished(this);
+            }
+
+            if (httpMethod != null) {
+                Log_OC.i(TAG, "releaseConnection");
+                httpMethod.releaseConnection();
             }
         }
     }

+ 7 - 2
src/main/java/com/nextcloud/client/etm/EtmViewModel.kt

@@ -39,6 +39,7 @@ import com.owncloud.android.R
 import com.owncloud.android.lib.common.accounts.AccountUtils
 import javax.inject.Inject
 
+@Suppress("LongParameterList") // Dependencies Injection
 class EtmViewModel @Inject constructor(
     private val defaultPreferences: SharedPreferences,
     private val platformAccountManager: AccountManager,
@@ -158,8 +159,12 @@ class EtmViewModel @Inject constructor(
         backgroundJobManager.cancelAllJobs()
     }
 
-    fun startTestJob() {
-        backgroundJobManager.scheduleTestJob()
+    fun startTestJob(periodic: Boolean) {
+        if (periodic) {
+            backgroundJobManager.scheduleTestJob()
+        } else {
+            backgroundJobManager.startImmediateTestJob()
+        }
     }
 
     fun cancelTestJob() {

+ 4 - 1
src/main/java/com/nextcloud/client/etm/pages/EtmBackgroundJobsFragment.kt

@@ -130,7 +130,10 @@ class EtmBackgroundJobsFragment : EtmBaseFragment() {
                 vm.pruneJobs(); true
             }
             R.id.etm_background_jobs_start_test -> {
-                vm.startTestJob(); true
+                vm.startTestJob(periodic = false); true
+            }
+            R.id.etm_background_jobs_schedule_test -> {
+                vm.startTestJob(periodic = true); true
             }
             R.id.etm_background_jobs_cancel_test -> {
                 vm.cancelTestJob(); true

+ 52 - 5
src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

@@ -32,26 +32,31 @@ import com.nextcloud.client.core.Clock
 import com.nextcloud.client.device.DeviceInfo
 import com.nextcloud.client.device.PowerManagementService
 import com.nextcloud.client.logger.Logger
+import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.client.preferences.AppPreferences
 import com.owncloud.android.datamodel.ArbitraryDataProvider
 import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.datamodel.UploadsStorageManager
 import javax.inject.Inject
 import javax.inject.Provider
 
 /**
- * This factory is responsible for creating all background jobs and for injecting job dependencies.
+ * This factory is responsible for creating all background jobs and for injecting worker dependencies.
  */
+@Suppress("LongParameterList") // satisfied by DI
 class BackgroundJobFactory @Inject constructor(
     private val logger: Logger,
     private val preferences: AppPreferences,
     private val contentResolver: ContentResolver,
     private val clock: Clock,
-    private val powerManagerService: PowerManagementService,
+    private val powerManagementService: PowerManagementService,
     private val backgroundJobManager: Provider<BackgroundJobManager>,
     private val deviceInfo: DeviceInfo,
     private val accountManager: UserAccountManager,
     private val resources: Resources,
-    private val dataProvider: ArbitraryDataProvider
+    private val dataProvider: ArbitraryDataProvider,
+    private val uploadsStorageManager: UploadsStorageManager,
+    private val connectivityService: ConnectivityService
 ) : WorkerFactory() {
 
     override fun createWorker(
@@ -70,7 +75,10 @@ class BackgroundJobFactory @Inject constructor(
             ContentObserverWork::class -> createContentObserverJob(context, workerParameters, clock)
             ContactsBackupWork::class -> createContactsBackupWork(context, workerParameters)
             ContactsImportWork::class -> createContactsImportWork(context, workerParameters)
-            else -> null // falls back to default factory
+            FilesSyncWork::class -> createFilesSyncWork(context, workerParameters)
+            OfflineSyncWork::class -> createOfflineSyncWork(context, workerParameters)
+            MediaFoldersDetectionWork::class -> createMediaFoldersDetectionWork(context, workerParameters)
+            else -> null // caller falls back to default factory
         }
     }
 
@@ -86,7 +94,7 @@ class BackgroundJobFactory @Inject constructor(
                 context,
                 workerParameters,
                 folderResolver,
-                powerManagerService,
+                powerManagementService,
                 backgroundJobManager.get()
             )
         } else {
@@ -113,4 +121,43 @@ class BackgroundJobFactory @Inject constructor(
             contentResolver
         )
     }
+
+    private fun createFilesSyncWork(context: Context, params: WorkerParameters): FilesSyncWork {
+        return FilesSyncWork(
+            context = context,
+            params = params,
+            resources = resources,
+            contentResolver = contentResolver,
+            userAccountManager = accountManager,
+            preferences = preferences,
+            uploadsStorageManager = uploadsStorageManager,
+            connectivityService = connectivityService,
+            powerManagementService = powerManagementService,
+            clock = clock,
+            backgroundJobManager = backgroundJobManager.get()
+        )
+    }
+
+    private fun createOfflineSyncWork(context: Context, params: WorkerParameters): OfflineSyncWork {
+        return OfflineSyncWork(
+            context = context,
+            params = params,
+            contentResolver = contentResolver,
+            userAccountManager = accountManager,
+            connectivityService = connectivityService,
+            powerManagementService = powerManagementService
+        )
+    }
+
+    private fun createMediaFoldersDetectionWork(context: Context, params: WorkerParameters): MediaFoldersDetectionWork {
+        return MediaFoldersDetectionWork(
+            context,
+            params,
+            resources,
+            contentResolver,
+            accountManager,
+            preferences,
+            clock
+        )
+    }
 }

+ 9 - 0
src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt

@@ -28,6 +28,7 @@ import com.nextcloud.client.account.User
  * This interface allows to control, schedule and monitor all application
  * long-running background tasks, such as periodic checks or synchronization.
  */
+@Suppress("TooManyFunctions") // we expect this implementation to have rich API
 interface BackgroundJobManager {
 
     /**
@@ -89,7 +90,15 @@ interface BackgroundJobManager {
         selectedContacts: IntArray
     ): LiveData<JobInfo?>
 
+    fun schedulePeriodicFilesSyncJob()
+    fun startImmediateFilesSyncJob(skipCustomFolders: Boolean = false, overridePowerSaving: Boolean = false)
+    fun scheduleOfflineSync()
+
+    fun scheduleMediaFoldersDetectionJob()
+    fun startMediaFoldersDetectionJob()
+
     fun scheduleTestJob()
+    fun startImmediateTestJob()
     fun cancelTestJob()
 
     fun pruneJobs()

+ 85 - 8
src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt

@@ -29,6 +29,7 @@ import androidx.work.Data
 import androidx.work.ExistingPeriodicWorkPolicy
 import androidx.work.ExistingWorkPolicy
 import androidx.work.ListenableWorker
+import androidx.work.NetworkType
 import androidx.work.OneTimeWorkRequest
 import androidx.work.Operation
 import androidx.work.PeriodicWorkRequest
@@ -67,6 +68,11 @@ internal class BackgroundJobManagerImpl(
         const val JOB_PERIODIC_CONTACTS_BACKUP = "periodic_contacts_backup"
         const val JOB_IMMEDIATE_CONTACTS_BACKUP = "immediate_contacts_backup"
         const val JOB_IMMEDIATE_CONTACTS_IMPORT = "immediate_contacts_import"
+        const val JOB_PERIODIC_FILES_SYNC = "periodic_files_sync"
+        const val JOB_IMMEDIATE_FILES_SYNC = "immediate_files_sync"
+        const val JOB_PERIODIC_OFFLINE_SYNC = "periodic_offline_sync"
+        const val JOB_PERIODIC_MEDIA_FOLDER_DETECTION = "periodic_media_folder_detection"
+        const val JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION = "immediate_media_folder_detection"
         const val JOB_TEST = "test_job"
 
         const val MAX_CONTENT_TRIGGER_DELAY_MS = 1500L
@@ -81,6 +87,7 @@ internal class BackgroundJobManagerImpl(
         const val INTERVAL_MINUTE = 60L * INTERVAL_SECOND
         const val INTERVAL_HOUR = 60 * INTERVAL_MINUTE
         const val INTERVAL_24H = 24L * INTERVAL_HOUR
+        const val DEFAULT_IMMEDIATE_JOB_DELAY_SEC = 3L
 
         fun formatNameTag(name: String, user: User? = null): String {
             return if (user == null) {
@@ -157,9 +164,16 @@ internal class BackgroundJobManagerImpl(
         jobClass: KClass<out ListenableWorker>,
         jobName: String,
         intervalMins: Long = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES,
+        flexIntervalMins: Long = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES,
         user: User? = null
     ): PeriodicWorkRequest.Builder {
-        val builder = PeriodicWorkRequest.Builder(jobClass.java, intervalMins, TimeUnit.MINUTES)
+        val builder = PeriodicWorkRequest.Builder(
+            jobClass.java,
+            intervalMins,
+            TimeUnit.MINUTES,
+            flexIntervalMins,
+            TimeUnit.MINUTES
+        )
             .addTag(TAG_ALL)
             .addTag(formatNameTag(jobName, user))
             .addTag(formatTimeTag(clock.currentTime))
@@ -203,7 +217,7 @@ internal class BackgroundJobManagerImpl(
             .setConstraints(constrains)
             .build()
 
-        workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.KEEP, request)
+        workManager.enqueueUniqueWork(JOB_CONTENT_OBSERVER, ExistingWorkPolicy.REPLACE, request)
     }
 
     override fun schedulePeriodicContactsBackup(user: User) {
@@ -211,12 +225,11 @@ internal class BackgroundJobManagerImpl(
             .putString(ContactsBackupWork.ACCOUNT, user.accountName)
             .putBoolean(ContactsBackupWork.FORCE, true)
             .build()
-
         val request = periodicRequestBuilder(
-            ContactsBackupWork::class,
-            JOB_PERIODIC_CONTACTS_BACKUP,
-            INTERVAL_24H,
-            user
+            jobClass = ContactsBackupWork::class,
+            jobName = JOB_PERIODIC_CONTACTS_BACKUP,
+            intervalMins = INTERVAL_24H,
+            user = user
         ).setInputData(data).build()
 
         workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_CONTACTS_BACKUP, ExistingPeriodicWorkPolicy.KEEP, request)
@@ -268,10 +281,74 @@ internal class BackgroundJobManagerImpl(
         return workManager.getJobInfo(request.id)
     }
 
+    override fun schedulePeriodicFilesSyncJob() {
+        val request = periodicRequestBuilder(
+            jobClass = FilesSyncWork::class,
+            jobName = JOB_PERIODIC_FILES_SYNC,
+            intervalMins = DEFAULT_PERIODIC_JOB_INTERVAL_MINUTES).build()
+        workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_FILES_SYNC, ExistingPeriodicWorkPolicy.REPLACE, request)
+    }
+
+    override fun startImmediateFilesSyncJob(skipCustomFolders: Boolean, overridePowerSaving: Boolean) {
+        val arguments = Data.Builder()
+            .putBoolean(FilesSyncWork.SKIP_CUSTOM, skipCustomFolders)
+            .putBoolean(FilesSyncWork.OVERRIDE_POWER_SAVING, overridePowerSaving)
+            .build()
+
+        val request = oneTimeRequestBuilder(
+            jobClass = FilesSyncWork::class,
+            jobName = JOB_IMMEDIATE_FILES_SYNC)
+            .setInputData(arguments)
+            .build()
+
+        workManager.enqueueUniqueWork(JOB_IMMEDIATE_FILES_SYNC, ExistingWorkPolicy.KEEP, request)
+    }
+
+    override fun scheduleOfflineSync() {
+        val constrains = Constraints.Builder()
+            .setRequiredNetworkType(NetworkType.UNMETERED)
+            .build()
+
+        val request = periodicRequestBuilder(OfflineSyncWork::class, JOB_PERIODIC_OFFLINE_SYNC)
+            .setConstraints(constrains)
+            .build()
+
+        workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_OFFLINE_SYNC, ExistingPeriodicWorkPolicy.KEEP, request)
+    }
+
+    override fun scheduleMediaFoldersDetectionJob() {
+        val request = periodicRequestBuilder(MediaFoldersDetectionWork::class, JOB_PERIODIC_MEDIA_FOLDER_DETECTION)
+            .build()
+
+        workManager.enqueueUniquePeriodicWork(
+            JOB_PERIODIC_MEDIA_FOLDER_DETECTION,
+            ExistingPeriodicWorkPolicy.KEEP,
+            request
+        )
+    }
+
+    override fun startMediaFoldersDetectionJob() {
+        val request = oneTimeRequestBuilder(MediaFoldersDetectionWork::class, JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION)
+            .build()
+
+        workManager.enqueueUniqueWork(
+            JOB_IMMEDIATE_MEDIA_FOLDER_DETECTION,
+            ExistingWorkPolicy.KEEP,
+            request
+        )
+    }
+
     override fun scheduleTestJob() {
         val request = periodicRequestBuilder(TestJob::class, JOB_TEST)
+            .setInitialDelay(DEFAULT_IMMEDIATE_JOB_DELAY_SEC, TimeUnit.SECONDS)
+            .build()
+        workManager.enqueueUniquePeriodicWork(JOB_TEST, ExistingPeriodicWorkPolicy.REPLACE, request)
+    }
+
+    override fun startImmediateTestJob() {
+        val request = oneTimeRequestBuilder(TestJob::class, JOB_TEST)
             .build()
-        workManager.enqueueUniquePeriodicWork(JOB_TEST, ExistingPeriodicWorkPolicy.KEEP, request)
+        workManager.enqueueUniqueWork(JOB_TEST, ExistingWorkPolicy.REPLACE, request)
     }
 
     override fun cancelTestJob() {

+ 1 - 0
src/main/java/com/nextcloud/client/jobs/ContactsBackupWork.kt

@@ -55,6 +55,7 @@ import java.io.InputStreamReader
 import java.util.ArrayList
 import java.util.Calendar
 
+@Suppress("LongParameterList") // legacy code
 class ContactsBackupWork(
     appContext: Context,
     params: WorkerParameters,

+ 1 - 0
src/main/java/com/nextcloud/client/jobs/ContactsImportWork.kt

@@ -65,6 +65,7 @@ class ContactsImportWork(
         val vCards = ArrayList<VCard>()
 
         var cursor: Cursor? = null
+        @Suppress("TooGenericExceptionCaught") // legacy code
         try {
             val operations = ContactOperations(applicationContext, contactsAccountName, contactsAccountType)
             vCards.addAll(Ezvcard.parse(file).all())

+ 2 - 22
src/main/java/com/nextcloud/client/jobs/ContentObserverWork.kt

@@ -24,12 +24,8 @@ import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.work.Worker
 import androidx.work.WorkerParameters
-import com.evernote.android.job.JobRequest
-import com.evernote.android.job.util.support.PersistableBundleCompat
 import com.nextcloud.client.device.PowerManagementService
 import com.owncloud.android.datamodel.SyncedFolderProvider
-import com.owncloud.android.jobs.FilesSyncJob
-import com.owncloud.android.jobs.MediaFoldersDetectionJob
 
 /**
  * This work is triggered when OS detects change in media folders.
@@ -50,7 +46,7 @@ class ContentObserverWork(
     override fun doWork(): Result {
         if (params.triggeredContentUris.size > 0) {
             checkAndStartFileSyncJob()
-            startMediaFolderDetectionJob()
+            backgroundJobManager.startMediaFoldersDetectionJob()
         }
         recheduleSelf()
         return Result.success()
@@ -63,23 +59,7 @@ class ContentObserverWork(
     private fun checkAndStartFileSyncJob() {
         val syncFolders = syncerFolderProvider.countEnabledSyncedFolders() > 0
         if (!powerManagementService.isPowerSavingEnabled && syncFolders) {
-            val persistableBundleCompat = PersistableBundleCompat()
-            persistableBundleCompat.putBoolean(FilesSyncJob.SKIP_CUSTOM, true)
-
-            JobRequest.Builder(FilesSyncJob.TAG)
-                .startNow()
-                .setExtras(persistableBundleCompat)
-                .setUpdateCurrent(false)
-                .build()
-                .schedule()
+            backgroundJobManager.startImmediateFilesSyncJob(true, false)
         }
     }
-
-    private fun startMediaFolderDetectionJob() {
-        JobRequest.Builder(MediaFoldersDetectionJob.TAG)
-            .startNow()
-            .setUpdateCurrent(false)
-            .build()
-            .schedule()
-    }
 }

+ 234 - 0
src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt

@@ -0,0 +1,234 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author Chris Narkiewicz
+ * Copyright (C) 2017 Mario Danic
+ * Copyright (C) 2017 Nextcloud
+ * Copyright (C) 2020 Chris Narkiewicz
+ *
+ * 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.nextcloud.client.jobs
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.res.Resources
+import android.os.Build
+import android.os.PowerManager
+import android.os.PowerManager.WakeLock
+import android.text.TextUtils
+import androidx.exifinterface.media.ExifInterface
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.core.Clock
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.network.ConnectivityService
+import com.nextcloud.client.preferences.AppPreferences
+import com.owncloud.android.MainApp
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.ArbitraryDataProvider
+import com.owncloud.android.datamodel.FilesystemDataProvider
+import com.owncloud.android.datamodel.MediaFolderType
+import com.owncloud.android.datamodel.SyncedFolder
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.datamodel.UploadsStorageManager
+import com.owncloud.android.files.services.FileUploader
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.operations.UploadFileOperation
+import com.owncloud.android.ui.activity.SettingsActivity
+import com.owncloud.android.utils.FileStorageUtils
+import com.owncloud.android.utils.FilesSyncHelper
+import com.owncloud.android.utils.MimeType
+import com.owncloud.android.utils.MimeTypeUtil
+import java.io.File
+import java.text.ParsePosition
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.TimeZone
+
+@Suppress("LongParameterList") // legacy code
+class FilesSyncWork(
+    private val context: Context,
+    params: WorkerParameters,
+    private val resources: Resources,
+    private val contentResolver: ContentResolver,
+    private val userAccountManager: UserAccountManager,
+    private val preferences: AppPreferences,
+    private val uploadsStorageManager: UploadsStorageManager,
+    private val connectivityService: ConnectivityService,
+    private val powerManagementService: PowerManagementService,
+    private val clock: Clock,
+    private val backgroundJobManager: BackgroundJobManager
+) : Worker(context, params) {
+
+    companion object {
+        const val TAG = "FilesSyncJob"
+        const val SKIP_CUSTOM = "skipCustom"
+        const val OVERRIDE_POWER_SAVING = "overridePowerSaving"
+        private const val WAKELOCK_TAG_SEPARATION = ":"
+        private const val WAKELOCK_ACQUIRE_TIMEOUT_MS = 10L * 60L * 1000L
+    }
+
+    override fun doWork(): Result {
+        var wakeLock: WakeLock? = null
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
+                WAKELOCK_TAG_SEPARATION + TAG)
+            wakeLock.acquire(WAKELOCK_ACQUIRE_TIMEOUT_MS)
+        }
+        val overridePowerSaving = inputData.getBoolean(OVERRIDE_POWER_SAVING, false)
+        // If we are in power save mode, better to postpone upload
+        if (powerManagementService.isPowerSavingEnabled && !overridePowerSaving) {
+            wakeLock?.release()
+            return Result.success()
+        }
+        val resources = context.resources
+        val lightVersion = resources.getBoolean(R.bool.syncedFolder_light)
+        val skipCustom = inputData.getBoolean(SKIP_CUSTOM, false)
+        FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager,
+            userAccountManager,
+            connectivityService,
+            powerManagementService)
+        FilesSyncHelper.insertAllDBEntries(preferences, clock, skipCustom)
+        // Create all the providers we'll needq
+        val filesystemDataProvider = FilesystemDataProvider(contentResolver)
+        val syncedFolderProvider = SyncedFolderProvider(contentResolver, preferences, clock)
+        val currentLocale = resources.configuration.locale
+        val dateFormat = SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale)
+        dateFormat.timeZone = TimeZone.getTimeZone(TimeZone.getDefault().id)
+        for (syncedFolder in syncedFolderProvider.syncedFolders) {
+            if (syncedFolder.isEnabled && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.type)) {
+                syncFolder(context, resources, lightVersion, filesystemDataProvider, currentLocale, dateFormat,
+                    syncedFolder)
+            }
+        }
+        wakeLock?.release()
+        return Result.success()
+    }
+
+    @Suppress("LongMethod") // legacy code
+    private fun syncFolder(
+        context: Context,
+        resources: Resources,
+        lightVersion: Boolean,
+        filesystemDataProvider: FilesystemDataProvider,
+        currentLocale: Locale,
+        sFormatter: SimpleDateFormat,
+        syncedFolder: SyncedFolder
+    ) {
+        var remotePath: String?
+        var subfolderByDate: Boolean
+        var uploadAction: Int?
+        var needsCharging: Boolean
+        var needsWifi: Boolean
+        var file: File
+        val accountName = syncedFolder.account
+        val optionalUser = userAccountManager.getUser(accountName)
+        if (!optionalUser.isPresent) {
+            return
+        }
+        val user = optionalUser.get()
+        val arbitraryDataProvider = if (lightVersion) {
+            ArbitraryDataProvider(contentResolver)
+        } else {
+            null
+        }
+        val paths = filesystemDataProvider.getFilesForUpload(
+            syncedFolder.localPath,
+            java.lang.Long.toString(syncedFolder.id)
+        )
+        for (path in paths) {
+            file = File(path)
+            val lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter)
+            val mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.absolutePath)
+            if (lightVersion) {
+                needsCharging = resources.getBoolean(R.bool.syncedFolder_light_on_charging)
+                needsWifi = arbitraryDataProvider!!.getBooleanValue(accountName,
+                    SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI)
+                val uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour)
+                uploadAction = getUploadAction(uploadActionString)
+                subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders)
+                remotePath = resources.getString(R.string.syncedFolder_remote_folder)
+            } else {
+                needsCharging = syncedFolder.isChargingOnly
+                needsWifi = syncedFolder.isWifiOnly
+                uploadAction = syncedFolder.uploadAction
+                subfolderByDate = syncedFolder.isSubfolderByDate
+                remotePath = syncedFolder.remotePath
+            }
+            FileUploader.uploadNewFile(
+                context,
+                user.toPlatformAccount(),
+                file.absolutePath,
+                FileStorageUtils.getInstantUploadFilePath(
+                    file,
+                    currentLocale,
+                    remotePath,
+                    syncedFolder.localPath,
+                    lastModificationTime,
+                    subfolderByDate
+                ),
+                uploadAction!!,
+                mimeType,
+                true, // create parent folder if not existent
+                UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
+                needsWifi,
+                needsCharging,
+                FileUploader.NameCollisionPolicy.ASK_USER
+            )
+            filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
+                java.lang.Long.toString(syncedFolder.id))
+        }
+    }
+
+    private fun hasExif(file: File): Boolean {
+        val mimeType = FileStorageUtils.getMimeTypeFromName(file.absolutePath)
+        return MimeType.JPEG.equals(mimeType, ignoreCase = true) || MimeType.TIFF.equals(mimeType, ignoreCase = true)
+    }
+
+    private fun calculateLastModificationTime(
+        file: File,
+        syncedFolder: SyncedFolder,
+        formatter: SimpleDateFormat
+    ): Long {
+        var lastModificationTime = file.lastModified()
+        if (MediaFolderType.IMAGE == syncedFolder.type && hasExif(file)) {
+            @Suppress("TooGenericExceptionCaught") // legacy code
+            try {
+                val exifInterface = ExifInterface(file.absolutePath)
+                val exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME)
+                if (!TextUtils.isEmpty(exifDate)) {
+                    val pos = ParsePosition(0)
+                    val dateTime = formatter.parse(exifDate, pos)
+                    lastModificationTime = dateTime.time
+                }
+            } catch (e: Exception) {
+                Log_OC.d(TAG, "Failed to get the proper time " + e.localizedMessage)
+            }
+        }
+        return lastModificationTime
+    }
+
+    private fun getUploadAction(action: String): Int? {
+        return when (action) {
+            "LOCAL_BEHAVIOUR_FORGET" -> FileUploader.LOCAL_BEHAVIOUR_FORGET
+            "LOCAL_BEHAVIOUR_MOVE" -> FileUploader.LOCAL_BEHAVIOUR_MOVE
+            "LOCAL_BEHAVIOUR_DELETE" -> FileUploader.LOCAL_BEHAVIOUR_DELETE
+            else -> FileUploader.LOCAL_BEHAVIOUR_FORGET
+        }
+    }
+}

+ 244 - 0
src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt

@@ -0,0 +1,244 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author Andy Scherzinger
+ * @author Chris Narkiewicz
+ * Copyright (C) 2018 Mario Danic
+ * Copyright (C) 2018 Andy Scherzinger
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * 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.nextcloud.client.jobs
+
+import android.app.Activity
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.graphics.BitmapFactory
+import android.media.RingtoneManager
+import android.text.TextUtils
+import androidx.core.app.NotificationCompat
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.google.gson.Gson
+import com.nextcloud.client.account.User
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.core.Clock
+import com.nextcloud.client.preferences.AppPreferences
+import com.nextcloud.client.preferences.AppPreferencesImpl
+import com.owncloud.android.R
+import com.owncloud.android.datamodel.ArbitraryDataProvider
+import com.owncloud.android.datamodel.MediaFoldersModel
+import com.owncloud.android.datamodel.MediaProvider
+import com.owncloud.android.datamodel.SyncedFolderProvider
+import com.owncloud.android.jobs.NotificationJob
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.ui.activity.ManageAccountsActivity
+import com.owncloud.android.ui.activity.SyncedFoldersActivity
+import com.owncloud.android.ui.notifications.NotificationUtils
+import com.owncloud.android.utils.ThemeUtils
+import java.util.ArrayList
+import java.util.Random
+
+@Suppress("LongParameterList") // dependencies injection
+class MediaFoldersDetectionWork constructor(
+    private val context: Context,
+    params: WorkerParameters,
+    private val resources: Resources,
+    private val contentResolver: ContentResolver,
+    private val userAccountManager: UserAccountManager,
+    private val preferences: AppPreferences,
+    private val clock: Clock
+) : Worker(context, params) {
+
+    companion object {
+        const val TAG = "MediaFoldersDetectionJob"
+        const val KEY_MEDIA_FOLDER_PATH = "KEY_MEDIA_FOLDER_PATH"
+        const val KEY_MEDIA_FOLDER_TYPE = "KEY_MEDIA_FOLDER_TYPE"
+        private const val ACCOUNT_NAME_GLOBAL = "global"
+        private const val KEY_MEDIA_FOLDERS = "media_folders"
+        const val NOTIFICATION_ID = "NOTIFICATION_ID"
+        private const val DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK"
+    }
+
+    private val randomIdGenerator = Random(clock.currentTime)
+
+    @Suppress("LongMethod", "ComplexMethod", "NestedBlockDepth") // legacy code
+    override fun doWork(): Result {
+        val arbitraryDataProvider = ArbitraryDataProvider(contentResolver)
+        val syncedFolderProvider = SyncedFolderProvider(contentResolver, preferences, clock)
+        val gson = Gson()
+        val arbitraryDataString: String
+        val mediaFoldersModel: MediaFoldersModel
+        val imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true)
+        val videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true)
+        val imageMediaFolderPaths: MutableList<String> = ArrayList()
+        val videoMediaFolderPaths: MutableList<String> = ArrayList()
+        for (imageMediaFolder in imageMediaFolders) {
+            imageMediaFolderPaths.add(imageMediaFolder.absolutePath)
+        }
+        for (videoMediaFolder in videoMediaFolders) {
+            imageMediaFolderPaths.add(videoMediaFolder.absolutePath)
+        }
+        arbitraryDataString = arbitraryDataProvider.getValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS)
+        if (!TextUtils.isEmpty(arbitraryDataString)) {
+            mediaFoldersModel = gson.fromJson(arbitraryDataString, MediaFoldersModel::class.java)
+            // merge new detected paths with already notified ones
+            for (existingImageFolderPath in mediaFoldersModel.imageMediaFolders) {
+                if (!imageMediaFolderPaths.contains(existingImageFolderPath)) {
+                    imageMediaFolderPaths.add(existingImageFolderPath)
+                }
+            }
+            for (existingVideoFolderPath in mediaFoldersModel.videoMediaFolders) {
+                if (!videoMediaFolderPaths.contains(existingVideoFolderPath)) {
+                    videoMediaFolderPaths.add(existingVideoFolderPath)
+                }
+            }
+            // Store updated values
+            arbitraryDataProvider.storeOrUpdateKeyValue(
+                ACCOUNT_NAME_GLOBAL,
+                KEY_MEDIA_FOLDERS,
+                gson.toJson(MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths))
+            )
+            if (preferences.isShowMediaScanNotifications) {
+                imageMediaFolderPaths.removeAll(mediaFoldersModel.imageMediaFolders)
+                videoMediaFolderPaths.removeAll(mediaFoldersModel.videoMediaFolders)
+                if (!imageMediaFolderPaths.isEmpty() || !videoMediaFolderPaths.isEmpty()) {
+                    val allUsers = userAccountManager.allUsers
+                    val activeUsers: MutableList<User> = ArrayList()
+                    for (account in allUsers) {
+                        if (!arbitraryDataProvider.getBooleanValue(account.toPlatformAccount(),
+                                ManageAccountsActivity.PENDING_FOR_REMOVAL)) {
+                            activeUsers.add(account)
+                        }
+                    }
+                    for (user in activeUsers) {
+                        for (imageMediaFolder in imageMediaFolderPaths) {
+                            val folder = syncedFolderProvider.findByLocalPathAndAccount(imageMediaFolder,
+                                user.toPlatformAccount())
+                            if (folder == null) {
+                                val contentTitle = String.format(
+                                    resources.getString(R.string.new_media_folder_detected),
+                                    resources.getString(R.string.new_media_folder_photos)
+                                )
+                                sendNotification(contentTitle,
+                                    imageMediaFolder.substring(imageMediaFolder.lastIndexOf('/') + 1),
+                                    user,
+                                    imageMediaFolder,
+                                    1)
+                            }
+                        }
+                        for (videoMediaFolder in videoMediaFolderPaths) {
+                            val folder = syncedFolderProvider.findByLocalPathAndAccount(videoMediaFolder,
+                                user.toPlatformAccount())
+                            if (folder == null) {
+                                val contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
+                                    context.getString(R.string.new_media_folder_videos))
+                                sendNotification(contentTitle,
+                                    videoMediaFolder.substring(videoMediaFolder.lastIndexOf('/') + 1),
+                                    user,
+                                    videoMediaFolder,
+                                    2)
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            mediaFoldersModel = MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths)
+            arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS,
+                gson.toJson(mediaFoldersModel))
+        }
+        return Result.success()
+    }
+
+    private fun sendNotification(contentTitle: String, subtitle: String, user: User, path: String, type: Int) {
+        val notificationId = randomIdGenerator.nextInt()
+        val context = context
+        val intent = Intent(context, SyncedFoldersActivity::class.java)
+        intent.putExtra(NOTIFICATION_ID, notificationId)
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        intent.putExtra(NotificationJob.KEY_NOTIFICATION_ACCOUNT, user.accountName)
+        intent.putExtra(KEY_MEDIA_FOLDER_PATH, path)
+        intent.putExtra(KEY_MEDIA_FOLDER_TYPE, type)
+        intent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, true)
+        val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT)
+        val notificationBuilder = NotificationCompat.Builder(
+            context, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL)
+            .setSmallIcon(R.drawable.notification_icon)
+            .setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
+            .setColor(ThemeUtils.primaryColor(context))
+            .setSubText(user.accountName)
+            .setContentTitle(contentTitle)
+            .setContentText(subtitle)
+            .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
+            .setAutoCancel(true)
+            .setContentIntent(pendingIntent)
+        val disableDetection = Intent(context, NotificationReceiver::class.java)
+        disableDetection.putExtra(NOTIFICATION_ID, notificationId)
+        disableDetection.action = DISABLE_DETECTION_CLICK
+        val disableIntent = PendingIntent.getBroadcast(
+            context,
+            notificationId,
+            disableDetection,
+            PendingIntent.FLAG_CANCEL_CURRENT
+        )
+        notificationBuilder.addAction(
+            NotificationCompat.Action(
+                R.drawable.ic_close,
+                context.getString(R.string.disable_new_media_folder_detection_notifications),
+                disableIntent
+            )
+        )
+        val configureIntent = PendingIntent.getActivity(
+            context,
+            notificationId,
+            intent,
+            PendingIntent.FLAG_CANCEL_CURRENT
+        )
+        notificationBuilder.addAction(
+            NotificationCompat.Action(
+                R.drawable.ic_settings,
+                context.getString(R.string.configure_new_media_folder_detection_notifications),
+                configureIntent
+            )
+        )
+        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+        notificationManager.notify(notificationId, notificationBuilder.build())
+    }
+
+    class NotificationReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            val action = intent.action
+            val notificationId = intent.getIntExtra(NOTIFICATION_ID, 0)
+            val preferences = AppPreferencesImpl.fromContext(context)
+            if (DISABLE_DETECTION_CLICK == action) {
+                Log_OC.d(this, "Disable media scan notifications")
+                preferences.isShowMediaScanNotifications = false
+                cancel(context, notificationId)
+            }
+        }
+
+        private fun cancel(context: Context, notificationId: Int) {
+            val notificationManager = context.getSystemService(Activity.NOTIFICATION_SERVICE) as NotificationManager
+            notificationManager.cancel(notificationId)
+        }
+    }
+}

+ 143 - 0
src/main/java/com/nextcloud/client/jobs/OfflineSyncWork.kt

@@ -0,0 +1,143 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Mario Danic
+ * @author Chris Narkiewicz
+ * Copyright (C) 2018 Mario Danic
+ * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ *
+ * 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.nextcloud.client.jobs
+
+import android.content.ContentResolver
+import android.content.Context
+import android.os.Build
+import android.os.PowerManager
+import android.os.PowerManager.WakeLock
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.account.User
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.network.ConnectivityService
+import com.owncloud.android.MainApp
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.lib.resources.files.CheckEtagRemoteOperation
+import com.owncloud.android.operations.SynchronizeFileOperation
+import com.owncloud.android.utils.FileStorageUtils
+import java.io.File
+
+@Suppress("LongParameterList") // Legacy code
+class OfflineSyncWork constructor(
+    private val context: Context,
+    params: WorkerParameters,
+    private val contentResolver: ContentResolver,
+    private val userAccountManager: UserAccountManager,
+    private val connectivityService: ConnectivityService,
+    private val powerManagementService: PowerManagementService
+) : Worker(context, params) {
+
+    companion object {
+        const val TAG = "OfflineSyncJob"
+        private const val WAKELOCK_TAG_SEPARATION = ":"
+        private const val WAKELOCK_ACQUISITION_TIMEOUT_MS = 10L * 60L * 1000L
+    }
+
+    override fun doWork(): Result {
+        var wakeLock: WakeLock? = null
+        if (!powerManagementService.isPowerSavingEnabled && !connectivityService.isInternetWalled) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+                val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+                val wakeLockTag = MainApp.getAuthority() + WAKELOCK_TAG_SEPARATION + TAG
+                wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag)
+                wakeLock.acquire(WAKELOCK_ACQUISITION_TIMEOUT_MS)
+            }
+            val users = userAccountManager.allUsers
+            for (user in users) {
+                val storageManager = FileDataStorageManager(user.toPlatformAccount(), contentResolver)
+                val ocRoot = storageManager.getFileByPath(OCFile.ROOT_PATH)
+                if (ocRoot.storagePath == null) {
+                    break
+                }
+                recursive(File(ocRoot.storagePath), storageManager, user)
+            }
+            wakeLock?.release()
+        }
+        return Result.success()
+    }
+
+    @Suppress("ReturnCount", "ComplexMethod") // legacy code
+    private fun recursive(folder: File, storageManager: FileDataStorageManager, user: User) {
+        val downloadFolder = FileStorageUtils.getSavePath(user.accountName)
+        val folderName = folder.absolutePath.replaceFirst(downloadFolder.toRegex(), "") + OCFile.PATH_SEPARATOR
+        Log_OC.d(TAG, "$folderName: enter")
+        // exit
+        if (folder.listFiles() == null) {
+            return
+        }
+        val ocFolder = storageManager.getFileByPath(folderName)
+        Log_OC.d(TAG, folderName + ": currentEtag: " + ocFolder.etag)
+        // check for etag change, if false, skip
+        val checkEtagOperation = CheckEtagRemoteOperation(ocFolder.remotePath,
+            ocFolder.etagOnServer)
+        val result = checkEtagOperation.execute(user.toPlatformAccount(), context)
+        when (result.code) {
+            ResultCode.ETAG_UNCHANGED -> {
+                Log_OC.d(TAG, "$folderName: eTag unchanged")
+                return
+            }
+            ResultCode.FILE_NOT_FOUND -> {
+                val removalResult = storageManager.removeFolder(ocFolder, true, true)
+                if (!removalResult) {
+                    Log_OC.e(TAG, "removal of " + ocFolder.storagePath + " failed: file not found")
+                }
+                return
+            }
+            ResultCode.ETAG_CHANGED -> Log_OC.d(TAG, "$folderName: eTag changed")
+            else -> Log_OC.d(TAG, "$folderName: eTag changed")
+        }
+        // iterate over downloaded files
+        val files = folder.listFiles { obj: File -> obj.isFile }
+        if (files != null) {
+            for (file in files) {
+                val ocFile = storageManager.getFileByLocalPath(file.path)
+                val synchronizeFileOperation = SynchronizeFileOperation(ocFile.remotePath,
+                    user,
+                    true,
+                    context)
+                synchronizeFileOperation.execute(storageManager, context)
+            }
+        }
+        // recursive into folder
+        val subfolders = folder.listFiles { obj: File -> obj.isDirectory }
+        if (subfolders != null) {
+            for (subfolder in subfolders) {
+                recursive(subfolder, storageManager, user)
+            }
+        }
+        // update eTag
+        @Suppress("TooGenericExceptionCaught") // legacy code
+        try {
+            val updatedEtag = result.data[0] as String
+            ocFolder.etagOnServer = updatedEtag
+            storageManager.saveFile(ocFolder)
+        } catch (e: Exception) {
+            Log_OC.e(TAG, "Failed to update etag on " + folder.absolutePath, e)
+        }
+    }
+}

+ 13 - 43
src/main/java/com/owncloud/android/MainApp.java

@@ -41,7 +41,6 @@ import android.text.TextUtils;
 import android.view.WindowManager;
 
 import com.evernote.android.job.JobManager;
-import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.appinfo.AppInfo;
@@ -70,7 +69,6 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.datastorage.DataStorageProvider;
 import com.owncloud.android.datastorage.StoragePoint;
-import com.owncloud.android.jobs.MediaFoldersDetectionJob;
 import com.owncloud.android.jobs.NCJobCreator;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -95,7 +93,6 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.concurrent.TimeUnit;
 
 import javax.inject.Inject;
 import javax.net.ssl.SSLContext;
@@ -264,10 +261,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
             new NCJobCreator(
                 getApplicationContext(),
                 accountManager,
-                preferences,
                 uploadsStorageManager,
-                connectivityService,
-                powerManagementService,
                 clock,
                 eventBus,
                 backgroundJobManager
@@ -302,7 +296,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
                 Log_OC.d("Debug", "Failed to disable uri exposure");
             }
         }
-        initSyncOperations(uploadsStorageManager,
+        initSyncOperations(preferences,
+                           uploadsStorageManager,
                            accountManager,
                            connectivityService,
                            powerManagementService,
@@ -311,18 +306,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         initContactsBackup(accountManager, backgroundJobManager);
         notificationChannels();
 
-
-        new JobRequest.Builder(MediaFoldersDetectionJob.TAG)
-            .setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
-            .setUpdateCurrent(true)
-            .build()
-            .schedule();
-
-        new JobRequest.Builder(MediaFoldersDetectionJob.TAG)
-            .startNow()
-            .setUpdateCurrent(false)
-            .build()
-            .schedule();
+        backgroundJobManager.scheduleMediaFoldersDetectionJob();
+        backgroundJobManager.startMediaFoldersDetectionJob();
 
         registerGlobalPassCodeProtection();
     }
@@ -461,11 +446,12 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
     }
 
     public static void initSyncOperations(
+        final AppPreferences preferences,
         final UploadsStorageManager uploadsStorageManager,
         final UserAccountManager accountManager,
         final ConnectivityService connectivityService,
         final PowerManagementService powerManagementService,
-        final BackgroundJobManager jobManager,
+        final BackgroundJobManager backgroundJobManager,
         final Clock clock
     ) {
         updateToAutoUpload();
@@ -477,20 +463,23 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
                                                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                 splitOutAutoUploadEntries(clock);
             } else {
-                AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
                 preferences.setAutoUploadSplitEntriesEnabled(true);
             }
         }
 
-        initiateExistingAutoUploadEntries(clock);
+        if (!preferences.isAutoUploadInitialized()) {
+            backgroundJobManager.startImmediateFilesSyncJob(false, false);
+            preferences.setAutoUploadInit(true);
+        }
 
-        FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, jobManager);
+        FilesSyncHelper.scheduleFilesSyncIfNeeded(mContext, backgroundJobManager);
         FilesSyncHelper.restartJobsIfNeeded(
             uploadsStorageManager,
             accountManager,
             connectivityService,
             powerManagementService);
-        FilesSyncHelper.scheduleOfflineSyncIfNeeded();
+
+        backgroundJobManager.scheduleOfflineSync();
 
         ReceiversHelper.registerNetworkChangeReceiver(uploadsStorageManager,
                                                       accountManager,
@@ -755,25 +744,6 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         }
     }
 
-    private static void initiateExistingAutoUploadEntries(Clock clock) {
-        new Thread(() -> {
-            AppPreferences preferences = AppPreferencesImpl.fromContext(getAppContext());
-            if (!preferences.isAutoUploadInitialized()) {
-                SyncedFolderProvider syncedFolderProvider =
-                    new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock);
-
-                for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
-                    if (syncedFolder.isEnabled()) {
-                        FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolder, true);
-                    }
-                }
-
-                preferences.setAutoUploadInit(true);
-            }
-
-        }).start();
-    }
-
     private static void cleanOldEntries(Clock clock) {
         // previous versions of application created broken entries in the SyncedFolderProvider
         // database, and this cleans all that and leaves 1 (newest) entry per synced folder

+ 123 - 122
src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java

@@ -254,6 +254,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     private long mWaitingForOpId = Long.MAX_VALUE;
 
     private boolean webViewLoginMethod;
+    private boolean showWebViewLoginUrl;
     private String webViewUser;
     private String webViewPassword;
     private boolean forceOldLoginMethod;
@@ -266,7 +267,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * {@inheritDoc}
-     *
+     * <p>
      * IMPORTANT ENTRY POINT 1: activity is shown to the user
      */
     @Override
@@ -321,6 +322,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             showLegacyLogin = false;
         } else {
             webViewLoginMethod = !TextUtils.isEmpty(getResources().getString(R.string.webview_login_url));
+            showWebViewLoginUrl = getResources().getBoolean(R.bool.show_server_url_input);
             showLegacyLogin = true;
         }
 
@@ -335,9 +337,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             /// initialize general UI elements
             initOverallUi();
 
-            findViewById(R.id.centeredRefreshButton).setOnClickListener(v -> checkOcServer());
-
-            findViewById(R.id.embeddedRefreshButton).setOnClickListener(v -> checkOcServer());
+            findViewById(R.id.centered_refresh_button).setOnClickListener(v -> checkOcServer());
+            findViewById(R.id.embedded_refresh_button).setOnClickListener(v -> checkOcServer());
 
             /// initialize block to be moved to single Fragment to check server and get info about it
 
@@ -592,13 +593,13 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         for (String value : values) {
             if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
                 loginUrlInfo.username = URLDecoder.decode(
-                        value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
+                    value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
             } else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
                 loginUrlInfo.password = URLDecoder.decode(
-                        value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
+                    value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
             } else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
                 loginUrlInfo.serverAddress = URLDecoder.decode(
-                        value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
+                    value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length()));
             }
         }
 
@@ -609,17 +610,17 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
      * Configures elements in the user interface under direct control of the Activity.
      */
     private void initOverallUi() {
-        mHostUrlInput = findViewById(R.id.hostUrlInput);
+        mHostUrlInput = findViewById(R.id.host_url_input);
         mPasswordInput = findViewById(R.id.account_password);
         mUsernameInput = findViewById(R.id.account_username);
         mAuthStatusView = findViewById(R.id.auth_status_text);
         mServerStatusView = findViewById(R.id.server_status_text);
-        mTestServerButton = findViewById(R.id.testServerButton);
+        mTestServerButton = findViewById(R.id.test_server_button);
 
-        mOkButton = findViewById(R.id.buttonOK);
+        mOkButton = findViewById(R.id.button_ok);
         mOkButton.setOnClickListener(v -> onOkClick());
 
-        scanQR = findViewById(R.id.scanQR);
+        scanQR = findViewById(R.id.scan_qr);
         if (deviceInfo.hasCamera(this)) {
             scanQR.setOnClickListener(v -> onScan());
             ThemeUtils.tintDrawable(scanQR.getDrawable(), getResources().getColor(R.color.login_text_color));
@@ -628,7 +629,6 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         }
 
         setupInstructionMessage();
-
         mTestServerButton.setVisibility(mAction == ACTION_CREATE ? View.VISIBLE : View.GONE);
     }
 
@@ -692,13 +692,13 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                 mServerInfo.mVersion = new OwnCloudVersion(ocVersion);
             }
             mServerInfo.mAuthMethod = AuthenticationMethod.valueOf(
-                    savedInstanceState.getString(KEY_SERVER_AUTH_METHOD));
+                savedInstanceState.getString(KEY_SERVER_AUTH_METHOD));
 
         }
 
         if (!webViewLoginMethod) {
             /// step 2 - set properties of UI elements (text, visibility, enabled...)
-            mHostUrlInput = findViewById(R.id.hostUrlInput);
+            mHostUrlInput = findViewById(R.id.host_url_input);
             // Convert IDN to Unicode
             mHostUrlInput.setText(DisplayUtils.convertIdn(mServerInfo.mBaseUrl, false));
             if (mAction != ACTION_CREATE) {
@@ -707,13 +707,13 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                 mHostUrlInput.setFocusable(false);
             }
             if (isUrlInputAllowed) {
-                mRefreshButton = findViewById(R.id.embeddedRefreshButton);
+                mRefreshButton = findViewById(R.id.embedded_refresh_button);
             } else {
-                findViewById(R.id.hostUrlFrame).setVisibility(View.GONE);
-                mRefreshButton = findViewById(R.id.centeredRefreshButton);
+                findViewById(R.id.host_url_frame).setVisibility(View.GONE);
+                mRefreshButton = findViewById(R.id.centered_refresh_button);
             }
             showRefreshButton(mServerIsChecked && !mServerIsValid &&
-                    mWaitingForOpId > Integer.MAX_VALUE);
+                                  mWaitingForOpId > Integer.MAX_VALUE);
             mServerStatusView = findViewById(R.id.server_status_text);
             showServerStatus();
 
@@ -727,8 +727,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                 @Override
                 public void afterTextChanged(Editable s) {
                     if (mOkButton.isEnabled() &&
-                            !mServerInfo.mBaseUrl.equals(
-                                    AuthenticatorUrlUtils.normalizeUrl(s.toString(), mServerInfo.mIsSslConn))) {
+                        !mServerInfo.mBaseUrl.equals(
+                            AuthenticatorUrlUtils.normalizeUrl(s.toString(), mServerInfo.mIsSslConn))) {
                         mOkButton.setEnabled(false);
                     }
                 }
@@ -810,7 +810,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Saves relevant state before {@link #onPause()}
-     *
+     * <p>
      * See {@link super#onSaveInstanceState(Bundle)}
      */
     @Override
@@ -880,11 +880,11 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     }
 
     /**
-     * The redirection triggered by the OAuth authentication server as response to the
-     * GET AUTHORIZATION request is caught here.
-     *
-     * To make this possible, this activity needs to be qualified with android:launchMode =
-     * "singleTask" in the AndroidManifest.xml file.
+     * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION request is
+     * caught here.
+     * <p>
+     * To make this possible, this activity needs to be qualified with android:launchMode = "singleTask" in the
+     * AndroidManifest.xml file.
      */
     @Override
     protected void onNewIntent(Intent intent) {
@@ -924,8 +924,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
 
     /**
-     * The redirection triggered by the OAuth authentication server as response to the
-     * GET AUTHORIZATION, and deferred in {@link #onNewIntent(Intent)}, is processed here.
+     * The redirection triggered by the OAuth authentication server as response to the GET AUTHORIZATION, and deferred
+     * in {@link #onNewIntent(Intent)}, is processed here.
      */
     @Override
     protected void onResume() {
@@ -957,8 +957,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         // bind to Operations Service
         mOperationsServiceConnection = new OperationsServiceConnection();
         if (!bindService(new Intent(this, OperationsService.class),
-                mOperationsServiceConnection,
-                Context.BIND_AUTO_CREATE)) {
+                         mOperationsServiceConnection,
+                         Context.BIND_AUTO_CREATE)) {
             DisplayUtils.showSnackMessage(findViewById(R.id.scroll), R.string.error_cant_bind_to_operations_service);
             finish();
         }
@@ -1004,7 +1004,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
      * Handles the change of focus on the text inputs for the server URL and the password
      */
     public void onFocusChange(View view, boolean hasFocus) {
-        if (view.getId() == R.id.hostUrlInput) {
+        if (view.getId() == R.id.host_url_input) {
             if (!hasFocus) {
                 onUrlInputFocusLost();
             } else {
@@ -1019,16 +1019,15 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Handles changes in focus on the text input for the server URL.
-     *
-     * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to
-     * other field. The operation to check the existence of the server in the entered URL is
-     * started.
-     *
+     * <p>
+     * IMPORTANT ENTRY POINT 2: When (!hasFocus), user wrote the server URL and changed to other field. The operation to
+     * check the existence of the server in the entered URL is started.
+     * <p>
      * When hasFocus:    user 'comes back' to write again the server URL.
      */
     private void onUrlInputFocusLost() {
         if (!mServerInfo.mBaseUrl.equals(
-                AuthenticatorUrlUtils.normalizeUrl(mHostUrlInput.getText().toString(), mServerInfo.mIsSslConn))) {
+            AuthenticatorUrlUtils.normalizeUrl(mHostUrlInput.getText().toString(), mServerInfo.mIsSslConn))) {
             // check server again only if the user changed something in the field
             checkOcServer();
         } else {
@@ -1075,7 +1074,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             Intent getServerInfoIntent = new Intent();
             getServerInfoIntent.setAction(OperationsService.ACTION_GET_SERVER_INFO);
             getServerInfoIntent.putExtra(OperationsService.EXTRA_SERVER_URL,
-                    AuthenticatorUrlUtils.normalizeUrlSuffix(uri));
+                                         AuthenticatorUrlUtils.normalizeUrlSuffix(uri));
 
             if (mOperationsServiceBinder != null) {
                 mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);
@@ -1095,9 +1094,9 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Handles changes in focus on the text input for the password (basic authorization).
-     *
+     * <p>
      * When (hasFocus), the button to toggle password visibility is shown.
-     *
+     * <p>
      * When (!hasFocus), the button is made invisible and the password is hidden.
      *
      * @param hasFocus 'True' if focus is received, 'false' if is lost
@@ -1121,7 +1120,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     private boolean isPasswordVisible() {
         return ((mPasswordInput.getInputType() & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) ==
-                InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+            InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
     }
 
     private void hidePasswordButton() {
@@ -1130,32 +1129,29 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     private void showPassword() {
         mPasswordInput.setInputType(
-                InputType.TYPE_CLASS_TEXT |
-                        InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD |
-                        InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+            InputType.TYPE_CLASS_TEXT |
+                InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD |
+                InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
         );
         showViewPasswordButton();
     }
 
     private void hidePassword() {
         mPasswordInput.setInputType(
-                InputType.TYPE_CLASS_TEXT |
-                        InputType.TYPE_TEXT_VARIATION_PASSWORD |
-                        InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
+            InputType.TYPE_CLASS_TEXT |
+                InputType.TYPE_TEXT_VARIATION_PASSWORD |
+                InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
         );
         showViewPasswordButton();
     }
 
     /**
-     * Checks the credentials of the user in the root of the ownCloud server
-     * before creating a new local account.
-     *
-     * For basic authorization, a check of existence of the root folder is
-     * performed.
-     *
-     * For OAuth, starts the flow to get an access token; the credentials test
-     * is postponed until it is available.
-     *
+     * Checks the credentials of the user in the root of the ownCloud server before creating a new local account.
+     * <p>
+     * For basic authorization, a check of existence of the root folder is performed.
+     * <p>
+     * For OAuth, starts the flow to get an access token; the credentials test is postponed until it is available.
+     * <p>
      * IMPORTANT ENTRY POINT 4
      */
     public void onOkClick() {
@@ -1175,8 +1171,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
 
     /**
-     * Tests the credentials entered by the user performing a check of existence on
-     * the root folder of the ownCloud server.
+     * Tests the credentials entered by the user performing a check of existence on the root folder of the ownCloud
+     * server.
      */
     private void checkBasicAuthorization(@Nullable String webViewUsername, @Nullable String webViewPassword) {
         /// get basic credentials entered by user
@@ -1210,7 +1206,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Callback method invoked when a RemoteOperation executed by this Activity finishes.
-     *
+     * <p>
      * Dispatches the operation flow to the right method.
      */
     @Override
@@ -1279,8 +1275,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     }
 
     /**
-     * Processes the result of the server check performed when the user finishes the enter of the
-     * server URL.
+     * Processes the result of the server check performed when the user finishes the enter of the server URL.
      *
      * @param result Result of the check.
      */
@@ -1312,7 +1307,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             webViewLoginMethod = !forceOldLoginMethod;
 
             if (webViewUser != null && !webViewUser.isEmpty() &&
-                    webViewPassword != null && !webViewPassword.isEmpty()) {
+                webViewPassword != null && !webViewPassword.isEmpty()) {
                 checkBasicAuthorization(webViewUser, webViewPassword);
             } else if (webViewLoginMethod) {
                 // hide old login
@@ -1447,10 +1442,10 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                 break;
             case UNKNOWN_ERROR:
                 if (result.getException() != null &&
-                        !TextUtils.isEmpty(result.getException().getMessage())) {
+                    !TextUtils.isEmpty(result.getException().getMessage())) {
                     mServerStatusText = getResources().getString(
-                            R.string.auth_unknown_error_exception_title,
-                            result.getException().getMessage()
+                        R.string.auth_unknown_error_exception_title,
+                        result.getException().getMessage()
                     );
                 } else {
                     mServerStatusText = getResources().getString(R.string.auth_unknown_error_title);
@@ -1490,12 +1485,14 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
             case OK_NO_SSL:
             case OK:
-                if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
-                    mAuthStatusText = getResources().getString(R.string.auth_connection_established);
-                    mAuthStatusIcon = R.drawable.ic_ok;
-                } else {
-                    mAuthStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
-                    mAuthStatusIcon = R.drawable.ic_lock_open_white;
+                if (showWebViewLoginUrl) {
+                    if (mHostUrlInput.getText().toString().trim().toLowerCase(Locale.ROOT).startsWith(HTTP_PROTOCOL)) {
+                        mAuthStatusText = getResources().getString(R.string.auth_connection_established);
+                        mAuthStatusIcon = R.drawable.ic_ok;
+                    } else {
+                        mAuthStatusText = getResources().getString(R.string.auth_nossl_plain_ok_title);
+                        mAuthStatusIcon = R.drawable.ic_lock_open_white;
+                    }
                 }
                 break;
 
@@ -1513,9 +1510,18 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             case HOST_NOT_AVAILABLE:
                 mAuthStatusText = getResources().getString(R.string.auth_unknown_host_title);
                 break;
+            case ACCOUNT_NOT_NEW:
+                mAuthStatusText = getString(R.string.auth_account_not_new);
+                if (webViewLoginMethod && !showWebViewLoginUrl) {
+                    DisplayUtils.showErrorAndFinishActivity(this, mAuthStatusText);
+                }
+                break;
             case UNHANDLED_HTTP_CODE:
             default:
                 mAuthStatusText = ErrorMessageAdapter.getErrorCauseMessage(result, null, getResources());
+                if (webViewLoginMethod && !showWebViewLoginUrl) {
+                    DisplayUtils.showErrorAndFinishActivity(this, mAuthStatusText);
+                }
         }
     }
 
@@ -1531,7 +1537,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Processes the result of the access check performed to try the user credentials.
-     *
+     * <p>
      * Creates a new account through the AccountManager.
      *
      * @param result Result of the operation.
@@ -1569,32 +1575,26 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             deleteCookies();
 
             if (success) {
-                finish();
-
                 accountManager.setCurrentOwnCloudAccount(mAccount.name);
-
                 if (!onlyAdd) {
                     Intent i = new Intent(this, FileDisplayActivity.class);
                     i.setAction(FileDisplayActivity.RESTART);
                     i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                     startActivity(i);
                 }
-
             } else {
                 // init webView again
                 if (mLoginWebView != null) {
                     mLoginWebView.setVisibility(View.GONE);
                 }
                 setContentView(R.layout.account_setup);
-
                 initOverallUi();
 
-                EditText serverAddressField = findViewById(R.id.hostUrlInput);
-                serverAddressField.setText(mServerInfo.mBaseUrl);
-
-                findViewById(R.id.server_status_text).setVisibility(View.GONE);
                 mAuthStatusView = findViewById(R.id.auth_status_text);
 
+
+                mHostUrlInput.setText(mServerInfo.mBaseUrl);
+                mServerStatusView.setVisibility(View.GONE);
                 showAuthStatus();
             }
 
@@ -1638,17 +1638,17 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                     if (mLoginWebView != null) {
                         mLoginWebView.setVisibility(View.GONE);
                     }
-                    setContentView(R.layout.account_setup);
 
-                    initOverallUi();
-
-                    EditText serverAddressField = findViewById(R.id.hostUrlInput);
-                    serverAddressField.setText(mServerInfo.mBaseUrl);
-
-                    findViewById(R.id.server_status_text).setVisibility(View.GONE);
-                    mAuthStatusView = findViewById(R.id.auth_status_text);
+                    if (webViewLoginMethod) {
+                        updateAuthStatusIconAndText(result);
+                    } else {
+                        setContentView(R.layout.account_setup);
+                        initOverallUi();
 
-                    showAuthStatus();
+                        mHostUrlInput.setText(mServerInfo.mBaseUrl);
+                        mServerStatusView.setVisibility(View.GONE);
+                        showAuthStatus();
+                    }
                 }
             } else {
                 updateAuthStatusIconAndText(result);
@@ -1665,12 +1665,12 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Updates the authentication token.
-     *
-     * Sets the proper response so that the AccountAuthenticator that started this activity
-     * saves a new authorization token for mAccount.
-     *
-     * Kills the session kept by OwnCloudClientManager so that a new one will created with
-     * the new credentials when needed.
+     * <p>
+     * Sets the proper response so that the AccountAuthenticator that started this activity saves a new authorization
+     * token for mAccount.
+     * <p>
+     * Kills the session kept by OwnCloudClientManager so that a new one will created with the new credentials when
+     * needed.
      */
     private void updateAccountAuthentication() throws AccountNotFoundException {
         Bundle response = new Bundle();
@@ -1698,9 +1698,9 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Creates a new account through the Account Authenticator that started this activity.
-     *
+     * <p>
      * This makes the account permanent.
-     *
+     * <p>
      * TODO Decide how to name the OAuth accounts
      */
     @SuppressFBWarnings("DMI")
@@ -1807,7 +1807,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     }
 
     @Override
-    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                            @NonNull int[] grantResults) {
         switch (requestCode) {
             case PermissionUtil.PERMISSIONS_CAMERA: {
@@ -1828,9 +1828,8 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     }
 
     /**
-    /**
-     * Updates the content and visibility state of the icon and text associated
-     * to the last check on the ownCloud server.
+     * /** Updates the content and visibility state of the icon and text associated to the last check on the ownCloud
+     * server.
      */
     private void showServerStatus() {
         if (mServerStatusIcon == NO_ICON && EMPTY_STRING.equals(mServerStatusText)) {
@@ -1843,16 +1842,18 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     }
 
     /**
-     * Updates the content and visibility state of the icon and text associated
-     * to the interactions with the OAuth authorization server.
+     * Updates the content and visibility state of the icon and text associated to the interactions with the OAuth
+     * authorization server.
      */
     private void showAuthStatus() {
-        if (mAuthStatusIcon == NO_ICON && EMPTY_STRING.equals(mAuthStatusText)) {
-            mAuthStatusView.setVisibility(View.INVISIBLE);
-        } else {
-            mAuthStatusView.setText(mAuthStatusText);
-            mAuthStatusView.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0);
-            mAuthStatusView.setVisibility(View.VISIBLE);
+        if (mAuthStatusView != null) {
+            if (mAuthStatusIcon == NO_ICON && EMPTY_STRING.equals(mAuthStatusText)) {
+                mAuthStatusView.setVisibility(View.INVISIBLE);
+            } else {
+                mAuthStatusView.setText(mAuthStatusText);
+                mAuthStatusView.setCompoundDrawablesWithIntrinsicBounds(mAuthStatusIcon, 0, 0, 0);
+                mAuthStatusView.setVisibility(View.VISIBLE);
+            }
         }
     }
 
@@ -1868,7 +1869,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Called when the eye icon in the password field is clicked.
-     *
+     * <p>
      * Toggles the visibility of the password in the field.
      */
     public void onViewPasswordClick() {
@@ -1884,20 +1885,20 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
 
     /**
      * Called when the 'action' button in an IME is pressed ('enter' in software keyboard).
-     *
-     * Used to trigger the authentication check when the user presses 'enter' after writing the
-     * password, or to throw the server test when the only field on screen is the URL input field.
+     * <p>
+     * Used to trigger the authentication check when the user presses 'enter' after writing the password, or to throw
+     * the server test when the only field on screen is the URL input field.
      */
     @Override
     public boolean onEditorAction(TextView inputField, int actionId, KeyEvent event) {
         if (actionId == EditorInfo.IME_ACTION_DONE && inputField != null &&
-                inputField.equals(mPasswordInput)) {
+            inputField.equals(mPasswordInput)) {
             if (mOkButton.isEnabled()) {
                 mOkButton.performClick();
             }
 
         } else if ((actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_NULL)
-                && inputField != null && inputField.equals(mHostUrlInput)) {
+            && inputField != null && inputField.equals(mHostUrlInput)) {
             checkOcServer();
         }
         return false;   // always return false to grant that the software keyboard is hidden anyway
@@ -1927,9 +1928,9 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                 final int y = (int) event.getY();
                 final Rect bounds = rightDrawable.getBounds();
                 if (x >= (view.getRight() - bounds.width() - fuzz) &&
-                        x <= (view.getRight() - view.getPaddingRight() + fuzz) &&
-                        y >= (view.getPaddingTop() - fuzz) &&
-                        y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) {
+                    x <= (view.getRight() - view.getPaddingRight() + fuzz) &&
+                    y >= (view.getPaddingTop() - fuzz) &&
+                    y <= (view.getHeight() - view.getPaddingBottom()) + fuzz) {
 
                     return onDrawableTouch(event);
                 }
@@ -1964,7 +1965,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     private void showUntrustedCertDialog(RemoteOperationResult result) {
         // Show a dialog with the certificate info
         SslUntrustedCertDialog dialog = SslUntrustedCertDialog.
-                newInstanceForFullSslError((CertificateCombinedException) result.getException());
+            newInstanceForFullSslError((CertificateCombinedException) result.getException());
         FragmentManager fm = getSupportFragmentManager();
         FragmentTransaction ft = fm.beginTransaction();
         ft.addToBackStack(null);
@@ -2007,7 +2008,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         @Override
         public void onServiceConnected(ComponentName component, IBinder service) {
             if (component.equals(
-                    new ComponentName(AuthenticatorActivity.this, OperationsService.class)
+                new ComponentName(AuthenticatorActivity.this, OperationsService.class)
             )) {
                 mOperationsServiceBinder = (OperationsServiceBinder) service;
 
@@ -2037,7 +2038,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         @Override
         public void onServiceDisconnected(ComponentName component) {
             if (component.equals(
-                    new ComponentName(AuthenticatorActivity.this, OperationsService.class)
+                new ComponentName(AuthenticatorActivity.this, OperationsService.class)
             )) {
                 Log_OC.e(TAG, "Operations service crashed");
                 mOperationsServiceBinder = null;

+ 4 - 1
src/main/java/com/owncloud/android/files/BootupBroadcastReceiver.java

@@ -32,6 +32,7 @@ import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.jobs.BackgroundJobManager;
 import com.nextcloud.client.network.ConnectivityService;
+import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -49,6 +50,7 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
 
     private static final String TAG = BootupBroadcastReceiver.class.getSimpleName();
 
+    @Inject AppPreferences preferences;
     @Inject UserAccountManager accountManager;
     @Inject UploadsStorageManager uploadsStorageManager;
     @Inject ConnectivityService connectivityService;
@@ -67,7 +69,8 @@ public class BootupBroadcastReceiver extends BroadcastReceiver {
         AndroidInjection.inject(this, context);
 
         if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
-            MainApp.initSyncOperations(uploadsStorageManager,
+            MainApp.initSyncOperations(preferences,
+                                       uploadsStorageManager,
                                        accountManager,
                                        connectivityService,
                                        powerManagementService,

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

@@ -1,273 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author Chris Narkiewicz
- * Copyright (C) 2017 Mario Danic
- * Copyright (C) 2017 Nextcloud
- * Copyright (C) 2919 Chris Narkiewicz
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.jobs;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Build;
-import android.os.PowerManager;
-import android.text.TextUtils;
-
-import com.evernote.android.job.Job;
-import com.evernote.android.job.util.support.PersistableBundleCompat;
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.core.Clock;
-import com.nextcloud.client.device.PowerManagementService;
-import com.nextcloud.client.network.ConnectivityService;
-import com.nextcloud.client.preferences.AppPreferences;
-import com.nextcloud.java.util.Optional;
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.ArbitraryDataProvider;
-import com.owncloud.android.datamodel.FilesystemDataProvider;
-import com.owncloud.android.datamodel.MediaFolderType;
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.datamodel.UploadsStorageManager;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.operations.UploadFileOperation;
-import com.owncloud.android.ui.activity.SettingsActivity;
-import com.owncloud.android.utils.FileStorageUtils;
-import com.owncloud.android.utils.FilesSyncHelper;
-import com.owncloud.android.utils.MimeType;
-import com.owncloud.android.utils.MimeTypeUtil;
-
-import java.io.File;
-import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import androidx.annotation.NonNull;
-import androidx.exifinterface.media.ExifInterface;
-
-/*
-    Job that:
-        - restarts existing jobs if required
-        - finds new and modified files since we last run this
-        - creates upload tasks
- */
-public class FilesSyncJob extends Job {
-    public static final String TAG = "FilesSyncJob";
-    public static final String SKIP_CUSTOM = "skipCustom";
-    public static final String OVERRIDE_POWER_SAVING = "overridePowerSaving";
-    private static final String WAKELOCK_TAG_SEPARATION = ":";
-
-    private final UserAccountManager userAccountManager;
-    private final AppPreferences preferences;
-    private final UploadsStorageManager uploadsStorageManager;
-    private final ConnectivityService connectivityService;
-    private final PowerManagementService powerManagementService;
-    private final Clock clock;
-
-    FilesSyncJob(final UserAccountManager userAccountManager,
-                        final AppPreferences preferences,
-                        final UploadsStorageManager uploadsStorageManager,
-                        final ConnectivityService connectivityService,
-                        final PowerManagementService powerManagementService,
-                        final Clock clock) {
-        this.userAccountManager = userAccountManager;
-        this.preferences = preferences;
-        this.uploadsStorageManager = uploadsStorageManager;
-        this.connectivityService = connectivityService;
-        this.powerManagementService = powerManagementService;
-        this.clock = clock;
-    }
-
-    @NonNull
-    @Override
-    protected Result onRunJob(@NonNull Params params) {
-        final Context context = MainApp.getAppContext();
-        PowerManager.WakeLock wakeLock = null;
-
-        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
-            PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-            wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
-                    WAKELOCK_TAG_SEPARATION + TAG);
-            wakeLock.acquire(10 * 60 * 1000);
-        }
-
-        PersistableBundleCompat bundle = params.getExtras();
-        final boolean overridePowerSaving = bundle.getBoolean(OVERRIDE_POWER_SAVING, false);
-
-        // If we are in power save mode, better to postpone upload
-        if (powerManagementService.isPowerSavingEnabled() && !overridePowerSaving) {
-            if (wakeLock != null) {
-                wakeLock.release();
-            }
-            return Result.SUCCESS;
-        }
-
-        Resources resources = MainApp.getAppContext().getResources();
-        boolean lightVersion = resources.getBoolean(R.bool.syncedFolder_light);
-
-        final boolean skipCustom = bundle.getBoolean(SKIP_CUSTOM, false);
-        FilesSyncHelper.restartJobsIfNeeded(uploadsStorageManager,
-                                            userAccountManager,
-                                            connectivityService,
-                                            powerManagementService);
-        FilesSyncHelper.insertAllDBEntries(preferences, clock, skipCustom, false);
-
-        // Create all the providers we'll need
-        final ContentResolver contentResolver = context.getContentResolver();
-        final FilesystemDataProvider filesystemDataProvider = new FilesystemDataProvider(contentResolver);
-        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
-
-        Locale currentLocale = context.getResources().getConfiguration().locale;
-        SimpleDateFormat sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", currentLocale);
-        sFormatter.setTimeZone(TimeZone.getTimeZone(TimeZone.getDefault().getID()));
-
-        for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
-            if ((syncedFolder.isEnabled()) && (!skipCustom || MediaFolderType.CUSTOM != syncedFolder.getType())) {
-                syncFolder(context, resources, lightVersion, filesystemDataProvider, currentLocale, sFormatter,
-                           syncedFolder);
-            }
-        }
-
-        if (wakeLock != null) {
-            wakeLock.release();
-        }
-
-        return Result.SUCCESS;
-    }
-
-    private void syncFolder(
-        Context context,
-        Resources resources,
-        boolean lightVersion,
-        FilesystemDataProvider filesystemDataProvider,
-        Locale currentLocale,
-        SimpleDateFormat sFormatter,
-        SyncedFolder syncedFolder
-    ) {
-        String accountName = syncedFolder.getAccount();
-        Optional<User> optionalUser = userAccountManager.getUser(accountName);
-        if (!optionalUser.isPresent()) {
-            return;
-        }
-        User user = optionalUser.get();
-
-        ArbitraryDataProvider arbitraryDataProvider = null;
-        if (lightVersion) {
-            arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
-        }
-
-        String remotePath;
-        boolean subfolderByDate;
-        Integer uploadAction;
-        FileUploader.NameCollisionPolicy nameCollisionPolicy;
-        boolean needsCharging;
-        boolean needsWifi;
-        File file;
-        for (String path : filesystemDataProvider.getFilesForUpload(syncedFolder.getLocalPath(),
-                Long.toString(syncedFolder.getId()))) {
-            file = new File(path);
-            Long lastModificationTime = calculateLastModificationTime(file, syncedFolder, sFormatter);
-            String mimeType = MimeTypeUtil.getBestMimeTypeByFilename(file.getAbsolutePath());
-
-            if (lightVersion) {
-                needsCharging = resources.getBoolean(R.bool.syncedFolder_light_on_charging);
-                needsWifi = arbitraryDataProvider.getBooleanValue(accountName,
-                                                                  SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI);
-                String uploadActionString = resources.getString(R.string.syncedFolder_light_upload_behaviour);
-                uploadAction = getUploadAction(uploadActionString);
-                nameCollisionPolicy = FileUploader.NameCollisionPolicy.ASK_USER;
-                subfolderByDate = resources.getBoolean(R.bool.syncedFolder_light_use_subfolders);
-                remotePath = resources.getString(R.string.syncedFolder_remote_folder);
-            } else {
-                needsCharging = syncedFolder.isChargingOnly();
-                needsWifi = syncedFolder.isWifiOnly();
-                uploadAction = syncedFolder.getUploadAction();
-                nameCollisionPolicy = FileUploader.NameCollisionPolicy.deserialize(
-                        syncedFolder.getNameCollisionPolicy());
-                subfolderByDate = syncedFolder.isSubfolderByDate();
-                remotePath = syncedFolder.getRemotePath();
-            }
-
-            FileUploader.uploadNewFile(
-                context,
-                user.toPlatformAccount(),
-                file.getAbsolutePath(),
-                FileStorageUtils.getInstantUploadFilePath(
-                        file,
-                        currentLocale,
-                        remotePath,
-                        syncedFolder.getLocalPath(),
-                        lastModificationTime,
-                        subfolderByDate),
-                uploadAction,
-                mimeType,
-                true,           // create parent folder if not existent
-                UploadFileOperation.CREATED_AS_INSTANT_PICTURE,
-                needsWifi,
-                needsCharging,
-                nameCollisionPolicy
-            );
-
-            filesystemDataProvider.updateFilesystemFileAsSentForUpload(path,
-                                                                       Long.toString(syncedFolder.getId()));
-        }
-    }
-
-    private Long calculateLastModificationTime(File file, SyncedFolder syncedFolder, SimpleDateFormat formatter) {
-        Long lastModificationTime = file.lastModified();
-
-        if (MediaFolderType.IMAGE == syncedFolder.getType()) {
-            String mimeTypeString = FileStorageUtils.getMimeTypeFromName(file.getAbsolutePath());
-            if (MimeType.JPEG.equalsIgnoreCase(mimeTypeString)
-                    || MimeType.TIFF.equalsIgnoreCase(mimeTypeString)) {
-                try {
-                    ExifInterface exifInterface = new ExifInterface(file.getAbsolutePath());
-                    String exifDate = exifInterface.getAttribute(ExifInterface.TAG_DATETIME);
-                    if (!TextUtils.isEmpty(exifDate)) {
-                        ParsePosition pos = new ParsePosition(0);
-                        Date dateTime = formatter.parse(exifDate, pos);
-                        lastModificationTime = dateTime.getTime();
-                    }
-                } catch (Exception e) {
-                    Log_OC.d(TAG, "Failed to get the proper time " + e.getLocalizedMessage());
-                }
-            }
-        }
-
-        return lastModificationTime;
-    }
-
-    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;
-        }
-    }
-}

+ 0 - 278
src/main/java/com/owncloud/android/jobs/MediaFoldersDetectionJob.java

@@ -1,278 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * @author Chris Narkiewicz
- * Copyright (C) 2018 Mario Danic
- * Copyright (C) 2018 Andy Scherzinger
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package com.owncloud.android.jobs;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.BitmapFactory;
-import android.media.RingtoneManager;
-import android.text.TextUtils;
-
-import com.evernote.android.job.Job;
-import com.google.gson.Gson;
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.core.Clock;
-import com.nextcloud.client.preferences.AppPreferences;
-import com.nextcloud.client.preferences.AppPreferencesImpl;
-import com.owncloud.android.R;
-import com.owncloud.android.datamodel.ArbitraryDataProvider;
-import com.owncloud.android.datamodel.MediaFolder;
-import com.owncloud.android.datamodel.MediaFoldersModel;
-import com.owncloud.android.datamodel.MediaProvider;
-import com.owncloud.android.datamodel.SyncedFolder;
-import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.ui.activity.ManageAccountsActivity;
-import com.owncloud.android.ui.activity.SyncedFoldersActivity;
-import com.owncloud.android.ui.notifications.NotificationUtils;
-import com.owncloud.android.utils.ThemeUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.NotificationCompat;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-@SuppressFBWarnings(value = "PREDICTABLE_RANDOM", justification = "Only used for notification id.")
-public class MediaFoldersDetectionJob extends Job {
-    public static final String TAG = "MediaFoldersDetectionJob";
-
-    public static final String KEY_MEDIA_FOLDER_PATH = "KEY_MEDIA_FOLDER_PATH";
-    public static final String KEY_MEDIA_FOLDER_TYPE = "KEY_MEDIA_FOLDER_TYPE";
-
-    private static final String ACCOUNT_NAME_GLOBAL = "global";
-    private static final String KEY_MEDIA_FOLDERS = "media_folders";
-    public static final String NOTIFICATION_ID = "NOTIFICATION_ID";
-
-    private static final String DISABLE_DETECTION_CLICK = "DISABLE_DETECTION_CLICK";
-
-    private final UserAccountManager userAccountManager;
-    private final Clock clock;
-    private final Random randomId = new Random();
-
-    MediaFoldersDetectionJob(UserAccountManager accountManager, Clock clock) {
-        this.userAccountManager = accountManager;
-        this.clock = clock;
-    }
-
-    @NonNull
-    @Override
-    protected Result onRunJob(@NonNull Params params) {
-        Context context = getContext();
-        ContentResolver contentResolver = context.getContentResolver();
-        ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
-        SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver,
-                                                                             AppPreferencesImpl.fromContext(context),
-                                                                             clock);
-        Gson gson = new Gson();
-        String arbitraryDataString;
-        MediaFoldersModel mediaFoldersModel;
-
-        List<MediaFolder> imageMediaFolders = MediaProvider.getImageFolders(contentResolver, 1, null, true);
-        List<MediaFolder> videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, 1, null, true);
-
-        List<String> imageMediaFolderPaths = new ArrayList<>();
-        List<String> videoMediaFolderPaths = new ArrayList<>();
-
-        for (MediaFolder imageMediaFolder : imageMediaFolders) {
-            imageMediaFolderPaths.add(imageMediaFolder.absolutePath);
-        }
-
-        for (MediaFolder videoMediaFolder : videoMediaFolders) {
-            imageMediaFolderPaths.add(videoMediaFolder.absolutePath);
-        }
-
-        arbitraryDataString = arbitraryDataProvider.getValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS);
-        if (!TextUtils.isEmpty(arbitraryDataString)) {
-            mediaFoldersModel = gson.fromJson(arbitraryDataString, MediaFoldersModel.class);
-
-            // merge new detected paths with already notified ones
-            for (String existingImageFolderPath : mediaFoldersModel.getImageMediaFolders()) {
-                if (!imageMediaFolderPaths.contains(existingImageFolderPath)) {
-                    imageMediaFolderPaths.add(existingImageFolderPath);
-                }
-            }
-
-            for (String existingVideoFolderPath : mediaFoldersModel.getVideoMediaFolders()) {
-                if (!videoMediaFolderPaths.contains(existingVideoFolderPath)) {
-                    videoMediaFolderPaths.add(existingVideoFolderPath);
-                }
-            }
-
-            // Store updated values
-            arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS, gson.toJson(new
-                MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths)));
-
-            final AppPreferences preferences = AppPreferencesImpl.fromContext(getContext());
-            if (preferences.isShowMediaScanNotifications()) {
-                imageMediaFolderPaths.removeAll(mediaFoldersModel.getImageMediaFolders());
-                videoMediaFolderPaths.removeAll(mediaFoldersModel.getVideoMediaFolders());
-
-                if (!imageMediaFolderPaths.isEmpty() || !videoMediaFolderPaths.isEmpty()) {
-                    List<User> allUsers = userAccountManager.getAllUsers();
-                    List<User> activeUsers = new ArrayList<>();
-                    for (User account : allUsers) {
-                        if (!arbitraryDataProvider.getBooleanValue(account.toPlatformAccount(),
-                                                                   ManageAccountsActivity.PENDING_FOR_REMOVAL)) {
-                            activeUsers.add(account);
-                        }
-                    }
-
-                    for (User user : activeUsers) {
-                        for (String imageMediaFolder : imageMediaFolderPaths) {
-                            final SyncedFolder folder = syncedFolderProvider.findByLocalPathAndAccount(imageMediaFolder,
-                                                                                                       user.toPlatformAccount());
-                            if (folder == null) {
-                                String contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
-                                                                    context.getString(R.string.new_media_folder_photos));
-                                sendNotification(contentTitle,
-                                                imageMediaFolder.substring(imageMediaFolder.lastIndexOf('/') + 1),
-                                                user,
-                                                imageMediaFolder,
-                                                 1);
-                            }
-                        }
-
-                        for (String videoMediaFolder : videoMediaFolderPaths) {
-                            final SyncedFolder folder = syncedFolderProvider.findByLocalPathAndAccount(videoMediaFolder,
-                                                                                                       user.toPlatformAccount());
-                            if (folder == null) {
-                                String contentTitle = String.format(context.getString(R.string.new_media_folder_detected),
-                                                                    context.getString(R.string.new_media_folder_videos));
-                                sendNotification(contentTitle,
-                                                 videoMediaFolder.substring(videoMediaFolder.lastIndexOf('/') + 1),
-                                                 user,
-                                                 videoMediaFolder,
-                                                 2);
-                            }
-                        }
-                    }
-                }
-            }
-
-        } else {
-            mediaFoldersModel = new MediaFoldersModel(imageMediaFolderPaths, videoMediaFolderPaths);
-            arbitraryDataProvider.storeOrUpdateKeyValue(ACCOUNT_NAME_GLOBAL, KEY_MEDIA_FOLDERS,
-                gson.toJson(mediaFoldersModel));
-        }
-
-        return Result.SUCCESS;
-    }
-
-    private void sendNotification(String contentTitle, String subtitle, User user, String path, int type) {
-        int notificationId = randomId.nextInt();
-
-        Context context = getContext();
-        Intent intent = new Intent(getContext(), SyncedFoldersActivity.class);
-        intent.putExtra(NOTIFICATION_ID, notificationId);
-        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        intent.putExtra(NotificationJob.KEY_NOTIFICATION_ACCOUNT, user.getAccountName());
-        intent.putExtra(KEY_MEDIA_FOLDER_PATH, path);
-        intent.putExtra(KEY_MEDIA_FOLDER_TYPE, type);
-        intent.putExtra(SyncedFoldersActivity.EXTRA_SHOW_SIDEBAR, true);
-        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
-
-        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(
-            context, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL)
-            .setSmallIcon(R.drawable.notification_icon)
-            .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.notification_icon))
-            .setColor(ThemeUtils.primaryColor(getContext()))
-            .setSubText(user.getAccountName())
-            .setContentTitle(contentTitle)
-            .setContentText(subtitle)
-            .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
-            .setAutoCancel(true)
-            .setContentIntent(pendingIntent);
-
-        Intent disableDetection = new Intent(context, NotificationReceiver.class);
-        disableDetection.putExtra(NOTIFICATION_ID, notificationId);
-        disableDetection.setAction(DISABLE_DETECTION_CLICK);
-
-        PendingIntent disableIntent = PendingIntent.getBroadcast(
-            context,
-            notificationId,
-            disableDetection,
-            PendingIntent.FLAG_CANCEL_CURRENT
-        );
-        notificationBuilder.addAction(
-            new NotificationCompat.Action(
-                R.drawable.ic_close,
-                context.getString(R.string.disable_new_media_folder_detection_notifications),
-                disableIntent
-            )
-        );
-
-        PendingIntent configureIntent = PendingIntent.getActivity(
-            context,
-            notificationId,
-            intent,
-            PendingIntent.FLAG_CANCEL_CURRENT
-        );
-        notificationBuilder.addAction(
-            new NotificationCompat.Action(
-                R.drawable.ic_settings,
-                context.getString(R.string.configure_new_media_folder_detection_notifications),
-                configureIntent
-            )
-        );
-
-        NotificationManager notificationManager = (NotificationManager)
-            context.getSystemService(Context.NOTIFICATION_SERVICE);
-
-        if (notificationManager != null) {
-            notificationManager.notify(notificationId, notificationBuilder.build());
-        }
-    }
-
-
-    public static class NotificationReceiver extends BroadcastReceiver {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            int notificationId = intent.getIntExtra(NOTIFICATION_ID, 0);
-            final AppPreferences preferences = AppPreferencesImpl.fromContext(context);
-
-            if (DISABLE_DETECTION_CLICK.equals(action)) {
-                Log_OC.d(this, "Disable media scan notifications");
-                preferences.setShowMediaScanNotifications(false);
-                cancel(context, notificationId);
-            }
-        }
-
-        private void cancel(Context context, int notificationId) {
-            NotificationManager notificationManager =
-                (NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
-            notificationManager.cancel(notificationId);
-        }
-    }
-}

+ 0 - 23
src/main/java/com/owncloud/android/jobs/NCJobCreator.java

@@ -30,10 +30,7 @@ import com.evernote.android.job.Job;
 import com.evernote.android.job.JobCreator;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
-import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.jobs.BackgroundJobManager;
-import com.nextcloud.client.network.ConnectivityService;
-import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 
 import org.greenrobot.eventbus.EventBus;
@@ -48,10 +45,7 @@ public class NCJobCreator implements JobCreator {
 
     private final Context context;
     private final UserAccountManager accountManager;
-    private final AppPreferences preferences;
     private final UploadsStorageManager uploadsStorageManager;
-    private final ConnectivityService connectivityService;
-    private final PowerManagementService powerManagementService;
     private final Clock clock;
     private final EventBus eventBus;
     private final BackgroundJobManager backgroundJobManager;
@@ -59,20 +53,14 @@ public class NCJobCreator implements JobCreator {
     public NCJobCreator(
         Context context,
         UserAccountManager accountManager,
-        AppPreferences preferences,
         UploadsStorageManager uploadsStorageManager,
-        ConnectivityService connectivityServices,
-        PowerManagementService powerManagementService,
         Clock clock,
         EventBus eventBus,
         BackgroundJobManager backgroundJobManager
     ) {
         this.context = context;
         this.accountManager = accountManager;
-        this.preferences = preferences;
         this.uploadsStorageManager = uploadsStorageManager;
-        this.connectivityService = connectivityServices;
-        this.powerManagementService = powerManagementService;
         this.clock = clock;
         this.eventBus = eventBus;
         this.backgroundJobManager = backgroundJobManager;
@@ -87,19 +75,8 @@ public class NCJobCreator implements JobCreator {
                                              backgroundJobManager,
                                              clock,
                                              eventBus);
-            case FilesSyncJob.TAG:
-                return new FilesSyncJob(accountManager,
-                                        preferences,
-                                        uploadsStorageManager,
-                                        connectivityService,
-                                        powerManagementService,
-                                        clock);
-            case OfflineSyncJob.TAG:
-                return new OfflineSyncJob(accountManager, connectivityService, powerManagementService);
             case NotificationJob.TAG:
                 return new NotificationJob(context, accountManager);
-            case MediaFoldersDetectionJob.TAG:
-                return new MediaFoldersDetectionJob(accountManager, clock);
             default:
                 return null;
         }

+ 0 - 185
src/main/java/com/owncloud/android/jobs/OfflineSyncJob.java

@@ -1,185 +0,0 @@
-/*
- * Nextcloud Android client application
- *
- * @author Mario Danic
- * @author Chris Narkiewicz
- * Copyright (C) 2018 Mario Danic
- * Copyright (C) 2019 Chris Narkiewicz <hello@ezaquarii.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package com.owncloud.android.jobs;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.PowerManager;
-
-import com.evernote.android.job.Job;
-import com.evernote.android.job.JobManager;
-import com.evernote.android.job.JobRequest;
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.device.PowerManagementService;
-import com.nextcloud.client.network.ConnectivityService;
-import com.owncloud.android.MainApp;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.files.CheckEtagRemoteOperation;
-import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.utils.FileStorageUtils;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-import androidx.annotation.NonNull;
-
-import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
-import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
-
-public class OfflineSyncJob extends Job {
-    public static final String TAG = "OfflineSyncJob";
-
-    private static final String WAKELOCK_TAG_SEPARATION = ":";
-    private final UserAccountManager userAccountManager;
-    private final ConnectivityService connectivityService;
-    private final PowerManagementService powerManagementService;
-
-    OfflineSyncJob(UserAccountManager userAccountManager, ConnectivityService connectivityService, PowerManagementService powerManagementService) {
-        this.userAccountManager = userAccountManager;
-        this.connectivityService = connectivityService;
-        this.powerManagementService = powerManagementService;
-    }
-
-    @NonNull
-    @Override
-    protected Result onRunJob(@NonNull Params params) {
-        final Context context = getContext();
-
-        PowerManager.WakeLock wakeLock = null;
-        if (!powerManagementService.isPowerSavingEnabled() &&
-                connectivityService.getActiveNetworkType() == JobRequest.NetworkType.UNMETERED &&
-                !connectivityService.isInternetWalled()) {
-            Set<Job> jobs = JobManager.instance().getAllJobsForTag(TAG);
-            for (Job job : jobs) {
-                if (!job.isFinished() && !job.equals(this)) {
-                    return Result.SUCCESS;
-                }
-            }
-
-            if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
-                PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
-
-                if (powerManager != null) {
-                    wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, MainApp.getAuthority() +
-                        WAKELOCK_TAG_SEPARATION + TAG);
-                    wakeLock.acquire(10 * 60 * 1000);
-                }
-            }
-
-            List<User> users = userAccountManager.getAllUsers();
-
-            for (User user : users) {
-                FileDataStorageManager storageManager = new FileDataStorageManager(user.toPlatformAccount(),
-                        getContext().getContentResolver());
-
-                OCFile ocRoot = storageManager.getFileByPath(ROOT_PATH);
-
-                if (ocRoot.getStoragePath() == null) {
-                    break;
-                }
-
-                recursive(new File(ocRoot.getStoragePath()), storageManager, user);
-            }
-
-            if (wakeLock != null) {
-                wakeLock.release();
-            }
-        }
-
-        return Result.SUCCESS;
-    }
-
-    private void recursive(File folder, FileDataStorageManager storageManager, User user) {
-        String downloadFolder = FileStorageUtils.getSavePath(user.getAccountName());
-        String folderName = folder.getAbsolutePath().replaceFirst(downloadFolder, "") + PATH_SEPARATOR;
-        Log_OC.d(TAG, folderName + ": enter");
-
-        // exit
-        if (folder.listFiles() == null) {
-            return;
-        }
-
-        OCFile ocFolder = storageManager.getFileByPath(folderName);
-        Log_OC.d(TAG, folderName + ": currentEtag: " + ocFolder.getEtag());
-
-        // check for etag change, if false, skip
-        CheckEtagRemoteOperation checkEtagOperation = new CheckEtagRemoteOperation(ocFolder.getRemotePath(),
-                                                                                   ocFolder.getEtagOnServer());
-        RemoteOperationResult result = checkEtagOperation.execute(user.toPlatformAccount(), getContext());
-
-        // eTag changed, sync file
-        switch (result.getCode()) {
-            case ETAG_UNCHANGED:
-                Log_OC.d(TAG, folderName + ": eTag unchanged");
-                return;
-
-            case FILE_NOT_FOUND:
-                boolean removalResult = storageManager.removeFolder(ocFolder, true, true);
-                if (!removalResult) {
-                    Log_OC.e(TAG, "removal of " + ocFolder.getStoragePath() + " failed: file not found");
-                }
-                return;
-
-            default:
-            case ETAG_CHANGED:
-                Log_OC.d(TAG, folderName + ": eTag changed");
-                break;
-        }
-
-        // iterate over downloaded files
-        File[] files = folder.listFiles(File::isFile);
-
-        if (files != null) {
-            for (File file : files) {
-                OCFile ocFile = storageManager.getFileByLocalPath(file.getPath());
-                SynchronizeFileOperation synchronizeFileOperation = new SynchronizeFileOperation(ocFile.getRemotePath(),
-                                                                                                 user,
-                                                                                                 true,
-                                                                                                 getContext());
-                synchronizeFileOperation.execute(storageManager, getContext());
-            }
-        }
-
-        // recursive into folder
-        File[] subfolders = folder.listFiles(File::isDirectory);
-
-        if (subfolders != null) {
-            for (File subfolder : subfolders) {
-                recursive(subfolder, storageManager, user);
-            }
-        }
-
-        // update eTag
-        try {
-            String updatedEtag = (String) result.getData().get(0);
-            ocFolder.setEtagOnServer(updatedEtag);
-            storageManager.saveFile(ocFolder);
-        } catch (Exception e) {
-            Log_OC.e(TAG, "Failed to update etag on " + folder.getAbsolutePath(), e);
-        }
-    }
-}

+ 1 - 10
src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java

@@ -116,7 +116,7 @@ public class ActivitiesActivity extends FileActivity implements ActivityListInte
         // setup toolbar
         setupToolbar();
 
-        onCreateSwipeToRefresh(swipeListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
 
         // setup drawer
         setupDrawer(R.id.nav_activity);
@@ -139,15 +139,6 @@ public class ActivitiesActivity extends FileActivity implements ActivityListInte
         emptyContentHeadline.setVisibility(View.INVISIBLE);
     }
 
-    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
-        int primaryColor = ThemeUtils.primaryColor(this);
-        int darkColor = ThemeUtils.primaryDarkColor(this);
-        int accentColor = ThemeUtils.primaryAccentColor(this);
-
-        // Colors in animations
-        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
-    }
-
     @Override
     public void onDestroy() {
         super.onDestroy();

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

@@ -288,6 +288,9 @@ public abstract class DrawerActivity extends ToolbarActivity
             }
         };
 
+        // proper coloring of drawer menu icon
+        mDrawerToggle.getDrawerArrowDrawable().setColor(ThemeUtils.toolbarTextColor(this));
+
         // Set the drawer toggle as the DrawerListener
         mDrawerLayout.addDrawerListener(mDrawerToggle);
         mDrawerToggle.setDrawerIndicatorEnabled(true);
@@ -302,7 +305,6 @@ public abstract class DrawerActivity extends ToolbarActivity
         mAccountEndAccountAvatar = (ImageView) findNavigationViewChildById(R.id.drawer_account_end);
 
         mAccountChooserToggle = (ImageView) findNavigationViewChildById(R.id.drawer_account_chooser_toggle);
-        mAccountChooserToggle.setColorFilter(ThemeUtils.fontColor(this, true));
 
         if (getResources().getBoolean(R.bool.allow_profile_click)) {
             mAccountChooserToggle.setImageResource(R.drawable.ic_down);
@@ -1272,12 +1274,10 @@ public abstract class DrawerActivity extends ToolbarActivity
     @Override
     protected void onResume() {
         super.onResume();
-        if (AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
-
-            getDelegate().setLocalNightMode(DarkMode.DARK == preferences.getDarkThemeMode() ?
-                                                AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
-            getDelegate().applyDayNight();
-        }
+        // TODO Check whether dark mode has been changed
+        getDelegate().setLocalNightMode(DarkMode.DARK == preferences.getDarkThemeMode() ?
+                                            AppCompatDelegate.MODE_NIGHT_YES : AppCompatDelegate.MODE_NIGHT_NO);
+        getDelegate().applyDayNight();
         setDrawerMenuItemChecked(mCheckedMenuItem);
     }
 

+ 14 - 0
src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java

@@ -3,8 +3,10 @@
  *
  * @author Andy Scherzinger
  * @author Chris Narkiewicz  <hello@ezaquarii.com>
+ * @author Chawki Chouib  <chouibc@gmail.com>
  * Copyright (C) 2016 ownCloud Inc.
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chawki Chouib  <chouibc@gmail.com>
  * <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,
@@ -70,6 +72,7 @@ import java.util.Set;
 
 import javax.inject.Inject;
 
+import androidx.appcompat.app.ActionBar;
 import androidx.core.content.ContextCompat;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.recyclerview.widget.LinearLayoutManager;
@@ -123,6 +126,17 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
         recyclerView = findViewById(R.id.account_list);
 
         setupToolbar();
+
+        // set the back button from action bar
+        ActionBar actionBar = getSupportActionBar();
+
+        // check if is not null
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setDisplayShowHomeEnabled(true);
+        }
+
+        // set title Action bar
         updateActionBarTitleAndHomeButtonByString(getResources().getString(R.string.prefs_manage_accounts));
 
         List<User> users = accountManager.getAllUsers();

+ 2 - 0
src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.java

@@ -140,6 +140,8 @@ public class NotificationsActivity extends FileActivity implements Notifications
 
         swipeEmptyListRefreshLayout = findViewById(R.id.swipe_containing_empty);
         swipeListRefreshLayout = findViewById(R.id.swipe_containing_list);
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeEmptyListRefreshLayout);
 
         // setup drawer
         setupDrawer(R.id.nav_notifications);

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

@@ -751,8 +751,8 @@ public class SettingsActivity extends ThemedPreferenceActivity
             actionBar.setBackgroundDrawable(new ColorDrawable(ThemeUtils.primaryColor(this)));
 
             Drawable backArrow = getResources().getDrawable(R.drawable.ic_arrow_back);
-            actionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow, ThemeUtils.fontColor(this,
-                                                                                                   !ThemeUtils.darkTheme(this))));
+            actionBar.setHomeAsUpIndicator(ThemeUtils.tintDrawable(backArrow,
+                                                                   ThemeUtils.toolbarTextColor(this)));
         }
 
         Window window = getWindow();

+ 9 - 8
src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.java

@@ -47,6 +47,8 @@ import com.nextcloud.client.account.User;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.di.Injectable;
+import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.MediaFoldersDetectionWork;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.java.util.Optional;
 import com.owncloud.android.BuildConfig;
@@ -61,7 +63,6 @@ import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.jobs.MediaFoldersDetectionJob;
 import com.owncloud.android.jobs.NotificationJob;
 import com.owncloud.android.ui.adapter.SyncedFolderAdapter;
 import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
@@ -140,6 +141,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
     @Inject AppPreferences preferences;
     @Inject PowerManagementService powerManagementService;
     @Inject Clock clock;
+    @Inject BackgroundJobManager backgroundJobManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -163,11 +165,11 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
                 }
             }
 
-            path = getIntent().getStringExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_PATH);
-            type = getIntent().getIntExtra(MediaFoldersDetectionJob.KEY_MEDIA_FOLDER_TYPE, -1);
+            path = getIntent().getStringExtra(MediaFoldersDetectionWork.KEY_MEDIA_FOLDER_PATH);
+            type = getIntent().getIntExtra(MediaFoldersDetectionWork.KEY_MEDIA_FOLDER_TYPE, -1);
 
             // Cancel notification
-            int notificationId = getIntent().getIntExtra(MediaFoldersDetectionJob.NOTIFICATION_ID, 0);
+            int notificationId = getIntent().getIntExtra(MediaFoldersDetectionWork.NOTIFICATION_ID, 0);
             NotificationManager notificationManager =
                 (NotificationManager) getSystemService(Activity.NOTIFICATION_SERVICE);
             notificationManager.cancel(notificationId);
@@ -637,8 +639,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
         }
 
         if (syncedFolderDisplayItem.isEnabled()) {
-            FilesSyncHelper.insertAllDBEntriesForSyncedFolder(syncedFolderDisplayItem, true);
-
+            backgroundJobManager.startImmediateFilesSyncJob(false, false);
             showBatteryOptimizationInfo();
         }
     }
@@ -779,7 +780,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
             // existing synced folder setup to be updated
             syncedFolderProvider.updateSyncFolder(item);
             if (item.isEnabled()) {
-                FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item, true);
+                backgroundJobManager.startImmediateFilesSyncJob(false, false);
             } else {
                 String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
 
@@ -797,7 +798,7 @@ public class SyncedFoldersActivity extends FileActivity implements SyncedFolderA
         if (storedId != -1) {
             item.setId(storedId);
             if (item.isEnabled()) {
-                FilesSyncHelper.insertAllDBEntriesForSyncedFolder(item, true);
+                backgroundJobManager.startImmediateFilesSyncJob(false, false);
             } else {
                 String syncedFolderInitiatedKey = "syncedFolderIntitiated_" + item.getId();
                 arbitraryDataProvider.deleteKeyForAccount("global", syncedFolderInitiatedKey);

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

@@ -65,7 +65,7 @@ public abstract class ToolbarActivity extends BaseActivity {
      */
     protected void setupToolbar(boolean useBackgroundImage) {
         int primaryColor = ThemeUtils.primaryColor(this, false);
-        int fontColor = ThemeUtils.fontColor(this, !ThemeUtils.darkTheme(getApplicationContext()));
+        int toolbarTextColor = ThemeUtils.toolbarTextColor(this);
 
         Toolbar toolbar = findViewById(R.id.toolbar);
         setSupportActionBar(toolbar);
@@ -86,11 +86,11 @@ public abstract class ToolbarActivity extends BaseActivity {
         ThemeUtils.colorStatusBar(this, primaryColor);
 
         if (toolbar.getOverflowIcon() != null) {
-            ThemeUtils.tintDrawable(toolbar.getOverflowIcon(), fontColor);
+            ThemeUtils.tintDrawable(toolbar.getOverflowIcon(), toolbarTextColor);
         }
 
         if (toolbar.getNavigationIcon() != null) {
-            ThemeUtils.tintDrawable(toolbar.getNavigationIcon(), fontColor);
+            ThemeUtils.tintDrawable(toolbar.getNavigationIcon(), toolbarTextColor);
         }
 
         if (!useBackgroundImage) {

+ 6 - 14
src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -45,6 +45,7 @@ import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.jobs.BackgroundJobManager;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.java.util.Optional;
 import com.owncloud.android.R;
@@ -52,7 +53,6 @@ import com.owncloud.android.databinding.UploadListLayoutBinding;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
-import com.owncloud.android.jobs.FilesSyncJob;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -99,6 +99,9 @@ public class UploadListActivity extends FileActivity {
     @Inject
     Clock clock;
 
+    @Inject
+    BackgroundJobManager backgroundJobManager;
+
     private UploadListLayoutBinding binding;
 
     @Override
@@ -164,6 +167,7 @@ public class UploadListActivity extends FileActivity {
         binding.list.setLayoutManager(lm);
         binding.list.setAdapter(uploadListAdapter);
 
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
         swipeListRefreshLayout.setOnRefreshListener(this::refresh);
 
         loadItems();
@@ -181,19 +185,7 @@ public class UploadListActivity extends FileActivity {
     }
 
     private void refresh() {
-        // scan for missing auto uploads files
-        Set<Job> jobs = JobManager.instance().getAllJobsForTag(FilesSyncJob.TAG);
-
-        if (jobs.isEmpty()) {
-            PersistableBundleCompat persistableBundleCompat = new PersistableBundleCompat();
-            persistableBundleCompat.putBoolean(FilesSyncJob.OVERRIDE_POWER_SAVING, true);
-            new JobRequest.Builder(FilesSyncJob.TAG)
-                .setExact(1_000L)
-                .setUpdateCurrent(false)
-                .setExtras(persistableBundleCompat)
-                .build()
-                .schedule();
-        }
+        backgroundJobManager.startImmediateFilesSyncJob(false, true);
 
         // retry failed uploads
         new Thread(() -> FileUploader.retryFailedUploads(

+ 16 - 0
src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java

@@ -4,10 +4,12 @@
  * @author Mario Danic
  * @author Andy Scherzinger
  * @author Chris Narkiewicz  <hello@ezaquarii.com>
+ * @author Chawki Chouib  <chouibc@gmail.com>
  * Copyright (C) 2017 Mario Danic
  * Copyright (C) 2017 Andy Scherzinger
  * Copyright (C) 2017 Nextcloud GmbH.
  * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
+ * Copyright (C) 2020 Chawki Chouib  <chouibc@gmail.com>
  *
  * 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
@@ -79,6 +81,7 @@ import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
+import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.app.AlertDialog;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.fragment.app.DialogFragment;
@@ -145,10 +148,23 @@ public class UserInfoActivity extends FileActivity implements Injectable {
         setContentView(R.layout.user_info_layout);
         unbinder = ButterKnife.bind(this);
 
+
         boolean useBackgroundImage = URLUtil.isValidUrl(
                 getStorageManager().getCapability(user.getAccountName()).getServerBackground());
 
         setupToolbar(useBackgroundImage);
+
+
+        // set the back button from action bar
+        ActionBar actionBar = getSupportActionBar();
+
+        // check if is not null
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setDisplayShowHomeEnabled(true);
+        }
+
+        // set title Action bar
         updateActionBarTitleAndHomeButtonByString("");
 
         mUserInfoList.setAdapter(new UserInfoAdapter(null, ThemeUtils.primaryColor(getAccount(), true, this)));

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

@@ -167,10 +167,7 @@ public class NotificationListAdapter extends RecyclerView.Adapter<NotificationLi
                 button.setTextColor(ThemeUtils.fontColor(notificationsActivity));
                 button.setTypeface(button.getTypeface(), Typeface.BOLD);
             } else {
-                button.setStrokeColor(ColorStateList.valueOf(resources.getColor(R.color.grey_200)));
-                button.setStrokeWidth(3);
-
-                button.setBackgroundColor(resources.getColor(R.color.transparent));
+                button.setBackgroundColor(resources.getColor(R.color.grey_200));
                 button.setTextColor(primaryColor);
                 button.setTypeface(button.getTypeface(), Typeface.BOLD);
             }

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

@@ -428,6 +428,7 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
                         if (i == 0 && sharees.size() > 3) {
                             avatar.setImageResource(R.drawable.ic_people);
+                            ThemeUtils.setIconColor(avatar.getDrawable());
                         } else {
                             if (sharee.getShareType().equals(ShareType.GROUP)) {
                                 try {
@@ -436,9 +437,12 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
                                 } catch (Exception e) {
                                     Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e);
                                     avatar.setImageResource(R.drawable.ic_people);
+                                    ThemeUtils.setIconColor(avatar.getDrawable());
                                 }
                             } else if (sharee.getShareType().equals(ShareType.CIRCLE)) {
                                 avatar.setImageResource(R.drawable.ic_circles);
+                                ThemeUtils.setIconColor(avatar.getDrawable());
+
                             } else if (sharee.getUserId().contains("@")) {
                                 showFederatedShareAvatar(sharee.getUserId(), avatarRadius, resources, avatar);
                             } else {

+ 6 - 3
src/main/java/com/owncloud/android/ui/dialog/ConflictsResolveDialog.java

@@ -27,7 +27,6 @@ import android.content.DialogInterface;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -51,6 +50,7 @@ import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.AppCompatCheckBox;
 import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentTransaction;
@@ -151,8 +151,11 @@ public class ConflictsResolveDialog extends DialogFragment {
         View view = inflater.inflate(R.layout.conflict_resolve_dialog, null);
         int accentColor = ThemeUtils.primaryAccentColor(getContext());
 
-        CheckBox newFileCheckbox = view.findViewById(R.id.new_checkbox);
-        CheckBox existingFileCheckbox = view.findViewById(R.id.existing_checkbox);
+        AppCompatCheckBox newFileCheckbox = view.findViewById(R.id.new_checkbox);
+        AppCompatCheckBox existingFileCheckbox = view.findViewById(R.id.existing_checkbox);
+
+        ThemeUtils.tintCheckbox(newFileCheckbox, ThemeUtils.primaryColor(getContext()));
+        ThemeUtils.tintCheckbox(existingFileCheckbox, ThemeUtils.primaryColor(getContext()));
 
         // Build the dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

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

@@ -381,7 +381,8 @@ public class ExtendedListFragment extends Fragment implements
 
         // Pull-down to refresh layout
         mRefreshListLayout = v.findViewById(R.id.swipe_containing_list);
-        onCreateSwipeToRefresh(mRefreshListLayout);
+        ThemeUtils.colorSwipeRefreshLayout(getContext(), mRefreshListLayout);
+        mRefreshListLayout.setOnRefreshListener(this);
 
         mFabMain = v.findViewById(R.id.fab_main);
         ThemeUtils.tintFloatingActionButton(mFabMain, R.drawable.ic_plus, getContext());
@@ -773,17 +774,6 @@ public class ExtendedListFragment extends Fragment implements
         return (mEmptyListContainer != null && mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : "";
     }
 
-    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
-        int primaryColor = ThemeUtils.primaryColor(getContext());
-        int darkColor = ThemeUtils.primaryDarkColor(getContext());
-        int accentColor = ThemeUtils.primaryAccentColor(getContext());
-
-        // Colors in animations
-        // TODO change this to use darker and lighter color, again.
-        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
-        refreshLayout.setOnRefreshListener(this);
-    }
-
     @Override
     public void onRefresh(boolean ignoreETag) {
         mRefreshListLayout.setRefreshing(false);

+ 2 - 11
src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java

@@ -175,8 +175,8 @@ public class FileDetailActivitiesFragment extends Fragment implements
 
         setupView();
 
-        onCreateSwipeToRefresh(swipeEmptyListRefreshLayout);
-        onCreateSwipeToRefresh(swipeListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(getContext(), swipeEmptyListRefreshLayout);
+        ThemeUtils.colorSwipeRefreshLayout(getContext(), swipeListRefreshLayout);
 
         fetchAndSetData(-1);
 
@@ -448,15 +448,6 @@ public class FileDetailActivitiesFragment extends Fragment implements
         });
     }
 
-    protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) {
-        int primaryColor = ThemeUtils.primaryColor(getContext());
-        int darkColor = ThemeUtils.primaryDarkColor(getContext());
-        int accentColor = ThemeUtils.primaryAccentColor(getContext());
-
-        // Colors in animations
-        refreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
-    }
-
     @Override
     public void onActivityClicked(RichObject richObject) {
         // TODO implement activity click

+ 1 - 0
src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java

@@ -144,6 +144,7 @@ public class TrashbinActivity extends FileActivity implements
         recyclerView.setHasFooter(true);
         recyclerView.setLayoutManager(new LinearLayoutManager(this));
 
+        ThemeUtils.colorSwipeRefreshLayout(this, swipeListRefreshLayout);
         swipeListRefreshLayout.setOnRefreshListener(this::loadFolder);
 
         loadFolder();

+ 19 - 19
src/main/java/com/owncloud/android/utils/CsrHelper.java

@@ -1,21 +1,21 @@
 package com.owncloud.android.utils;
 
-import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
-import org.spongycastle.asn1.x500.X500Name;
-import org.spongycastle.asn1.x509.AlgorithmIdentifier;
-import org.spongycastle.asn1.x509.BasicConstraints;
-import org.spongycastle.asn1.x509.Extension;
-import org.spongycastle.asn1.x509.ExtensionsGenerator;
-import org.spongycastle.crypto.params.AsymmetricKeyParameter;
-import org.spongycastle.crypto.util.PrivateKeyFactory;
-import org.spongycastle.operator.ContentSigner;
-import org.spongycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
-import org.spongycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
-import org.spongycastle.operator.OperatorCreationException;
-import org.spongycastle.operator.bc.BcRSAContentSignerBuilder;
-import org.spongycastle.pkcs.PKCS10CertificationRequest;
-import org.spongycastle.pkcs.PKCS10CertificationRequestBuilder;
-import org.spongycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.ExtensionsGenerator;
+import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
+import org.bouncycastle.crypto.util.PrivateKeyFactory;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
+import org.bouncycastle.pkcs.PKCS10CertificationRequest;
+import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
+import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
 
 import java.io.IOException;
 import java.security.KeyPair;
@@ -25,7 +25,7 @@ import java.security.KeyPair;
  * https://github.com/awslabs/aws-sdk-android-samples/blob/master/CreateIotCertWithCSR/src/com/amazonaws/demo/csrcert/CsrHelper.java
  * accessed at 31.08.17
  * Original parts are licensed under the Apache License, Version 2.0: http://aws.amazon.com/apache2.0
- * Own parts are licensed unter GPLv3+.
+ * Own parts are licensed under GPLv3+.
  */
 
 public final class CsrHelper {
@@ -50,7 +50,7 @@ public final class CsrHelper {
         return "-----BEGIN CERTIFICATE REQUEST-----\n" + android.util.Base64.encodeToString(derCSR,
                 android.util.Base64.NO_WRAP) + "\n-----END CERTIFICATE REQUEST-----";
     }
-    
+
     /**
      * Create the certificate signing request (CSR) from private and public keys
      *
@@ -69,7 +69,7 @@ public final class CsrHelper {
         ContentSigner signer = new BcRSAContentSignerBuilder(signatureAlgorithm, digestAlgorithm).build(privateKey);
 
         PKCS10CertificationRequestBuilder csrBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(principal),
-                keyPair.getPublic());
+                                                                                                keyPair.getPublic());
         ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
         extensionsGenerator.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
         csrBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensionsGenerator.generate());

+ 57 - 51
src/main/java/com/owncloud/android/utils/DisplayUtils.java

@@ -46,6 +46,7 @@ import android.text.style.StyleSpan;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.View;
+import android.widget.Toast;
 
 import com.bumptech.glide.GenericRequestBuilder;
 import com.bumptech.glide.Glide;
@@ -160,7 +161,7 @@ public final class DisplayUtils {
             }
 
             return new BigDecimal(String.valueOf(result)).setScale(
-                    sizeScales[suffixIndex], BigDecimal.ROUND_HALF_UP) + " " + sizeSuffixes[suffixIndex];
+                sizeScales[suffixIndex], BigDecimal.ROUND_HALF_UP) + " " + sizeSuffixes[suffixIndex];
         }
     }
 
@@ -297,7 +298,7 @@ public final class DisplayUtils {
      */
     public static CharSequence getRelativeTimestamp(Context context, long modificationTimestamp) {
         return getRelativeDateTimeString(context, modificationTimestamp, DateUtils.SECOND_IN_MILLIS,
-                DateUtils.WEEK_IN_MILLIS, 0);
+                                         DateUtils.WEEK_IN_MILLIS, 0);
     }
 
 
@@ -386,7 +387,7 @@ public final class DisplayUtils {
         }
 
         SpannableStringBuilder sb = new SpannableStringBuilder(text);
-        if(spanText == null) {
+        if (spanText == null) {
             return sb;
         }
 
@@ -523,13 +524,13 @@ public final class DisplayUtils {
 
     private static void downloadPNGIcon(Context context, String iconUrl, SimpleTarget imageView, int placeholder) {
         Glide
-                .with(context)
-                .load(iconUrl)
-                .centerCrop()
-                .placeholder(placeholder)
-                .error(placeholder)
-                .crossFade()
-                .into(imageView);
+            .with(context)
+            .load(iconUrl)
+            .centerCrop()
+            .placeholder(placeholder)
+            .error(placeholder)
+            .crossFade()
+            .into(imageView);
     }
 
     private static void downloadSVGIcon(CurrentAccountProvider currentAccountProvider,
@@ -542,33 +543,33 @@ public final class DisplayUtils {
                                         int height) {
         GenericRequestBuilder<Uri, InputStream, SVG, PictureDrawable> requestBuilder = Glide.with(context)
             .using(new CustomGlideUriLoader(currentAccountProvider, clientFactory), InputStream.class)
-                .from(Uri.class)
-                .as(SVG.class)
-                .transcode(new SvgDrawableTranscoder(), PictureDrawable.class)
-                .sourceEncoder(new StreamEncoder())
-                .cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder(height, width)))
-                .decoder(new SvgDecoder(height, width))
-                .placeholder(placeholder)
-                .error(placeholder)
-                .animate(android.R.anim.fade_in);
+            .from(Uri.class)
+            .as(SVG.class)
+            .transcode(new SvgDrawableTranscoder(), PictureDrawable.class)
+            .sourceEncoder(new StreamEncoder())
+            .cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder(height, width)))
+            .decoder(new SvgDecoder(height, width))
+            .placeholder(placeholder)
+            .error(placeholder)
+            .animate(android.R.anim.fade_in);
 
 
         Uri uri = Uri.parse(iconUrl);
         requestBuilder
-                .diskCacheStrategy(DiskCacheStrategy.SOURCE)
-                .load(uri)
-                .into(imageView);
+            .diskCacheStrategy(DiskCacheStrategy.SOURCE)
+            .load(uri)
+            .into(imageView);
     }
 
     public static Bitmap downloadImageSynchronous(Context context, String imageUrl) {
         try {
             return Glide.with(context)
-                    .load(imageUrl)
-                    .asBitmap()
-                    .diskCacheStrategy(DiskCacheStrategy.NONE)
-                    .skipMemoryCache(true)
-                    .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
-                    .get();
+                .load(imageUrl)
+                .asBitmap()
+                .diskCacheStrategy(DiskCacheStrategy.NONE)
+                .skipMemoryCache(true)
+                .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+                .get();
         } catch (Exception e) {
             Log_OC.e(TAG, "Could not download image " + imageUrl);
             return null;
@@ -679,32 +680,32 @@ public final class DisplayUtils {
      */
     public static void showSnackMessage(Context context, View view, @StringRes int messageResource, Object... formatArgs) {
         Snackbar.make(
-                view,
-                String.format(context.getString(messageResource, formatArgs)),
-                Snackbar.LENGTH_LONG)
-                .show();
+            view,
+            String.format(context.getString(messageResource, formatArgs)),
+            Snackbar.LENGTH_LONG)
+            .show();
     }
 
     // Solution inspired by https://stackoverflow.com/questions/34936590/why-isnt-my-vector-drawable-scaling-as-expected
     // Copied from https://raw.githubusercontent.com/nextcloud/talk-android/8ec8606bc61878e87e3ac8ad32c8b72d4680013c/app/src/main/java/com/nextcloud/talk/utils/DisplayUtils.java
     // under GPL3
     public static void useCompatVectorIfNeeded() {
-            try {
-                @SuppressLint("RestrictedApi") AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
-                Class<?> inflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$InflateDelegate");
-                Class<?> vdcInflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$VdcInflateDelegate");
-
-                Constructor<?> constructor = vdcInflateDelegateClass.getDeclaredConstructor();
-                constructor.setAccessible(true);
-                Object vdcInflateDelegate = constructor.newInstance();
-
-                Class<?> args[] = {String.class, inflateDelegateClass};
-                Method addDelegate = AppCompatDrawableManager.class.getDeclaredMethod("addDelegate", args);
-                addDelegate.setAccessible(true);
-                addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate);
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to use reflection to enable proper vector scaling");
-            }
+        try {
+            @SuppressLint("RestrictedApi") AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
+            Class<?> inflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$InflateDelegate");
+            Class<?> vdcInflateDelegateClass = Class.forName("android.support.v7.widget.AppCompatDrawableManager$VdcInflateDelegate");
+
+            Constructor<?> constructor = vdcInflateDelegateClass.getDeclaredConstructor();
+            constructor.setAccessible(true);
+            Object vdcInflateDelegate = constructor.newInstance();
+
+            Class<?> args[] = {String.class, inflateDelegateClass};
+            Method addDelegate = AppCompatDrawableManager.class.getDeclaredMethod("addDelegate", args);
+            addDelegate.setAccessible(true);
+            addDelegate.invoke(drawableManager, "vector", vdcInflateDelegate);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to use reflection to enable proper vector scaling");
+        }
     }
 
     public static int convertDpToPixel(float dp, Context context) {
@@ -717,9 +718,9 @@ public final class DisplayUtils {
     static public void showServerOutdatedSnackbar(Activity activity, int length) {
         Snackbar.make(activity.findViewById(android.R.id.content),
                       R.string.outdated_server, length)
-                .setAction(R.string.dismiss, v -> {
-                })
-                .show();
+            .setAction(R.string.dismiss, v -> {
+            })
+            .show();
     }
 
     static public void startLinkIntent(Activity activity, @StringRes int link) {
@@ -734,4 +735,9 @@ public final class DisplayUtils {
             DisplayUtils.showSnackMessage(activity, error);
         }
     }
+
+    static public void showErrorAndFinishActivity(Activity activity, String errorMessage) {
+        Toast.makeText(activity, errorMessage, Toast.LENGTH_LONG).show();
+        activity.finish();
+    }
 }

+ 6 - 35
src/main/java/com/owncloud/android/utils/FilesSyncHelper.java

@@ -31,7 +31,6 @@ import android.net.Uri;
 import android.os.Build;
 import android.provider.MediaStore;
 
-import com.evernote.android.job.JobManager;
 import com.evernote.android.job.JobRequest;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
@@ -47,8 +46,6 @@ import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.jobs.FilesSyncJob;
-import com.owncloud.android.jobs.OfflineSyncJob;
 import com.owncloud.android.lib.common.utils.Log_OC;
 
 import org.lukhnos.nnio.file.FileVisitResult;
@@ -60,8 +57,6 @@ import org.lukhnos.nnio.file.attribute.BasicFileAttributes;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
 
 import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
 
@@ -79,7 +74,7 @@ public final class FilesSyncHelper {
         // utility class -> private constructor
     }
 
-    public static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder, boolean syncNow) {
+    private static void insertAllDBEntriesForSyncedFolder(SyncedFolder syncedFolder) {
         final Context context = MainApp.getAppContext();
         final ContentResolver contentResolver = context.getContentResolver();
 
@@ -124,26 +119,19 @@ public final class FilesSyncHelper {
                     Log_OC.e(TAG, "Something went wrong while indexing files for auto upload", e);
                 }
             }
-
-            if (syncNow) {
-                new JobRequest.Builder(FilesSyncJob.TAG)
-                    .setExact(1_000L)
-                    .setUpdateCurrent(false)
-                    .build()
-                    .schedule();
-            }
         }
     }
 
-    public static void insertAllDBEntries(AppPreferences preferences, Clock clock, boolean skipCustom,
-                                          boolean syncNow) {
+    public static void insertAllDBEntries(AppPreferences preferences,
+                                          Clock clock,
+                                          boolean skipCustom) {
         final Context context = MainApp.getAppContext();
         final ContentResolver contentResolver = context.getContentResolver();
         SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock);
 
         for (SyncedFolder syncedFolder : syncedFolderProvider.getSyncedFolders()) {
             if (syncedFolder.isEnabled() && (!skipCustom || syncedFolder.getType() != MediaFolderType.CUSTOM)) {
-                insertAllDBEntriesForSyncedFolder(syncedFolder, syncNow);
+                insertAllDBEntriesForSyncedFolder(syncedFolder);
             }
         }
     }
@@ -233,27 +221,10 @@ public final class FilesSyncHelper {
     }
 
     public static void scheduleFilesSyncIfNeeded(Context context, BackgroundJobManager jobManager) {
-        // always run this because it also allows us to perform retries of manual uploads
-        new JobRequest.Builder(FilesSyncJob.TAG)
-                .setPeriodic(900000L, 300000L)
-                .setUpdateCurrent(true)
-                .build()
-                .schedule();
-
+        jobManager.schedulePeriodicFilesSyncJob();
         if (context != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             jobManager.scheduleContentObserverJob();
         }
     }
-
-    public static void scheduleOfflineSyncIfNeeded() {
-        Set<JobRequest> jobRequests = JobManager.instance().getAllJobRequestsForTag(OfflineSyncJob.TAG);
-        if (jobRequests.isEmpty()) {
-            new JobRequest.Builder(OfflineSyncJob.TAG)
-                .setPeriodic(TimeUnit.MINUTES.toMillis(15), TimeUnit.MINUTES.toMillis(5))
-                .setUpdateCurrent(false)
-                .build()
-                .schedule();
-        }
-    }
 }
 

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

@@ -71,6 +71,7 @@ import androidx.core.graphics.ColorUtils;
 import androidx.core.graphics.drawable.DrawableCompat;
 import androidx.core.widget.CompoundButtonCompat;
 import androidx.fragment.app.FragmentActivity;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
@@ -187,8 +188,11 @@ public final class ThemeUtils {
     }
 
     /**
+     * returns the font color based on the server side theming and uses black/white as a fallback based on replaceWhite.
+     *
+     * @param context the context
+     * @param replaceWhite FLAG to return white/black if server side color isn't available
      * @return int font color to use
-     * adapted from https://github.com/nextcloud/server/blob/master/apps/theming/lib/Util.php#L90-L102
      */
     public static int fontColor(Context context, boolean replaceWhite) {
         if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
@@ -199,6 +203,10 @@ public final class ThemeUtils {
             }
         }
 
+        return toolbarTextColor(context);
+    }
+
+    public static int toolbarTextColor(Context context) {
         try {
             return Color.parseColor(getCapability(context).getServerTextColor());
         } catch (Exception e) {
@@ -248,7 +256,7 @@ public final class ThemeUtils {
                 actionBar.setTitle(title);
             } else {
                 Spannable text = new SpannableString(title);
-                text.setSpan(new ForegroundColorSpan(fontColor(context, !darkTheme(context))),
+                text.setSpan(new ForegroundColorSpan(toolbarTextColor(context)),
                              0,
                              text.length(),
                              Spannable.SPAN_INCLUSIVE_INCLUSIVE);
@@ -414,6 +422,15 @@ public final class ThemeUtils {
         seekBar.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN);
     }
 
+    public static void colorSwipeRefreshLayout(Context context, SwipeRefreshLayout swipeRefreshLayout) {
+        int primaryColor = ThemeUtils.primaryColor(context);
+        int darkColor = ThemeUtils.primaryDarkColor(context);
+        int accentColor = ThemeUtils.primaryAccentColor(context);
+
+        swipeRefreshLayout.setColorSchemeColors(accentColor, primaryColor, darkColor);
+        swipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.bg_elevation_one);
+    }
+
     /**
      * set the Nextcloud standard colors for the snackbar.
      *
@@ -602,7 +619,7 @@ public final class ThemeUtils {
             drawable, Context context) {
         button.setBackgroundTintList(ColorStateList.valueOf(ThemeUtils.primaryColor(context)));
         button.setRippleColor(ThemeUtils.primaryDarkColor(context));
-        button.setImageDrawable(ThemeUtils.tintDrawable(drawable, ThemeUtils.fontColor(context)));
+        button.setImageDrawable(ThemeUtils.tintDrawable(drawable, ThemeUtils.toolbarTextColor(context)));
     }
 
     private static OCCapability getCapability(Context context) {
@@ -627,6 +644,14 @@ public final class ThemeUtils {
         }
     }
 
+    public static Drawable setIconColor(Drawable drawable) {
+        int color = Color.BLACK;
+        if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) {
+            color = Color.WHITE;
+        }
+        return tintDrawable(drawable, color);
+    }
+
     /**
      * Lifted from SO.
      * FindBugs surpressed because of lack of public API to alter the cursor color.

+ 13 - 0
src/main/res/drawable-night/favorite.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="16dp"
+        android:height="16dp"
+        android:viewportWidth="16"
+        android:viewportHeight="16">
+
+    <path
+        android:fillColor="#ffcc00"
+        android:strokeColor="#121212"
+        android:strokeWidth="0.46367699"
+        android:pathData="M 7.9999993,1.0934068 10.040178,6.0083827 15.418832,6.4256921 11.245738,9.9032694 12.636769,15.003716 7.9999993,12.128919 3.3632295,15.003716 4.7542604,9.9032694 0.5811676,6.4256921 5.9598206,6.0083827 Z"/>
+</vector>

+ 2 - 2
src/main/res/drawable/uploader_list_separator.xml

@@ -20,7 +20,7 @@
 <shape
   xmlns:android="http://schemas.android.com/apk/res/android">
     <gradient
-        android:startColor="@color/uploader_list_separator_color"
-        android:endColor="@color/uploader_list_separator_color"
+        android:startColor="@color/list_divider_background"
+        android:endColor="@color/list_divider_background"
         android:angle="0" />
 </shape>

+ 8 - 8
src/main/res/layout-land/account_setup.xml

@@ -54,7 +54,7 @@
             android:padding="@dimen/standard_half_padding">
 
             <com.google.android.material.button.MaterialButton
-                android:id="@+id/centeredRefreshButton"
+                android:id="@+id/centered_refresh_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_gravity="center_horizontal"
@@ -77,7 +77,7 @@
                 android:visibility="gone" />
 
             <FrameLayout
-                android:id="@+id/hostUrlFrame"
+                android:id="@+id/host_url_frame"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginBottom="@dimen/zero"
@@ -89,7 +89,7 @@
                     android:orientation="vertical">
 
                     <TextView
-                        android:id="@+id/editText"
+                        android:id="@+id/edit_text"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:ems="10"
@@ -97,7 +97,7 @@
                         android:textColor="@color/login_text_color" />
 
                     <com.google.android.material.textfield.TextInputEditText
-                        android:id="@+id/hostUrlInput"
+                        android:id="@+id/host_url_input"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:layout_gravity="bottom"
@@ -116,7 +116,7 @@
                 </LinearLayout>
 
                 <ImageButton
-                    android:id="@+id/testServerButton"
+                    android:id="@+id/test_server_button"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_vertical|end"
@@ -130,7 +130,7 @@
                     android:tint="@color/login_btn_tint" />
 
                 <ImageButton
-                    android:id="@+id/embeddedRefreshButton"
+                    android:id="@+id/embedded_refresh_button"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_vertical|end"
@@ -193,7 +193,7 @@
                 android:textColor="@color/login_text_color" />
 
             <ImageButton
-                android:id="@+id/scanQR"
+                android:id="@+id/scan_qr"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:background="@color/transparent"
@@ -201,7 +201,7 @@
                 android:src="@drawable/qrcode_scan" />
 
             <com.google.android.material.button.MaterialButton
-                android:id="@+id/buttonOK"
+                android:id="@+id/button_ok"
                 style="@style/Button.Login"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"

+ 8 - 8
src/main/res/layout/account_setup.xml

@@ -52,7 +52,7 @@
             android:padding="@dimen/standard_half_padding">
 
             <com.google.android.material.button.MaterialButton
-                android:id="@+id/centeredRefreshButton"
+                android:id="@+id/centered_refresh_button"
                 style="@style/Button.Primary"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
@@ -77,7 +77,7 @@
 
 
             <FrameLayout
-                android:id="@+id/hostUrlFrame"
+                android:id="@+id/host_url_frame"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginBottom="@dimen/zero"
@@ -89,7 +89,7 @@
                     android:orientation="vertical">
 
                     <TextView
-                        android:id="@+id/editText"
+                        android:id="@+id/edit_text"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:ems="10"
@@ -97,7 +97,7 @@
                         android:textColor="@color/login_text_color" />
 
                     <com.google.android.material.textfield.TextInputEditText
-                        android:id="@+id/hostUrlInput"
+                        android:id="@+id/host_url_input"
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:layout_gravity="bottom"
@@ -116,7 +116,7 @@
                 </LinearLayout>
 
                 <ImageButton
-                    android:id="@+id/testServerButton"
+                    android:id="@+id/test_server_button"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_vertical|end"
@@ -130,7 +130,7 @@
                     android:tint="@color/login_text_color" />
 
                 <ImageButton
-                    android:id="@+id/embeddedRefreshButton"
+                    android:id="@+id/embedded_refresh_button"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_gravity="center_vertical|end"
@@ -194,7 +194,7 @@
                 android:textColor="@color/login_text_color" />
 
             <ImageButton
-                android:id="@+id/scanQR"
+                android:id="@+id/scan_qr"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:background="@color/transparent"
@@ -203,7 +203,7 @@
                 android:theme="@style/Button.Login" />
 
             <com.google.android.material.button.MaterialButton
-                android:id="@+id/buttonOK"
+                android:id="@+id/button_ok"
                 style="@style/Button.Login"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"

+ 2 - 2
src/main/res/layout/conflict_resolve_dialog.xml

@@ -45,7 +45,7 @@
             android:layout_weight="1"
             android:orientation="vertical">
 
-            <CheckBox
+            <androidx.appcompat.widget.AppCompatCheckBox
                 android:id="@+id/new_checkbox"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
@@ -79,7 +79,7 @@
             android:layout_weight="1"
             android:orientation="vertical">
 
-            <CheckBox
+            <androidx.appcompat.widget.AppCompatCheckBox
                 android:id="@+id/existing_checkbox"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"

+ 8 - 8
src/main/res/layout/file_list_actions_bottom_sheet_fragment.xml

@@ -127,9 +127,9 @@
         android:id="@+id/divider"
         android:layout_width="match_parent"
         android:layout_height="1dp"
-        android:layout_marginLeft="@dimen/standard_margin"
+        android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
         android:layout_marginTop="@dimen/standard_half_margin"
-        android:layout_marginRight="@dimen/standard_margin"
+        android:layout_marginEnd="@dimen/standard_margin"
         android:layout_marginBottom="@dimen/standard_half_margin"
         android:background="@color/list_divider_background"/>
 
@@ -166,9 +166,9 @@
     <View
         android:layout_width="match_parent"
         android:layout_height="1dp"
-        android:layout_marginLeft="@dimen/standard_margin"
+        android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
         android:layout_marginTop="@dimen/standard_half_margin"
-        android:layout_marginRight="@dimen/standard_margin"
+        android:layout_marginEnd="@dimen/standard_margin"
         android:layout_marginBottom="@dimen/standard_half_margin"
         android:background="@color/list_divider_background" />
 
@@ -266,9 +266,9 @@
         <View
             android:layout_width="match_parent"
             android:layout_height="1dp"
-            android:layout_marginLeft="@dimen/standard_margin"
+            android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
             android:layout_marginTop="@dimen/standard_half_margin"
-            android:layout_marginRight="@dimen/standard_margin"
+            android:layout_marginEnd="@dimen/standard_margin"
             android:layout_marginBottom="@dimen/standard_half_margin"
             android:background="@color/list_divider_background" />
     </LinearLayout>
@@ -291,9 +291,9 @@
         <View
             android:layout_width="match_parent"
             android:layout_height="1dp"
-            android:layout_marginLeft="@dimen/standard_margin"
+            android:layout_marginStart="@dimen/bottom_sheet_menu_item_divider_standard_margin"
             android:layout_marginTop="@dimen/standard_half_margin"
-            android:layout_marginRight="@dimen/standard_margin"
+            android:layout_marginEnd="@dimen/standard_margin"
             android:layout_marginBottom="@dimen/standard_half_margin"
             android:background="@color/list_divider_background" />
     </LinearLayout>

+ 1 - 1
src/main/res/layout/fragment_etm_background_jobs.xml

@@ -3,7 +3,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".etm.pages.EtmBackgroundJobsFragment">
+    tools:context="com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment">
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/etm_background_jobs_list"

+ 6 - 0
src/main/res/menu/fragment_etm_background_jobs.xml

@@ -42,6 +42,12 @@
         app:showAsAction="never"
         android:showAsAction="never" />
 
+    <item
+        android:id="@+id/etm_background_jobs_schedule_test"
+        android:title="@string/etm_background_jobs_schedule_test_job"
+        app:showAsAction="never"
+        android:showAsAction="never" />
+
     <item
         android:id="@+id/etm_background_jobs_cancel_test"
         android:title="@string/etm_background_jobs_stop_test_job"

+ 13 - 0
src/main/res/values-bg-rBG/strings.xml

@@ -141,6 +141,7 @@
     <string name="confirmation_remove_folders_alert">Наистина ли желаете избраните елементи и съдържанието им да бъдат премахнати?</string>
     <string name="confirmation_remove_local">Само локално</string>
     <string name="conflict_already_existing_file">Файла вече съществува</string>
+    <string name="conflict_dialog_error">Грешка при създаване на диалог за конфликт!</string>
     <string name="conflict_message_description">Ако изберете и двете версии, ще бъде добавен номер към името на локалния файл.</string>
     <string name="conflict_message_headline">Кои файлове желете да запазите?</string>
     <string name="conflict_new_file">Нов файл</string>
@@ -190,6 +191,7 @@
     <string name="disable_new_media_folder_detection_notifications">Изключване</string>
     <string name="dismiss">Отхвърляне</string>
     <string name="dismiss_notification_description">Спри известията</string>
+    <string name="download_latest_dev_version">Сваляне на най-новата dev версия</string>
     <string name="downloader_download_failed_content">%1$sне може да бъде изтеглен</string>
     <string name="downloader_download_failed_credentials_error">Неуспешно качване, нужно е да се впишете отново</string>
     <string name="downloader_download_failed_ticker">Изтеглянето е неуспешно.</string>
@@ -200,6 +202,7 @@
     <string name="downloader_download_succeeded_ticker">Изтеглено</string>
     <string name="downloader_not_downloaded_yet">Все още не е изтеглен</string>
     <string name="drawer_close">Затвори страничното меню</string>
+    <string name="drawer_community">Общност</string>
     <string name="drawer_current_account">Текущ профил</string>
     <string name="drawer_end_account">Последен профил</string>
     <string name="drawer_item_activities">Активност</string>
@@ -225,11 +228,21 @@
     <string name="edit_permission_label">редактиране</string>
     <string name="edit_rich_workspace">редактирай информацията за папката</string>
     <string name="encrypted">Включване на криптиране</string>
+    <string name="end_to_end_encryption_confirm_button">Настройки за криптиране</string>
+    <string name="end_to_end_encryption_decrypting">Декриптиране...</string>
     <string name="end_to_end_encryption_dialog_close">Затваряне</string>
+    <string name="end_to_end_encryption_enter_password">Моля въведете парола за декриптиране на личния ключ.</string>
     <string name="end_to_end_encryption_folder_not_empty">Папката не е празна.</string>
     <string name="end_to_end_encryption_generating_keys">Генериране на нови ключове...</string>
+    <string name="end_to_end_encryption_keywords_description">Всички 12 думи заедно правят много силна парола, позволявайки само Вие да преглеждате и използвате вашите криптирани файлове. Моля запишете я и я пазете на сигурно място.</string>
+    <string name="end_to_end_encryption_not_enabled">Криптиране End-to-end е забранено на сървъра.</string>
+    <string name="end_to_end_encryption_not_supported">Криптирането работи само на KitKat(4.4) или по-висока версия.</string>
     <string name="end_to_end_encryption_password">Парола…</string>
+    <string name="end_to_end_encryption_retrieving_keys">Извличане на ключове...</string>
     <string name="end_to_end_encryption_storing_keys">Записване на ключовете</string>
+    <string name="end_to_end_encryption_title">Настройки за криптиране</string>
+    <string name="end_to_end_encryption_unsuccessful">Ключовете не могат да бъдат запазени, моля опитайте отново.</string>
+    <string name="end_to_end_encryption_wrong_password">Грешка при декриптиране. Грешна парола?</string>
     <string name="enter_filename">Въведете име на файла</string>
     <string name="error__upload__local_file_not_copied">%1$s не може да бъде копиран в локалната папка %2$s</string>
     <string name="error_cant_bind_to_operations_service">Критична грешка: операциите не могат да бъдат изпълнени</string>

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

@@ -141,7 +141,11 @@
     <string name="confirmation_remove_folder_alert">Esteu segur que voleu suprimir %1$s i els seus continguts?</string>
     <string name="confirmation_remove_folders_alert">Esteu segur que voleu suprimir els elements seleccionats i el seu contingut?</string>
     <string name="confirmation_remove_local">Només local</string>
+    <string name="conflict_already_existing_file">Aquest fitxer ja existeix</string>
+    <string name="conflict_dialog_error">Error creant diàleg de conflicte!</string>
+    <string name="conflict_message_description">Si seleccioneu ambdues versions, s\'afegirà un numero al nom de la versió local.</string>
     <string name="conflict_message_headline">Quins fitxers voleu conservar?</string>
+    <string name="conflict_new_file">Nou fitxer</string>
     <string name="contaclist_restore_selected">Restaura els contactes seleccionats</string>
     <string name="contactlist_account_chooser_title">Escolliu un compte per a importar</string>
     <string name="contactlist_item_icon">Icona d\'usuari per a la llista de contactes</string>
@@ -165,11 +169,15 @@
     <string name="copy_move_to_encrypted_folder_not_supported">No hi ha suport actualment per copiar o moure a una carpeta xifrada.</string>
     <string name="copy_to">Copia a…</string>
     <string name="could_not_download_image">No s\'ha pogut descarregar la imatge completa</string>
+    <string name="could_not_retrieve_url">No s\'ha pogut recuperar la URL</string>
     <string name="create_dir_fail_msg">No s\'ha pogut crear la carpeta</string>
+    <string name="create_new">Crear nou</string>
     <string name="create_new_document">Crea un nou document</string>
     <string name="create_new_folder">Crea una nova carpeta</string>
     <string name="create_new_presentation">Crea una nova presentació</string>
     <string name="create_new_spreadsheet">Crea un nou full de càlcul</string>
+    <string name="create_rich_workspace">Afegir informació de carpeta</string>
+    <string name="creates_rich_workspace">crea informació de carpeta</string>
     <string name="credentials_disabled">Credencials desactivades</string>
     <string name="date_unknown">Desconegut</string>
     <string name="default_credentials_wrong">Credencials incorrectes</string>
@@ -220,6 +228,7 @@
     <string name="drawer_quota_unlimited">%1$s utilitzat</string>
     <string name="drawer_synced_folders">Càrrega automàtica</string>
     <string name="edit_permission_label">edita</string>
+    <string name="edit_rich_workspace">modificar informació de carpeta</string>
     <string name="encrypted">Activa el xifrat</string>
     <string name="end_to_end_encryption_confirm_button">Arranja el xifrat</string>
     <string name="end_to_end_encryption_decrypting">S\'està desxifrant…</string>
@@ -248,13 +257,24 @@
     <string name="error_retrieving_templates">S\'ha produït un error recuperant les plantilles</string>
     <string name="error_starting_direct_camera_upload">S\'ha produït un error iniciant la càmera</string>
     <string name="etm_accounts">Comptes</string>
+    <string name="etm_background_job_name">Nom de la tasca</string>
     <string name="etm_background_job_progress">Progrés</string>
+    <string name="etm_background_job_started">Iniciat</string>
+    <string name="etm_background_job_state">Estat</string>
+    <string name="etm_background_job_user">Usuari</string>
+    <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">Tasques en segon pla</string>
+    <string name="etm_background_jobs_cancel_all">Cancel·lar totes les tasques</string>
+    <string name="etm_background_jobs_prune">Tallar les tasques inactives</string>
+    <string name="etm_background_jobs_start_test_job">Iniciar tasca de prova</string>
+    <string name="etm_background_jobs_stop_test_job">Aturar tasca de prova</string>
+    <string name="etm_migrations">Migracions (actualització d\'aplicació)</string>
     <string name="etm_preferences">Preferències</string>
     <string name="etm_title">Mode de proves d\'enginyeria</string>
     <string name="fab_label">Afegeix o carrega</string>
     <string name="failed_to_download">Ha fallat el lliurament del fitxer al gestor de descàrregues</string>
     <string name="failed_to_print">Ha fallat imprimir el fitxer</string>
+    <string name="failed_to_start_editor">Ha fallat l\'arrancada de l\'editor</string>
     <string name="fallback_weblogin_back">Enrere</string>
     <string name="fallback_weblogin_text">Revertiu l\'antic mètode d\'inici de sessió</string>
     <string name="favorite">Afegix a preferits</string>
@@ -352,8 +372,10 @@
     <string name="hint_password">Contrasenya</string>
     <string name="host_not_available">Els servidor no està disponible</string>
     <string name="host_your_own_server">Allotgeu el vostre propi servidor</string>
+    <string name="instant_upload_existing">També pujar fitxers existents</string>
     <string name="instant_upload_on_charging">Carrega només durant la càrrega de bateria</string>
     <string name="instant_upload_path">/CàrregaInstantània</string>
+    <string name="invalid_url">URL no vàlida</string>
     <string name="learn_more">Més informació</string>
     <string name="link">Enllaç</string>
     <string name="list_layout">Disposició llistada</string>
@@ -456,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">suprimit</string>
     <string name="pref_behaviour_entries_keep_file">mantingut a la carpeta original</string>
     <string name="pref_behaviour_entries_move">mogut a la carpeta de l\'aplicació</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Què s\'ha de fer si el fitxer ja existeix?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Pregunta\'m cada cop</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Sobreescriure la versió remota</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Reanomenar la nova versió</string>
+    <string name="pref_instant_name_collision_policy_title">Què s\'ha de fer si el fitxer ja existeix?</string>
     <string name="prefs_add_account">Afegeix compte</string>
     <string name="prefs_calendar_contacts">Sincronitza el calendari i els contactes</string>
     <string name="prefs_calendar_contacts_address_resolve_error">L\'adreça del servidor per al compte no s\'ha pogut resoldre per DAVx5 (conegut anteriorment com a DAVdroid)</string>
@@ -497,6 +524,7 @@
     <string name="prefs_theme_title">Tema</string>
     <string name="prefs_value_theme_dark">Fosc</string>
     <string name="prefs_value_theme_light">Clar</string>
+    <string name="prefs_value_theme_system">Seguir al sistema</string>
     <string name="preview_image_description">Visualització prèvia d\'imatge</string>
     <string name="preview_image_error_no_local_file">No hi ha cap fitxer local per fer una vista prèvia</string>
     <string name="preview_image_error_unknown_format">No es pot mostrar la imatge</string>
@@ -528,6 +556,7 @@
     <string name="saml_authentication_wrong_pass">Contrasenya incorrecta</string>
     <string name="scanQR_description">Inicia la sessió via un codi QR</string>
     <string name="screenshot_01_gridView_heading">Protegint les vostres dades </string>
+    <string name="screenshot_01_gridView_subline">productivitat auto-hostatjada </string>
     <string name="screenshot_02_listView_heading">Explorar i compartir</string>
     <string name="screenshot_02_listView_subline">totes les accions als vostres dits</string>
     <string name="screenshot_03_drawer_heading">Activitat, comparticions, …</string>
@@ -556,6 +585,8 @@
     <string name="share_get_public_link_button">Obtén l\'enllaç</string>
     <string name="share_group_clarification">%1$s (grup)</string>
     <string name="share_internal_link">Compartir l\'enllaç intern</string>
+    <string name="share_internal_link_to_file_text">L\'enllaç de compartició intern només funciona per a usuaris amb permís d\'accés a aquest fitxer</string>
+    <string name="share_internal_link_to_folder_text">L\'enllaç de compartició intern només funciona per a usuaris amb permís d\'accés a aquesta carpeta</string>
     <string name="share_known_remote_clarification">%1$s (a %2$s)</string>
     <string name="share_link_empty_password">Heu d\'escriure una contrasenya</string>
     <string name="share_link_file_error">S\'ha produït un error mentre s\'intentava compartir aquest fitxer o carpeta</string>
@@ -656,15 +687,21 @@
     <string name="sync_fail_ticker">La sincronització ha fallat</string>
     <string name="sync_fail_ticker_unauthorized">La Sincronització ha fallat, torneu a iniciar la sessió</string>
     <string name="sync_file_nothing_to_do_msg">Contingut del fitxer ja sincronitzat</string>
+    <string name="sync_folder_failed_content">La sincronització de la carpeta %1$s podria no ser completa</string>
     <string name="sync_foreign_files_forgotten_explanation">A partir de la versió 1.3.16, els fitxers carregats des d\'un dispositiu es copien a la carpeta local %1$s per evitar la pèrdua de dades quan un únic fitxer es sincronitza entre múltiples comptes.\n\nA causa d\'aquest canvi, tots els fitxers carregats amb versions anteriors d\'aquesta aplicació s\'han copiat a la carpeta %2$s. Tot i així, un error ha impedit completar aquesta operació durant la sincronització de comptes. Podeu deixar el(s) fitxer(s) tal com està(n) i suprimir l\'enllaç a %3$s, o moure\'l(s) a la carpeta %1$s i conservar l\'enllaç cap a %4$s.\n\nA continuació es mostren el(s) fitxer(s) local(s) i el(s) remot(s) a %5$s, on hi estaven enllaçats.</string>
     <string name="sync_foreign_files_forgotten_ticker">S\'han oblidat alguns fitxers locals</string>
     <string name="sync_in_progress">S\'està obtenint la versió més recent del fitxer.</string>
+    <string name="sync_not_enough_space_dialog_action_choose">Escollir què sincronitzar</string>
+    <string name="sync_not_enough_space_dialog_action_free_space">Alliberar espai</string>
+    <string name="sync_not_enough_space_dialog_placeholder">%1$s és %2$s, però només hi ha %3$s disponible al dispositiu.</string>
+    <string name="sync_not_enough_space_dialog_title">No hi ha suficient espai</string>
     <string name="sync_status_button">Botó de l\'estat de la sincronització</string>
     <string name="sync_string_files">Fitxers</string>
     <string name="synced_folder_settings_button">Botó de paràmetres</string>
     <string name="synced_folders_configure_folders">Configureu les carpetes</string>
     <string name="synced_folders_loading_folders">S\'està carregant les carpetes…</string>
     <string name="synced_folders_new_info">La càrrega instantània s\'ha renovat completament. Torneu a configurar la vostra càrrega automàtica des de dins del menú principal.\n\nGaudiu de la nova càrrega automàtica amb més funcionalitats.</string>
+    <string name="synced_folders_no_results">No s\'han trobat carpetes multimèdia </string>
     <string name="synced_folders_preferences">Preferències de la càrrega automàtica</string>
     <string name="synced_folders_preferences_folder_path">Per %1$s</string>
     <string name="synced_folders_type">Tipus</string>
@@ -672,6 +709,8 @@
     <string name="tags">Etiquetes</string>
     <string name="test_server_button">Proveu la connexió amb el servidor</string>
     <string name="thumbnail">Miniatura</string>
+    <string name="thumbnail_for_existing_file_description">Miniatura pel fitxer existent</string>
+    <string name="thumbnail_for_new_file_desc">Miniatura pel nou fitxer</string>
     <string name="timeout_richDocuments">La càrrega triga molt…</string>
     <string name="trashbin_activity_title">Fitxers suprimits</string>
     <string name="trashbin_empty_headline">No hi ha cap fitxer suprimit</string>
@@ -707,9 +746,11 @@
     <string name="upload_list_delete">Eliminar</string>
     <string name="upload_list_empty_headline">No hi ha càrregues disponibles</string>
     <string name="upload_list_empty_text_auto_upload">Carregueu algun contingut o activeu la càrrega automàtica.</string>
+    <string name="upload_list_resolve_conflict">Resoldre conflicte</string>
     <string name="upload_local_storage_full">L\'emmagatzematge local és ple</string>
     <string name="upload_local_storage_not_copied">El fitxer no es pot copiar a l\'emmagatzematge local</string>
     <string name="upload_lock_failed">Ha fallat el blocatge de la carpeta</string>
+    <string name="upload_old_android">El xifratge només és possible amb &gt;= Android 5.0</string>
     <string name="upload_query_move_foreign_files">L\'espai insuficient evita la còpia dels fitxers seleccionats a la carpeta %1$s. Els hi voleu moure?</string>
     <string name="upload_sync_conflict">Conflicte de sincronització. Si us plau, resoleu manualment.</string>
     <string name="upload_unknown_error">Error desconegut</string>
@@ -725,6 +766,8 @@
     <string name="uploader_top_message">Escolliu la carpeta de càrrega</string>
     <string name="uploader_upload_failed_content_single">No s\'ha pogut pujar %1$s</string>
     <string name="uploader_upload_failed_credentials_error">La càrrega ha fallat, torneu a iniciar la sessió</string>
+    <string name="uploader_upload_failed_sync_conflict_error">Conflicte en pujar fitxer</string>
+    <string name="uploader_upload_failed_sync_conflict_error_content">Escollir quina versió conservar de %1$s</string>
     <string name="uploader_upload_failed_ticker">Ha fallat la pujada</string>
     <string name="uploader_upload_files_behaviour">Opció de càrrega:</string>
     <string name="uploader_upload_files_behaviour_move_to_nextcloud_folder">Mou al fitxer a la carpeta %1$s</string>
@@ -753,6 +796,7 @@
     <string name="uploads_view_upload_status_failed_localfile_error">No s\'ha trobat el fitxer local</string>
     <string name="uploads_view_upload_status_failed_permission_error">Error de permisos</string>
     <string name="uploads_view_upload_status_failed_ssl_certificate_not_trusted">Certificat de servidor no fiable</string>
+    <string name="uploads_view_upload_status_fetching_server_version">Obtenint la versió del servidor...</string>
     <string name="uploads_view_upload_status_service_interrupted">L\'aplicació s\'ha tancat</string>
     <string name="uploads_view_upload_status_succeeded">Completat</string>
     <string name="uploads_view_upload_status_unknown_fail">Error desconegut</string>

+ 5 - 0
src/main/res/values-da/strings.xml

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">Slettet</string>
     <string name="pref_behaviour_entries_keep_file">forblevet i oprindelig mappe</string>
     <string name="pref_behaviour_entries_move">Overført til app mappe</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Hvad skal ske, hvis filen allerede findes?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Spørg mig altid</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Overskriv fjerntliggende version</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Omdøb ny version</string>
+    <string name="pref_instant_name_collision_policy_title">Hvad skal ske, hvis filen allerede findes?</string>
     <string name="prefs_add_account">Tilføj konto</string>
     <string name="prefs_calendar_contacts">Synkroniser kalender &amp; kontakter</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Server adressen for kontoen kunne ikke findes for DAVx5 (før kendt som DAVdroid)</string>

+ 4 - 0
src/main/res/values-fa/strings.xml

@@ -142,6 +142,7 @@
     <string name="confirmation_remove_folders_alert">آیا واقعاً می خواهید موارد انتخاب شده و محتوای آنها حذف شود؟</string>
     <string name="confirmation_remove_local">فقط محلی</string>
     <string name="conflict_message_headline">کدام فایل ها را می خواهید نگه دارید ؟</string>
+    <string name="conflict_new_file">فایل جدید</string>
     <string name="contaclist_restore_selected">بازیابی مخاطبین انتخاب شده</string>
     <string name="contactlist_account_chooser_title">حساب را برای وارد کردن انتخاب کنید</string>
     <string name="contactlist_item_icon">آیکون کاربر برای لیست تماس</string>
@@ -253,6 +254,8 @@
     <string name="error_starting_direct_camera_upload">خطا در شروع دوربین</string>
     <string name="etm_accounts">حساب‌ها</string>
     <string name="etm_background_job_progress">پیش رفتن</string>
+    <string name="etm_background_job_state">وضعیت</string>
+    <string name="etm_background_job_user">کاربر</string>
     <string name="etm_background_jobs">کارهای پس زمینه</string>
     <string name="etm_preferences">اولویت ها</string>
     <string name="etm_title">حالت تست مهندسی</string>
@@ -359,6 +362,7 @@
     <string name="host_your_own_server">میزبان سرور خود</string>
     <string name="instant_upload_on_charging">فقط هنگامی که گوشی شارژ می شود آپلود کنید</string>
     <string name="instant_upload_path">آپلود فوری</string>
+    <string name="invalid_url">آدرس اینترنتی اشتباه</string>
     <string name="learn_more">بیشتر بدانید</string>
     <string name="link">Link</string>
     <string name="list_layout">Listed layout</string>

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

@@ -37,7 +37,7 @@
     <string name="auth_access_failed">Acceso fallado: %1$s</string>
     <string name="auth_account_does_not_exist">A conta aínda non existe no dispositivo</string>
     <string name="auth_account_not_new">Xa existe unha conta neste dispositivo cos mesmos datos de usuario e servidor</string>
-    <string name="auth_account_not_the_same">O usuario que inseriu non coincide co usuario desta conta</string>
+    <string name="auth_account_not_the_same">O usuario que introduciu non coincide co usuario desta conta</string>
     <string name="auth_bad_oc_version_title">Versión do servidor non recoñecida</string>
     <string name="auth_can_not_auth_against_server">Non é posíbel autenticarse neste servidor</string>
     <string name="auth_check_server">Comprobar o servidor</string>

+ 5 - 0
src/main/res/values-hu-rHU/strings.xml

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">törölt</string>
     <string name="pref_behaviour_entries_keep_file">megtartva az eredeti mappában</string>
     <string name="pref_behaviour_entries_move">áthelyezve az alkalmazásmappába</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Mit tegyen, ha a fájl már létezik?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Kérdezzen rá mindig</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Távoli verzió felülírása</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Új verzió átnevezése</string>
+    <string name="pref_instant_name_collision_policy_title">Mit tegyen, ha a fájl már létezik?</string>
     <string name="prefs_add_account">Fiók hozzáadása</string>
     <string name="prefs_calendar_contacts">Naptár és névjegyek szinkronizálása</string>
     <string name="prefs_calendar_contacts_address_resolve_error">A fiók kiszolgálócíme nem oldható fel a DAVx5 (régi nevén DAVdroid) számára</string>

+ 45 - 0
src/main/res/values-iw/strings.xml

@@ -141,7 +141,11 @@
     <string name="confirmation_remove_folder_alert">למחוק את %1$s על תוכנו?</string>
     <string name="confirmation_remove_folders_alert">למחוק את הפריטים הנבחרים ואת תוכנם?</string>
     <string name="confirmation_remove_local">מקומי בלבד</string>
+    <string name="conflict_already_existing_file">קובץ קיים</string>
+    <string name="conflict_dialog_error">שגיאה ביצירת תיבת דו־שיח סתירה!</string>
+    <string name="conflict_message_description">בבחירה של שתי הגרסאות, יתווסף מספר בסוף שמו של הקובץ המקומי.</string>
     <string name="conflict_message_headline">אילו קבצים ברצונך לשמור?</string>
+    <string name="conflict_new_file">קובץ חדש</string>
     <string name="contaclist_restore_selected">שחזור אנשי הקשר הנבחרים</string>
     <string name="contactlist_account_chooser_title">נא לבחור חשבון לייבוא</string>
     <string name="contactlist_item_icon">סמן משתמש לרשימת אנשי קשר</string>
@@ -167,10 +171,13 @@
     <string name="could_not_download_image">לא ניתן להוריד את התמונה המלאה</string>
     <string name="could_not_retrieve_url">לא ניתן לקבל כתובת</string>
     <string name="create_dir_fail_msg">לא ניתן ליצור תיקייה</string>
+    <string name="create_new">יצירת חדש</string>
     <string name="create_new_document">יצירת מסמך חדש</string>
     <string name="create_new_folder">יצירת תיקייה חדשה</string>
     <string name="create_new_presentation">יצירת מצגת חדשה</string>
     <string name="create_new_spreadsheet">יצירת גיליון חדש</string>
+    <string name="create_rich_workspace">הוספת פרטי תיקייה</string>
+    <string name="creates_rich_workspace">יוצר פרטי תיקייה</string>
     <string name="credentials_disabled">פרטי גישה מושבתים</string>
     <string name="date_unknown">לא ידוע</string>
     <string name="default_credentials_wrong">פרטי הגישה שגויים</string>
@@ -221,6 +228,7 @@
     <string name="drawer_quota_unlimited">%1$s בשימוש</string>
     <string name="drawer_synced_folders">העלאה אוטומטית</string>
     <string name="edit_permission_label">עריכה</string>
+    <string name="edit_rich_workspace">עריכת פרטי תיקייה</string>
     <string name="encrypted">הגדרה כמוצפן</string>
     <string name="end_to_end_encryption_confirm_button">הגדרת הצפנה</string>
     <string name="end_to_end_encryption_decrypting">מתבצע פענוח…</string>
@@ -249,12 +257,24 @@
     <string name="error_retrieving_templates">קבלת התבניות נכשלה</string>
     <string name="error_starting_direct_camera_upload">הפעלת המצלמה נכשלה</string>
     <string name="etm_accounts">חשבונות</string>
+    <string name="etm_background_job_name">שם המשימה</string>
+    <string name="etm_background_job_progress">תהליך</string>
+    <string name="etm_background_job_started">התחלה</string>
+    <string name="etm_background_job_state">מצב</string>
+    <string name="etm_background_job_user">משתמש</string>
+    <string name="etm_background_job_uuid">מזהה ייחודי</string>
     <string name="etm_background_jobs">משימות רקע</string>
+    <string name="etm_background_jobs_cancel_all">ביטול על המשימות</string>
+    <string name="etm_background_jobs_prune">ניקוי משימות בלתי פעילות</string>
+    <string name="etm_background_jobs_start_test_job">התחלת משימת בדיקה</string>
+    <string name="etm_background_jobs_stop_test_job">עצירת משימת בדיקה</string>
+    <string name="etm_migrations">נדידות (שדרוגי יישומים)</string>
     <string name="etm_preferences">העדפות</string>
     <string name="etm_title">מצב בדיקת מהנדסים</string>
     <string name="fab_label">הוספה או העלאה</string>
     <string name="failed_to_download">העברת הקובץ למנהל ההורדות נכשלה</string>
     <string name="failed_to_print">הדפסת הקובץ נכשלה</string>
+    <string name="failed_to_start_editor">התחלת העורך נכשלה</string>
     <string name="fallback_weblogin_back">חזרה</string>
     <string name="fallback_weblogin_text">החזרה לשיטת כניסה ישנה</string>
     <string name="favorite">הוספה למועדפים</string>
@@ -352,8 +372,10 @@
     <string name="hint_password">ססמה</string>
     <string name="host_not_available">השרת אינו זמין</string>
     <string name="host_your_own_server">אירוח שרת משלך</string>
+    <string name="instant_upload_existing">להעלות גם קבצים קיימים</string>
     <string name="instant_upload_on_charging">להעלות רק בעת טעינה</string>
     <string name="instant_upload_path">/InstantUpload</string>
+    <string name="invalid_url">כתובת שגויה</string>
     <string name="learn_more">לקריאה נוספת</string>
     <string name="link">קישור</string>
     <string name="list_layout">פריסה מוצגת</string>
@@ -456,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">נמחק</string>
     <string name="pref_behaviour_entries_keep_file">נשמר בתיקייה מקורית</string>
     <string name="pref_behaviour_entries_move">הועבר לתיקיית ישומים</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">מה לעשות אם הקובץ כבר קיים?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">לשאול בכל פעם</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">לשכתב על הגרסה המרוחקת</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">לשנות את שם הגרסה החדשה</string>
+    <string name="pref_instant_name_collision_policy_title">מה לעשות אם הקובץ כבר קיים?מה לעשות אם הקובץ קיים?</string>
     <string name="prefs_add_account">הוספת חשבון</string>
     <string name="prefs_calendar_contacts">סנכרון לוח שנה ואנשי קשר</string>
     <string name="prefs_calendar_contacts_address_resolve_error">לא ניתן לפתור את כתובת השרת לחשבון עבור DAVx5 (לשעבר DAVdroid)</string>
@@ -558,6 +585,8 @@
     <string name="share_get_public_link_button">קבלת קישור</string>
     <string name="share_group_clarification">%1$s (קבוצה)</string>
     <string name="share_internal_link">שיתוף קישור פנימי</string>
+    <string name="share_internal_link_to_file_text">קישורי שיתוף פנימיים עובדים רק עבור משתמש עם גישה לקובץ הזה</string>
+    <string name="share_internal_link_to_folder_text">קישורי שיתוף פנימיים עובדים רק עבור משתמש עם גישה לתיקייה הזו</string>
     <string name="share_known_remote_clarification">%1$s ( ב- %2$s )</string>
     <string name="share_link_empty_password">יש להכניס סיסמא</string>
     <string name="share_link_file_error">שגיאה אירעה בזמן ניסיון לשתף קובץ זה או תיקייה זו</string>
@@ -656,11 +685,14 @@
     <string name="sync_fail_ticker">סנכרון נכשל</string>
     <string name="sync_fail_ticker_unauthorized">הסנכרון נכשל, יש להיכנס שוב</string>
     <string name="sync_file_nothing_to_do_msg">תוכן הקובץ כבר מסונכרן</string>
+    <string name="sync_folder_failed_content">לא ניתן להשלים את סנכרון התיקייה %1$s</string>
     <string name="sync_foreign_files_forgotten_explanation">החל מגרסה 1.3.16, קבצים שנשלחים מהמכשיר הזה מועתקים אל התיקייה המקומית %1$s כדי למנוע אבדן נתונים במקרה שקובץ אחד מסונכרן בין כמה חשבונות.\n\nעקב השינוי הזה, כל הקבצים שנשלחו בעזרת גרסאות קודמות של היישומון הועתקו לתיקייה %2$s. עם זאת, שגיאה מנעה את השלמת הפעולה הזאת במהלך סנכרון החשבון. באפשרותך להשאיר את הקובץ או הקבצים כמו שהם ולמחוק את הקישור אל %3$s, או להעביר את הקבצים אל התיקייה %1$s ולשמור את הקישור אל %4$s.\n\nלהלן כל הקבצים המקומיים והמרוחקים שתחת %5$s שאליהם הם מקושרים.</string>
     <string name="sync_foreign_files_forgotten_ticker">חלק מהקבצים המקומיים נשכחו</string>
     <string name="sync_in_progress">מתקבלת הגרסה העדכנית ביותר של הקובץ.</string>
     <string name="sync_not_enough_space_dialog_action_choose">נא לבחור מה לסנכרן</string>
     <string name="sync_not_enough_space_dialog_action_free_space">פינוי מקום</string>
+    <string name="sync_not_enough_space_dialog_placeholder">הגודל של %1$s הוא %2$s, אך יש רק %3$s פנויים במכשיר הזה.</string>
+    <string name="sync_not_enough_space_dialog_title">אין מספיק מקום</string>
     <string name="sync_status_button">כפתור מצב סנכרון</string>
     <string name="sync_string_files">קבצים</string>
     <string name="synced_folder_settings_button">כפתור הגדרות</string>
@@ -675,6 +707,8 @@
     <string name="tags">תגיות</string>
     <string name="test_server_button">בדיקת החיבור לשרת</string>
     <string name="thumbnail">תמונה ממוזערת</string>
+    <string name="thumbnail_for_existing_file_description">תמונה ממוזערת לקובץ קיים</string>
+    <string name="thumbnail_for_new_file_desc">תמונה ממוזערת לקובץ חדש</string>
     <string name="timeout_richDocuments">הטעינה אורכת זמן רב…</string>
     <string name="trashbin_activity_title">קבצים שנמחקו</string>
     <string name="trashbin_empty_headline">אין קבצים שנמחקו</string>
@@ -710,9 +744,11 @@
     <string name="upload_list_delete">מחיקה</string>
     <string name="upload_list_empty_headline">אין העלאות זמינות</string>
     <string name="upload_list_empty_text_auto_upload">יש להעלות תוכן כלשהו או להפעיל העלאה אוטומטית.</string>
+    <string name="upload_list_resolve_conflict">פתירת סתירה</string>
     <string name="upload_local_storage_full">האחסון המקומי מלא</string>
     <string name="upload_local_storage_not_copied">לא ניתן להעתיק קובץ לאחסון המקומי</string>
     <string name="upload_lock_failed">נעילת התיקייה נכשלה</string>
+    <string name="upload_old_android">הצפנה אפשרית רק עם &gt;= Android 5.0</string>
     <string name="upload_query_move_foreign_files">מחסור במקום מונע את העתקת הקבצים הנבחרים לתוך התיקייה %1$s. להעביר אותם לשם במקום?</string>
     <string name="upload_sync_conflict">סתירת סנכרון, נא לפתור ידנית</string>
     <string name="upload_unknown_error">שגיאה בלתי ידועה</string>
@@ -728,6 +764,8 @@
     <string name="uploader_top_message">יש לבחור בתיקיית העלאה</string>
     <string name="uploader_upload_failed_content_single">לא ניתן להעלות %1$s</string>
     <string name="uploader_upload_failed_credentials_error">ההעלאה נכשלה, יש להיכנס שוב</string>
+    <string name="uploader_upload_failed_sync_conflict_error">סתירת העלאת קובץ</string>
+    <string name="uploader_upload_failed_sync_conflict_error_content">נא לבחור איזו גרסה של %1$s להשאיר</string>
     <string name="uploader_upload_failed_ticker">ההעלאה נכשלה</string>
     <string name="uploader_upload_files_behaviour">אפשרויות העלאה:</string>
     <string name="uploader_upload_files_behaviour_move_to_nextcloud_folder">העברת קובץ לתיקייה %1$s</string>
@@ -756,6 +794,7 @@
     <string name="uploads_view_upload_status_failed_localfile_error">קובץ מקומי לא נמצא</string>
     <string name="uploads_view_upload_status_failed_permission_error">שגיאת הרשאה</string>
     <string name="uploads_view_upload_status_failed_ssl_certificate_not_trusted">אישור השרת אינו מהימן</string>
+    <string name="uploads_view_upload_status_fetching_server_version">גרסת השרת מתושאלת…</string>
     <string name="uploads_view_upload_status_service_interrupted">היישומון הופסק</string>
     <string name="uploads_view_upload_status_succeeded">הושלם</string>
     <string name="uploads_view_upload_status_unknown_fail">שגיאה בלתי ידועה</string>
@@ -804,6 +843,12 @@
         <item quantity="many">%1$d קבצים</item>
         <item quantity="other">%1$d קבצים</item>
     </plurals>
+    <plurals name="synced_folders_show_hidden_folders">
+        <item quantity="one">הצגת תיקייה (%1$d) מוסתרת</item>
+        <item quantity="two">הצגת %1$d תיקיות מוסתרות</item>
+        <item quantity="many">הצגת %1$d תיקיות מוסתרות</item>
+        <item quantity="other">הצגת %1$d תיקיות מוסתרות</item>
+    </plurals>
     <plurals name="items_selected_count">
         <item quantity="one">נבחר %d</item>
         <item quantity="two">נבחרו %d</item>

+ 55 - 0
src/main/res/values-ko/strings.xml

@@ -141,7 +141,11 @@
     <string name="confirmation_remove_folder_alert">%1$s 및 포함된 모든 내용을 삭제하시겠습니까?</string>
     <string name="confirmation_remove_folders_alert">선택한 항목과 포함된 내용을 삭제하시겠습니까?</string>
     <string name="confirmation_remove_local">로컬만</string>
+    <string name="conflict_already_existing_file">파일이 이미 존재합니다</string>
+    <string name="conflict_dialog_error">다이얼로그의 충돌로 인한 오류</string>
+    <string name="conflict_message_description">두 버전을 모두 선택하면 기존 파일 이름에 번호가 추가됩니다.</string>
     <string name="conflict_message_headline">어느 파일을 유지하시겠습니까?</string>
+    <string name="conflict_new_file">새 파일</string>
     <string name="contaclist_restore_selected">선택한 주소록 복원</string>
     <string name="contactlist_account_chooser_title">가져올 계정 선택</string>
     <string name="contactlist_item_icon">연락처 목록 사용자 아이콘</string>
@@ -173,6 +177,7 @@
     <string name="create_new_presentation">새 프리젠테이션 작성</string>
     <string name="create_new_spreadsheet">새 스프레드시트 작성</string>
     <string name="create_rich_workspace">폴더 정보 추가</string>
+    <string name="creates_rich_workspace">폴더 정보 만들기</string>
     <string name="credentials_disabled">인증 비활성화됨</string>
     <string name="date_unknown">알 수 없음</string>
     <string name="default_credentials_wrong">인증 정보 틀림</string>
@@ -252,8 +257,18 @@
     <string name="error_retrieving_templates">템플릿 검색 중 오류 발생</string>
     <string name="error_starting_direct_camera_upload">카메라 시작 중 오류 발생</string>
     <string name="etm_accounts">Accounts</string>
+    <string name="etm_background_job_name">작업 이름</string>
     <string name="etm_background_job_progress">진행 상황</string>
+    <string name="etm_background_job_started">시작됨</string>
+    <string name="etm_background_job_state">상태</string>
+    <string name="etm_background_job_user">사용자</string>
+    <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">배경 작업</string>
+    <string name="etm_background_jobs_cancel_all">모든 작업 취소하기</string>
+    <string name="etm_background_jobs_prune">비활성 작업 삭제하기</string>
+    <string name="etm_background_jobs_start_test_job">테스트 작업 시작하기</string>
+    <string name="etm_background_jobs_stop_test_job">테스트 작업 정지하기</string>
+    <string name="etm_migrations">이전 작업 (앱 업그레이드)</string>
     <string name="etm_preferences">환경 설정</string>
     <string name="etm_title">엔지니어링 테스트 모드</string>
     <string name="fab_label">추가 또는 업로드</string>
@@ -357,8 +372,10 @@
     <string name="hint_password">암호</string>
     <string name="host_not_available">서버를 사용할 수 없음</string>
     <string name="host_your_own_server">자체 서버 호스팅</string>
+    <string name="instant_upload_existing">이미 존재하는 파일도 올리기</string>
     <string name="instant_upload_on_charging">충전 중에만 업로드</string>
     <string name="instant_upload_path">/InstantUpload</string>
+    <string name="invalid_url">잘못된 URL</string>
     <string name="learn_more">자세히 알아보기</string>
     <string name="link">링크</string>
     <string name="list_layout">목록 보기</string>
@@ -372,6 +389,7 @@
     <string name="logs_menu_refresh">새로 고침</string>
     <string name="logs_menu_search">로그 검색</string>
     <string name="logs_menu_send">이메일로 로그 보내기</string>
+    <string name="logs_status_filtered">로그: %1$d kB, 쿼리가 %3$d개 중에서 %2$d개 를 찾음, 소요시간 %4$d ms</string>
     <string name="logs_status_loading">불러오는 중…</string>
     <string name="logs_status_not_filtered">기록: %1$dkB, 필터 없음</string>
     <string name="logs_title">로그</string>
@@ -460,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">삭제됨</string>
     <string name="pref_behaviour_entries_keep_file">원래 폴더에 유지됨</string>
     <string name="pref_behaviour_entries_move">앱 폴더로 이동함</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">파일이 이미 존재한다면 어떻게 처리할까요?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">매번 물어보기</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">원격 버전을 덮어 씌우기</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">새 버전의 이름을 바꾸기</string>
+    <string name="pref_instant_name_collision_policy_title">파일이 이미 존재한다면 어떻게 처리할까요?</string>
     <string name="prefs_add_account">계정 추가</string>
     <string name="prefs_calendar_contacts">달력 및 연락처 동기화</string>
     <string name="prefs_calendar_contacts_address_resolve_error">DAVx5 (이전의 DAVdroid)에서 계정의 서버 주소를 확인할 수 없습니다.</string>
@@ -488,6 +511,7 @@
     <string name="prefs_lock_device_credentials_enabled">기기 인증 활성화됨</string>
     <string name="prefs_lock_device_credentials_not_setup">기기 인증이 설정되지 않았습니다.</string>
     <string name="prefs_lock_none">없음</string>
+    <string name="prefs_lock_title">사용하여 앱을 보호</string>
     <string name="prefs_lock_using_device_credentials">기기 인증 정보</string>
     <string name="prefs_lock_using_passcode">암호</string>
     <string name="prefs_manage_accounts">계정 관리</string>
@@ -500,6 +524,7 @@
     <string name="prefs_theme_title">테마</string>
     <string name="prefs_value_theme_dark">어둡게</string>
     <string name="prefs_value_theme_light">밝게</string>
+    <string name="prefs_value_theme_system">시스템을 따르기</string>
     <string name="preview_image_description">사진 미리 보기</string>
     <string name="preview_image_error_no_local_file">미리 볼 로컬 파일 없음</string>
     <string name="preview_image_error_unknown_format">이 사진을 미리 볼 수 없음</string>
@@ -531,8 +556,10 @@
     <string name="saml_authentication_wrong_pass">잘못된 암호</string>
     <string name="scanQR_description">QR 코드로 로그인</string>
     <string name="screenshot_01_gridView_heading">데이터 보호</string>
+    <string name="screenshot_01_gridView_subline">자체 운영되는 생산성</string>
     <string name="screenshot_02_listView_heading">탐색하고 공유</string>
     <string name="screenshot_02_listView_subline">모든 터치 액션</string>
+    <string name="screenshot_03_drawer_heading">활동, 공유, ...</string>
     <string name="screenshot_03_drawer_subline">빠르게 접근 가능한 모든 것.</string>
     <string name="screenshot_04_accounts_heading">모든 계정</string>
     <string name="screenshot_04_accounts_subline">한곳에서</string>
@@ -558,6 +585,8 @@
     <string name="share_get_public_link_button">링크 얻기</string>
     <string name="share_group_clarification">%1$s(그룹)</string>
     <string name="share_internal_link">내부 링크 공유</string>
+    <string name="share_internal_link_to_file_text">내부 공유 링크 (이 파일에 접근 권한이 있는 사람만 사용가능)</string>
+    <string name="share_internal_link_to_folder_text">내부 공유 링크 (이 폴더에 접근 권한이 있는 사람만 사용가능)</string>
     <string name="share_known_remote_clarification">%1$s(%2$s에서)</string>
     <string name="share_link_empty_password">암호를 입력해야 합니다</string>
     <string name="share_link_file_error">이 파일이나 폴더를 공유하는 중 오류 발생</string>
@@ -641,6 +670,28 @@
     <string name="storage_movies">영화</string>
     <string name="storage_music">음악</string>
     <string name="storage_pictures">그림</string>
+    <string name="store_full_desc">당신이 관리할 수 있는 자체 운영되는 생산성 플랫폼.
+
+특징:
+당신의 Nextcloud 서버 테마에 맞는 쉽고 현대적인 인터페이스를 지니고 있음
+파일을 Nextcloud 서버에 올리고 다른 이들과 공유할 수 있음
+당신의 중요 파일과 폴더를 동기화 된 상태로 유지 할 수 있음
+당신의 폴더들 구석구석을 검색할 수 있음
+당신의 장치로 찍힌 사진과 동영상을 자동 업로드 할 수 있음
+Nextcloud의 알림으로 최신 상태로 유지할 수 있음
+다-계정 지원 가능함
+지문이나 PIN을 통한 안전한 잠금을 지원함
+DAVx5(DAVdroid) 와의 통합으로 캘린더와 주소록을 쉽게 동기화할 수 있음
+
+문제가 있다면 https://github.com/nextcloud/android/issues 여기서 토론을
+앱에 문제가 있다면 https://help.nextcloud.com/c/clients/android 여기서 토론을 부탁드립니다.
+
+Nextcloud가 처음이신가요?
+Nextcloud는 개인적인 파일 동기화, 공유 그리고 의사소통을 위한 서버입니다.
+Nextcloud는 자유 소프트웨어이고, 당신이 직접 운영하거나 돈을 내고 다른 서버의 계정를 빌릴 수 있습니다.
+그럼으로써, 당신은 당신의 사진, 캘린더와 주소록, 당신의 문서 등을 관리할 수 있습니다.
+
+Nextcloud를 여기서 확인하십시오: https://nextcloud.com</string>
     <string name="store_full_dev_desc">컨트롤 가능한 자체 호스팅 생산성 플랫폼. \ n 이는 공식적으로 개발 된 버전으로, 테스트되지 않은 새로운 기능을 매일 샘플로 제공하므로 불안정과 데이터 손실이 발생할 수 있습니다. 이 응용 프로그램은 기꺼이 테스트하고 버그가 발생하면 보고하는 사용자를 대상으로 합니다. 생산적인 작업에는 사용하지 마십시오! \ n \ n 공식 개발자 및 일반 버전은 모두 F-droid에서 사용할 수 있으며 동시에 설치할 수 있습니다.</string>
     <string name="store_short_desc">컨트롤 가능한 자체 호스팅 생산성 플랫폼</string>
     <string name="store_short_dev_desc">컨트롤 가능한 자체 호스팅 생산성 플랫폼 (디바이스 미리 보기 버전)</string>
@@ -657,11 +708,13 @@
     <string name="sync_fail_ticker">동기화 실패</string>
     <string name="sync_fail_ticker_unauthorized">동기화 실패, 다시 로그인하십시오</string>
     <string name="sync_file_nothing_to_do_msg">파일 내용이 이미 동기화됨</string>
+    <string name="sync_folder_failed_content">%1$s 폴더의 동기화를 완료할 수 없음</string>
     <string name="sync_foreign_files_forgotten_explanation">버전 1.3.16부터 단일 파일을 여러 계정과 동기화 시 자료 손실을 방지하기 위하여 이 장치에서 업로드된 파일은 로컬 %1$s 폴더에 일단 복사됩니다.\n\n이 변경 사항으로 인하여 이전 버전 앱에서 업로드한 모든 파일을 %2$s 폴더로 복사했습니다. 하지만 계정 동기화 중 오류가 발생하여 이 작업이 중단되었습니다. 파일을 그대로 두고 %3$s 연결을 삭제하거나, 파일을 %1$s 폴더로 이동하고 %4$s 연결을 그대로 유지할 수 있습니다.\n\n아래의 목록은 로컬 파일과 연결된 %5$s에 있는 원격 파일 목록입니다.</string>
     <string name="sync_foreign_files_forgotten_ticker">일부 로컬 파일이 사라짐</string>
     <string name="sync_in_progress">이 파일의 최신 버전을 가져옵니다.</string>
     <string name="sync_not_enough_space_dialog_action_choose">동기화 대상 선택</string>
     <string name="sync_not_enough_space_dialog_action_free_space">남는 공간 늘리기</string>
+    <string name="sync_not_enough_space_dialog_placeholder">%1$s 파일은 %2$s 이지만,  장치에는 %3$s 밖에 남아있지 않습니다.  </string>
     <string name="sync_not_enough_space_dialog_title">남은 공간 부족</string>
     <string name="sync_status_button">동기화 상태 단추</string>
     <string name="sync_string_files">파일</string>
@@ -677,6 +730,8 @@
     <string name="tags">태그</string>
     <string name="test_server_button">서버 연결 테스트</string>
     <string name="thumbnail">섬네일</string>
+    <string name="thumbnail_for_existing_file_description">이미 존재하는 파일의 썸네일</string>
+    <string name="thumbnail_for_new_file_desc">새로운 파일의 썸네일</string>
     <string name="timeout_richDocuments">오래 걸립니다...</string>
     <string name="trashbin_activity_title">삭제된 파일</string>
     <string name="trashbin_empty_headline">삭제된 파일 없음</string>

+ 1 - 1
src/main/res/values-lt-rLT/strings.xml

@@ -385,7 +385,7 @@
     <string name="notification_icon">Pranešimo piktograma</string>
     <string name="notifications_loading_activity">Įkeliami pranešimai…</string>
     <string name="notifications_no_results_headline">Pranešimų nėra</string>
-    <string name="notifications_no_results_message">Prašome patikrinti vėliau</string>
+    <string name="notifications_no_results_message">Prašome patikrinti vėliau.</string>
     <string name="offline_mode">Nėra interneto ryšio</string>
     <string name="overflow_menu">Daugiau meniu</string>
     <string name="pass_code_configure_your_pass_code">Įveskite užraktą</string>

+ 30 - 0
src/main/res/values-lv/strings.xml

@@ -43,6 +43,7 @@
     <string name="auth_check_server">Pārbaudīt serveri</string>
     <string name="auth_connection_established">Savienojums ir izveidots</string>
     <string name="auth_expired_basic_auth_toast">Lūdzu, ievadiet pašreizējo paroli</string>
+    <string name="auth_fail_get_user_name">Jūsu serveris neatgriež pareizu lietotāja ID, lūdzu, sazinieties ar administratoru</string>
     <string name="auth_host_address">Servera adrese</string>
     <string name="auth_host_url">Servera adrese https://…</string>
     <string name="auth_illegal_login_used">Nepareizs pierakstīšanās datu URL izmantots</string>
@@ -71,13 +72,22 @@
     <string name="auth_username">Lietotājvārds</string>
     <string name="auth_wrong_connection_title">Neizdevās izveidot savienojumu</string>
     <string name="auth_wtf_reenter_URL">Neparedzēts stāvoklis, lūdzu ievadiet servera adresi no jauna</string>
+    <string name="auto_upload_file_behaviour_kept_in_folder">glabāts oriģinālajā mapē, jo tā ir tikai lasāma</string>
     <string name="auto_upload_on_wifi">Augšupielādēt tikai neirobežotā Wi-Fi</string>
+    <string name="autoupload_configure">Konfigurē</string>
+    <string name="autoupload_create_new_custom_folder">Izveidot jaunu pielāgotu mapes iestatījumu</string>
+    <string name="autoupload_custom_folder">Pielāgotas mapes iestatīšana</string>
+    <string name="autoupload_disable_power_save_check">Atspējot enerģijas taupīšanas pārbaudi</string>
+    <string name="autoupload_hide_folder">Paslēpt mapi</string>
     <string name="avatar">Profila attēls</string>
     <string name="battery_optimization_close">Aizvērt</string>
     <string name="battery_optimization_disable">Deaktivēt</string>
+    <string name="battery_optimization_message">Jūsu ierīcē ir iespējota akumulatora optimizācija. Auto augšupielāde pareizi darbosies tikai tad, kad jūs lietotni izņemsiet no šīs opcijas.</string>
+    <string name="battery_optimization_title">Akumulatora optimizācija</string>
     <string name="certificate_load_problem">Radusies problēma, ielādējot sertifikātu.</string>
     <string name="choose_local_folder">Izvēlies lokālo mapi…</string>
     <string name="choose_remote_folder">Izvēlies attālināto mapi…</string>
+    <string name="clear_notifications_failed">Neizdevās notīrīt paziņojumus.</string>
     <string name="clipboard_label">Teksts kopēts no %1$s</string>
     <string name="clipboard_no_text_to_copy">Nav teksta, ko saglabāt starpliktuvē</string>
     <string name="clipboard_text_copied">Saite nokopēta</string>
@@ -116,14 +126,19 @@
     <string name="community_testing_headline">Palīdzi testējot</string>
     <string name="community_testing_report_text">Ziņojiet par problēmu Github</string>
     <string name="community_testing_version_text">Interesēts mums palīdzēt izmēģinot jauno testēšanas versiju?</string>
+    <string name="configure_new_media_folder_detection_notifications">Konfigurēt</string>
     <string name="confirmation_remove_file_alert">Vai tiešām vēlaties izdzēst %1$s?</string>
     <string name="confirmation_remove_files_alert">Vai tiešām vēlies dzēst izvēlētos objektus?</string>
     <string name="confirmation_remove_folder_alert">Vai tiešām vēlaties izdzēst %1$s un tā saturu?</string>
     <string name="confirmation_remove_folders_alert">Vai tiešām vēlies dzēst izvēlētos objektus un to saturu?</string>
     <string name="confirmation_remove_local">Tikai lokālos</string>
+    <string name="conflict_already_existing_file">Jau esošs fails</string>
+    <string name="conflict_dialog_error">Kļūda veidojot konfliktu dialogu!</string>
     <string name="conflict_message_headline">Kuras datnes vēlies paturēt?</string>
+    <string name="conflict_new_file">Jauns fails</string>
     <string name="contaclist_restore_selected">Atjaunot izvēlētos kontaktus</string>
     <string name="contactlist_account_chooser_title">Izvēlieties kontu importēšanai</string>
+    <string name="contactlist_item_icon">Lietotāja ikona kontaktpersonu sarakstam</string>
     <string name="contactlist_no_permission">Nav dota atļauja, importēšana neizdevās</string>
     <string name="contacts_automatic_backup">Automātiskā dublēšana</string>
     <string name="contacts_backup_button">Dublēt tagad</string>
@@ -133,23 +148,37 @@
     <string name="contacts_preferences_no_file_found">Datne nav atrasta</string>
     <string name="contacts_preferences_something_strange_happened">Nevarēja atrast pēdējo dublējumu!</string>
     <string name="contacts_read_permission">Vajadzīga atļauja lasīt kontaktu sarakstu</string>
+    <string name="copied_to_clipboard">Nokopēts starpliktuvē</string>
     <string name="copy_file_error">Radās kļūda, mēģinot kopēt šo datni vai mapi</string>
     <string name="copy_file_invalid_into_descendent">Nav iespējams kopēt mapi tās apakšmapē</string>
     <string name="copy_file_invalid_overwrite">Datne jau pastāv mērķa mapē</string>
     <string name="copy_file_not_found">Nevar nokopēt. Lūdzu, pārbaudiet, vai datne pastāv</string>
     <string name="copy_link">Kopēt saiti</string>
     <string name="copy_to">Kopēt uz…</string>
+    <string name="could_not_download_image">Nevarēja lejupielādēt pilnu attēlu</string>
+    <string name="could_not_retrieve_url">Neizdevās iegūt URL </string>
     <string name="create_dir_fail_msg">Nevarēja izveidot mapi</string>
+    <string name="create_new">Izveidot jaunu</string>
+    <string name="create_new_document">Izveidot jaunu dokumentu </string>
     <string name="create_new_folder">Izveidot jaunu mapi</string>
+    <string name="create_new_presentation">Izveidot jaunu prezentāciju</string>
+    <string name="create_new_spreadsheet">Izveidot jaunu izklājlapu</string>
+    <string name="create_rich_workspace">Pievienot mapes informāciju</string>
+    <string name="creates_rich_workspace">pievieno mapes informāciju</string>
     <string name="credentials_disabled">Akreditācijas dati atspējoti</string>
     <string name="date_unknown">Nezināms</string>
     <string name="default_credentials_wrong">Nepareizi akreditācijas dati</string>
     <string name="delete_account">Noņemt kontu</string>
     <string name="delete_account_warning">Noņemt kontu %s un izdzēst visas vietēji saglabātās datnes?\n\nDzēšanu nevar atsaukt.</string>
+    <string name="delete_entries">Dzēst ierakstus</string>
     <string name="deselect_all">Atcelt atzīmi</string>
+    <string name="dev_version_new_version_available">Pieejama jauna versija  </string>
+    <string name="dev_version_no_information_available">Informācija nav pieejama.</string>
+    <string name="dev_version_no_new_version_available">Jauna versija nav pieejama.</string>
     <string name="digest_algorithm_not_available">Šis algoritms nav pieejams jūsu tālrunī.</string>
     <string name="disable_new_media_folder_detection_notifications">Deaktivēt</string>
     <string name="dismiss">Atmest</string>
+    <string name="dismiss_notification_description">Noraidīt paziņojumu</string>
     <string name="downloader_download_failed_content">Nevarēja lejupielādēt %1$s</string>
     <string name="downloader_download_failed_credentials_error">Lejupielāde neizdevās, piesakieties vēlreiz.</string>
     <string name="downloader_download_failed_ticker">Neizdevās lejupielādēt</string>
@@ -160,6 +189,7 @@
     <string name="downloader_download_succeeded_ticker">Lejupielādēts</string>
     <string name="downloader_not_downloaded_yet">Vēl nav lejupielādēts</string>
     <string name="drawer_close">Aizvērt malu</string>
+    <string name="drawer_community">Kopiena</string>
     <string name="drawer_current_account">Pašreizējais konts</string>
     <string name="drawer_end_account">Pēdējais konts</string>
     <string name="drawer_item_activities">Darbības</string>

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

@@ -466,6 +466,11 @@
     <string name="pref_behaviour_entries_delete_file">slettet</string>
     <string name="pref_behaviour_entries_keep_file">beholdt i opprinnelig mappe</string>
     <string name="pref_behaviour_entries_move">flyttet til app-mappe</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Hva skal gjøres om filen finnes?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Spør hver gang</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Skriv over versjon på tjener</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Endre navn på ny versjon</string>
+    <string name="pref_instant_name_collision_policy_title">Hva skal gjøres om filen finnes?</string>
     <string name="prefs_add_account">Legg til en konto</string>
     <string name="prefs_calendar_contacts">Synkroniser kalender &amp; kontakter</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Adresse til server kan ikke nås for DAVx5 (tidligere DAVdroid)</string>
@@ -490,6 +495,7 @@
     <string name="prefs_instant_upload_path_use_subfolders_summary">Lagre filer i undermapper basert på år og måned</string>
     <string name="prefs_instant_upload_path_use_subfolders_title">Bruk undermapper</string>
     <string name="prefs_license">Lisens</string>
+    <string name="prefs_lock">App tilgangskode</string>
     <string name="prefs_lock_device_credentials_enabled">Enhetens legitimasjon er aktivert</string>
     <string name="prefs_lock_device_credentials_not_setup">Ingen legitimasjon for enhet er satt opp.</string>
     <string name="prefs_lock_none">Ingen</string>
@@ -538,7 +544,9 @@
     <string name="saml_authentication_wrong_pass">Feil passord</string>
     <string name="scanQR_description">Legg inn med QR-kode</string>
     <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_03_drawer_heading">Aktivitet, delinger...</string>
     <string name="screenshot_04_accounts_heading">Alle kontoene dine</string>
     <string name="screenshot_04_accounts_subline">på et plass</string>
     <string name="screenshot_05_autoUpload_heading">Automatisk opplastning</string>

+ 3 - 3
src/main/res/values-night/colors.xml

@@ -23,12 +23,13 @@
     <color name="text_color">#E3E3E3</color>
     <color name="text_color_inverse">#000000</color>
     <color name="secondary_text_color">#A5A5A5</color>
+    <color name="list_divider_background">#222222</color>
 
     <!-- Colors -->
-    <color name="bg_default">#151515</color>
+    <color name="bg_default">#121212</color>
     <color name="fg_contrast">#717171</color>
     <color name="primary_button_text_color">#000000</color>
-    <color name="uploader_list_separator_color">#2a2a2a</color>
+    <color name="bg_elevation_one">#2D2D2D</color>
 
     <!-- Multiselect backgrounds -->
     <color name="action_mode_status_bar_background">#ECECEC</color>
@@ -41,5 +42,4 @@
     <color name="switch_thumb_color_unchecked">#2a2a2a</color>
     <color name="switch_track_color_unchecked">#B3FFFFFF</color>
     <color name="drawer_active_item_background">@color/white</color>
-
 </resources>

+ 16 - 11
src/main/res/values-nl/strings.xml

@@ -142,8 +142,8 @@
     <string name="confirmation_remove_folders_alert">Wil je de geselecteerde objecten en hun inhoud echt verwijderen?</string>
     <string name="confirmation_remove_local">Alleen lokaal</string>
     <string name="conflict_already_existing_file">Al bestaand bestand</string>
-    <string name="conflict_dialog_error">Fout bij het maken van conflict dialoog!</string>
-    <string name="conflict_message_description">Als je beide versies selecteerde, zal het lokale bestand een nummer aan de naam toegevoegd krijgen.</string>
+    <string name="conflict_dialog_error">Fout bij het maken van conflictdialoog!</string>
+    <string name="conflict_message_description">Als je beide versies selecteert, zal het lokale bestand een nummer aan de naam toegevoegd krijgen.</string>
     <string name="conflict_message_headline">Welke bestanden wil je bewaren?</string>
     <string name="conflict_new_file">Nieuw bestand</string>
     <string name="contaclist_restore_selected">Herstel de geselecteerde contactpersonen</string>
@@ -152,7 +152,7 @@
     <string name="contactlist_no_permission">Geen permissies, niets geïnporteerd!</string>
     <string name="contacts_automatic_backup">Automatische backup</string>
     <string name="contacts_backup_button">Maak nu een backup</string>
-    <string name="contacts_last_backup">Laatsts backup</string>
+    <string name="contacts_last_backup">Laatste backup</string>
     <string name="contacts_preference_backup_never">nooit</string>
     <string name="contacts_preference_choose_date">Kies datum</string>
     <string name="contacts_preferences_backup_scheduled">Backup ingepland en zal zo starten</string>
@@ -171,13 +171,13 @@
     <string name="could_not_download_image">Kan de volledige afbeelding niet downloaden</string>
     <string name="could_not_retrieve_url">Kon URL niet ophalen</string>
     <string name="create_dir_fail_msg">Kan map niet aanmaken</string>
-    <string name="create_new">Creëer nieuw</string>
+    <string name="create_new">Maak nieuw</string>
     <string name="create_new_document">Creëer nieuw document</string>
     <string name="create_new_folder">Nieuwe map aanmaken</string>
     <string name="create_new_presentation">Creëer nieuwe presentatie</string>
     <string name="create_new_spreadsheet">Creëren nieuw werkblad</string>
     <string name="create_rich_workspace">Toevoegen mapinfo</string>
-    <string name="creates_rich_workspace">creëert mapinfo</string>
+    <string name="creates_rich_workspace">maakt mapinfo</string>
     <string name="credentials_disabled">Inloggegevens uitgeschakeld</string>
     <string name="date_unknown">Onbekend</string>
     <string name="default_credentials_wrong">Ongeldige inloggegevens</string>
@@ -257,17 +257,17 @@
     <string name="error_retrieving_templates">Fout bij ophalen sjablonen</string>
     <string name="error_starting_direct_camera_upload">Fout bij starten camera</string>
     <string name="etm_accounts">Accounts</string>
-    <string name="etm_background_job_name">Job naam</string>
+    <string name="etm_background_job_name">Taaknaam</string>
     <string name="etm_background_job_progress">Voortgang</string>
     <string name="etm_background_job_started">Gestart</string>
-    <string name="etm_background_job_state">Staat</string>
+    <string name="etm_background_job_state">Status</string>
     <string name="etm_background_job_user">Gebruiker</string>
     <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">Achtergrondtaken</string>
-    <string name="etm_background_jobs_cancel_all">Annuleer alle jobs</string>
+    <string name="etm_background_jobs_cancel_all">Annuleer alle taken</string>
     <string name="etm_background_jobs_prune">Inactieve taken opruimen</string>
-    <string name="etm_background_jobs_start_test_job">Start Test Job</string>
-    <string name="etm_background_jobs_stop_test_job">Stop Test Job</string>
+    <string name="etm_background_jobs_start_test_job">Start testtaak</string>
+    <string name="etm_background_jobs_stop_test_job">Stop testtaak</string>
     <string name="etm_migrations">Migratie (app upgrade)</string>
     <string name="etm_preferences">Voorkeuren</string>
     <string name="etm_title">Engineering Testmodus</string>
@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">verwijderd worden</string>
     <string name="pref_behaviour_entries_keep_file">bewaard worden in originele map</string>
     <string name="pref_behaviour_entries_move">verplaatst worden naar app-map</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Wat als het bestand al bestaat?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Vraag me elke keer</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Overschrijven </string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Hernoem nieuwe versie</string>
+    <string name="pref_instant_name_collision_policy_title">Wat als het bestand al bestaat?</string>
     <string name="prefs_add_account">Account toevoegen</string>
     <string name="prefs_calendar_contacts">Synchroniseren agenda &amp; contactpersonen</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Serveradres voor deze account kon nicht gevonden worden voor DAVx5 (vroeger bekend als DAVdroid)</string>
@@ -551,7 +556,7 @@
     <string name="saml_authentication_wrong_pass">Onjuist wachtwoord</string>
     <string name="scanQR_description">Inloggen via QR code</string>
     <string name="screenshot_01_gridView_heading">Beschermt jouw gegevens</string>
-    <string name="screenshot_01_gridView_subline">eigen-hosting productiviteit</string>
+    <string name="screenshot_01_gridView_subline">self-hosting productiviteit</string>
     <string name="screenshot_02_listView_heading">Blader en deel</string>
     <string name="screenshot_02_listView_subline">alle taken onder je vindertoppen</string>
     <string name="screenshot_03_drawer_heading">Activiteiten, delen, ...</string>

+ 14 - 0
src/main/res/values-pt-rPT/strings.xml

@@ -79,6 +79,7 @@
     <string name="autoupload_create_new_custom_folder">Criar nova estrutura de pasta personalizada</string>
     <string name="autoupload_custom_folder">Definir uma pasta personalizada</string>
     <string name="autoupload_disable_power_save_check">Desabilitar a verificação de poupança de energia</string>
+    <string name="autoupload_hide_folder">Esconder pasta</string>
     <string name="avatar">Avatar</string>
     <string name="battery_optimization_close">Fechar</string>
     <string name="battery_optimization_disable">Desativar</string>
@@ -139,6 +140,7 @@
     <string name="confirmation_remove_folder_alert">Deseja realmente apagar %1$s e o seu conteúdo?</string>
     <string name="confirmation_remove_folders_alert">Quer realmente apagar os itens seleccionados e os seus conteúdos?</string>
     <string name="confirmation_remove_local">Apenas localmente</string>
+    <string name="conflict_already_existing_file">Ficheiro já existente</string>
     <string name="conflict_message_headline">Quais os ficheiros que pretende manter?</string>
     <string name="conflict_new_file">Ficheiro novo</string>
     <string name="contaclist_restore_selected">Restaurar contactos seleccionados</string>
@@ -169,6 +171,7 @@
     <string name="create_new_folder">Criar nova pasta</string>
     <string name="create_new_presentation">Criar nova apresentação</string>
     <string name="create_new_spreadsheet">Criar nova folha de cálculo</string>
+    <string name="create_rich_workspace">Adicionar info da pasta</string>
     <string name="credentials_disabled">Credenciais desativadas</string>
     <string name="date_unknown">Desconhecido</string>
     <string name="default_credentials_wrong">Credenciais incorretas</string>
@@ -247,10 +250,12 @@
     <string name="etm_background_job_started">Iniciado</string>
     <string name="etm_background_job_state">Estado</string>
     <string name="etm_background_job_user">Utilizador</string>
+    <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">Tarefas de segundo plano</string>
     <string name="etm_preferences">Preferências</string>
     <string name="fab_label">Adicionar ou enviar</string>
     <string name="failed_to_download">Falhou a passagem do ficheiro ao gestor de transferências</string>
+    <string name="failed_to_print">Falhou a impressão do ficheiro</string>
     <string name="fallback_weblogin_back">Anterior</string>
     <string name="fallback_weblogin_text">Reverter para método de início de sessão anterior</string>
     <string name="favorite">Adicionar aos favoritos</string>
@@ -445,6 +450,7 @@
     <string name="pref_behaviour_entries_delete_file">eliminado</string>
     <string name="pref_behaviour_entries_keep_file">mantido na pasta original</string>
     <string name="pref_behaviour_entries_move">movido para pasta da aplicação</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Perguntar-me todas as vezes</string>
     <string name="prefs_add_account">Adicionar conta</string>
     <string name="prefs_calendar_contacts">Sincronizar calendário e contactos</string>
     <string name="prefs_calendar_contacts_address_resolve_error">O endereço do servidor da conta não pôde ser resolvido pelo DAVx5 (anteriormente conhecido como DAVdroid)</string>
@@ -515,6 +521,8 @@
     <string name="saml_authentication_required_text">Necessário palavra-passe</string>
     <string name="saml_authentication_wrong_pass">Palavra-passe errada</string>
     <string name="scanQR_description">acesso através de código QR</string>
+    <string name="screenshot_04_accounts_subline">Num único lugar</string>
+    <string name="screenshot_06_davdroid_subline">Sincronizar com DAVx5</string>
     <string name="search_users_and_groups_hint">Pesquisar utilizadores e grupos</string>
     <string name="select_all">Selecionar tudo</string>
     <string name="select_template">Seleccionar modelo</string>
@@ -635,6 +643,7 @@
     <string name="sync_foreign_files_forgotten_explanation">Para a versão 1.3.16, os ficheiros enviados deste dispositivo são copiados para a seguinte pasta %1$s para evitar a falha de dados quando um só ficheiro é sincronizado em múltiplas contas.\n\nDevido a esta alteração, todos os ficheiros enviados com as versões mais antigas desta aplicação serão copiadas para a seguinte pasta %2$s. Contudo, um erro evitou a conclusão desta operação durante a sincronização da conta. Pode deixar o(s) ficheiro(s) como estão e eliminar a associação para %3$s, ou mover o(s) ficheiro(s) para a pasta %1$s e manter a associação para %4$s.\n\nEm baixo está a lista dos ficheiros locais e dos ficheiros remotos na pasta %5$s em que foram associados.</string>
     <string name="sync_foreign_files_forgotten_ticker">Alguns ficheiros locais foram esquecidos</string>
     <string name="sync_in_progress">Obter a versão mais recente do ficheiro.</string>
+    <string name="sync_not_enough_space_dialog_action_choose">Escolher o que sincronizar</string>
     <string name="sync_not_enough_space_dialog_action_free_space">Libertar espaço</string>
     <string name="sync_not_enough_space_dialog_title">Espaço insuficiente</string>
     <string name="sync_status_button">Botão de estado de sincronização</string>
@@ -686,6 +695,7 @@ Aproveite o novo e melhorado envio automático.</string>
     <string name="upload_list_delete">Apagar</string>
     <string name="upload_list_empty_headline">Sem envios disponíveis</string>
     <string name="upload_list_empty_text_auto_upload">Envie algum conteúdo ou ative o envio automático.</string>
+    <string name="upload_list_resolve_conflict">Resolver conflito</string>
     <string name="upload_local_storage_full">Armazenamento local cheio</string>
     <string name="upload_local_storage_not_copied">O ficheiro não pôde ser copiado para o armazenamento local</string>
     <string name="upload_lock_failed">Falha ao trancar pasta</string>
@@ -772,6 +782,10 @@ Aproveite o novo e melhorado envio automático.</string>
         <item quantity="one">%1$d ficheiro</item>
         <item quantity="other">%1$d ficheiros</item>
     </plurals>
+    <plurals name="synced_folders_show_hidden_folders">
+        <item quantity="one">Mostrar %1$d pasta oculta</item>
+        <item quantity="other">Show %1$d pastas ocultas</item>
+    </plurals>
     <plurals name="items_selected_count">
         <item quantity="one">%d selecionado</item>
         <item quantity="other">%d selecionados</item>

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

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">удалён</string>
     <string name="pref_behaviour_entries_keep_file">оставлен в исходном каталоге</string>
     <string name="pref_behaviour_entries_move">перемещен в каталог приложения</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Действие в случае, если файл уже существует</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Всегда спрашивать</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Перезаписать удалённую версию</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Переименовать новую версию</string>
+    <string name="pref_instant_name_collision_policy_title">Действие в случае, если файл уже существует</string>
     <string name="prefs_add_account">Добавить аккаунт</string>
     <string name="prefs_calendar_contacts">Синхронизировать календарь и контакты</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Не удалось определить адрес сервера этой учётной записи для его передачи в приложение  DAVx5 (ранее называвшееся DAVdroid)</string>

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

@@ -478,6 +478,11 @@
     <string name="pref_behaviour_entries_delete_file">vymazané</string>
     <string name="pref_behaviour_entries_keep_file">ponechané v pôvodnom priečinku</string>
     <string name="pref_behaviour_entries_move">presunuté do aplikačného priečinka</string>
+    <string name="pref_instant_name_collision_policy_dialogTitle">Čo urobiť, ak súbor už existuje?</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">Spýtať sa zakaždým</string>
+    <string name="pref_instant_name_collision_policy_entries_overwrite">Prepísať vzdialenú verziu</string>
+    <string name="pref_instant_name_collision_policy_entries_rename">Premenovať novú verziu</string>
+    <string name="pref_instant_name_collision_policy_title">Čo urobiť, ak súbor už existuje?</string>
     <string name="prefs_add_account">Pridať účet</string>
     <string name="prefs_calendar_contacts">Synchronizovať kalendár &amp; kontakty</string>
     <string name="prefs_calendar_contacts_address_resolve_error">Nepodarilo sa zistiť adresu účtu pre DAVx5 (predtým známy ako DAVdroid)</string>

+ 30 - 0
src/main/res/values-uk/strings.xml

@@ -285,6 +285,7 @@
     <string name="file_list_empty_headline_server_search_photos">Жодного фото</string>
     <string name="file_list_empty_headline_server_search_videos">Жодного відеофайлу</string>
     <string name="file_list_empty_moving">Нічого немає. Ви можете додати теку.</string>
+    <string name="file_list_empty_recently_added">Не знайдено нещодавно доданих файлів</string>
     <string name="file_list_empty_recently_added_filter">Нещодавно додані файли відсутні</string>
     <string name="file_list_empty_recently_modified">Відсутні зміни серед файлів за останні 7 днів</string>
     <string name="file_list_empty_recently_modified_filter">Жодного зміненого файлу за останні 7 днів.</string>
@@ -293,21 +294,37 @@
     <string name="file_list_empty_shared_headline">Жодного спільного файлу</string>
     <string name="file_list_empty_text_photos">Завантажити фотографії чи активувати автозавантаження</string>
     <string name="file_list_empty_text_photos_filter">Немає фотографій.</string>
+    <string name="file_list_empty_text_videos">Додайте файли з відео або увімкніть автоматичне завантаження.</string>
     <string name="file_list_empty_text_videos_filter">Немає відеозаписів.</string>
     <string name="file_list_folder">тека</string>
     <string name="file_list_loading">Завантаження…</string>
     <string name="file_list_no_app_for_file_type">Відсутній застосунок для відкриття файлу такого типу.</string>
     <string name="file_list_seconds_ago">секунди тому</string>
+    <string name="file_migration_checking_destination">Перевірка місця призначення...</string>
     <string name="file_migration_cleaning">Очищення...</string>
+    <string name="file_migration_dialog_title">Оновлення місця збереження</string>
+    <string name="file_migration_directory_already_exists">Тека з даними вже існує. Виберіть одну з таких:</string>
+    <string name="file_migration_failed_dir_already_exists">Тека Nextcloud вже існує</string>
     <string name="file_migration_failed_not_enough_space">Потрібно більше місця</string>
+    <string name="file_migration_failed_not_readable">Неможливо прочитати вихідний файл</string>
+    <string name="file_migration_failed_not_writable">Неможливо записати до файлу призначення</string>
+    <string name="file_migration_failed_while_coping">Помилка під час міграції</string>
+    <string name="file_migration_failed_while_updating_index">Неможливо оновити індекс</string>
     <string name="file_migration_migrating">Переміщення даних…</string>
     <string name="file_migration_ok_finished">Завершено</string>
     <string name="file_migration_override_data_folder">Замінити</string>
+    <string name="file_migration_preparing">Підготовка до міграції</string>
+    <string name="file_migration_restoring_accounts_configuration">Відновлення налаштувань облікового запису...</string>
+    <string name="file_migration_saving_accounts_configuration">Збереження налаштувань облікового запису...</string>
+    <string name="file_migration_source_not_readable_title">Неможливо прочитати вихідну теку!</string>
     <string name="file_migration_updating_index">Оновлення індексу...</string>
     <string name="file_migration_use_data_folder">Використовувати</string>
     <string name="file_migration_waiting_for_unfinished_sync">Очікування повної синхронізації…</string>
     <string name="file_not_found">Файл не знайдено</string>
+    <string name="file_not_synced">Неможливо синхронізувати файл. Буде показано останню доступну версію.</string>
     <string name="file_rename">Перейменувати</string>
+    <string name="file_version_restored_error">Помилка під час відновлення версії файлу!</string>
+    <string name="file_version_restored_successfully">Успішно відновлено версію файлу.</string>
     <string name="filedetails_download">Завантажити</string>
     <string name="filedetails_renamed_in_upload_msg">Під час завантаження назву файлу було змінено %1$s</string>
     <string name="filedetails_sync_file">Синхронізація</string>
@@ -318,13 +335,17 @@
     <string name="filename_hint">Ім\'я файлу</string>
     <string name="folder_already_exists">Тека вже існує</string>
     <string name="folder_confirm_create">Створити</string>
+    <string name="folder_icon">Іконка теки</string>
     <string name="folder_list_empty_headline">Тут немає папок</string>
     <string name="folder_picker_choose_button_text">Обрати</string>
     <string name="forbidden_permissions">У вас відсутні повноваження %s</string>
     <string name="forbidden_permissions_copy">скопіювати даний файл</string>
+    <string name="forbidden_permissions_create">щоб створити цей файл</string>
     <string name="forbidden_permissions_delete">на видалення цього файла</string>
     <string name="forbidden_permissions_move">перемістити цей файл</string>
     <string name="forbidden_permissions_rename">на перейменування цього файла</string>
+    <string name="foreground_service_download">Звантаження файлів...</string>
+    <string name="foreground_service_upload">Завантаження файлів...</string>
     <string name="foreign_files_fail">Окремі файли неможливо перемістити</string>
     <string name="foreign_files_local_text">Локально: %1$s</string>
     <string name="foreign_files_move">Перемістити все</string>
@@ -334,20 +355,28 @@
     <string name="hint_note">Нотатка</string>
     <string name="hint_password">Пароль</string>
     <string name="host_not_available">Сервер недоступний</string>
+    <string name="host_your_own_server">Розмістити на власному сервері</string>
+    <string name="instant_upload_existing">Також завантажити існуючі файли</string>
     <string name="instant_upload_on_charging">Завантаження тільки під час заряджання</string>
     <string name="instant_upload_path">/InstantUpload</string>
+    <string name="invalid_url">Неправильний URL</string>
+    <string name="learn_more">Дізнатися більше</string>
     <string name="link">Посилання</string>
     <string name="local_file_list_empty">В цій теці немає файлів.</string>
+    <string name="local_file_not_found_message">Файл не знайдено у локальній файловій системі</string>
     <string name="local_folder_list_empty">Тут відсутні інші каталоги.</string>
     <string name="log_send_mail_subject">%1$s Android лог додатку</string>
     <string name="login">Увійти</string>
     <string name="logs_menu_refresh">Оновити</string>
     <string name="logs_status_loading">Завантаження…</string>
     <string name="logs_title">Журнали</string>
+    <string name="maintenance_mode">Сервер у режимі обслуговування</string>
     <string name="manage_space_clear_data">Очистити дані</string>
     <string name="manage_space_title">Керувати простором</string>
+    <string name="media_err_invalid_progressive_playback">Неможливо транслювати мультимедійний файл</string>
     <string name="media_err_io">Неможливо відкрити мультимедійний файл</string>
     <string name="media_err_malformed">Мультимедійний файл має неправильно кодування</string>
+    <string name="media_err_timeout">Час на відтворення файлу вичерпано</string>
     <string name="media_err_unsupported">Кодек не підтримується</string>
     <string name="media_forward_description">Перемотка вперед</string>
     <string name="media_notif_ticker">%1$s музичний програвач</string>
@@ -405,6 +434,7 @@
     <string name="prefs_manage_accounts">Управління обліковими записами</string>
     <string name="prefs_recommend">Порадити друзям</string>
     <string name="prefs_show_hidden_files">Відобразити сховані файли</string>
+    <string name="prefs_storage_path">Шлях до місця збереження</string>
     <string name="prefs_synced_folders_local_path_title">Локальний каталог</string>
     <string name="prefs_synced_folders_remote_path_title">Віддалений каталог</string>
     <string name="prefs_theme_title">Тема</string>

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

@@ -478,6 +478,7 @@
     <string name="pref_behaviour_entries_delete_file">已删除</string>
     <string name="pref_behaviour_entries_keep_file">保留在原始文件夹</string>
     <string name="pref_behaviour_entries_move">移动到应用文件夹</string>
+    <string name="pref_instant_name_collision_policy_entries_always_ask">每次问我</string>
     <string name="prefs_add_account">添加账号</string>
     <string name="prefs_calendar_contacts">同步日历和联系人</string>
     <string name="prefs_calendar_contacts_address_resolve_error">DAVx5(前身为 DAVdroid)无法解析该账号的服务器地址</string>

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

@@ -142,6 +142,8 @@
     <string name="confirmation_remove_folders_alert">您真的想要刪除所選項目和裡面的內容嗎?</string>
     <string name="confirmation_remove_local">只有本地</string>
     <string name="conflict_already_existing_file">檔案已存在</string>
+    <string name="conflict_dialog_error">建立衝突訊息時發生錯誤!</string>
+    <string name="conflict_message_description">若您同時選擇兩個版本,本地的檔案會在檔名後附加編號。</string>
     <string name="conflict_message_headline">您要保留哪一個檔案?</string>
     <string name="conflict_new_file">新增檔案</string>
     <string name="contaclist_restore_selected">還原所選的聯絡人</string>
@@ -259,6 +261,7 @@
     <string name="etm_background_job_progress">進度</string>
     <string name="etm_background_job_started">開始於</string>
     <string name="etm_background_job_state">狀態</string>
+    <string name="etm_background_job_user">使用者</string>
     <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_jobs">背景工作</string>
     <string name="etm_background_jobs_cancel_all">取消所有作業</string>

+ 2 - 3
src/main/res/values/colors.xml

@@ -28,7 +28,7 @@
     <color name="drawer_text_color">@color/secondary_text_color</color>
     <color name="text_color_inverse">#ffffff</color>
     <color name="disabled_text">#ff888888</color>
-    <color name="list_divider_background">#eee</color>
+    <color name="list_divider_background">#eeeeee</color>
     <color name="fg_contrast">#656565</color>
     <color name="fg_inverse">#FFFFFF</color>
     <color name="filelist_icon_background">#DDDDDD</color>
@@ -53,6 +53,7 @@
     <color name="login_btn_tint">#ffffff</color>
     <color name="login_btn_stroke">#ffffff</color>
     <color name="bg_default">#FFFFFF</color>
+    <color name="bg_elevation_one">#FFFFFF</color>
     <color name="background_color_inverse">#000000</color>
     <color name="primary_button_background_color">@color/color_accent</color>
     <color name="primary_button_text_color">#ffffff</color>
@@ -65,8 +66,6 @@
 
     <color name="background_color_png">#FFFFFF</color>
 
-    <color name="uploader_list_separator_color">#ededed</color>
-
     <!-- special transparent action bar colors for image preview -->
     <color name="color_transparent">#201D2D44</color>
     <color name="color_dark_transparent">#40162233</color>

+ 1 - 0
src/main/res/values/dims.xml

@@ -28,6 +28,7 @@
     <dimen name="nav_drawer_header_avatar_second_account_margin">56dp</dimen>
     <dimen name="nav_drawer_menu_avatar_radius">12sp</dimen>
     <dimen name="list_item_avatar_icon_radius">20dp</dimen>
+    <dimen name="bottom_sheet_menu_item_divider_standard_margin">56dp</dimen>
     <dimen name="file_icon_size">40dp</dimen>
     <dimen name="file_icon_size_grid">128dp</dimen>
     <dimen name="file_icon_rounded_corner_radius">8dp</dimen>

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

@@ -895,6 +895,7 @@
     <string name="etm_background_jobs_cancel_all">Cancel all jobs</string>
     <string name="etm_background_jobs_prune">Prune inactive jobs</string>
     <string name="etm_background_jobs_start_test_job">Start test job</string>
+    <string name="etm_background_jobs_schedule_test_job">Schedule test job</string>
     <string name="etm_background_jobs_stop_test_job">Stop test job</string>
     <string name="etm_background_job_uuid">UUID</string>
     <string name="etm_background_job_name">Job name</string>