diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b878eca049..bb89080a32 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,7 +32,7 @@ jobs: with: swap-size-gb: 10 - name: Initialize CodeQL - uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/init@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # v3.24.1 with: languages: ${{ matrix.language }} - name: Set up JDK 17 @@ -46,4 +46,4 @@ jobs: echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties" ./gradlew assembleDebug - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/analyze@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # v3.24.1 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index dd9432112b..d38175bd34 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -37,6 +37,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 + uses: github/codeql-action/upload-sarif@e675ced7a7522a761fc9c8eb26682c8b27c42b2b # v3.24.1 with: sarif_file: results.sarif diff --git a/app/build.gradle b/app/build.gradle index cdd556d452..7a751e05b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -291,7 +291,7 @@ dependencies { qaImplementation project(':appscan') spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0' - spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.4' + spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.0' implementation "com.google.dagger:dagger:$daggerVersion" implementation "com.google.dagger:dagger-android:$daggerVersion" diff --git a/app/detekt.yml b/app/detekt.yml index 34dae685e8..5e9aad61c9 100644 --- a/app/detekt.yml +++ b/app/detekt.yml @@ -59,7 +59,7 @@ complexity: excludes: ['**/androidTest/**'] LabeledExpression: active: false - ignoredLabels: "" + ignoredLabels: [] LargeClass: active: true threshold: 600 @@ -132,7 +132,7 @@ exceptions: active: true ExceptionRaisedInUnexpectedLocation: active: false - methodNames: 'toString,hashCode,equals,finalize' + methodNames: [toString,hashCode,equals,finalize] InstanceOfCheckForException: active: false NotImplementedDeclaration: @@ -145,14 +145,14 @@ exceptions: active: false SwallowedException: active: false - ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' + ignoredExceptionTypes: [InterruptedException,NumberFormatException,ParseException,MalformedURLException] ThrowingExceptionFromFinally: active: false ThrowingExceptionInMain: active: false ThrowingExceptionsWithoutMessageOrCause: active: false - exceptions: 'IllegalArgumentException,IllegalStateException,IOException' + exceptions: [IllegalArgumentException,IllegalStateException,IOException] ThrowingNewInstanceOfSameException: active: false TooGenericExceptionCaught: @@ -190,7 +190,7 @@ naming: enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' ForbiddenClassName: active: false - forbiddenName: '' + forbiddenName: [] FunctionMaxLength: active: false maximumFunctionNameLength: 30 @@ -291,7 +291,7 @@ style: active: false DataClassContainsFunctions: active: false - conversionFunctionPrefix: 'to' + conversionFunctionPrefix: [to] EqualsNullCall: active: false EqualsOnSignatureLine: @@ -306,19 +306,19 @@ style: values: 'TODO:,FIXME:,STOPSHIP:' ForbiddenImport: active: false - imports: '' + imports: [] ForbiddenVoid: active: false FunctionOnlyReturningConstant: active: false ignoreOverridableFunction: true - excludedFunctions: 'describeContents' + excludedFunctions: [describeContents] LoopWithTooManyJumpStatements: active: false maxJumpCount: 1 MagicNumber: active: true - ignoreNumbers: '-1,0,1,2' + ignoreNumbers: ["-1","0","1","2"] ignoreHashCodeFunction: true ignorePropertyDeclaration: false ignoreConstantDeclaration: true @@ -362,7 +362,7 @@ style: ReturnCount: active: true max: 2 - excludedFunctions: "equals" + excludedFunctions: [equals] excludeLabeled: false excludeReturnFromLambda: true SafeCast: @@ -409,4 +409,4 @@ style: active: false WildcardImport: active: true - excludeImports: 'java.util.*,kotlinx.android.synthetic.*' + excludeImports: [java.util.*,kotlinx.android.synthetic.*] diff --git a/app/schemas/com.nextcloud.client.database.NextcloudDatabase/78.json b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/78.json new file mode 100644 index 0000000000..21af9f58d8 --- /dev/null +++ b/app/schemas/com.nextcloud.client.database.NextcloudDatabase/78.json @@ -0,0 +1,1197 @@ +{ + "formatVersion": 1, + "database": { + "version": 78, + "identityHash": "f26afed3b9b87a3acb578947a26223ac", + "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, `end_to_end_encryption_api_version` TEXT, `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": "endToEndEncryptionApiVersion", + "columnName": "end_to_end_encryption_api_version", + "affinity": "TEXT", + "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, `e2e_counter` INTEGER)", + "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 + }, + { + "fieldPath": "e2eCounter", + "columnName": "e2e_counter", + "affinity": "INTEGER", + "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, `exclude_hidden` 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 + }, + { + "fieldPath": "excludeHidden", + "columnName": "exclude_hidden", + "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, 'f26afed3b9b87a3acb578947a26223ac')" + ] + } +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/nextcloud/client/SyncedFoldersActivityIT.java b/app/src/androidTest/java/com/nextcloud/client/SyncedFoldersActivityIT.java index b28c89db95..6ebeaaa054 100644 --- a/app/src/androidTest/java/com/nextcloud/client/SyncedFoldersActivityIT.java +++ b/app/src/androidTest/java/com/nextcloud/client/SyncedFoldersActivityIT.java @@ -74,7 +74,8 @@ public class SyncedFoldersActivityIT extends AbstractIT { "Name", MediaFolderType.IMAGE, false, - SubFolderRule.YEAR_MONTH); + SubFolderRule.YEAR_MONTH, + false); SyncedFolderPreferencesDialogFragment sut = SyncedFolderPreferencesDialogFragment.newInstance(item, 0); Intent intent = new Intent(targetContext, SyncedFoldersActivity.class); diff --git a/app/src/androidTest/java/com/owncloud/android/utils/SyncedFolderUtilsTest.kt b/app/src/androidTest/java/com/owncloud/android/utils/SyncedFolderUtilsTest.kt index ff4ec14b05..9cd65727ad 100644 --- a/app/src/androidTest/java/com/owncloud/android/utils/SyncedFolderUtilsTest.kt +++ b/app/src/androidTest/java/com/owncloud/android/utils/SyncedFolderUtilsTest.kt @@ -187,7 +187,8 @@ class SyncedFolderUtilsTest : AbstractIT() { 0L, MediaFolderType.IMAGE, false, - SubFolderRule.YEAR_MONTH + SubFolderRule.YEAR_MONTH, + false ) Assert.assertFalse(SyncedFolderUtils.isQualifyingMediaFolder(folder)) } @@ -210,7 +211,8 @@ class SyncedFolderUtilsTest : AbstractIT() { 0L, MediaFolderType.IMAGE, false, - SubFolderRule.YEAR_MONTH + SubFolderRule.YEAR_MONTH, + false ) Assert.assertFalse(SyncedFolderUtils.isQualifyingMediaFolder(folder)) } diff --git a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt index 5f3e16a036..57560286dd 100644 --- a/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt +++ b/app/src/main/java/com/nextcloud/client/database/NextcloudDatabase.kt @@ -70,7 +70,8 @@ import com.owncloud.android.db.ProviderMeta AutoMigration(from = 73, to = 74, spec = DatabaseMigrationUtil.ResetCapabilitiesPostMigration::class), AutoMigration(from = 74, to = 75), AutoMigration(from = 75, to = 76), - AutoMigration(from = 76, to = 77) + AutoMigration(from = 76, to = 77), + AutoMigration(from = 77, to = 78) ], exportSchema = true ) diff --git a/app/src/main/java/com/nextcloud/client/database/entity/SyncedFolderEntity.kt b/app/src/main/java/com/nextcloud/client/database/entity/SyncedFolderEntity.kt index 5934bae96c..811cb0423d 100644 --- a/app/src/main/java/com/nextcloud/client/database/entity/SyncedFolderEntity.kt +++ b/app/src/main/java/com/nextcloud/client/database/entity/SyncedFolderEntity.kt @@ -59,5 +59,7 @@ data class SyncedFolderEntity( @ColumnInfo(name = ProviderTableMeta.SYNCED_FOLDER_HIDDEN) val hidden: Int?, @ColumnInfo(name = ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE) - val subFolderRule: Int? + val subFolderRule: Int?, + @ColumnInfo(name = ProviderTableMeta.SYNCED_EXCLUDE_HIDDEN) + val excludeHidden: Int? ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt index e445bf2231..03ece02158 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -251,6 +251,7 @@ class BackgroundJobFactory @Inject constructor( viewThemeUtils.get(), localBroadcastManager.get(), backgroundJobManager.get(), + preferences, context, params ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 8c71a8b667..99c86cbe36 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -160,7 +160,7 @@ class FileUploadHelper { } } - private fun cancelAndRestartUploadJob(user: User) { + fun cancelAndRestartUploadJob(user: User) { backgroundJobManager.run { cancelFilesUploadJob(user) startFilesUploadJob(user) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index b00d3a3735..91b2586ef8 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -32,6 +32,7 @@ import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.jobs.BackgroundJobManagerImpl import com.nextcloud.client.network.ConnectivityService +import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.model.WorkerState import com.nextcloud.model.WorkerStateLiveData import com.owncloud.android.datamodel.FileDataStorageManager @@ -58,6 +59,7 @@ class FileUploadWorker( val viewThemeUtils: ViewThemeUtils, val localBroadcastManager: LocalBroadcastManager, private val backgroundJobManager: BackgroundJobManager, + val preferences: AppPreferences, val context: Context, params: WorkerParameters ) : Worker(context, params), OnDatatransferProgressListener { @@ -136,11 +138,20 @@ class FileUploadWorker( WorkerStateLiveData.instance().setWorkState(WorkerState.Idle) } + @Suppress("ReturnCount") private fun retrievePagesBySortingUploadsByID(): Result { val accountName = inputData.getString(ACCOUNT) ?: return Result.failure() var currentPage = uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(-1, accountName) while (currentPage.isNotEmpty() && !isStopped) { + if (preferences.isGlobalUploadPaused) { + Log_OC.d(TAG, "Upload is paused, skip uploading files!") + notificationManager.notifyPaused( + intents.notificationStartIntent(null) + ) + return Result.success() + } + Log_OC.d(TAG, "Handling ${currentPage.size} uploads for account $accountName") val lastId = currentPage.last().uploadId uploadFiles(currentPage, accountName) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt index c068d7aa25..cb9e620a40 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt @@ -97,10 +97,10 @@ class FileUploaderIntents(private val context: Context) { ) } - fun notificationStartIntent(operation: UploadFileOperation): PendingIntent { + fun notificationStartIntent(operation: UploadFileOperation?): PendingIntent { val intent = UploadListActivity.createIntent( - operation.file, - operation.user, + operation?.file, + operation?.user, Intent.FLAG_ACTIVITY_CLEAR_TOP, context ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt index 2c3a01f7f9..76bbce3594 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt @@ -35,17 +35,14 @@ import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.notifications.NotificationUtils import com.owncloud.android.utils.theme.ViewThemeUtils -class UploadNotificationManager(private val context: Context, private val viewThemeUtils: ViewThemeUtils) { +class UploadNotificationManager(private val context: Context, viewThemeUtils: ViewThemeUtils) { companion object { private const val ID = 411 } private var notification: Notification? = null - private var notificationBuilder: NotificationCompat.Builder - private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - init { - notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply { + private var notificationBuilder: NotificationCompat.Builder = + NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply { setContentTitle(context.getString(R.string.foreground_service_upload)) setSmallIcon(R.drawable.notification_icon) setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon)) @@ -54,18 +51,16 @@ class UploadNotificationManager(private val context: Context, private val viewTh setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD) } } + private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + init { notification = notificationBuilder.build() } @Suppress("MagicNumber") fun prepareForStart(upload: UploadFileOperation, pendingIntent: PendingIntent, startIntent: PendingIntent) { - notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply { - setSmallIcon(R.drawable.notification_icon) - setOngoing(true) - setTicker(context.getString(R.string.foreground_service_upload)) + notificationBuilder.run { setContentTitle(context.getString(R.string.uploader_upload_in_progress_ticker)) - setProgress(100, 0, false) setContentText( String.format( context.getString(R.string.uploader_upload_in_progress), @@ -73,6 +68,9 @@ class UploadNotificationManager(private val context: Context, private val viewTh upload.fileName ) ) + setTicker(context.getString(R.string.foreground_service_upload)) + setProgress(100, 0, false) + setOngoing(true) clearActions() addAction( @@ -81,10 +79,6 @@ class UploadNotificationManager(private val context: Context, private val viewTh pendingIntent ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD) - } - setContentIntent(startIntent) } @@ -192,4 +186,18 @@ class UploadNotificationManager(private val context: Context, private val viewTh fun dismissWorkerNotifications() { notificationManager.cancel(ID) } + + fun notifyPaused(intent: PendingIntent) { + notificationBuilder.apply { + setContentTitle(context.getString(R.string.upload_global_pause_title)) + setTicker(context.getString(R.string.upload_global_pause_title)) + setOngoing(true) + setAutoCancel(false) + setProgress(0, 0, false) + clearActions() + setContentIntent(intent) + } + + showNotification() + } } diff --git a/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java index bb93d3fa5e..febd96c621 100644 --- a/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/client/preferences/AppPreferences.java @@ -387,6 +387,10 @@ public interface AppPreferences { void setCalendarLastBackup(long timestamp); + boolean isGlobalUploadPaused(); + + void setGlobalUploadPaused(boolean globalPausedState); + void setPdfZoomTipShownCount(int count); int getPdfZoomTipShownCount(); diff --git a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java index 4643f18dbc..ac69794aaf 100644 --- a/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java +++ b/app/src/main/java/com/nextcloud/client/preferences/AppPreferencesImpl.java @@ -107,6 +107,8 @@ public final class AppPreferencesImpl implements AppPreferences { private static final String PREF__CALENDAR_AUTOMATIC_BACKUP = "calendar_automatic_backup"; private static final String PREF__CALENDAR_LAST_BACKUP = "calendar_last_backup"; + private static final String PREF__GLOBAL_PAUSE_STATE = "global_pause_state"; + private static final String PREF__PDF_ZOOM_TIP_SHOWN = "pdf_zoom_tip_shown"; private static final String PREF__MEDIA_FOLDER_LAST_PATH = "media_folder_last_path"; @@ -741,6 +743,16 @@ public final class AppPreferencesImpl implements AppPreferences { preferences.edit().putLong(PREF__CALENDAR_LAST_BACKUP, timestamp).apply(); } + @Override + public boolean isGlobalUploadPaused() { + return preferences.getBoolean(PREF__GLOBAL_PAUSE_STATE,false); + } + + @Override + public void setGlobalUploadPaused(boolean globalPausedState) { + preferences.edit().putBoolean(PREF__GLOBAL_PAUSE_STATE, globalPausedState).apply(); + } + @Override public void setPdfZoomTipShownCount(int count) { preferences.edit().putInt(PREF__PDF_ZOOM_TIP_SHOWN, count).apply(); diff --git a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java index 4d86f435c8..17655bdc80 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java +++ b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolder.java @@ -51,6 +51,7 @@ public class SyncedFolder implements Serializable, Cloneable { private MediaFolderType type; private boolean hidden; private SubFolderRule subfolderRule; + private boolean excludeHidden; /** * constructor for new, to be persisted entity. @@ -68,6 +69,8 @@ public class SyncedFolder implements Serializable, Cloneable { * @param timestampMs the current timestamp in milliseconds * @param type the type of the folder * @param hidden hide item flag + * @param subFolderRule whether to filter subFolder by year/month/day + * @param excludeHidden exclude hidden file or folder, for {@link MediaFolderType#CUSTOM} only */ public SyncedFolder(String localPath, String remotePath, @@ -82,7 +85,8 @@ public class SyncedFolder implements Serializable, Cloneable { long timestampMs, MediaFolderType type, boolean hidden, - SubFolderRule subFolderRule) { + SubFolderRule subFolderRule, + boolean excludeHidden) { this(UNPERSISTED_ID, localPath, remotePath, @@ -97,7 +101,8 @@ public class SyncedFolder implements Serializable, Cloneable { timestampMs, type, hidden, - subFolderRule); + subFolderRule, + excludeHidden); } /** @@ -119,7 +124,8 @@ public class SyncedFolder implements Serializable, Cloneable { long timestampMs, MediaFolderType type, boolean hidden, - SubFolderRule subFolderRule) { + SubFolderRule subFolderRule, + boolean excludeHidden) { this.id = id; this.localPath = localPath; this.remotePath = remotePath; @@ -134,6 +140,7 @@ public class SyncedFolder implements Serializable, Cloneable { this.type = type; this.hidden = hidden; this.subfolderRule = subFolderRule; + this.excludeHidden = excludeHidden; } /** @@ -263,4 +270,12 @@ public class SyncedFolder implements Serializable, Cloneable { } public void setSubFolderRule(SubFolderRule subFolderRule) { this.subfolderRule = subFolderRule; } + + public boolean isExcludeHidden() { + return excludeHidden; + } + + public void setExcludeHidden(boolean excludeHidden) { + this.excludeHidden = excludeHidden; + } } diff --git a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java index c8ec28197e..fe716270fe 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java +++ b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderDisplayItem.java @@ -54,6 +54,7 @@ public class SyncedFolderDisplayItem extends SyncedFolder { * @param type the type of the folder * @param hidden hide item flag * @param subFolderRule whether to filter subFolder by year/month/day + * @param excludeHidden exclude hidden file or folder, for {@link MediaFolderType#CUSTOM} only */ public SyncedFolderDisplayItem(long id, String localPath, @@ -72,7 +73,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder { long numberOfFiles, MediaFolderType type, boolean hidden, - SubFolderRule subFolderRule) { + SubFolderRule subFolderRule, + boolean excludeHidden) { super(id, localPath, remotePath, @@ -87,7 +89,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder { timestampMs, type, hidden, - subFolderRule); + subFolderRule, + excludeHidden); this.filePaths = filePaths; this.folderName = folderName; this.numberOfFiles = numberOfFiles; @@ -108,7 +111,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder { String folderName, MediaFolderType type, boolean hidden, - SubFolderRule subFolderRule) { + SubFolderRule subFolderRule, + boolean excludeHidden) { super(id, localPath, remotePath, @@ -123,7 +127,8 @@ public class SyncedFolderDisplayItem extends SyncedFolder { timestampMs, type, hidden, - subFolderRule); + subFolderRule, + excludeHidden); this.folderName = folderName; } diff --git a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java index 3c25f30fc9..370f5de824 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java +++ b/app/src/main/java/com/owncloud/android/datamodel/SyncedFolderProvider.java @@ -372,6 +372,8 @@ public class SyncedFolderProvider extends Observable { ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN)) == 1; SubFolderRule subFolderRule = SubFolderRule.values()[cursor.getInt( cursor.getColumnIndexOrThrow(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE))]; + boolean excludeHidden = cursor.getInt(cursor.getColumnIndexOrThrow( + ProviderMeta.ProviderTableMeta.SYNCED_EXCLUDE_HIDDEN)) == 1; syncedFolder = new SyncedFolder(id, @@ -388,7 +390,8 @@ public class SyncedFolderProvider extends Observable { enabledTimestampMs, type, hidden, - subFolderRule); + subFolderRule, + excludeHidden); } return syncedFolder; } @@ -417,6 +420,7 @@ public class SyncedFolderProvider extends Observable { cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_TYPE, syncedFolder.getType().id); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_HIDDEN, syncedFolder.isHidden()); cv.put(ProviderMeta.ProviderTableMeta.SYNCED_FOLDER_SUBFOLDER_RULE, syncedFolder.getSubfolderRule().ordinal()); + cv.put(ProviderMeta.ProviderTableMeta.SYNCED_EXCLUDE_HIDDEN, syncedFolder.isExcludeHidden()); return cv; } diff --git a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java b/app/src/main/java/com/owncloud/android/db/ProviderMeta.java index 1a4ccc5845..6d2f72438f 100644 --- a/app/src/main/java/com/owncloud/android/db/ProviderMeta.java +++ b/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 = 77; + public static final int DB_VERSION = 78; private ProviderMeta() { // No instance @@ -302,6 +302,7 @@ public class ProviderMeta { public static final String SYNCED_FOLDER_NAME_COLLISION_POLICY = "name_collision_policy"; public static final String SYNCED_FOLDER_HIDDEN = "hidden"; public static final String SYNCED_FOLDER_SUBFOLDER_RULE = "sub_folder_rule"; + public static final String SYNCED_EXCLUDE_HIDDEN = "exclude_hidden"; // Columns of external links table public static final String EXTERNAL_LINKS_ICON_URL = "icon_url"; diff --git a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java index e01059aa2f..d863c2ba58 100644 --- a/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RemoveRemoteEncryptedFileOperation.java @@ -24,8 +24,6 @@ package com.owncloud.android.operations; import android.content.Context; import com.nextcloud.client.account.User; -import com.owncloud.android.datamodel.ArbitraryDataProvider; -import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile; @@ -55,7 +53,6 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation { private final String fileName; private final Context context; private final boolean isFolder; - private final ArbitraryDataProvider arbitraryDataProvider; /** * Constructor @@ -75,8 +72,6 @@ public class RemoveRemoteEncryptedFileOperation extends RemoteOperation { this.context = context; this.parentFolder = parentFolder; this.isFolder = isFolder; - - arbitraryDataProvider = new ArbitraryDataProviderImpl(context); } /** diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 359ea619d2..61ad9629e0 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -1155,6 +1155,10 @@ public abstract class DrawerActivity extends ToolbarActivity return true; } + public AppPreferences getAppPreferences(){ + return preferences; + } + @Override protected void onStart() { super.onStart(); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt index e4f3242cee..55cf725a00 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt @@ -403,7 +403,8 @@ class SyncedFoldersActivity : files.size.toLong(), syncedFolder.type, syncedFolder.isHidden, - syncedFolder.subfolderRule + syncedFolder.subfolderRule, + syncedFolder.isExcludeHidden ) } @@ -433,7 +434,8 @@ class SyncedFoldersActivity : mediaFolder.numberOfFiles, mediaFolder.type, syncedFolder.isHidden, - syncedFolder.subfolderRule + syncedFolder.subfolderRule, + syncedFolder.isExcludeHidden ) } @@ -462,7 +464,8 @@ class SyncedFoldersActivity : mediaFolder.numberOfFiles, mediaFolder.type, false, - SubFolderRule.YEAR_MONTH + SubFolderRule.YEAR_MONTH, + false ) } @@ -554,7 +557,8 @@ class SyncedFoldersActivity : null, MediaFolderType.CUSTOM, false, - SubFolderRule.YEAR_MONTH + SubFolderRule.YEAR_MONTH, + false ) onSyncFolderSettingsClick(0, emptyCustomFolder) } else { @@ -670,7 +674,8 @@ class SyncedFoldersActivity : File(syncedFolder.localPath).name, syncedFolder.type, syncedFolder.isHidden, - syncedFolder.subFolderRule + syncedFolder.subFolderRule, + syncedFolder.isExcludeHidden ) saveOrUpdateSyncedFolder(newCustomFolder) adapter.addSyncFolderItem(newCustomFolder) @@ -688,7 +693,8 @@ class SyncedFoldersActivity : syncedFolder.uploadAction, syncedFolder.nameCollisionPolicy.serialize(), syncedFolder.isEnabled, - syncedFolder.subFolderRule + syncedFolder.subFolderRule, + syncedFolder.isExcludeHidden ) saveOrUpdateSyncedFolder(item) @@ -759,6 +765,7 @@ class SyncedFoldersActivity : * @param uploadAction upload action * @param nameCollisionPolicy what to do on name collision * @param enabled is sync enabled + * @param excludeHidden exclude hidden file or folder, for {@link MediaFolderType#CUSTOM} only */ @Suppress("LongParameterList") private fun updateSyncedFolderItem( @@ -773,7 +780,8 @@ class SyncedFoldersActivity : uploadAction: Int, nameCollisionPolicy: Int, enabled: Boolean, - subFolderRule: SubFolderRule + subFolderRule: SubFolderRule, + excludeHidden: Boolean ) { item.id = id item.localPath = localPath @@ -786,6 +794,7 @@ class SyncedFoldersActivity : item.setNameCollisionPolicy(nameCollisionPolicy) item.setEnabled(enabled, clock.currentTime) item.setSubFolderRule(subFolderRule) + item.setExcludeHidden(excludeHidden) } override fun onRequestPermissionsResult( diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java index 6bf94c222d..543dee7bb4 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java @@ -24,6 +24,7 @@ package com.owncloud.android.ui.activity; import android.accounts.Account; +import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -49,7 +50,6 @@ import com.owncloud.android.R; import com.owncloud.android.databinding.UploadListLayoutBinding; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.UploadsStorageManager; -import com.owncloud.android.db.OCUpload; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; @@ -65,11 +65,11 @@ import javax.inject.Inject; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** - * Activity listing pending, active, and completed uploads. User can delete - * completed uploads from view. Content of this list of coming from - * {@link UploadsStorageManager}. + * Activity listing pending, active, and completed uploads. User can delete completed uploads from view. Content of this + * list of coming from {@link UploadsStorageManager}. */ public class UploadListActivity extends FileActivity { @@ -210,13 +210,17 @@ public class UploadListActivity extends FileActivity { private void refresh() { backgroundJobManager.startImmediateFilesSyncJob(false, true); - if(uploadsStorageManager.getFailedUploads().length > 0){ - new Thread(() -> FileUploadHelper.Companion.instance().retryFailedUploads( - uploadsStorageManager, - connectivityService, - userAccountManager, - powerManagementService)) - .start(); + if (uploadsStorageManager.getFailedUploads().length > 0) { + new Thread(() -> { + FileUploadHelper.Companion.instance().retryFailedUploads( + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService); + this.runOnUiThread(() -> { + uploadListAdapter.loadUploadItemsFromDb(); + }); + }).start(); DisplayUtils.showSnackMessage(this, R.string.uploader_local_files_uploaded); } @@ -265,13 +269,47 @@ public class UploadListActivity extends FileActivity { public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.activity_upload_list, menu); - + updateGlobalPauseIcon(menu.getItem(0)); return true; } + @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT") + private void updateGlobalPauseIcon(MenuItem pauseMenuItem) { + if (pauseMenuItem.getItemId() != R.id.action_toggle_global_pause) { + return; + } + + int iconId; + String title; + if (preferences.isGlobalUploadPaused()) { + iconId = R.drawable.ic_play; + title = getString(R.string.upload_action_global_upload_resume); + } else { + iconId = R.drawable.ic_pause; + title = getString(R.string.upload_action_global_upload_pause); + } + + pauseMenuItem.setIcon(iconId); + pauseMenuItem.setTitle(title); + } + + @SuppressLint("NotifyDataSetChanged") + private void toggleGlobalPause(MenuItem pauseMenuItem) { + preferences.setGlobalUploadPaused(!preferences.isGlobalUploadPaused()); + updateGlobalPauseIcon(pauseMenuItem); + + for (User user : accountManager.getAllUsers()) { + if (user != null) { + FileUploadHelper.Companion.instance().cancelAndRestartUploadJob(user); + } + } + + uploadListAdapter.notifyDataSetChanged(); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { - boolean retval = true; + int itemId = item.getItemId(); if (itemId == android.R.id.home) { @@ -280,17 +318,13 @@ public class UploadListActivity extends FileActivity { } else { openDrawer(); } - } else if (itemId == R.id.action_clear_failed_uploads) { - for (OCUpload upload : uploadsStorageManager.getFailedButNotDelayedUploadsForCurrentAccount()){ - uploadListAdapter.cancelOldErrorNotification(upload); - } - uploadsStorageManager.clearFailedButNotDelayedUploads(); - uploadListAdapter.loadUploadItemsFromDb(); + } else if (itemId == R.id.action_toggle_global_pause) { + toggleGlobalPause(item); } else { - retval = super.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item); } - return retval; + return true; } @Override diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java index 3a5e26fc97..50168c5b0d 100755 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -29,7 +29,6 @@ import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.format.DateUtils; @@ -123,31 +122,57 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close); - case FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_sync); + case FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical); } headerViewHolder.binding.uploadListAction.setOnClickListener(v -> { switch (group.type) { case CURRENT -> { + // cancel all current uploads for (OCUpload upload : group.getItems()) { uploadHelper.cancelFileUpload(upload.getRemotePath(), upload.getAccountName()); } + loadUploadItemsFromDb(); } - case FINISHED -> uploadsStorageManager.clearSuccessfulUploads(); - case FAILED -> new Thread(() -> FileUploadHelper.Companion.instance().retryFailedUploads( - uploadsStorageManager, - connectivityService, - accountManager, - powerManagementService)).start(); - default -> { + case FINISHED -> { + // clear successfully uploaded section + uploadsStorageManager.clearSuccessfulUploads(); + loadUploadItemsFromDb(); + } + case FAILED -> { + // show popup with option clear or retry filed uploads + createFailedPopupMenu(headerViewHolder); } - // do nothing } - - loadUploadItemsFromDb(); }); } + private void createFailedPopupMenu(HeaderViewHolder headerViewHolder) { + PopupMenu failedPopup = new PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction); + failedPopup.inflate(R.menu.upload_list_failed_options); + failedPopup.setOnMenuItemClickListener(i -> { + int itemId = i.getItemId(); + + if (itemId == R.id.action_upload_list_failed_clear) { + uploadsStorageManager.clearFailedButNotDelayedUploads(); + loadUploadItemsFromDb(); + } else { + + new Thread(() -> { + FileUploadHelper.Companion.instance().retryFailedUploads( + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService); + parentActivity.runOnUiThread(this::loadUploadItemsFromDb); + }).start(); + + } + return true; + }); + failedPopup.show(); + } + @Override public void onBindFooterViewHolder(SectionedViewHolder holder, int section) { // not needed @@ -228,7 +253,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter onUploadedItemClick(item)); } // click on thumbnail to open locally - if (item.getUploadStatus() != UploadStatus.UPLOAD_SUCCEEDED){ + if (item.getUploadStatus() != UploadStatus.UPLOAD_SUCCEEDED) { itemViewHolder.binding.thumbnail.setOnClickListener(v -> onUploadingItemClick(item)); } @@ -395,17 +418,17 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter user = parentActivity.getUser(); if (allowedToCreateNewThumbnail && user.isPresent()) { final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask( - itemViewHolder.binding.thumbnail, - parentActivity.getStorageManager(), - user.get() - ); + new ThumbnailsCacheManager.ThumbnailGenerationTask( + itemViewHolder.binding.thumbnail, + parentActivity.getStorageManager(), + user.get() + ); if (thumbnail == null) { if (MimeTypeUtil.isVideo(fakeFileToCheatThumbnailsCacheManagerInterface)) { thumbnail = ThumbnailsCacheManager.mDefaultVideo; @@ -426,20 +449,20 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter { status = parentActivity.getString(R.string.uploads_view_later_waiting_to_upload); if (uploadHelper.isUploadingNow(upload)) { // really uploading, bind the progress bar to listen for progress updates status = parentActivity.getString(R.string.uploader_upload_in_progress_ticker); } - break; - - case UPLOAD_SUCCEEDED: - if (upload.getLastResult() == UploadResult.SAME_FILE_CONFLICT){ + if (parentActivity.getAppPreferences().isGlobalUploadPaused()) { + status = parentActivity.getString(R.string.upload_global_pause_title); + } + } + case UPLOAD_SUCCEEDED -> { + if (upload.getLastResult() == UploadResult.SAME_FILE_CONFLICT) { status = parentActivity.getString(R.string.uploads_view_upload_status_succeeded_same_file); - }else if (upload.getLastResult() == UploadResult.FILE_NOT_FOUND) { + } else if (upload.getLastResult() == UploadResult.FILE_NOT_FOUND) { status = getUploadFailedStatusText(upload.getLastResult()); } else { status = parentActivity.getString(R.string.uploads_view_upload_status_succeeded); } - break; - - case UPLOAD_FAILED: - status = getUploadFailedStatusText(upload.getLastResult()); - break; - - default: - status = "Uncontrolled status: " + upload.getUploadStatus(); + } + case UPLOAD_FAILED -> status = getUploadFailedStatusText(upload.getLastResult()); + default -> status = "Uncontrolled status: " + upload.getUploadStatus(); } return status; } @@ -690,8 +709,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter