Fixed contact sharing in chat + reworked how file picker is created

This commit is contained in:
Sylvain Berfini 2021-08-23 11:55:09 +02:00
parent beb9d0db45
commit 2a2315e944
6 changed files with 171 additions and 112 deletions

View file

@ -464,7 +464,7 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch { lifecycleScope.launch {
for (fileToUploadPath in ImageUtils.getFilesPathFromPickerIntent( for (fileToUploadPath in FileUtils.getFilesPathFromPickerIntent(
data, data,
chatSendingViewModel.temporaryFileUploadPath chatSendingViewModel.temporaryFileUploadPath
)) { )) {
@ -617,13 +617,11 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
} }
private fun pickFile() { private fun pickFile() {
val cameraIntents = ArrayList<Intent>() val intentsList = ArrayList<Intent>()
// Handles image & video picking val pickerIntent = Intent(Intent.ACTION_GET_CONTENT)
val galleryIntent = Intent(Intent.ACTION_PICK) pickerIntent.type = "*/*"
galleryIntent.type = "*/*" pickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
galleryIntent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
if (PermissionHelper.get().hasCameraPermission()) { if (PermissionHelper.get().hasCameraPermission()) {
// Allows to capture directly from the camera // Allows to capture directly from the camera
@ -639,25 +637,17 @@ class DetailChatRoomFragment : MasterFragment<ChatRoomDetailFragmentBinding, Cha
capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri) capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri)
capturePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) capturePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
capturePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) capturePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
cameraIntents.add(capturePictureIntent) intentsList.add(capturePictureIntent)
val captureVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) val captureVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
cameraIntents.add(captureVideoIntent) intentsList.add(captureVideoIntent)
}
if (PermissionHelper.get().hasReadExternalStorage()) {
// Finally allow any kind of file
val fileIntent = Intent(Intent.ACTION_GET_CONTENT)
fileIntent.type = "*/*"
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
cameraIntents.add(fileIntent)
} }
val chooserIntent = val chooserIntent =
Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog)) Intent.createChooser(pickerIntent, getString(R.string.chat_message_pick_file_dialog))
chooserIntent.putExtra( chooserIntent.putExtra(
Intent.EXTRA_INITIAL_INTENTS, Intent.EXTRA_INITIAL_INTENTS,
cameraIntents.toArray(arrayOf<Parcelable>()) intentsList.toArray(arrayOf<Parcelable>())
) )
startActivityForResult(chooserIntent, 0) startActivityForResult(chooserIntent, 0)

View file

@ -46,7 +46,6 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.ContactEditorFragmentBinding import org.linphone.databinding.ContactEditorFragmentBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.FileUtils import org.linphone.utils.FileUtils
import org.linphone.utils.ImageUtils
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), SyncAccountPickerFragment.SyncAccountPickedListener { class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), SyncAccountPickerFragment.SyncAccountPickedListener {
@ -146,7 +145,7 @@ class ContactEditorFragment : GenericFragment<ContactEditorFragmentBinding>(), S
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch { lifecycleScope.launch {
val contactImageFilePath = ImageUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
if (contactImageFilePath != null) { if (contactImageFilePath != null) {
viewModel.setPictureFromPath(contactImageFilePath) viewModel.setPictureFromPath(contactImageFilePath)
} }

View file

@ -46,7 +46,6 @@ import org.linphone.core.tools.Log
import org.linphone.databinding.SideMenuFragmentBinding import org.linphone.databinding.SideMenuFragmentBinding
import org.linphone.utils.Event import org.linphone.utils.Event
import org.linphone.utils.FileUtils import org.linphone.utils.FileUtils
import org.linphone.utils.ImageUtils
import org.linphone.utils.PermissionHelper import org.linphone.utils.PermissionHelper
class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() { class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
@ -121,7 +120,7 @@ class SideMenuFragment : GenericFragment<SideMenuFragmentBinding>() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
lifecycleScope.launch { lifecycleScope.launch {
val contactImageFilePath = ImageUtils.getFilePathFromPickerIntent(data, temporaryPicturePath) val contactImageFilePath = FileUtils.getFilePathFromPickerIntent(data, temporaryPicturePath)
if (contactImageFilePath != null) { if (contactImageFilePath != null) {
viewModel.setPictureFromPath(contactImageFilePath) viewModel.setPictureFromPath(contactImageFilePath)
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
}
}
}

View file

@ -149,6 +149,95 @@ class FileUtils {
return file return file
} }
suspend fun getFilesPathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): List<String> {
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<String>()
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) { fun deleteFile(filePath: String) {
val file = File(filePath) val file = File(filePath)
if (file.exists()) { if (file.exists()) {

View file

@ -20,13 +20,10 @@
package org.linphone.utils package org.linphone.utils
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.* import android.graphics.*
import android.media.ThumbnailUtils import android.media.ThumbnailUtils
import android.net.Uri import android.net.Uri
import android.provider.MediaStore import android.provider.MediaStore
import java.io.File
import org.linphone.LinphoneApplication.Companion.coreContext
import org.linphone.compatibility.Compatibility import org.linphone.compatibility.Compatibility
import org.linphone.core.tools.Log import org.linphone.core.tools.Log
@ -70,92 +67,6 @@ class ImageUtils {
return ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND) return ThumbnailUtils.createVideoThumbnail(path, MediaStore.Images.Thumbnails.MINI_KIND)
} }
suspend fun getFilesPathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): List<String> {
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<String>()
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? { private fun getRoundBitmap(bitmap: Bitmap): Bitmap? {
val output = val output =
Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888) Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)