From 97cae93bb544e385c366f0775fba68347e742a13 Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Thu, 9 Nov 2023 14:21:41 +0100 Subject: [PATCH] Started external file sharing --- app/src/main/AndroidManifest.xml | 19 ++++ .../java/org/linphone/ui/main/MainActivity.kt | 90 ++++++++++++++++++- .../chat/fragment/ConversationFragment.kt | 11 +++ .../chat/viewmodel/ConversationViewModel.kt | 4 +- .../ui/main/viewmodel/SharedMainViewModel.kt | 2 + .../main/java/org/linphone/utils/FileUtils.kt | 56 ++++++------ .../java/org/linphone/utils/LinphoneUtils.kt | 19 +++- app/src/main/res/xml/locales_config.xml | 4 + 8 files changed, 174 insertions(+), 31 deletions(-) create mode 100644 app/src/main/res/xml/locales_config.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 816507750..cc1acb824 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -41,6 +41,7 @@ android:label="@string/app_name" android:enableOnBackInvokedCallback="true" android:theme="@style/Theme.Linphone" + android:localeConfig="@xml/locales_config" tools:targetApi="34"> + + + + + + + + + + + + + + + + + + { + handleMainIntent(intent, defaultDestination, isNewIntent) + } + Intent.ACTION_SEND -> { + handleSendIntent(intent, false) + } + Intent.ACTION_SEND_MULTIPLE -> { + handleSendIntent(intent, true) + } + } + } + + private fun handleMainIntent(intent: Intent, defaultDestination: Int, isNewIntent: Boolean) { + val navGraph = findNavController().navInflater.inflate(R.navigation.main_nav_graph) if (intent.hasExtra("Chat")) { Log.i("$TAG New intent with [Chat] extra") coreContext.postOnMainThread { @@ -319,6 +342,69 @@ class MainActivity : AppCompatActivity() { } } + private fun handleSendIntent(intent: Intent, multiple: Boolean) { + val parcelablesUri = arrayListOf() + if (multiple) { + val parcelables = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) + for (parcelable in parcelables.orEmpty()) { + val uri = parcelable as? Uri + if (uri != null) { + Log.i("$TAG Found URI [$uri] in parcelable extra list") + parcelablesUri.add(uri) + } + } + } else { + val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri + if (uri != null) { + Log.i("$TAG Found URI [$uri] in parcelable extra") + parcelablesUri.add(uri) + } + } + + val list = arrayListOf() + lifecycleScope.launch() { + val deferred = arrayListOf>() + for (uri in parcelablesUri) { + deferred.add(async { FileUtils.getFilePath(this@MainActivity, uri, false) }) + } + + val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID + if (shortcutId != null) { + Log.i("$TAG Found shortcut ID [$shortcutId]") + val pair = LinphoneUtils.getLocalAndPeerSipUrisFromChatRoomId(shortcutId) + if (pair != null) { + val localSipUri = pair.first + val remoteSipUri = pair.second + Log.i( + "$TAG Navigating to conversation with local [$localSipUri] and peer [$remoteSipUri] addresses, computed from shortcut ID" + ) + intent.putExtra("LocalSipUri", localSipUri) + intent.putExtra("RemoteSipUri", remoteSipUri) + } else { + Log.e("$TAG Failed to parse shortcut ID, going to conversations list") + } + } else { + Log.i("$TAG Going into conversations list as no shortcut ID as found") + } + + val navGraph = findNavController().navInflater.inflate(R.navigation.main_nav_graph) + navGraph.setStartDestination(R.id.conversationsFragment) + + val paths = deferred.awaitAll() + for (path in paths) { + Log.i("$TAG Found file to share [$path] in intent") + if (path != null) list.add(path) + } + if (list.isNotEmpty()) { + sharedViewModel.filesToShareFromIntent.value = list + } else { + Log.w("$TAG Failed to find at least one file to share!") + } + + findNavController().setGraph(navGraph, intent.extras) + } + } + private fun loadContacts() { coreContext.contactsManager.loadContacts(this) diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index 0a9bbcdd7..3194e4bba 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -385,6 +385,17 @@ class ConversationFragment : GenericFragment() { } } + sharedViewModel.filesToShareFromIntent.observe(viewLifecycleOwner) { files -> + if (files.isNotEmpty()) { + Log.i("$TAG Found [${files.size}] files to share from intent") + for (path in files) { + viewModel.addAttachment(path) + } + + sharedViewModel.filesToShareFromIntent.value = arrayListOf() + } + } + binding.sendArea.messageToSend.setControlEnterListener(object : RichEditText.RichEditTextSendListener { override fun onControlEnterPressedAndReleased() { diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index daec0be86..aa246e42c 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -454,7 +454,9 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { list.add(model) attachments.value = list - isFileAttachmentsListOpen.value = true + if (list.isNotEmpty()) { + isFileAttachmentsListOpen.value = true + } } @UiThread diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt index 9450a6409..4be2ff6b9 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt @@ -107,6 +107,8 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val filesToShareFromIntent = MutableLiveData>() + var displayedChatRoom: ChatRoom? = null // Prevents the need to go look for the chat room val showConversationEvent: MutableLiveData>> by lazy { MutableLiveData>>() diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt index 1c1f2059f..7bbcc6447 100644 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ b/app/src/main/java/org/linphone/utils/FileUtils.kt @@ -143,35 +143,37 @@ class FileUtils { } suspend fun getFilePath(context: Context, uri: Uri, overrideExisting: Boolean): String? { - val name: String = getNameFromUri(uri, context) - try { - if (Os.fstat( - ParcelFileDescriptor.open( - File(uri.path), - ParcelFileDescriptor.MODE_READ_ONLY - ).fileDescriptor - ).st_uid != Process.myUid() - ) { - Log.e("$TAG File descriptor UID different from our, denying copy!") - return null + return withContext(Dispatchers.IO) { + val name: String = getNameFromUri(uri, context) + try { + if (Os.fstat( + ParcelFileDescriptor.open( + File(uri.path), + ParcelFileDescriptor.MODE_READ_ONLY + ).fileDescriptor + ).st_uid != Process.myUid() + ) { + Log.e("$TAG File descriptor UID different from our, denying copy!") + return@withContext null + } + } catch (e: Exception) { + Log.e("$TAG Can't check file ownership: ", e) } - } catch (e: Exception) { - Log.e("$TAG Can't check file ownership: ", e) + + val extension = getExtensionFromFileName(name) + val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) + val isImage = getMimeType(type) == MimeType.Image + + try { + val localFile: File = getFileStoragePath(name, isImage, overrideExisting) + copyFile(uri, localFile) + return@withContext localFile.absolutePath + } catch (e: Exception) { + Log.e("$TAG Can't copy file in local storage: ", e) + } + + return@withContext null } - - val extension = getExtensionFromFileName(name) - val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - val isImage = getMimeType(type) == MimeType.Image - - try { - val localFile: File = getFileStoragePath(name, isImage, overrideExisting) - copyFile(uri, localFile) - return localFile.absolutePath - } catch (e: Exception) { - Log.e("$TAG Can't copy file in local storage: ", e) - } - - return null } @AnyThread diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt index 52f4a1118..38561be26 100644 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt @@ -43,6 +43,7 @@ class LinphoneUtils { private const val TAG = "[Linphone Utils]" private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss" + private const val CHAT_ROOM_ID_SEPARATOR = "~" @WorkerThread fun getDefaultAccount(): Account? { @@ -226,7 +227,23 @@ class LinphoneUtils { @AnyThread fun getChatRoomId(localSipUri: String, remoteSipUri: String): String { - return "$localSipUri~$remoteSipUri" + return "$localSipUri$CHAT_ROOM_ID_SEPARATOR$remoteSipUri" + } + + @AnyThread + fun getLocalAndPeerSipUrisFromChatRoomId(id: String): Pair? { + val split = id.split(CHAT_ROOM_ID_SEPARATOR) + if (split.size == 2) { + val localAddress = split[0] + val peerAddress = split[1] + Log.i( + "$TAG Got local [$localAddress] and peer [$peerAddress] SIP URIs from chat room id [$id]" + ) + return Pair(localAddress, peerAddress) + } else { + Log.e("$TAG Failed to parse chat room id [$id]") + } + return null } @WorkerThread diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml new file mode 100644 index 000000000..2f9a67e1c --- /dev/null +++ b/app/src/main/res/xml/locales_config.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file