diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt index debf5a346..d2f744f10 100644 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt @@ -464,7 +464,7 @@ class DetailChatRoomFragment : MasterFragment() + val intentsList = ArrayList() - // Handles image & video picking - val galleryIntent = Intent(Intent.ACTION_PICK) - galleryIntent.type = "*/*" - galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*")) - galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + val pickerIntent = Intent(Intent.ACTION_GET_CONTENT) + pickerIntent.type = "*/*" + pickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) if (PermissionHelper.get().hasCameraPermission()) { // Allows to capture directly from the camera @@ -639,25 +637,17 @@ class DetailChatRoomFragment : MasterFragment()) + intentsList.toArray(arrayOf()) ) startActivityForResult(chooserIntent, 0) diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt index e0f48bbf1..e656a86ec 100644 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt @@ -46,7 +46,6 @@ import org.linphone.core.tools.Log import org.linphone.databinding.ContactEditorFragmentBinding import org.linphone.utils.Event import org.linphone.utils.FileUtils -import org.linphone.utils.ImageUtils import org.linphone.utils.PermissionHelper class ContactEditorFragment : GenericFragment(), SyncAccountPickerFragment.SyncAccountPickedListener { @@ -146,7 +145,7 @@ class ContactEditorFragment : GenericFragment(), S override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK) { lifecycleScope.launch { - val contactImageFilePath = ImageUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) + val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) if (contactImageFilePath != null) { viewModel.setPictureFromPath(contactImageFilePath) } diff --git a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt b/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt index 2c1b46ebb..687c6c948 100644 --- a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt +++ b/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt @@ -46,7 +46,6 @@ import org.linphone.core.tools.Log import org.linphone.databinding.SideMenuFragmentBinding import org.linphone.utils.Event import org.linphone.utils.FileUtils -import org.linphone.utils.ImageUtils import org.linphone.utils.PermissionHelper class SideMenuFragment : GenericFragment() { @@ -121,7 +120,7 @@ class SideMenuFragment : GenericFragment() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK) { lifecycleScope.launch { - val contactImageFilePath = ImageUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) + val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) if (contactImageFilePath != null) { viewModel.setPictureFromPath(contactImageFilePath) } diff --git a/app/src/main/java/org/linphone/utils/ContactUtils.kt b/app/src/main/java/org/linphone/utils/ContactUtils.kt new file mode 100644 index 000000000..49a52a4ee --- /dev/null +++ b/app/src/main/java/org/linphone/utils/ContactUtils.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010-2021 Belledonne Communications SARL. + * + * This file is part of linphone-android + * (see https://www.linphone.org). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.linphone.utils + +import android.content.ContentResolver +import android.net.Uri +import android.provider.ContactsContract +import java.io.* +import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.core.tools.Log + +class ContactUtils { + companion object { + fun getContactVcardFilePath(contactUri: Uri): String? { + val contentResolver: ContentResolver = coreContext.context.contentResolver + val lookupUri = ContactsContract.Contacts.getLookupUri(contentResolver, contactUri) + Log.i("[Contact Utils] Contact lookup URI is $lookupUri") + + val contactID = FileUtils.getNameFromFilePath(lookupUri.toString()) + Log.i("[Contact Utils] Contact ID is $contactID") + + val contact = coreContext.contactsManager.findContactById(contactID) + if (contact == null) { + Log.e("[Contact Utils] Failed to find contact with ID $contactID") + return null + } + + val vcard = contact.friend?.vcard?.asVcard4String() + if (vcard == null) { + Log.e("[Contact Utils] Failed to get vCard from contact $contactID") + return null + } + + val contactName = contact.fullName?.replace(" ", "_") ?: contactID + val vcardPath = FileUtils.getFileStoragePath("$contactName.vcf") + val inputStream = ByteArrayInputStream(vcard.toByteArray()) + try { + FileOutputStream(vcardPath).use { out -> + val buffer = ByteArray(4096) + var bytesRead: Int + while (inputStream.read(buffer).also { bytesRead = it } >= 0) { + out.write(buffer, 0, bytesRead) + } + } + } catch (e: IOException) { + Log.e("[Contact Utils] creating vcard file exception: $e") + return null + } + + Log.i("[Contact Utils] Contact vCard path is $vcardPath") + return vcardPath.absolutePath + } + } +} diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 00a512070..dbdfafe68 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -149,6 +149,95 @@ class FileUtils { return file } + suspend fun getFilesPathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): List { + var filePath: String? = null + if (data != null) { + val clipData = data.clipData + if (clipData != null && clipData.itemCount > 1) { // Multiple selection + Log.i("[File Utils] Found ${clipData.itemCount} elements") + val list = arrayListOf() + for (i in 0 until clipData.itemCount) { + val dataUri = clipData.getItemAt(i).uri + if (dataUri != null) { + filePath = dataUri.toString() + Log.i("[File Utils] Using data URI $filePath") + } + filePath = cleanFilePath(filePath) + if (filePath != null) list.add(filePath) + } + return list + } else { // Single selection + val dataUri = if (clipData != null && clipData.itemCount == 1) { + clipData.getItemAt(0).uri + } else { + data.data + } + if (dataUri != null) { + filePath = dataUri.toString() + Log.i("[File Utils] Using data URI $filePath") + } else if (temporaryImageFilePath?.exists() == true) { + filePath = temporaryImageFilePath.absolutePath + Log.i("[File Utils] Data URI is null, using $filePath") + } + filePath = cleanFilePath(filePath) + if (filePath != null) return arrayListOf(filePath) + } + } else if (temporaryImageFilePath?.exists() == true) { + filePath = temporaryImageFilePath.absolutePath + Log.i("[File Utils] Data is null, using $filePath") + filePath = cleanFilePath(filePath) + if (filePath != null) return arrayListOf(filePath) + } + return arrayListOf() + } + + suspend fun getFilePathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): String? { + var filePath: String? = null + if (data != null) { + val clipData = data.clipData + if (clipData != null && clipData.itemCount > 1) { // Multiple selection + Log.e("[File Utils] Expecting only one file, got ${clipData.itemCount}") + } else { // Single selection + val dataUri = if (clipData != null && clipData.itemCount == 1) { + clipData.getItemAt(0).uri + } else { + data.data + } + if (dataUri != null) { + filePath = dataUri.toString() + Log.i("[File Utils] Using data URI $filePath") + } else if (temporaryImageFilePath?.exists() == true) { + filePath = temporaryImageFilePath.absolutePath + Log.i("[File Utils] Data URI is null, using $filePath") + } + } + } else if (temporaryImageFilePath?.exists() == true) { + filePath = temporaryImageFilePath.absolutePath + Log.i("[File Utils] Data is null, using $filePath") + } + return cleanFilePath(filePath) + } + + private suspend fun cleanFilePath(filePath: String?): String? { + if (filePath != null) { + val uriToParse = Uri.parse(filePath) + if (filePath.startsWith("content://com.android.contacts/contacts/lookup/")) { + Log.i("[File Utils] Contact sharing URI detected") + return ContactUtils.getContactVcardFilePath(uriToParse) + } else if (filePath.startsWith("content://") || + filePath.startsWith("file://") + ) { + val result = getFilePath(coreContext.context, uriToParse) + Log.i("[File Utils] Path was using a content or file scheme, real path is: $result") + if (result == null) { + Log.e("[File Utils] Failed to get access to file $uriToParse") + } + return result + } + } + return filePath + } + fun deleteFile(filePath: String) { val file = File(filePath) if (file.exists()) { diff --git a/app/src/main/java/org/linphone/utils/ImageUtils.kt b/app/src/main/java/org/linphone/utils/ImageUtils.kt index 5710fdc23..69351ebe6 100644 --- a/app/src/main/java/org/linphone/utils/ImageUtils.kt +++ b/app/src/main/java/org/linphone/utils/ImageUtils.kt @@ -20,13 +20,10 @@ package org.linphone.utils import android.content.Context -import android.content.Intent import android.graphics.* import android.media.ThumbnailUtils import android.net.Uri import android.provider.MediaStore -import java.io.File -import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.compatibility.Compatibility import org.linphone.core.tools.Log @@ -70,92 +67,6 @@ class ImageUtils { return ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND) } - suspend fun getFilesPathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): List { - var imageFilePath: String? = null - if (data != null) { - val clipData = data.clipData - if (clipData != null && clipData.itemCount > 1) { // Multiple selection - Log.i("[Image Utils] Found ${clipData.itemCount} elements") - val list = arrayListOf() - for (i in 0 until clipData.itemCount) { - val dataUri = clipData.getItemAt(i).uri - if (dataUri != null) { - imageFilePath = dataUri.toString() - Log.i("[Image Utils] Using data URI $imageFilePath") - } - imageFilePath = cleanFilePath(imageFilePath) - if (imageFilePath != null) list.add(imageFilePath) - } - return list - } else { // Single selection - val dataUri = if (clipData != null && clipData.itemCount == 1) { - clipData.getItemAt(0).uri - } else { - data.data - } - if (dataUri != null) { - imageFilePath = dataUri.toString() - Log.i("[Image Utils] Using data URI $imageFilePath") - } else if (temporaryImageFilePath?.exists() == true) { - imageFilePath = temporaryImageFilePath.absolutePath - Log.i("[Image Utils] Data URI is null, using $imageFilePath") - } - imageFilePath = cleanFilePath(imageFilePath) - if (imageFilePath != null) return arrayListOf(imageFilePath) - } - } else if (temporaryImageFilePath?.exists() == true) { - imageFilePath = temporaryImageFilePath.absolutePath - Log.i("[Image Utils] Data is null, using $imageFilePath") - imageFilePath = cleanFilePath(imageFilePath) - if (imageFilePath != null) return arrayListOf(imageFilePath) - } - return arrayListOf() - } - - suspend fun getFilePathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): String? { - var imageFilePath: String? = null - if (data != null) { - val clipData = data.clipData - if (clipData != null && clipData.itemCount > 1) { // Multiple selection - Log.e("[Image Utils] Expecting only one file, got ${clipData.itemCount}") - } else { // Single selection - val dataUri = if (clipData != null && clipData.itemCount == 1) { - clipData.getItemAt(0).uri - } else { - data.data - } - if (dataUri != null) { - imageFilePath = dataUri.toString() - Log.i("[Image Utils] Using data URI $imageFilePath") - } else if (temporaryImageFilePath?.exists() == true) { - imageFilePath = temporaryImageFilePath.absolutePath - Log.i("[Image Utils] Data URI is null, using $imageFilePath") - } - } - } else if (temporaryImageFilePath?.exists() == true) { - imageFilePath = temporaryImageFilePath.absolutePath - Log.i("[Image Utils] Data is null, using $imageFilePath") - } - return cleanFilePath(imageFilePath) - } - - private suspend fun cleanFilePath(filePath: String?): String? { - if (filePath != null) { - if (filePath.startsWith("content://") || - filePath.startsWith("file://") - ) { - val uriToParse = Uri.parse(filePath) - val result = FileUtils.getFilePath(coreContext.context, uriToParse) - Log.i("[Image Utils] Path was using a content or file scheme, real path is: $filePath") - if (result == null) { - Log.e("[Image Utils] Failed to get access to file $uriToParse") - } - return result - } - } - return filePath - } - private fun getRoundBitmap(bitmap: Bitmap): Bitmap? { val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)