From 737ce209277958bcf4f752459ee7e450182e2205 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 23 Mar 2023 10:05:20 +0100 Subject: [PATCH 1/2] Bump target and compile SDK to 33 Signed-off-by: Andy Scherzinger --- app/build.gradle | 4 ++-- .../com/nextcloud/talk/messagesearch/MessageSearchActivity.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 505982c5c..0c32af4c5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,14 +36,14 @@ apply plugin: "org.jlleitschuh.gradle.ktlint" apply plugin: 'kotlinx-serialization' android { - compileSdkVersion 32 + compileSdkVersion 33 buildToolsVersion '33.0.2' namespace 'com.nextcloud.talk' defaultConfig { minSdkVersion 24 - targetSdkVersion 31 + targetSdkVersion 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" // mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable) diff --git a/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt b/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt index ddf794841..e54baa6aa 100644 --- a/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/messagesearch/MessageSearchActivity.kt @@ -220,12 +220,12 @@ class MessageSearchActivity : BaseActivity() { searchView = menuItem.actionView as SearchView setupSearchView() menuItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { - override fun onMenuItemActionExpand(item: MenuItem?): Boolean { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { searchView.requestFocus() return true } - override fun onMenuItemActionCollapse(item: MenuItem?): Boolean { + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { onBackPressed() return false } From 3d4edb6c9c6f6b6d67c938970e806ff8d6b247e7 Mon Sep 17 00:00:00 2001 From: Andy Scherzinger Date: Thu, 23 Mar 2023 15:17:22 +0100 Subject: [PATCH 2/2] Add SDK=33 permission checks for file access Signed-off-by: Andy Scherzinger --- app/src/main/AndroidManifest.xml | 7 ++- .../talk/controllers/ChatController.kt | 51 ++++++++++++++++-- .../ConversationsListController.kt | 7 ++- .../talk/jobs/UploadAndShareFilesWorker.kt | 49 ++++++----------- .../permissions/PlatformPermissionUtil.kt | 2 + .../permissions/PlatformPermissionUtilImpl.kt | 53 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 130 insertions(+), 40 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 279b00951..c9cbe6f53 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -71,7 +71,12 @@ android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" tools:ignore="ScopedStorage" /> - + + + + + diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index 711be18ea..48a693b6b 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -801,7 +801,7 @@ class ChatController(args: Bundle) : requestRecordAudioPermissions() return true } - if (!UploadAndShareFilesWorker.isStoragePermissionGranted(context)) { + if (!permissionUtil.isFilesPermissionGranted()) { UploadAndShareFilesWorker.requestStoragePermission(this@ChatController) return true } @@ -1312,6 +1312,26 @@ class ChatController(args: Bundle) : ) } + private fun requestReadFilesPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestPermissions( + arrayOf( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_AUDIO + ), + REQUEST_SHARE_FILE_PERMISSION + ) + } else { + requestPermissions( + arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE + ), + REQUEST_SHARE_FILE_PERMISSION + ) + } + } + private fun checkShowCallButtons() { if (isAlive()) { if (isReadOnlyConversation() || shouldShowLobby()) { @@ -1490,7 +1510,7 @@ class ChatController(args: Bundle) : .setTitle(confirmationQuestion) .setMessage(filenamesWithLineBreaks.toString()) .setPositiveButton(R.string.nc_yes) { _, _ -> - if (UploadAndShareFilesWorker.isStoragePermissionGranted(context)) { + if (permissionUtil.isFilesPermissionGranted()) { uploadFiles(filesToUpload) } else { UploadAndShareFilesWorker.requestStoragePermission(this) @@ -1557,7 +1577,7 @@ class ChatController(args: Bundle) : throw IllegalStateException("Failed to get data from intent and uri") } - if (UploadAndShareFilesWorker.isStoragePermissionGranted(context)) { + if (permissionUtil.isFilesPermissionGranted()) { uploadFiles(filesToUpload) } else { UploadAndShareFilesWorker.requestStoragePermission(this) @@ -1618,6 +1638,10 @@ class ChatController(args: Bundle) : } } + private fun hasGrantedPermissions(grantResults: IntArray): Boolean { + return permissionUtil.isFilesPermissionGranted() + } + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { @@ -1630,6 +1654,16 @@ class ChatController(args: Bundle) : .makeText(context, context.getString(R.string.read_storage_no_permission), Toast.LENGTH_LONG) .show() } + } else if (requestCode == REQUEST_SHARE_FILE_PERMISSION) { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + showLocalFilePicker() + } else { + Toast.makeText( + context, + context.getString(R.string.nc_file_storage_permission), + Toast.LENGTH_LONG + ).show() + } } else if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // do nothing. user will tap on the microphone again if he wants to record audio.. @@ -1696,7 +1730,7 @@ class ChatController(args: Bundle) : } } - fun sendSelectLocalFileIntent() { + private fun showLocalFilePicker() { val action = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { type = "*/*" addCategory(Intent.CATEGORY_OPENABLE) @@ -1713,6 +1747,14 @@ class ChatController(args: Bundle) : ) } + fun sendSelectLocalFileIntent() { + if (!permissionUtil.isFilesPermissionGranted()) { + requestReadFilesPermissions() + } else { + showLocalFilePicker() + } + } + fun sendChooseContactIntent() { requestReadContacts() } @@ -3490,6 +3532,7 @@ class ChatController(args: Bundle) : private const val REQUEST_CODE_CHOOSE_FILE: Int = 555 private const val REQUEST_CODE_SELECT_CONTACT: Int = 666 private const val REQUEST_CODE_MESSAGE_SEARCH: Int = 777 + private const val REQUEST_SHARE_FILE_PERMISSION: Int = 221 private const val REQUEST_RECORD_AUDIO_PERMISSION = 222 private const val REQUEST_READ_CONTACT_PERMISSION = 234 private const val REQUEST_CAMERA_PERMISSION = 223 diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt index f675124b3..ed538dab8 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ConversationsListController.kt @@ -87,7 +87,6 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.ContactAddressBookWorker.Companion.run import com.nextcloud.talk.jobs.DeleteConversationWorker import com.nextcloud.talk.jobs.UploadAndShareFilesWorker -import com.nextcloud.talk.jobs.UploadAndShareFilesWorker.Companion.isStoragePermissionGranted import com.nextcloud.talk.jobs.UploadAndShareFilesWorker.Companion.requestStoragePermission import com.nextcloud.talk.messagesearch.MessageSearchHelper import com.nextcloud.talk.messagesearch.MessageSearchHelper.MessageSearchResults @@ -119,6 +118,7 @@ import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.hasSpreedFeatu import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isServerEOL import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUnifiedSearchAvailable import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew.isUserStatusAvailable +import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.remapchat.ConductorRemapping.remapChatController import com.nextcloud.talk.utils.rx.SearchViewObservable.Companion.observeSearchView import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder @@ -159,6 +159,9 @@ class ConversationsListController(bundle: Bundle) : @Inject lateinit var unifiedSearchRepository: UnifiedSearchRepository + @Inject + lateinit var platformPermissionUtil: PlatformPermissionUtil + private val binding: ControllerConversationsRvBinding? by viewBinding(ControllerConversationsRvBinding::bind) override val title: String @@ -960,7 +963,7 @@ class ConversationsListController(bundle: Bundle) : } private fun showSendFilesConfirmDialog() { - if (isStoragePermissionGranted(context)) { + if (platformPermissionUtil.isFilesPermissionGranted()) { val fileNamesWithLineBreaks = StringBuilder("\n") for (file in filesToShare!!) { val filename = FileUtils.getFileName(Uri.parse(file), context) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt index bf157467b..4f106c609 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt +++ b/app/src/main/java/com/nextcloud/talk/jobs/UploadAndShareFilesWorker.kt @@ -32,7 +32,6 @@ import android.os.Bundle import android.os.SystemClock import android.util.Log import androidx.core.app.NotificationCompat -import androidx.core.content.PermissionChecker import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest @@ -57,6 +56,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FROM_NOTIFICATION_START_CA import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USER_ENTITY import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew +import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil import com.nextcloud.talk.utils.preferences.AppPreferences import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient @@ -78,6 +78,9 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa @Inject lateinit var okHttpClient: OkHttpClient + @Inject + lateinit var platformPermissionUtil: PlatformPermissionUtil + lateinit var fileName: String private var mNotifyManager: NotificationManager? = null @@ -93,7 +96,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa override fun doWork(): Result { NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - if (!isStoragePermissionGranted(context)) { + if (!platformPermissionUtil.isFilesPermissionGranted()) { Log.w( TAG, "Storage permission is not granted. As a developer please make sure you check for" + @@ -285,39 +288,19 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa private const val ZERO_PERCENT = 0 const val REQUEST_PERMISSION = 3123 - fun isStoragePermissionGranted(context: Context): Boolean { - return when { - Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> { - if (PermissionChecker.checkSelfPermission( - context, - Manifest.permission.READ_EXTERNAL_STORAGE - ) == PermissionChecker.PERMISSION_GRANTED - ) { - Log.d(TAG, "Permission is granted (SDK 30 or greater)") - true - } else { - Log.d(TAG, "Permission is revoked (SDK 30 or greater)") - false - } - } - else -> { - if (PermissionChecker.checkSelfPermission( - context, - Manifest.permission.WRITE_EXTERNAL_STORAGE - ) == PermissionChecker.PERMISSION_GRANTED - ) { - Log.d(TAG, "Permission is granted") - true - } else { - Log.d(TAG, "Permission is revoked") - false - } - } - } - } - fun requestStoragePermission(controller: Controller) { when { + Build.VERSION + .SDK_INT >= Build.VERSION_CODES.TIRAMISU -> { + controller.requestPermissions( + arrayOf( + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_AUDIO + ), + REQUEST_PERMISSION + ) + } Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> { controller.requestPermissions( arrayOf( diff --git a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt index aa7ddc4f6..0acc73f19 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtil.kt @@ -24,4 +24,6 @@ package com.nextcloud.talk.utils.permissions interface PlatformPermissionUtil { val privateBroadcastPermission: String fun isCameraPermissionGranted(): Boolean + + fun isFilesPermissionGranted(): Boolean } diff --git a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt index 27f4a6f71..3a358f3dc 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/permissions/PlatformPermissionUtilImpl.kt @@ -23,6 +23,8 @@ package com.nextcloud.talk.utils.permissions import android.Manifest import android.content.Context +import android.os.Build +import android.util.Log import androidx.core.content.PermissionChecker import com.nextcloud.talk.BuildConfig @@ -36,4 +38,55 @@ class PlatformPermissionUtilImpl(private val context: Context) : PlatformPermiss Manifest.permission.CAMERA ) == PermissionChecker.PERMISSION_GRANTED } + + override fun isFilesPermissionGranted(): Boolean { + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> { + if ( + PermissionChecker.checkSelfPermission(context, Manifest.permission.READ_MEDIA_IMAGES) + == PermissionChecker.PERMISSION_GRANTED || + PermissionChecker.checkSelfPermission(context, Manifest.permission.READ_MEDIA_VIDEO) + == PermissionChecker.PERMISSION_GRANTED || + PermissionChecker.checkSelfPermission(context, Manifest.permission.READ_MEDIA_AUDIO) + == PermissionChecker.PERMISSION_GRANTED + ) { + Log.d(TAG, "Permission is granted (SDK 33 or greater)") + true + } else { + Log.d(TAG, "Permission is revoked (SDK 33 or greater)") + false + } + } + Build.VERSION.SDK_INT > Build.VERSION_CODES.Q -> { + if (PermissionChecker.checkSelfPermission( + context, + Manifest.permission.READ_EXTERNAL_STORAGE + ) == PermissionChecker.PERMISSION_GRANTED + ) { + Log.d(TAG, "Permission is granted (SDK 30 or greater)") + true + } else { + Log.d(TAG, "Permission is revoked (SDK 30 or greater)") + false + } + } + else -> { + if (PermissionChecker.checkSelfPermission( + context, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) == PermissionChecker.PERMISSION_GRANTED + ) { + Log.d(TAG, "Permission is granted") + true + } else { + Log.d(TAG, "Permission is revoked") + false + } + } + } + } + + companion object { + private val TAG = PlatformPermissionUtilImpl::class.simpleName + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 05b0bb30f..6e39f6c44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -464,6 +464,7 @@ How to translate with transifex: %1$s to %2$s - %3$s\%% Failure Failed to upload %1$s + Permission for file access is required Video recording from %1$s