Browse Source

Solve git conflicts

Signed-off-by: alperozturk <alper_ozturk@proton.me>
alperozturk 1 year ago
parent
commit
157fee3b2a
45 changed files with 2017 additions and 530 deletions
  1. 2 2
      .github/workflows/screenShotTest.yml
  2. 1179 0
      app/schemas/com.nextcloud.client.database.NextcloudDatabase/76.json
  3. 20 0
      app/src/androidTest/java/com/owncloud/android/datamodel/ArbitraryDataProviderIT.kt
  4. 6 1
      app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java
  5. 2 1
      app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt
  6. 3 0
      app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt
  7. 3 1
      app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt
  8. 7 1
      app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt
  9. 2 2
      app/src/main/java/com/nextcloud/client/di/AppModule.java
  10. 12 2
      app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
  11. 2 0
      app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
  12. 23 0
      app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
  13. 51 24
      app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt
  14. 131 0
      app/src/main/java/com/nextcloud/client/jobs/HealthStatusWork.kt
  15. 27 0
      app/src/main/java/com/nextcloud/model/HTTPStatusCodes.kt
  16. 39 0
      app/src/main/java/com/nextcloud/utils/extensions/ContextExtensions.kt
  17. 2 0
      app/src/main/java/com/owncloud/android/MainApp.java
  18. 4 0
      app/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.kt
  19. 11 0
      app/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProviderImpl.java
  20. 14 1
      app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java
  21. 35 0
      app/src/main/java/com/owncloud/android/datamodel/ReceiverFlag.kt
  22. 25 21
      app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
  23. 2 1
      app/src/main/java/com/owncloud/android/db/ProviderMeta.java
  24. 3 1
      app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java
  25. 7 1
      app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java
  26. 3 1
      app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
  27. 16 4
      app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt
  28. 194 343
      app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  29. 1 0
      app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt
  30. 3 1
      app/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
  31. 6 4
      app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
  32. 63 65
      app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java
  33. 2 0
      app/src/main/java/com/owncloud/android/ui/dialog/SetupEncryptionDialogFragment.kt
  34. 28 27
      app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  35. 2 2
      app/src/main/java/com/owncloud/android/ui/fragment/GroupfolderListFragment.kt
  36. 3 1
      app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java
  37. 2 2
      app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt
  38. 4 8
      app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java
  39. 59 6
      app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java
  40. 5 3
      app/src/main/java/com/owncloud/android/utils/ReceiversHelper.java
  41. 1 0
      app/src/main/res/values-sr/strings.xml
  42. 3 0
      app/src/main/res/values/strings.xml
  43. 8 2
      app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt
  44. 1 1
      build.gradle
  45. 1 1
      scripts/analysis/lint-results.txt

+ 2 - 2
.github/workflows/screenShotTest.yml

@@ -47,7 +47,7 @@ jobs:
 
             -   name: create AVD and generate snapshot for caching
                 if: steps.avd-cache.outputs.cache-hit != 'true'
-                uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b # v2
+                uses: reactivecircus/android-emulator-runner@99a4aac18b4df9b3af66c4a1f04c1f23fa10c270 # v2.29.0
                 with:
                     api-level: ${{ matrix.api-level }}
                     force-avd-creation: false
@@ -73,7 +73,7 @@ jobs:
                 run: scripts/deleteOldComments.sh "${{ matrix.color }}-${{ matrix.scheme }}" "Screenshot" ${{github.event.number}}
 
             -   name: Run screenshot tests
-                uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b # v2
+                uses: reactivecircus/android-emulator-runner@99a4aac18b4df9b3af66c4a1f04c1f23fa10c270 # v2.29.0
                 with:
                     api-level: ${{ matrix.api-level }}
                     force-avd-creation: false

+ 1179 - 0
app/schemas/com.nextcloud.client.database.NextcloudDatabase/76.json

@@ -0,0 +1,1179 @@
+{
+    "formatVersion": 1,
+    "database": {
+        "version": 76,
+        "identityHash": "0d639ab041aa87e6c2ef9504395545f7",
+        "entities": [
+            {
+                "tableName": "arbitrary_data",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `cloud_id` TEXT, `key` TEXT, `value` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "cloudId",
+                        "columnName": "cloud_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "key",
+                        "columnName": "key",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "value",
+                        "columnName": "value",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "capabilities",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` TEXT, `version_mayor` INTEGER, `version_minor` INTEGER, `version_micro` INTEGER, `version_string` TEXT, `version_edition` TEXT, `extended_support` INTEGER, `core_pollinterval` INTEGER, `sharing_api_enabled` INTEGER, `sharing_public_enabled` INTEGER, `sharing_public_password_enforced` INTEGER, `sharing_public_expire_date_enabled` INTEGER, `sharing_public_expire_date_days` INTEGER, `sharing_public_expire_date_enforced` INTEGER, `sharing_public_send_mail` INTEGER, `sharing_public_upload` INTEGER, `sharing_user_send_mail` INTEGER, `sharing_resharing` INTEGER, `sharing_federation_outgoing` INTEGER, `sharing_federation_incoming` INTEGER, `files_bigfilechunking` INTEGER, `files_undelete` INTEGER, `files_versioning` INTEGER, `external_links` INTEGER, `server_name` TEXT, `server_color` TEXT, `server_text_color` TEXT, `server_element_color` TEXT, `server_slogan` TEXT, `server_logo` TEXT, `background_url` TEXT, `end_to_end_encryption` INTEGER, `end_to_end_encryption_keys_exist` INTEGER, `activity` INTEGER, `background_default` INTEGER, `background_plain` INTEGER, `richdocument` INTEGER, `richdocument_mimetype_list` TEXT, `richdocument_direct_editing` INTEGER, `richdocument_direct_templates` INTEGER, `richdocument_optional_mimetype_list` TEXT, `sharing_public_ask_for_optional_password` INTEGER, `richdocument_product_name` TEXT, `direct_editing_etag` TEXT, `user_status` INTEGER, `user_status_supports_emoji` INTEGER, `etag` TEXT, `files_locking_version` TEXT, `groupfolders` INTEGER, `drop_account` INTEGER, `security_guard` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountName",
+                        "columnName": "account",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionMajor",
+                        "columnName": "version_mayor",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionMinor",
+                        "columnName": "version_minor",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionMicro",
+                        "columnName": "version_micro",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionString",
+                        "columnName": "version_string",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "versionEditor",
+                        "columnName": "version_edition",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "extendedSupport",
+                        "columnName": "extended_support",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "corePollinterval",
+                        "columnName": "core_pollinterval",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingApiEnabled",
+                        "columnName": "sharing_api_enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicEnabled",
+                        "columnName": "sharing_public_enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicPasswordEnforced",
+                        "columnName": "sharing_public_password_enforced",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicExpireDateEnabled",
+                        "columnName": "sharing_public_expire_date_enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicExpireDateDays",
+                        "columnName": "sharing_public_expire_date_days",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicExpireDateEnforced",
+                        "columnName": "sharing_public_expire_date_enforced",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicSendMail",
+                        "columnName": "sharing_public_send_mail",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicUpload",
+                        "columnName": "sharing_public_upload",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingUserSendMail",
+                        "columnName": "sharing_user_send_mail",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingResharing",
+                        "columnName": "sharing_resharing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingFederationOutgoing",
+                        "columnName": "sharing_federation_outgoing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingFederationIncoming",
+                        "columnName": "sharing_federation_incoming",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesBigfilechunking",
+                        "columnName": "files_bigfilechunking",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesUndelete",
+                        "columnName": "files_undelete",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesVersioning",
+                        "columnName": "files_versioning",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "externalLinks",
+                        "columnName": "external_links",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverName",
+                        "columnName": "server_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverColor",
+                        "columnName": "server_color",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverTextColor",
+                        "columnName": "server_text_color",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverElementColor",
+                        "columnName": "server_element_color",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverSlogan",
+                        "columnName": "server_slogan",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverLogo",
+                        "columnName": "server_logo",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverBackgroundUrl",
+                        "columnName": "background_url",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "endToEndEncryption",
+                        "columnName": "end_to_end_encryption",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "endToEndEncryptionKeysExist",
+                        "columnName": "end_to_end_encryption_keys_exist",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "activity",
+                        "columnName": "activity",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverBackgroundDefault",
+                        "columnName": "background_default",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "serverBackgroundPlain",
+                        "columnName": "background_plain",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocument",
+                        "columnName": "richdocument",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentMimetypeList",
+                        "columnName": "richdocument_mimetype_list",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentDirectEditing",
+                        "columnName": "richdocument_direct_editing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentTemplates",
+                        "columnName": "richdocument_direct_templates",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentOptionalMimetypeList",
+                        "columnName": "richdocument_optional_mimetype_list",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharingPublicAskForOptionalPassword",
+                        "columnName": "sharing_public_ask_for_optional_password",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richdocumentProductName",
+                        "columnName": "richdocument_product_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "directEditingEtag",
+                        "columnName": "direct_editing_etag",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "userStatus",
+                        "columnName": "user_status",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "userStatusSupportsEmoji",
+                        "columnName": "user_status_supports_emoji",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etag",
+                        "columnName": "etag",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "filesLockingVersion",
+                        "columnName": "files_locking_version",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "groupfolders",
+                        "columnName": "groupfolders",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "dropAccount",
+                        "columnName": "drop_account",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "securityGuard",
+                        "columnName": "security_guard",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "external_links",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `icon_url` TEXT, `language` TEXT, `type` INTEGER, `name` TEXT, `url` TEXT, `redirect` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "iconUrl",
+                        "columnName": "icon_url",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "language",
+                        "columnName": "language",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "type",
+                        "columnName": "type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "name",
+                        "columnName": "name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "url",
+                        "columnName": "url",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "redirect",
+                        "columnName": "redirect",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "filelist",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `filename` TEXT, `encrypted_filename` TEXT, `path` TEXT, `path_decrypted` TEXT, `parent` INTEGER, `created` INTEGER, `modified` INTEGER, `content_type` TEXT, `content_length` INTEGER, `media_path` TEXT, `file_owner` TEXT, `last_sync_date` INTEGER, `last_sync_date_for_data` INTEGER, `modified_at_last_sync_for_data` INTEGER, `etag` TEXT, `etag_on_server` TEXT, `share_by_link` INTEGER, `permissions` TEXT, `remote_id` TEXT, `local_id` INTEGER NOT NULL DEFAULT -1, `update_thumbnail` INTEGER, `is_downloading` INTEGER, `favorite` INTEGER, `hidden` INTEGER, `is_encrypted` INTEGER, `etag_in_conflict` TEXT, `shared_via_users` INTEGER, `mount_type` INTEGER, `has_preview` INTEGER, `unread_comments_count` INTEGER, `owner_id` TEXT, `owner_display_name` TEXT, `note` TEXT, `sharees` TEXT, `rich_workspace` TEXT, `metadata_size` TEXT, `metadata_live_photo` TEXT, `locked` INTEGER, `lock_type` INTEGER, `lock_owner` TEXT, `lock_owner_display_name` TEXT, `lock_owner_editor` TEXT, `lock_timestamp` INTEGER, `lock_timeout` INTEGER, `lock_token` TEXT, `tags` TEXT, `metadata_gps` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "name",
+                        "columnName": "filename",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "encryptedName",
+                        "columnName": "encrypted_filename",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "path",
+                        "columnName": "path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "pathDecrypted",
+                        "columnName": "path_decrypted",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "parent",
+                        "columnName": "parent",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "creation",
+                        "columnName": "created",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "modified",
+                        "columnName": "modified",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "contentType",
+                        "columnName": "content_type",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "contentLength",
+                        "columnName": "content_length",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "storagePath",
+                        "columnName": "media_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountOwner",
+                        "columnName": "file_owner",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lastSyncDate",
+                        "columnName": "last_sync_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lastSyncDateForData",
+                        "columnName": "last_sync_date_for_data",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "modifiedAtLastSyncForData",
+                        "columnName": "modified_at_last_sync_for_data",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etag",
+                        "columnName": "etag",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etagOnServer",
+                        "columnName": "etag_on_server",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharedViaLink",
+                        "columnName": "share_by_link",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "permissions",
+                        "columnName": "permissions",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "remoteId",
+                        "columnName": "remote_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localId",
+                        "columnName": "local_id",
+                        "affinity": "INTEGER",
+                        "notNull": true,
+                        "defaultValue": "-1"
+                    },
+                    {
+                        "fieldPath": "updateThumbnail",
+                        "columnName": "update_thumbnail",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isDownloading",
+                        "columnName": "is_downloading",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "favorite",
+                        "columnName": "favorite",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "hidden",
+                        "columnName": "hidden",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isEncrypted",
+                        "columnName": "is_encrypted",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "etagInConflict",
+                        "columnName": "etag_in_conflict",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharedWithSharee",
+                        "columnName": "shared_via_users",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "mountType",
+                        "columnName": "mount_type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "hasPreview",
+                        "columnName": "has_preview",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "unreadCommentsCount",
+                        "columnName": "unread_comments_count",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "ownerId",
+                        "columnName": "owner_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "ownerDisplayName",
+                        "columnName": "owner_display_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "note",
+                        "columnName": "note",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharees",
+                        "columnName": "sharees",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "richWorkspace",
+                        "columnName": "rich_workspace",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "metadataSize",
+                        "columnName": "metadata_size",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "metadataLivePhoto",
+                        "columnName": "metadata_live_photo",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "locked",
+                        "columnName": "locked",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockType",
+                        "columnName": "lock_type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockOwner",
+                        "columnName": "lock_owner",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockOwnerDisplayName",
+                        "columnName": "lock_owner_display_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockOwnerEditor",
+                        "columnName": "lock_owner_editor",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockTimestamp",
+                        "columnName": "lock_timestamp",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockTimeout",
+                        "columnName": "lock_timeout",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lockToken",
+                        "columnName": "lock_token",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "tags",
+                        "columnName": "tags",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "metadataGPS",
+                        "columnName": "metadata_gps",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "filesystem",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `is_folder` INTEGER, `found_at` INTEGER, `upload_triggered` INTEGER, `syncedfolder_id` TEXT, `crc32` TEXT, `modified_at` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localPath",
+                        "columnName": "local_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileIsFolder",
+                        "columnName": "is_folder",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileFoundRecently",
+                        "columnName": "found_at",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileSentForUpload",
+                        "columnName": "upload_triggered",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "syncedFolderId",
+                        "columnName": "syncedfolder_id",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "crc32",
+                        "columnName": "crc32",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileModified",
+                        "columnName": "modified_at",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "ocshares",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `file_source` INTEGER, `item_source` INTEGER, `share_type` INTEGER, `shate_with` TEXT, `path` TEXT, `permissions` INTEGER, `shared_date` INTEGER, `expiration_date` INTEGER, `token` TEXT, `shared_with_display_name` TEXT, `is_directory` INTEGER, `user_id` INTEGER, `id_remote_shared` INTEGER, `owner_share` TEXT, `is_password_protected` INTEGER, `note` TEXT, `hide_download` INTEGER, `share_link` TEXT, `share_label` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileSource",
+                        "columnName": "file_source",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "itemSource",
+                        "columnName": "item_source",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareType",
+                        "columnName": "share_type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareWith",
+                        "columnName": "shate_with",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "path",
+                        "columnName": "path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "permissions",
+                        "columnName": "permissions",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "sharedDate",
+                        "columnName": "shared_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "expirationDate",
+                        "columnName": "expiration_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "token",
+                        "columnName": "token",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareWithDisplayName",
+                        "columnName": "shared_with_display_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isDirectory",
+                        "columnName": "is_directory",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "userId",
+                        "columnName": "user_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "idRemoteShared",
+                        "columnName": "id_remote_shared",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountOwner",
+                        "columnName": "owner_share",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isPasswordProtected",
+                        "columnName": "is_password_protected",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "note",
+                        "columnName": "note",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "hideDownload",
+                        "columnName": "hide_download",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareLink",
+                        "columnName": "share_link",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "shareLabel",
+                        "columnName": "share_label",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "synced_folders",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `wifi_only` INTEGER, `charging_only` INTEGER, `existing` INTEGER, `enabled` INTEGER, `enabled_timestamp_ms` INTEGER, `subfolder_by_date` INTEGER, `account` TEXT, `upload_option` INTEGER, `name_collision_policy` INTEGER, `type` INTEGER, `hidden` INTEGER, `sub_folder_rule` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localPath",
+                        "columnName": "local_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "remotePath",
+                        "columnName": "remote_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "wifiOnly",
+                        "columnName": "wifi_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "chargingOnly",
+                        "columnName": "charging_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "existing",
+                        "columnName": "existing",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "enabled",
+                        "columnName": "enabled",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "enabledTimestampMs",
+                        "columnName": "enabled_timestamp_ms",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "subfolderByDate",
+                        "columnName": "subfolder_by_date",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "account",
+                        "columnName": "account",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "uploadAction",
+                        "columnName": "upload_option",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "nameCollisionPolicy",
+                        "columnName": "name_collision_policy",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "type",
+                        "columnName": "type",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "hidden",
+                        "columnName": "hidden",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "subFolderRule",
+                        "columnName": "sub_folder_rule",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "list_of_uploads",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `local_path` TEXT, `remote_path` TEXT, `account_name` TEXT, `file_size` INTEGER, `status` INTEGER, `local_behaviour` INTEGER, `upload_time` INTEGER, `name_collision_policy` INTEGER, `is_create_remote_folder` INTEGER, `upload_end_timestamp` INTEGER, `last_result` INTEGER, `is_while_charging_only` INTEGER, `is_wifi_only` INTEGER, `created_by` INTEGER, `folder_unlock_token` TEXT)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localPath",
+                        "columnName": "local_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "remotePath",
+                        "columnName": "remote_path",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "accountName",
+                        "columnName": "account_name",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "fileSize",
+                        "columnName": "file_size",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "status",
+                        "columnName": "status",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "localBehaviour",
+                        "columnName": "local_behaviour",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "uploadTime",
+                        "columnName": "upload_time",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "nameCollisionPolicy",
+                        "columnName": "name_collision_policy",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isCreateRemoteFolder",
+                        "columnName": "is_create_remote_folder",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "uploadEndTimestamp",
+                        "columnName": "upload_end_timestamp",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "lastResult",
+                        "columnName": "last_result",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isWhileChargingOnly",
+                        "columnName": "is_while_charging_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "isWifiOnly",
+                        "columnName": "is_wifi_only",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "createdBy",
+                        "columnName": "created_by",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "folderUnlockToken",
+                        "columnName": "folder_unlock_token",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            },
+            {
+                "tableName": "virtual",
+                "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` TEXT, `ocfile_id` INTEGER)",
+                "fields": [
+                    {
+                        "fieldPath": "id",
+                        "columnName": "_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "type",
+                        "columnName": "type",
+                        "affinity": "TEXT",
+                        "notNull": false
+                    },
+                    {
+                        "fieldPath": "ocFileId",
+                        "columnName": "ocfile_id",
+                        "affinity": "INTEGER",
+                        "notNull": false
+                    }
+                ],
+                "primaryKey": {
+                    "autoGenerate": true,
+                    "columnNames": [
+                        "_id"
+                    ]
+                },
+                "indices": [],
+                "foreignKeys": []
+            }
+        ],
+        "views": [],
+        "setupQueries": [
+            "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+            "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0d639ab041aa87e6c2ef9504395545f7')"
+        ]
+    }
+}

+ 20 - 0
app/src/androidTest/java/com/owncloud/android/datamodel/ArbitraryDataProviderIT.kt

@@ -75,4 +75,24 @@ class ArbitraryDataProviderIT : AbstractIT() {
         arbitraryDataProvider.storeOrUpdateKeyValue(user.accountName, key, value.toString())
         assertEquals(value, arbitraryDataProvider.getIntegerValue(user.accountName, key))
     }
+
+    @Test
+    fun testIncrement() {
+        val key = "INCREMENT"
+
+        // key does not exist
+        assertEquals(-1, arbitraryDataProvider.getIntegerValue(user.accountName, key))
+
+        // increment -> 1
+        arbitraryDataProvider.incrementValue(user.accountName, key)
+        assertEquals(1, arbitraryDataProvider.getIntegerValue(user.accountName, key))
+
+        // increment -> 2
+        arbitraryDataProvider.incrementValue(user.accountName, key)
+        assertEquals(2, arbitraryDataProvider.getIntegerValue(user.accountName, key))
+
+        // delete
+        arbitraryDataProvider.deleteKeyForAccount(user.accountName, key)
+        assertEquals(-1, arbitraryDataProvider.getIntegerValue(user.accountName, key))
+    }
 }

+ 6 - 1
app/src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java

@@ -765,7 +765,12 @@ public class EncryptionTestIT extends AbstractIT {
         // verify authentication tag
         assertTrue(Arrays.equals(expectedAuthTag, authenticationTag));
 
-        byte[] decryptedBytes = decryptFile(encryptedTempFile, key, iv, authenticationTag);
+        byte[] decryptedBytes = decryptFile(encryptedTempFile,
+                                            key,
+                                            iv,
+                                            authenticationTag,
+                                            new ArbitraryDataProviderImpl(targetContext),
+                                            user);
 
         File decryptedFile = File.createTempFile("file", "dec");
         FileOutputStream fileOutputStream1 = new FileOutputStream(decryptedFile);

+ 2 - 1
app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt

@@ -68,7 +68,8 @@ import com.owncloud.android.db.ProviderMeta
         AutoMigration(from = 71, to = 72),
         AutoMigration(from = 72, to = 73),
         AutoMigration(from = 73, to = 74, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class),
-        AutoMigration(from = 74, to = 75)
+        AutoMigration(from = 74, to = 75),
+        AutoMigration(from = 75, to = 76)
     ],
     exportSchema = true
 )

+ 3 - 0
app/src/main/java/com/nextcloud/client/database/dao/FileDao.kt

@@ -61,4 +61,7 @@ interface FileDao {
 
     @Query("SELECT * FROM filelist WHERE path LIKE :pathPattern AND file_owner = :fileOwner ORDER BY path ASC")
     fun getFolderWithDescendants(pathPattern: String, fileOwner: String): List<FileEntity>
+
+    @Query("SELECT * FROM filelist where file_owner = :fileOwner AND etag_in_conflict IS NOT NULL")
+    fun getFilesWithSyncConflict(fileOwner: String): List<FileEntity>
 }

+ 3 - 1
app/src/main/java/com/nextcloud/client/database/entity/CapabilityEntity.kt

@@ -131,5 +131,7 @@ data class CapabilityEntity(
     @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_GROUPFOLDERS)
     val groupfolders: Int?,
     @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT)
-    val dropAccount: Int?
+    val dropAccount: Int?,
+    @ColumnInfo(name = ProviderTableMeta.CAPABILITIES_SECURITY_GUARD)
+    val securityGuard: Int?
 )

+ 7 - 1
app/src/main/java/com/nextcloud/client/device/PowerManagementServiceImpl.kt

@@ -28,6 +28,8 @@ import android.os.BatteryManager
 import android.os.PowerManager
 import com.nextcloud.client.preferences.AppPreferences
 import com.nextcloud.client.preferences.AppPreferencesImpl
+import com.nextcloud.utils.extensions.registerBroadcastReceiver
+import com.owncloud.android.datamodel.ReceiverFlag
 
 internal class PowerManagementServiceImpl(
     private val context: Context,
@@ -67,7 +69,11 @@ internal class PowerManagementServiceImpl(
     @Suppress("MagicNumber") // 100% is 100, we're not doing Cobol
     override val battery: BatteryStatus
         get() {
-            val intent: Intent? = context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
+            val intent: Intent? = context.registerBroadcastReceiver(
+                null,
+                IntentFilter(Intent.ACTION_BATTERY_CHANGED),
+                ReceiverFlag.NotExported
+            )
             val isCharging = intent?.let {
                 when (it.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)) {
                     BatteryManager.BATTERY_PLUGGED_USB -> true

+ 2 - 2
app/src/main/java/com/nextcloud/client/di/AppModule.java

@@ -145,8 +145,8 @@ class AppModule {
     }
 
     @Provides
-    UploadsStorageManager uploadsStorageManager(Context context,
-                                                CurrentAccountProvider currentAccountProvider) {
+    UploadsStorageManager uploadsStorageManager(CurrentAccountProvider currentAccountProvider,
+                                                Context context) {
         return new UploadsStorageManager(currentAccountProvider, context.getContentResolver());
     }
 

+ 12 - 2
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

@@ -62,7 +62,7 @@ class BackgroundJobFactory @Inject constructor(
     private val deviceInfo: DeviceInfo,
     private val accountManager: UserAccountManager,
     private val resources: Resources,
-    private val dataProvider: ArbitraryDataProvider,
+    private val arbitraryDataProvider: ArbitraryDataProvider,
     private val uploadsStorageManager: UploadsStorageManager,
     private val connectivityService: ConnectivityService,
     private val notificationManager: NotificationManager,
@@ -103,6 +103,7 @@ class BackgroundJobFactory @Inject constructor(
                 FilesExportWork::class -> createFilesExportWork(context, workerParameters)
                 FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
                 GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
+                HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
                 else -> null // caller falls back to default factory
             }
         }
@@ -139,7 +140,7 @@ class BackgroundJobFactory @Inject constructor(
             context,
             params,
             resources,
-            dataProvider,
+            arbitraryDataProvider,
             contentResolver,
             accountManager
         )
@@ -260,4 +261,13 @@ class BackgroundJobFactory @Inject constructor(
             params = params
         )
     }
+
+    private fun createHealthStatusWork(context: Context, params: WorkerParameters): HealthStatusWork {
+        return HealthStatusWork(
+            context,
+            params,
+            accountManager,
+            arbitraryDataProvider
+        )
+    }
 }

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

@@ -147,4 +147,6 @@ interface BackgroundJobManager {
 
     fun pruneJobs()
     fun cancelAllJobs()
+    fun schedulePeriodicHealthStatus()
+    fun startHealthStatus()
 }

+ 23 - 0
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt

@@ -82,6 +82,8 @@ internal class BackgroundJobManagerImpl(
         const val JOB_PDF_GENERATION = "pdf_generation"
         const val JOB_IMMEDIATE_CALENDAR_BACKUP = "immediate_calendar_backup"
         const val JOB_IMMEDIATE_FILES_EXPORT = "immediate_files_export"
+        const val JOB_PERIODIC_HEALTH_STATUS = "periodic_health_status"
+        const val JOB_IMMEDIATE_HEALTH_STATUS = "immediate_health_status"
 
         const val JOB_TEST = "test_job"
 
@@ -507,4 +509,25 @@ internal class BackgroundJobManagerImpl(
     override fun cancelAllJobs() {
         workManager.cancelAllWorkByTag(TAG_ALL)
     }
+
+    override fun schedulePeriodicHealthStatus() {
+        val request = periodicRequestBuilder(
+            jobClass = HealthStatusWork::class,
+            jobName = JOB_PERIODIC_HEALTH_STATUS,
+            intervalMins = PERIODIC_BACKUP_INTERVAL_MINUTES
+        ).build()
+
+        workManager.enqueueUniquePeriodicWork(JOB_PERIODIC_HEALTH_STATUS, ExistingPeriodicWorkPolicy.KEEP, request)
+    }
+
+    override fun startHealthStatus() {
+        val request = oneTimeRequestBuilder(HealthStatusWork::class, JOB_IMMEDIATE_HEALTH_STATUS)
+            .build()
+
+        workManager.enqueueUniqueWork(
+            JOB_IMMEDIATE_HEALTH_STATUS,
+            ExistingWorkPolicy.KEEP,
+            request
+        )
+    }
 }

+ 51 - 24
app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt

@@ -253,6 +253,50 @@ class FilesUploadWorker(
         // TODO generalize for automated uploads
     }
 
+    private fun createConflictResolveAction(context: Context, uploadFileOperation: UploadFileOperation): PendingIntent {
+        val intent = ConflictsResolveActivity.createIntent(
+            uploadFileOperation.file,
+            uploadFileOperation.user,
+            uploadFileOperation.ocUploadId,
+            Intent.FLAG_ACTIVITY_CLEAR_TOP,
+            context
+        )
+
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE)
+        } else {
+            PendingIntent.getActivity(
+                context,
+                0,
+                intent,
+                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+            )
+        }
+    }
+
+    private fun addConflictResolveActionToNotification(uploadFileOperation: UploadFileOperation) {
+        val intent: PendingIntent = createConflictResolveAction(context, uploadFileOperation)
+
+        notificationBuilder.addAction(
+            R.drawable.ic_cloud_upload,
+            context.getString(R.string.upload_list_resolve_conflict),
+            intent
+        )
+    }
+
+    private fun addUploadListContentIntent(uploadFileOperation: UploadFileOperation) {
+        val uploadListIntent = createUploadListIntent(uploadFileOperation)
+
+        notificationBuilder.setContentIntent(
+            PendingIntent.getActivity(
+                context,
+                System.currentTimeMillis().toInt(),
+                uploadListIntent,
+                PendingIntent.FLAG_IMMUTABLE
+            )
+        )
+    }
+
     /**
      * adapted from [com.owncloud.android.files.services.FileUploader.notifyUploadResult]
      */
@@ -300,23 +344,16 @@ class FilesUploadWorker(
 
             val content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, uploadFileOperation, context.resources)
 
+            addUploadListContentIntent(uploadFileOperation)
+
+            if (uploadResult.code == ResultCode.SYNC_CONFLICT) {
+                addConflictResolveActionToNotification(uploadFileOperation)
+            }
+
             if (needsToUpdateCredentials) {
                 createUpdateCredentialsNotification(uploadFileOperation.user.toPlatformAccount())
-            } else {
-                val intent = if (uploadResult.code == ResultCode.SYNC_CONFLICT) {
-                    createResolveConflictIntent(uploadFileOperation)
-                } else {
-                    createUploadListIntent(uploadFileOperation)
-                }
-                notificationBuilder.setContentIntent(
-                    PendingIntent.getActivity(
-                        context,
-                        System.currentTimeMillis().toInt(),
-                        intent,
-                        PendingIntent.FLAG_IMMUTABLE
-                    )
-                )
             }
+
             notificationBuilder.setContentText(content)
 
             notificationManager.notify(
@@ -336,16 +373,6 @@ class FilesUploadWorker(
         )
     }
 
-    private fun createResolveConflictIntent(uploadFileOperation: UploadFileOperation): Intent {
-        return ConflictsResolveActivity.createIntent(
-            uploadFileOperation.file,
-            uploadFileOperation.user,
-            uploadFileOperation.ocUploadId,
-            Intent.FLAG_ACTIVITY_CLEAR_TOP,
-            context
-        )
-    }
-
     private fun createUpdateCredentialsNotification(account: Account) {
         // let the user update credentials with one click
         val updateAccountCredentials = Intent(context, AuthenticatorActivity::class.java)

+ 131 - 0
app/src/main/java/com/nextcloud/client/jobs/HealthStatusWork.kt

@@ -0,0 +1,131 @@
+/*
+ *
+ * Nextcloud Android client application
+ *
+ * @author Tobias Kaminsky
+ * Copyright (C) 2023 Tobias Kaminsky
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.jobs
+
+import android.content.Context
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.account.User
+import com.nextcloud.client.account.UserAccountManager
+import com.owncloud.android.datamodel.ArbitraryDataProvider
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.UploadsStorageManager
+import com.owncloud.android.db.UploadResult
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.lib.resources.status.Problem
+import com.owncloud.android.lib.resources.status.SendClientDiagnosticRemoteOperation
+import com.owncloud.android.utils.EncryptionUtils
+import com.owncloud.android.utils.theme.CapabilityUtils
+
+class HealthStatusWork(
+    private val context: Context,
+    params: WorkerParameters,
+    private val userAccountManager: UserAccountManager,
+    private val arbitraryDataProvider: ArbitraryDataProvider
+) : Worker(context, params) {
+    override fun doWork(): Result {
+        for (user in userAccountManager.allUsers) {
+            // only if security guard is enabled
+            if (!CapabilityUtils.getCapability(user, context).securityGuard.isTrue) {
+                continue
+            }
+
+            val syncConflicts = collectSyncConflicts(user)
+
+            val problems = mutableListOf<Problem>().apply {
+                addAll(
+                    collectUploadProblems(
+                        user,
+                        listOf(
+                            UploadResult.CREDENTIAL_ERROR,
+                            UploadResult.CANNOT_CREATE_FILE,
+                            UploadResult.FOLDER_ERROR,
+                            UploadResult.SERVICE_INTERRUPTED
+                        )
+                    )
+                )
+            }
+
+            val virusDetected = collectUploadProblems(user, listOf(UploadResult.VIRUS_DETECTED)).firstOrNull()
+
+            val e2eErrors = EncryptionUtils.readE2eError(arbitraryDataProvider, user)
+
+            val nextcloudClient = OwnCloudClientManagerFactory.getDefaultSingleton()
+                .getNextcloudClientFor(user.toOwnCloudAccount(), context)
+            val result =
+                SendClientDiagnosticRemoteOperation(
+                    syncConflicts,
+                    problems,
+                    virusDetected,
+                    e2eErrors
+                ).execute(
+                    nextcloudClient
+                )
+
+            if (!result.isSuccess) {
+                if (result.exception == null) {
+                    Log_OC.e(TAG, "Update client health NOT successful!")
+                } else {
+                    Log_OC.e(TAG, "Update client health NOT successful!", result.exception)
+                }
+            }
+        }
+
+        return Result.success()
+    }
+
+    private fun collectSyncConflicts(user: User): Problem? {
+        val fileDataStorageManager = FileDataStorageManager(user, context.contentResolver)
+
+        val conflicts = fileDataStorageManager.getFilesWithSyncConflict(user)
+
+        return if (conflicts.isEmpty()) {
+            null
+        } else {
+            Problem("sync_conflicts", conflicts.size, conflicts.minOf { it.lastSyncDateForData })
+        }
+    }
+
+    private fun collectUploadProblems(user: User, errorCodes: List<UploadResult>): List<Problem> {
+        val uploadsStorageManager = UploadsStorageManager(userAccountManager, context.contentResolver)
+
+        val problems = uploadsStorageManager
+            .getUploadsForAccount(user.accountName)
+            .filter {
+                errorCodes.contains(it.lastResult)
+            }.groupBy { it.lastResult }
+
+        return if (problems.isEmpty()) {
+            emptyList()
+        } else {
+            return problems.map { problem ->
+                Problem(problem.key.toString(), problem.value.size, problem.value.minOf { it.uploadEndTimestamp })
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "Health Status"
+    }
+}

+ 27 - 0
app/src/main/java/com/nextcloud/model/HTTPStatusCodes.kt

@@ -0,0 +1,27 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.model
+
+@Suppress("MagicNumber")
+enum class HTTPStatusCodes(val code: Int) {
+    NOT_FOUND(404)
+}

+ 39 - 0
app/src/main/java/com/nextcloud/utils/extensions/ContextExtensions.kt

@@ -0,0 +1,39 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.utils.extensions
+
+import android.annotation.SuppressLint
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Build
+import com.owncloud.android.datamodel.ReceiverFlag
+
+@SuppressLint("UnspecifiedRegisterReceiverFlag")
+fun Context.registerBroadcastReceiver(receiver: BroadcastReceiver?, filter: IntentFilter, flag: ReceiverFlag): Intent? {
+    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+        registerReceiver(receiver, filter, flag.getId())
+    } else {
+        registerReceiver(receiver, filter)
+    }
+}

+ 2 - 0
app/src/main/java/com/owncloud/android/MainApp.java

@@ -349,6 +349,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         backgroundJobManager.scheduleMediaFoldersDetectionJob();
         backgroundJobManager.startMediaFoldersDetectionJob();
 
+        backgroundJobManager.schedulePeriodicHealthStatus();
+
         registerGlobalPassCodeProtection();
     }
 

+ 4 - 0
app/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProvider.kt

@@ -28,6 +28,8 @@ interface ArbitraryDataProvider {
     fun deleteKeyForAccount(account: String, key: String)
 
     fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: Long)
+
+    fun incrementValue(accountName: String, key: String)
     fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: Boolean)
     fun storeOrUpdateKeyValue(accountName: String, key: String, newValue: String)
 
@@ -43,5 +45,7 @@ interface ArbitraryDataProvider {
         const val DIRECT_EDITING = "DIRECT_EDITING"
         const val DIRECT_EDITING_ETAG = "DIRECT_EDITING_ETAG"
         const val PREDEFINED_STATUS = "PREDEFINED_STATUS"
+        const val E2E_ERRORS = "E2E_ERRORS"
+        const val E2E_ERRORS_TIMESTAMP = "E2E_ERRORS_TIMESTAMP"
     }
 }

+ 11 - 0
app/src/main/java/com/owncloud/android/datamodel/ArbitraryDataProviderImpl.java

@@ -63,6 +63,17 @@ public class ArbitraryDataProviderImpl implements ArbitraryDataProvider {
         storeOrUpdateKeyValue(accountName, key, String.valueOf(newValue));
     }
 
+    @Override
+    public void incrementValue(@NonNull String accountName, @NonNull String key) {
+        int oldValue = getIntegerValue(accountName, key);
+
+        int value = 1;
+        if (oldValue > 0) {
+            value = oldValue + 1;
+        }
+        storeOrUpdateKeyValue(accountName, key, value);
+    }
+
     @Override
     public void storeOrUpdateKeyValue(@NonNull final String accountName, @NonNull final String key, final boolean newValue) {
         storeOrUpdateKeyValue(accountName, key, String.valueOf(newValue));

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

@@ -1954,6 +1954,7 @@ public class FileDataStorageManager {
                           capability.getFilesLockingVersion());
         contentValues.put(ProviderTableMeta.CAPABILITIES_GROUPFOLDERS, capability.getGroupfolders().getValue());
         contentValues.put(ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT, capability.getDropAccount().getValue());
+        contentValues.put(ProviderTableMeta.CAPABILITIES_SECURITY_GUARD, capability.getSecurityGuard().getValue());
 
         return contentValues;
     }
@@ -2111,6 +2112,7 @@ public class FileDataStorageManager {
                 getString(cursor, ProviderTableMeta.CAPABILITIES_FILES_LOCKING_VERSION));
             capability.setGroupfolders(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_GROUPFOLDERS));
             capability.setDropAccount(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_DROP_ACCOUNT));
+            capability.setSecurityGuard(getBoolean(cursor, ProviderTableMeta.CAPABILITIES_SECURITY_GUARD));
         }
         return capability;
     }
@@ -2287,7 +2289,18 @@ public class FileDataStorageManager {
         return user;
     }
 
-    public OCFile getDefaultRootPath(){
+    public OCFile getDefaultRootPath() {
         return new OCFile(OCFile.ROOT_PATH);
     }
+
+    public List<OCFile> getFilesWithSyncConflict(User user) {
+        List<FileEntity> fileEntities = fileDao.getFilesWithSyncConflict(user.getAccountName());
+        List<OCFile> files = new ArrayList<>(fileEntities.size());
+
+        for (FileEntity fileEntity : fileEntities) {
+            files.add(createFileInstance(fileEntity));
+        }
+
+        return files;
+    }
 }

+ 35 - 0
app/src/main/java/com/owncloud/android/datamodel/ReceiverFlag.kt

@@ -0,0 +1,35 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 Nextcloud GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package com.owncloud.android.datamodel
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.RequiresApi
+
+enum class ReceiverFlag {
+    NotExported;
+
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    fun getId(): Int {
+        return Context.RECEIVER_NOT_EXPORTED
+    }
+}

+ 25 - 21
app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java

@@ -567,6 +567,10 @@ public class UploadsStorageManager extends Observable {
             , String.valueOf(UploadStatus.UPLOAD_FAILED.value));
     }
 
+    public OCUpload[] getUploadsForAccount(final @NonNull String accountName) {
+        return getUploads(ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", accountName);
+    }
+
     public OCUpload[] getFinishedUploadsForCurrentAccount() {
         User user = currentAccountProvider.getUser();
 
@@ -586,14 +590,14 @@ public class UploadsStorageManager extends Observable {
         User user = currentAccountProvider.getUser();
 
         return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
-                        "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
-                        "<>" + UploadResult.LOCK_FAILED.getValue() +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
-                        "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
-                        "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
+                              AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                              "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+                              AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                              "<>" + UploadResult.LOCK_FAILED.getValue() +
+                              AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                              "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+                              AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                              "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
                         AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
                         user.getAccountName());
     }
@@ -624,15 +628,15 @@ public class UploadsStorageManager extends Observable {
     public long clearFailedButNotDelayedUploads() {
         User user = currentAccountProvider.getUser();
         final long deleted = getDB().delete(
-                ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
-                        "<>" + UploadResult.LOCK_FAILED.getValue() +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
-                        "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
-                        "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
-                        AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+            ProviderTableMeta.CONTENT_URI_UPLOADS,
+            ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_FAILED.value +
+                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                "<>" + UploadResult.LOCK_FAILED.getValue() +
+                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                "<>" + UploadResult.DELAYED_FOR_WIFI.getValue() +
+                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
+                "<>" + UploadResult.DELAYED_FOR_CHARGING.getValue() +
+                AND + ProviderTableMeta.UPLOADS_LAST_RESULT +
                         "<>" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() +
                         AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?",
                 new String[]{user.getAccountName()}
@@ -647,10 +651,10 @@ public class UploadsStorageManager extends Observable {
     public long clearSuccessfulUploads() {
         User user = currentAccountProvider.getUser();
         final long deleted = getDB().delete(
-                ProviderTableMeta.CONTENT_URI_UPLOADS,
-                ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
-                        ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{user.getAccountName()}
-        );
+            ProviderTableMeta.CONTENT_URI_UPLOADS,
+            ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_SUCCEEDED.value + AND +
+                ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{user.getAccountName()}
+                                           );
 
         Log_OC.d(TAG, "delete all successful uploads");
         if (deleted > 0) {

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

@@ -35,7 +35,7 @@ import java.util.List;
  */
 public class ProviderMeta {
     public static final String DB_NAME = "filelist";
-    public static final int DB_VERSION = 75;
+    public static final int DB_VERSION = 76;
 
     private ProviderMeta() {
         // No instance
@@ -264,6 +264,7 @@ public class ProviderMeta {
         public static final String CAPABILITIES_USER_STATUS_SUPPORTS_EMOJI = "user_status_supports_emoji";
         public static final String CAPABILITIES_GROUPFOLDERS = "groupfolders";
         public static final String CAPABILITIES_DROP_ACCOUNT = "drop_account";
+        public static final String CAPABILITIES_SECURITY_GUARD = "security_guard";
 
         //Columns of Uploads table
         public static final String UPLOADS_LOCAL_PATH = "local_path";

+ 3 - 1
app/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java

@@ -163,7 +163,9 @@ public class CreateFolderOperation extends SyncOperation implements OnRemoteOper
                                                serializedFolderMetadata,
                                                token,
                                                client,
-                                               metadataExists);
+                                               metadataExists,
+                                               arbitraryDataProvider,
+                                               user);
 
                 // unlock folder
                 if (token != null) {

+ 7 - 1
app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java

@@ -26,6 +26,7 @@ import android.text.TextUtils;
 import android.webkit.MimeTypeMap;
 
 import com.nextcloud.client.account.User;
+import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
 import com.owncloud.android.datamodel.DecryptedFolderMetadata;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
@@ -213,7 +214,12 @@ public class DownloadFileOperation extends RemoteOperation {
                         .get(file.getEncryptedFileName()).getAuthenticationTag());
 
                 try {
-                    byte[] decryptedBytes = EncryptionUtils.decryptFile(tmpFile, key, iv, authenticationTag);
+                    byte[] decryptedBytes = EncryptionUtils.decryptFile(tmpFile,
+                                                                        key,
+                                                                        iv,
+                                                                        authenticationTag,
+                                                                        new ArbitraryDataProviderImpl(context),
+                                                                        user);
 
                     try (FileOutputStream fileOutputStream = new FileOutputStream(tmpFile)) {
                         fileOutputStream.write(decryptedBytes);

+ 3 - 1
app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -638,7 +638,9 @@ public class UploadFileOperation extends SyncOperation {
                                                serializedFolderMetadata,
                                                token,
                                                client,
-                                               metadataExists);
+                                               metadataExists,
+                                               arbitraryDataProvider,
+                                               user);
 
                 // unlock
                 result = EncryptionUtils.unlockFolder(parentFile, client, token);

+ 16 - 4
app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt

@@ -21,6 +21,7 @@ import android.content.Intent
 import android.os.Bundle
 import android.widget.Toast
 import com.nextcloud.client.account.User
+import com.nextcloud.model.HTTPStatusCodes
 import com.nextcloud.utils.extensions.getParcelableArgument
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.OCFile
@@ -164,7 +165,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
                         startDialog()
                     } else {
                         Log_OC.e(TAG, "ReadFileRemoteOp returned failure with code: " + result.httpCode)
-                        showErrorAndFinish()
+                        showErrorAndFinish(result.httpCode)
                     }
                 } catch (e: Exception) {
                     Log_OC.e(TAG, "Error when trying to fetch remote file", e)
@@ -203,9 +204,20 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
         }
     }
 
-    private fun showErrorAndFinish() {
-        runOnUiThread { Toast.makeText(this, R.string.conflict_dialog_error, Toast.LENGTH_LONG).show() }
-        finish()
+    private fun showErrorAndFinish(code: Int? = null) {
+        val message = parseErrorMessage(code)
+        runOnUiThread {
+            Toast.makeText(this, message, Toast.LENGTH_LONG).show()
+            finish()
+        }
+    }
+
+    private fun parseErrorMessage(code: Int?): String {
+        return if (code == HTTPStatusCodes.NOT_FOUND.code) {
+            getString(R.string.uploader_file_not_found_on_server_message)
+        } else {
+            getString(R.string.conflict_dialog_error)
+        }
     }
 
     /**

File diff suppressed because it is too large
+ 194 - 343
app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java


+ 1 - 0
app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt

@@ -128,6 +128,7 @@ class NotificationsActivity : DrawerActivity(), NotificationsContract.View {
         return
     }
 
+    @Suppress("NestedBlockDepth")
     private fun setupPushWarning() {
         if (!resources.getBoolean(R.bool.show_push_warning)) {
             return

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

@@ -197,7 +197,6 @@ public abstract class ToolbarActivity extends BaseActivity implements Injectable
     /**
      * checks if the given file is the root folder.
      *
-     *
      * @param file file to be checked if it is the root folder
      * @return <code>true</code> if it is <code>null</code> or the root folder, else returns <code>false</code>
      */
@@ -243,6 +242,9 @@ public abstract class ToolbarActivity extends BaseActivity implements Injectable
         findViewById(R.id.sort_list_button_group).setVisibility(show ? View.VISIBLE : View.GONE);
     }
 
+    public boolean sortListGroupVisibility(){
+        return findViewById(R.id.sort_list_button_group).getVisibility() == View.VISIBLE;
+    }
     /**
      * Change the bitmap for the toolbar's preview image.
      *

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

@@ -57,6 +57,7 @@ import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CheckCurrentCredentialsOperation;
 import com.owncloud.android.ui.adapter.UploadListAdapter;
 import com.owncloud.android.ui.decoration.MediaGridItemDecoration;
+import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.theme.ViewThemeUtils;
 
@@ -201,12 +202,13 @@ public class UploadListActivity extends FileActivity {
             uploadsStorageManager,
             connectivityService,
             userAccountManager,
-            powerManagementService
-                                                        )).start();
+            powerManagementService))
+            .start();
 
         // update UI
         uploadListAdapter.loadUploadItemsFromDb();
         swipeListRefreshLayout.setRefreshing(false);
+        DisplayUtils.showSnackMessage(this, R.string.uploader_local_files_uploaded);
     }
 
     @Override
@@ -335,11 +337,11 @@ public class UploadListActivity extends FileActivity {
                     uploadListAdapter.loadUploadItemsFromDb();
                 } else {
                     Log_OC.d(TAG, "mUploaderBinder already set. mUploaderBinder: " +
-                            mUploaderBinder + " service:" + service);
+                        mUploaderBinder + " service:" + service);
                 }
             } else {
                 Log_OC.d(TAG, "UploadListActivity not connected to Upload service. component: " +
-                        component + " service: " + service);
+                    component + " service: " + service);
             }
         }
 

+ 63 - 65
app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java

@@ -83,17 +83,17 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
     private static final String TAG = UploadListAdapter.class.getSimpleName();
 
     private ProgressListener progressListener;
-    private FileActivity parentActivity;
-    private UploadsStorageManager uploadsStorageManager;
-    private FileDataStorageManager storageManager;
-    private ConnectivityService connectivityService;
-    private PowerManagementService powerManagementService;
-    private UserAccountManager accountManager;
+    private final FileActivity parentActivity;
+    private final UploadsStorageManager uploadsStorageManager;
+    private final FileDataStorageManager storageManager;
+    private final ConnectivityService connectivityService;
+    private final PowerManagementService powerManagementService;
+    private final UserAccountManager accountManager;
+    private final Clock clock;
+    private final UploadGroup[] uploadGroups;
+    private final boolean showUser;
+    private final ViewThemeUtils viewThemeUtils;
     private NotificationManager mNotificationManager;
-    private Clock clock;
-    private UploadGroup[] uploadGroups;
-    private boolean showUser;
-    private final  ViewThemeUtils viewThemeUtils;
 
     @Override
     public int getSectionCount() {
@@ -119,42 +119,31 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         headerViewHolder.binding.uploadListTitle.setOnClickListener(v -> toggleSectionExpanded(section));
 
         switch (group.type) {
-            case CURRENT:
-            case FINISHED:
-                headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close);
-                break;
-            case FAILED:
-                headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_sync);
-                break;
+            case CURRENT, FINISHED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close);
+            case FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_sync);
         }
 
         headerViewHolder.binding.uploadListAction.setOnClickListener(v -> {
             switch (group.type) {
-                case CURRENT:
+                case CURRENT -> {
                     FileUploader.FileUploaderBinder uploaderBinder = parentActivity.getFileUploaderBinder();
-
                     if (uploaderBinder != null) {
                         for (OCUpload upload : group.getItems()) {
                             uploaderBinder.cancel(upload);
                         }
                     }
-                    break;
-                case FINISHED:
-                    uploadsStorageManager.clearSuccessfulUploads();
-                    break;
-                case FAILED:
-                    new Thread(() -> FileUploader.retryFailedUploads(
-                        parentActivity,
-                        uploadsStorageManager,
-                        connectivityService,
-                        accountManager,
-                        powerManagementService
-                                                                    )).start();
-                    break;
-
-                default:
-                    // do nothing
-                    break;
+                }
+                case FINISHED -> uploadsStorageManager.clearSuccessfulUploads();
+                case FAILED -> new Thread(() -> FileUploader.retryFailedUploads(
+                    parentActivity,
+                    uploadsStorageManager,
+                    connectivityService,
+                    accountManager,
+                    powerManagementService
+                                                                               )).start();
+                default -> {
+                }
+                // do nothing
             }
 
             loadUploadItemsFromDb();
@@ -217,6 +206,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         loadUploadItemsFromDb();
     }
 
+
     @Override
     public void onBindViewHolder(SectionedViewHolder holder, int section, int relativePosition, int absolutePosition) {
         ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
@@ -276,11 +266,10 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         // Update information depending of upload details
         String status = getStatusText(item);
         switch (item.getUploadStatus()) {
-            case UPLOAD_IN_PROGRESS:
+            case UPLOAD_IN_PROGRESS -> {
                 viewThemeUtils.platform.themeHorizontalProgressBar(itemViewHolder.binding.uploadProgressBar);
                 itemViewHolder.binding.uploadProgressBar.setProgress(0);
                 itemViewHolder.binding.uploadProgressBar.setVisibility(View.VISIBLE);
-
                 FileUploader.FileUploaderBinder binder = parentActivity.getFileUploaderBinder();
                 if (binder != null) {
                     if (binder.isUploadingNow(item)) {
@@ -290,7 +279,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                             binder.removeDatatransferProgressListener(
                                 progressListener,
                                 progressListener.getUpload()   // the one that was added
-                            );
+                                                                     );
                         }
                         // ... then, bind the current progress bar to listen for updates
                         progressListener = new ProgressListener(item, itemViewHolder.binding.uploadProgressBar);
@@ -308,19 +297,12 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                 } else {
                     Log_OC.w(TAG, "FileUploaderBinder not ready yet for upload " + item.getRemotePath());
                 }
-
                 itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
                 itemViewHolder.binding.uploadFileSize.setVisibility(View.GONE);
                 itemViewHolder.binding.uploadProgressBar.invalidate();
-                break;
-
-            case UPLOAD_FAILED:
-                itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
-                break;
-
-            case UPLOAD_SUCCEEDED:
-                itemViewHolder.binding.uploadStatus.setVisibility(View.GONE);
-                break;
+            }
+            case UPLOAD_FAILED -> itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
+            case UPLOAD_SUCCEEDED -> itemViewHolder.binding.uploadStatus.setVisibility(View.GONE);
         }
         itemViewHolder.binding.uploadStatus.setText(status);
 
@@ -507,29 +489,22 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                                                          OCUpload item,
                                                          String status) {
         String remotePath = item.getRemotePath();
-        OCFile ocFile = storageManager.getFileByPath(remotePath);
+        OCFile localFile = storageManager.getFileByEncryptedRemotePath(remotePath);
 
-        if (ocFile == null) {
+        if (localFile == null) {
             // Remote file doesn't exist, try to refresh folder
-            OCFile folder = storageManager.getFileByPath(new File(remotePath).getParent() + "/");
+            OCFile folder = storageManager.getFileByEncryptedRemotePath(new File(remotePath).getParent() + "/");
+
             if (folder != null && folder.isFolder()) {
-                this.refreshFolder(itemViewHolder, user, folder, (caller, result) -> {
-                    itemViewHolder.binding.uploadStatus.setText(status);
-                    if (result.isSuccess()) {
-                        OCFile file = storageManager.getFileByPath(remotePath);
-                        if (file != null) {
-                            this.openConflictActivity(file, item);
-                        }
-                    }
-                });
+                refreshFolderAndUpdateUI(itemViewHolder, user, folder, remotePath, item, status);
                 return true;
             }
 
             // Destination folder doesn't exist anymore
         }
 
-        if (ocFile != null) {
-            this.openConflictActivity(ocFile, item);
+        if (localFile != null) {
+            this.openConflictActivity(localFile, item);
             return true;
         }
 
@@ -537,6 +512,29 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         return false;
     }
 
+    private void refreshFolderAndUpdateUI(ItemViewHolder holder, User user, OCFile folder, String remotePath, OCUpload item, String status) {
+        Context context = MainApp.getAppContext();
+
+        this.refreshFolder(context, holder, user, folder, (caller, result) -> {
+            holder.binding.uploadStatus.setText(status);
+
+            if (result.isSuccess()) {
+                OCFile fileOnServer = storageManager.getFileByEncryptedRemotePath(remotePath);
+
+                if (fileOnServer != null) {
+                    openConflictActivity(fileOnServer, item);
+                } else {
+                    displayFileNotFoundError(holder.itemView, context);
+                }
+            }
+        });
+    }
+
+    private void displayFileNotFoundError(View itemView, Context context) {
+        String message = context.getString(R.string.uploader_file_not_found_message);
+        DisplayUtils.showSnackMessage(itemView, message);
+    }
+
     private void showItemConflictPopup(User user,
                                        ItemViewHolder itemViewHolder,
                                        OCUpload item,
@@ -558,20 +556,20 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         popup.show();
     }
 
-    private void removeUpload(OCUpload item) {
+    public void removeUpload(OCUpload item) {
         uploadsStorageManager.removeUpload(item);
         cancelOldErrorNotification(item);
         loadUploadItemsFromDb();
     }
 
     private void refreshFolder(
+        Context context,
         ItemViewHolder view,
         User user,
         OCFile folder,
         OnRemoteOperationListener listener) {
         view.binding.uploadListItemLayout.setClickable(false);
         view.binding.uploadStatus.setText(R.string.uploads_view_upload_status_fetching_server_version);
-        Context context = MainApp.getAppContext();
         new RefreshFolderOperation(folder,
                                    clock.getCurrentTime(),
                                    false,

+ 2 - 0
app/src/main/java/com/owncloud/android/ui/dialog/SetupEncryptionDialogFragment.kt

@@ -223,6 +223,7 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
             val secondKey = EncryptionUtils.decodeStringToBase64Bytes(decryptedString)
 
             if (!Arrays.equals(firstKey, secondKey)) {
+                EncryptionUtils.reportE2eError(arbitraryDataProvider, user)
                 throw Exception("Keys do not match")
             }
 
@@ -404,6 +405,7 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
                 if (result.isSuccess) {
                     publicKeyString = result.data[0] as String
                     if (!EncryptionUtils.isMatchingKeys(keyPair, publicKeyString)) {
+                        EncryptionUtils.reportE2eError(arbitraryDataProvider, user)
                         throw RuntimeException("Wrong CSR returned")
                     }
                     Log_OC.d(TAG, "public key success")

+ 28 - 27
app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -144,12 +144,12 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
 
     /**
      * Public factory method to create new FileDetailFragment instances.
-     *
+     * <p>
      * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before).
      *
-     * @param fileToDetail      An {@link OCFile} to show in the fragment
-     * @param user              Currently active user
-     *                          @param activeTab to be active tab
+     * @param fileToDetail An {@link OCFile} to show in the fragment
+     * @param user         Currently active user
+     * @param activeTab    to be active tab
      * @return New fragment with arguments set
      */
     public static FileDetailFragment newInstance(OCFile fileToDetail, User user, int activeTab) {
@@ -224,7 +224,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             user = savedInstanceState.getParcelable(ARG_USER);
         }
 
-        binding = FileDetailsFragmentBinding.inflate(inflater,container,false);
+        binding = FileDetailsFragmentBinding.inflate(inflater, container, false);
         view = binding.getRoot();
 
         if (getFile() == null || user == null) {
@@ -373,8 +373,6 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         super.onResume();
 
         if (toolbarActivity != null) {
-            toolbarActivity.showSortListGroup(false);
-
             if (previewLoaded) {
                 toolbarActivity.setPreviewImageVisibility(true);
             }
@@ -477,7 +475,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
     }
 
     /**
-     * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced.
+     * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be
+     * replaced.
      *
      * @return True when the fragment was created with the empty layout.
      */
@@ -498,11 +497,11 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
 
     /**
      * Updates the view with all relevant details about that file.
-     *
+     * <p>
      * TODO Remove parameter when the transferring state of files is kept in database.
      *
-     * @param transferring Flag signaling if the file should be considered as downloading or uploading,
-     *                     although {@link FileDownloaderBinder#isDownloading(User, OCFile)}  and
+     * @param transferring Flag signaling if the file should be considered as downloading or uploading, although
+     *                     {@link FileDownloaderBinder#isDownloading(User, OCFile)}  and
      *                     {@link FileUploaderBinder#isUploading(User, OCFile)} return false.
      * @param refresh      If 'true', try to refresh the whole file from the database
      */
@@ -537,8 +536,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             FileDownloaderBinder downloaderBinder = containerActivity.getFileDownloaderBinder();
             FileUploaderBinder uploaderBinder = containerActivity.getFileUploaderBinder();
             if (transferring
-                    || (downloaderBinder != null && downloaderBinder.isDownloading(user, file))
-                    || (uploaderBinder != null && uploaderBinder.isUploading(user, file))) {
+                || (downloaderBinder != null && downloaderBinder.isDownloading(user, file))
+                || (uploaderBinder != null && uploaderBinder.isUploading(user, file))) {
                 setButtonsForTransferring();
 
             } else if (file.isDown()) {
@@ -567,7 +566,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             binding.lastModificationTimestamp.setText(DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp()));
         } else {
             binding.lastModificationTimestamp.setText(DisplayUtils.getRelativeTimestamp(getContext(),
-                                                                            file.getModificationTimestamp()));
+                                                                                        file.getModificationTimestamp()));
         }
     }
 
@@ -576,8 +575,8 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             binding.favorite.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_star, null));
         } else {
             binding.favorite.setImageDrawable(ResourcesCompat.getDrawable(getResources(),
-                                                                      R.drawable.ic_star_outline,
-                                                                      null));
+                                                                          R.drawable.ic_star_outline,
+                                                                          null));
         }
     }
 
@@ -618,7 +617,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
 
                 // generate new resized image
                 if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(getFile(), toolbarActivity.getPreviewImageView()) &&
-                        containerActivity.getStorageManager() != null) {
+                    containerActivity.getStorageManager() != null) {
                     final ThumbnailsCacheManager.ResizedImageGenerationTask task =
                         new ThumbnailsCacheManager.ResizedImageGenerationTask(this,
                                                                               toolbarActivity.getPreviewImageView(),
@@ -635,11 +634,11 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
                     }
 
                     final ThumbnailsCacheManager.AsyncResizedImageDrawable asyncDrawable =
-                            new ThumbnailsCacheManager.AsyncResizedImageDrawable(
-                                    MainApp.getAppContext().getResources(),
-                                    resizedImage,
-                                    task
-                            );
+                        new ThumbnailsCacheManager.AsyncResizedImageDrawable(
+                            MainApp.getAppContext().getResources(),
+                            resizedImage,
+                            task
+                        );
 
                     toolbarActivity.setPreviewImageDrawable(asyncDrawable);
                     previewLoaded = true;
@@ -664,8 +663,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             //if (getFile().isDownloading()) {
             if (downloaderBinder != null && downloaderBinder.isDownloading(user, getFile())) {
                 binding.progressText.setText(R.string.downloader_download_in_progress_ticker);
-            }
-            else {
+            } else {
                 if (uploaderBinder != null && uploaderBinder.isUploading(user, getFile())) {
                     binding.progressText.setText(R.string.uploader_upload_in_progress_ticker);
                 }
@@ -701,7 +699,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             }
             if (containerActivity.getFileUploaderBinder() != null) {
                 containerActivity.getFileUploaderBinder().
-                        addDatatransferProgressListener(progressListener, user, getFile());
+                    addDatatransferProgressListener(progressListener, user, getFile());
             }
         } else {
             Log_OC.d(TAG, "progressListener == null");
@@ -716,7 +714,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             }
             if (containerActivity.getFileUploaderBinder() != null) {
                 containerActivity.getFileUploaderBinder().
-                        removeDatatransferProgressListener(progressListener, user, getFile());
+                    removeDatatransferProgressListener(progressListener, user, getFile());
             }
         }
     }
@@ -733,6 +731,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
 
     /**
      * open the sharing process fragment for creating new share
+     *
      * @param shareeName
      * @param shareType
      */
@@ -749,6 +748,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
 
     /**
      * method will handle the views need to be hidden when sharing process fragment shows
+     *
      * @param isFragmentReplaced
      */
     public void showHideFragmentView(boolean isFragmentReplaced) {
@@ -765,6 +765,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
 
     /**
      * open the new sharing screen process to modify the created share
+     *
      * @param share
      * @param screenTypePermission
      * @param isReshareShown
@@ -816,7 +817,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         @Override
         public void onTransferProgress(long progressRate, long totalTransferredSoFar,
                                        long totalToTransfer, String filename) {
-            int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
+            int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
             if (percent != lastPercent) {
                 ProgressBar pb = progressBarReference.get();
                 if (pb != null) {

+ 2 - 2
app/src/main/java/com/owncloud/android/ui/fragment/GroupfolderListFragment.kt

@@ -125,8 +125,8 @@ class GroupfolderListFragment : OCFileListFragment(), Injectable, GroupfolderLis
             val fetchResult = ReadFileRemoteOperation(partialFile.remotePath).execute(user, context)
             if (!fetchResult.isSuccess) {
                 logger.e(SHARED_TAG, "Error fetching file")
-                if (fetchResult.isException) {
-                    logger.e(SHARED_TAG, "exception: ", fetchResult.exception)
+                if (fetchResult.isException && fetchResult.exception != null) {
+                    logger.e(SHARED_TAG, "exception: ", fetchResult.exception!!)
                 }
                 null
             } else {

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

@@ -1766,7 +1766,9 @@ public class OCFileListFragment extends ExtendedListFragment implements
                                                serializedFolderMetadata,
                                                token,
                                                client,
-                                               metadataExists);
+                                               metadataExists,
+                                               arbitraryDataProvider,
+                                               user);
 
                 // unlock folder
                 EncryptionUtils.unlockFolder(folder, client, token);

+ 2 - 2
app/src/main/java/com/owncloud/android/ui/fragment/SharedListFragment.kt

@@ -87,8 +87,8 @@ class SharedListFragment : OCFileListFragment(), Injectable {
             val fetchResult = ReadFileRemoteOperation(partialFile.remotePath).execute(user, context)
             if (!fetchResult.isSuccess) {
                 logger.e(SHARED_TAG, "Error fetching file")
-                if (fetchResult.isException) {
-                    logger.e(SHARED_TAG, "exception: ", fetchResult.exception)
+                if (fetchResult.isException && fetchResult.exception != null) {
+                    logger.e(SHARED_TAG, "exception: ", fetchResult.exception!!)
                 }
                 null
             } else {

+ 4 - 8
app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -73,7 +73,6 @@ import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.activity.DrawerActivity;
-import com.owncloud.android.ui.activity.FileDisplayActivity;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
 import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
 import com.owncloud.android.ui.fragment.FileFragment;
@@ -101,8 +100,8 @@ import kotlin.jvm.functions.Function0;
 /**
  * This fragment shows a preview of a downloaded media file (audio or video).
  * <p>
- * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link User} values will produce an {@link
- * IllegalStateException}.
+ * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link User} values will produce an
+ * {@link IllegalStateException}.
  * <p>
  * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on
  * instantiation too.
@@ -116,7 +115,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     public static final String EXTRA_USER = "USER";
     public static final String EXTRA_AUTOPLAY = "AUTOPLAY";
     public static final String EXTRA_START_POSITION = "START_POSITION";
-    
+
     private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION";
     private static final String EXTRA_PLAYING = "PLAYING";
     private static final double MIN_DENSITY_RATIO = 24.0;
@@ -365,7 +364,7 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
                     Executors.newSingleThreadExecutor().execute(() -> {
                         try {
                             nextcloudClient = clientFactory.createNextcloudClient(accountManager.getUser());
-                            handler.post(() ->{
+                            handler.post(() -> {
                                 exoPlayer = NextcloudExoPlayer.createNextcloudExoplayer(requireContext(), nextcloudClient);
 
                                 exoPlayer.addListener(new ExoplayerListener(requireContext(), binding.exoplayerView, exoPlayer, () -> {
@@ -619,9 +618,6 @@ public class PreviewMediaFragment extends FileFragment implements OnTouchListene
     @Override
     public void onResume() {
         super.onResume();
-        if(getActivity() instanceof FileDisplayActivity){
-            ((FileDisplayActivity) getActivity()).configureToolbarForMediaPreview(getFile());
-        }
         Log_OC.v(TAG, "onResume");
     }
 

+ 59 - 6
app/src/main/java/com/owncloud/android/utils/EncryptionUtils.java

@@ -47,6 +47,8 @@ import com.owncloud.android.lib.resources.e2ee.StoreMetadataRemoteOperation;
 import com.owncloud.android.lib.resources.e2ee.UnlockFileRemoteOperation;
 import com.owncloud.android.lib.resources.e2ee.UpdateMetadataRemoteOperation;
 import com.owncloud.android.lib.resources.status.NextcloudVersion;
+import com.owncloud.android.lib.resources.status.Problem;
+import com.owncloud.android.lib.resources.status.SendClientDiagnosticRemoteOperation;
 import com.owncloud.android.operations.UploadException;
 
 import org.apache.commons.httpclient.HttpStatus;
@@ -326,10 +328,12 @@ public final class EncryptionUtils {
 
         if (TextUtils.isEmpty(decryptedFolderChecksum) &&
             isFolderMigrated(remoteId, user, arbitraryDataProvider)) {
+            reportE2eError(arbitraryDataProvider, user);
             throw new IllegalStateException("Possible downgrade attack detected!");
         }
 
         if (!TextUtils.isEmpty(decryptedFolderChecksum) && !decryptedFolderChecksum.equals(checksum)) {
+            reportE2eError(arbitraryDataProvider, user);
             throw new IllegalStateException("Wrong checksum!");
         }
 
@@ -349,7 +353,9 @@ public final class EncryptionUtils {
                     encryptedFile.getEncrypted(),
                     decodeStringToBase64Bytes(encryptedKey),
                     decodeStringToBase64Bytes(encryptedFile.getEncryptedInitializationVector()),
-                    decodeStringToBase64Bytes(encryptedFile.getEncryptedTag())
+                    decodeStringToBase64Bytes(encryptedFile.getEncryptedTag()),
+                    arbitraryDataProvider,
+                    user
                                                              );
 
                 DecryptedFolderMetadata.DecryptedFile decryptedFile = new DecryptedFolderMetadata.DecryptedFile();
@@ -430,7 +436,9 @@ public final class EncryptionUtils {
                                                serializedFolderMetadata,
                                                token,
                                                client,
-                                               true);
+                                               true,
+                                               arbitraryDataProvider,
+                                               user);
 
                 // unlock folder
                 RemoteOperationResult unlockFolderResult = EncryptionUtils.unlockFolder(folder, client, token);
@@ -534,7 +542,12 @@ public final class EncryptionUtils {
      * @param authenticationTag  authenticationTag from metadata
      * @return decrypted byte[]
      */
-    public static byte[] decryptFile(File file, byte[] encryptionKeyBytes, byte[] iv, byte[] authenticationTag)
+    public static byte[] decryptFile(File file,
+                                     byte[] encryptionKeyBytes,
+                                     byte[] iv,
+                                     byte[] authenticationTag,
+                                     ArbitraryDataProvider arbitraryDataProvider,
+                                     User user)
         throws NoSuchAlgorithmException,
         InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
         BadPaddingException, IllegalBlockSizeException, IOException {
@@ -554,6 +567,7 @@ public final class EncryptionUtils {
                                                                fileBytes.length - (128 / 8), fileBytes.length);
 
         if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
+            reportE2eError(arbitraryDataProvider, user);
             throw new SecurityException("Tag not correct");
         }
 
@@ -713,7 +727,9 @@ public final class EncryptionUtils {
     public static String decryptStringSymmetric(String string,
                                                 byte[] encryptionKeyBytes,
                                                 byte[] iv,
-                                                byte[] authenticationTag)
+                                                byte[] authenticationTag,
+                                                ArbitraryDataProvider arbitraryDataProvider,
+                                                User user)
         throws NoSuchPaddingException,
         NoSuchAlgorithmException,
         InvalidAlgorithmParameterException,
@@ -733,6 +749,7 @@ public final class EncryptionUtils {
                                                                bytes.length);
 
         if (!Arrays.equals(extractedAuthenticationTag, authenticationTag)) {
+            reportE2eError(arbitraryDataProvider, user);
             throw new SecurityException("Tag not correct");
         }
 
@@ -1057,7 +1074,7 @@ public final class EncryptionUtils {
 
             return new Pair<>(Boolean.FALSE, metadata);
         } else {
-            // TODO error
+            reportE2eError(arbitraryDataProvider, user);
             throw new UploadException("something wrong");
         }
     }
@@ -1066,7 +1083,9 @@ public final class EncryptionUtils {
                                       String serializedFolderMetadata,
                                       String token,
                                       OwnCloudClient client,
-                                      boolean metadataExists) throws UploadException {
+                                      boolean metadataExists,
+                                      ArbitraryDataProvider arbitraryDataProvider,
+                                      User user) throws UploadException {
         RemoteOperationResult uploadMetadataOperationResult;
         if (metadataExists) {
             // update metadata
@@ -1081,6 +1100,7 @@ public final class EncryptionUtils {
         }
 
         if (!uploadMetadataOperationResult.isSuccess()) {
+            reportE2eError(arbitraryDataProvider, user);
             throw new UploadException("Storing/updating metadata was not successful");
         }
     }
@@ -1207,4 +1227,37 @@ public final class EncryptionUtils {
 
         return arrayList.contains(id);
     }
+
+    public static void reportE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
+        arbitraryDataProvider.incrementValue(user.getAccountName(), ArbitraryDataProvider.E2E_ERRORS);
+
+        if (arbitraryDataProvider.getLongValue(user.getAccountName(),
+                                               ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP) == -1L) {
+            arbitraryDataProvider.storeOrUpdateKeyValue(
+                user.getAccountName(),
+                ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP,
+                System.currentTimeMillis() / 1000
+                                                       );
+        }
+    }
+
+    @Nullable
+    public static Problem readE2eError(ArbitraryDataProvider arbitraryDataProvider, User user) {
+        int value = arbitraryDataProvider.getIntegerValue(user.getAccountName(),
+                                                          ArbitraryDataProvider.E2E_ERRORS);
+        long timestamp = arbitraryDataProvider.getLongValue(user.getAccountName(),
+                                                            ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
+
+        arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
+                                                  ArbitraryDataProvider.E2E_ERRORS);
+
+        arbitraryDataProvider.deleteKeyForAccount(user.getAccountName(),
+                                                  ArbitraryDataProvider.E2E_ERRORS_TIMESTAMP);
+
+        if (value > 0 && timestamp > 0) {
+            return new Problem(SendClientDiagnosticRemoteOperation.E2E_ERRORS, value, timestamp);
+        } else {
+            return null;
+        }
+    }
 }

+ 5 - 3
app/src/main/java/com/owncloud/android/utils/ReceiversHelper.java

@@ -32,7 +32,9 @@ import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.network.WalledCheckCache;
 import com.nextcloud.common.DNSCache;
+import com.nextcloud.utils.extensions.ContextExtensionsKt;
 import com.owncloud.android.MainApp;
+import com.owncloud.android.datamodel.ReceiverFlag;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 
 /**
@@ -69,7 +71,7 @@ public final class ReceiversHelper {
             }
         };
 
-        context.registerReceiver(broadcastReceiver, intentFilter);
+        ContextExtensionsKt.registerBroadcastReceiver(context, broadcastReceiver, intentFilter, ReceiverFlag.NotExported);
     }
 
     public static void registerPowerChangeReceiver(
@@ -96,7 +98,7 @@ public final class ReceiversHelper {
             }
         };
 
-        context.registerReceiver(broadcastReceiver, intentFilter);
+        ContextExtensionsKt.registerBroadcastReceiver(context, broadcastReceiver, intentFilter, ReceiverFlag.NotExported);
     }
 
     public static void registerPowerSaveReceiver(
@@ -122,6 +124,6 @@ public final class ReceiversHelper {
             }
         };
 
-        context.registerReceiver(broadcastReceiver, intentFilter);
+        ContextExtensionsKt.registerBroadcastReceiver(context, broadcastReceiver, intentFilter, ReceiverFlag.NotExported);
     }
 }

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

@@ -344,6 +344,7 @@
     <string name="file_list_empty_shared_headline">Још се ништа не дели</string>
     <string name="file_list_empty_unified_search_no_results">Ваш упит није вратио ниједан резултат</string>
     <string name="file_list_folder">фасцикла</string>
+    <string name="file_list_live">УЖИВО</string>
     <string name="file_list_loading">Учитавам…</string>
     <string name="file_list_no_app_for_file_type">Немате апликацију за овај тип фајла.</string>
     <string name="file_list_seconds_ago">пре пар секунди</string>

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

@@ -913,6 +913,9 @@
     <string name="failed_to_start_editor">Failed to start editor</string>
     <string name="create_rich_workspace">Add folder info</string>
     <string name="creates_rich_workspace">creates folder info</string>
+    <string name="uploader_local_files_uploaded">Try to upload local files again</string>
+    <string name="uploader_file_not_found_on_server_message">We couldnt locate the file on server. Another user may have deleted the file</string>
+    <string name="uploader_file_not_found_message">File not found. Are you sure this file exist or conflict not solved before?</string>
     <string name="uploader_upload_failed_sync_conflict_error">File upload conflict</string>
     <string name="uploader_upload_failed_sync_conflict_error_content">Pick which version to keep of %1$s</string>
     <string name="upload_list_resolve_conflict">Resolve conflict</string>

+ 8 - 2
app/src/test/java/com/nextcloud/client/device/TestPowerManagementService.kt

@@ -21,6 +21,7 @@
 
 package com.nextcloud.client.device
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.content.Intent
 import android.os.BatteryManager
@@ -126,6 +127,7 @@ class TestPowerManagementService {
         }
     }
 
+    @SuppressLint("UnspecifiedRegisterReceiverFlag")
     class Battery : Base() {
 
         companion object {
@@ -137,7 +139,9 @@ class TestPowerManagementService {
 
         @Before
         fun setUp() {
-            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(intent)
+            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(
+                intent
+            )
         }
 
         @Test
@@ -193,7 +197,9 @@ class TestPowerManagementService {
             //      device has API level P or below
             //      battery status sticky intent is NOT available
             whenever(deviceInfo.apiLevel).thenReturn(Build.VERSION_CODES.P)
-            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(null)
+            whenever(context.registerReceiver(anyOrNull(), anyOrNull())).thenReturn(
+                null
+            )
 
             // THEN
             //     charging flag is false

+ 1 - 1
build.gradle

@@ -14,7 +14,7 @@ buildscript {
         mockkVersion = "1.13.3"
         espressoVersion = "3.5.1"
         workRuntime = "2.8.1"
-        fidoVersion = "4.1.0-patch1"
+        fidoVersion = "4.1.0-patch2"
         checkerVersion = "3.21.2"
         exoplayerVersion = "2.19.1"
         documentScannerVersion = "1.1.1"

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

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

Some files were not shown because too many files changed in this diff