mirror of
https://gitlab.linphone.org/BC/public/linphone-android.git
synced 2026-01-17 11:28:06 +00:00
Added export file to Android's MediaStore from FileViewer
This commit is contained in:
parent
746ddf6457
commit
294f7f6fae
7 changed files with 242 additions and 8 deletions
|
|
@ -21,6 +21,8 @@ package org.linphone.compatibility
|
|||
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import org.linphone.core.tools.Log
|
||||
|
||||
class Api28Compatibility {
|
||||
|
|
@ -37,5 +39,20 @@ class Api28Compatibility {
|
|||
Log.e("$TAG Can't start service as foreground! $e")
|
||||
}
|
||||
}
|
||||
|
||||
fun getMediaCollectionUri(isImage: Boolean, isVideo: Boolean, isAudio: Boolean): Uri {
|
||||
return when {
|
||||
isImage -> {
|
||||
MediaStore.Images.Media.getContentUri("external")
|
||||
}
|
||||
isVideo -> {
|
||||
MediaStore.Video.Media.getContentUri("external")
|
||||
}
|
||||
isAudio -> {
|
||||
MediaStore.Audio.Media.getContentUri("external")
|
||||
}
|
||||
else -> Uri.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ package org.linphone.compatibility
|
|||
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.linphone.core.tools.Log
|
||||
|
||||
|
|
@ -46,5 +48,26 @@ class Api29Compatibility {
|
|||
Log.e("$TAG Can't start service as foreground! $e")
|
||||
}
|
||||
}
|
||||
|
||||
fun getMediaCollectionUri(isImage: Boolean, isVideo: Boolean, isAudio: Boolean): Uri {
|
||||
return when {
|
||||
isImage -> {
|
||||
MediaStore.Images.Media.getContentUri(
|
||||
MediaStore.VOLUME_EXTERNAL_PRIMARY
|
||||
)
|
||||
}
|
||||
isVideo -> {
|
||||
MediaStore.Video.Media.getContentUri(
|
||||
MediaStore.VOLUME_EXTERNAL_PRIMARY
|
||||
)
|
||||
}
|
||||
isAudio -> {
|
||||
MediaStore.Audio.Media.getContentUri(
|
||||
MediaStore.VOLUME_EXTERNAL_PRIMARY
|
||||
)
|
||||
}
|
||||
else -> Uri.EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ package org.linphone.compatibility
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import org.linphone.mediastream.Version
|
||||
|
||||
|
|
@ -63,5 +64,17 @@ class Compatibility {
|
|||
Api31Compatibility.removeBlurRenderEffect(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMediaCollectionUri(
|
||||
isImage: Boolean = false,
|
||||
isVideo: Boolean = false,
|
||||
isAudio: Boolean = false
|
||||
): Uri {
|
||||
return if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) {
|
||||
Api29Compatibility.getMediaCollectionUri(isImage, isVideo, isAudio)
|
||||
} else {
|
||||
Api28Compatibility.getMediaCollectionUri(isImage, isVideo, isAudio)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
package org.linphone.ui.main.viewer.viewmodel
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.pdf.PdfRenderer
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.UiThread
|
||||
|
|
@ -13,7 +18,11 @@ import java.io.File
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.linphone.LinphoneApplication.Companion.coreContext
|
||||
import org.linphone.R
|
||||
import org.linphone.compatibility.Compatibility
|
||||
import org.linphone.core.tools.Log
|
||||
import org.linphone.utils.AppUtils
|
||||
import org.linphone.utils.Event
|
||||
import org.linphone.utils.FileUtils
|
||||
|
||||
|
|
@ -45,6 +54,8 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
// Below are required for PDF viewer
|
||||
private lateinit var pdfRenderer: PdfRenderer
|
||||
|
||||
private lateinit var filePath: String
|
||||
|
||||
var screenWidth: Int = 0
|
||||
var screenHeight: Int = 0
|
||||
// End of PDF viewer required variables
|
||||
|
|
@ -58,6 +69,7 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
|
||||
@UiThread
|
||||
fun loadFile(file: String) {
|
||||
filePath = file
|
||||
val name = FileUtils.getNameFromFilePath(file)
|
||||
fileName.value = name
|
||||
|
||||
|
|
@ -138,4 +150,143 @@ class FileViewModel @UiThread constructor() : ViewModel() {
|
|||
isVideoPlaying.value = playVideo
|
||||
toggleVideoPlayPauseEvent.value = Event(playVideo)
|
||||
}
|
||||
|
||||
@UiThread
|
||||
fun exportToMediaStore() {
|
||||
if (::filePath.isInitialized) {
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
Log.i("$TAG Export file [$filePath] to Android's MediaStore")
|
||||
if (addContentToMediaStore(filePath)) {
|
||||
Log.i("$TAG File [$filePath] has been successfully exported to MediaStore")
|
||||
// TODO: show toast
|
||||
} else {
|
||||
Log.e("$TAG Failed to export file [$filePath] to MediaStore!")
|
||||
// TODO: show toast
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e("$TAG Filepath wasn't initialized!")
|
||||
}
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private suspend fun addContentToMediaStore(
|
||||
path: String
|
||||
): Boolean {
|
||||
if (path.isEmpty()) {
|
||||
Log.e("$TAG No file path to export to MediaStore!")
|
||||
return false
|
||||
}
|
||||
|
||||
val isImage = FileUtils.isExtensionImage(path)
|
||||
val isVideo = FileUtils.isExtensionVideo(path)
|
||||
val isAudio = FileUtils.isExtensionAudio(path)
|
||||
|
||||
val directory = when {
|
||||
isImage -> Environment.DIRECTORY_PICTURES
|
||||
isVideo -> Environment.DIRECTORY_MOVIES
|
||||
isAudio -> Environment.DIRECTORY_MUSIC
|
||||
else -> Environment.DIRECTORY_DOWNLOADS
|
||||
}
|
||||
|
||||
val appName = AppUtils.getString(R.string.app_name)
|
||||
val relativePath = "$directory/$appName"
|
||||
val fileName = FileUtils.getNameFromFilePath(path)
|
||||
val extension = FileUtils.getExtensionFromFileName(fileName)
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
|
||||
val context = coreContext.context
|
||||
val mediaStoreFilePath = when {
|
||||
isImage -> {
|
||||
val values = ContentValues().apply {
|
||||
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Images.Media.MIME_TYPE, mime)
|
||||
put(MediaStore.Images.Media.RELATIVE_PATH, relativePath)
|
||||
put(MediaStore.Images.Media.IS_PENDING, 1)
|
||||
}
|
||||
val collection = Compatibility.getMediaCollectionUri(isImage = true)
|
||||
addContentValuesToCollection(
|
||||
context,
|
||||
path,
|
||||
collection,
|
||||
values,
|
||||
MediaStore.Images.Media.IS_PENDING
|
||||
)
|
||||
}
|
||||
isVideo -> {
|
||||
val values = ContentValues().apply {
|
||||
put(MediaStore.Video.Media.TITLE, fileName)
|
||||
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Video.Media.MIME_TYPE, mime)
|
||||
put(MediaStore.Video.Media.RELATIVE_PATH, relativePath)
|
||||
put(MediaStore.Video.Media.IS_PENDING, 1)
|
||||
}
|
||||
val collection = Compatibility.getMediaCollectionUri(isVideo = true)
|
||||
addContentValuesToCollection(
|
||||
context,
|
||||
path,
|
||||
collection,
|
||||
values,
|
||||
MediaStore.Video.Media.IS_PENDING
|
||||
)
|
||||
}
|
||||
isAudio -> {
|
||||
val values = ContentValues().apply {
|
||||
put(MediaStore.Audio.Media.TITLE, fileName)
|
||||
put(MediaStore.Audio.Media.DISPLAY_NAME, fileName)
|
||||
put(MediaStore.Audio.Media.MIME_TYPE, mime)
|
||||
put(MediaStore.Audio.Media.RELATIVE_PATH, relativePath)
|
||||
put(MediaStore.Audio.Media.IS_PENDING, 1)
|
||||
}
|
||||
val collection = Compatibility.getMediaCollectionUri(isAudio = true)
|
||||
addContentValuesToCollection(
|
||||
context,
|
||||
path,
|
||||
collection,
|
||||
values,
|
||||
MediaStore.Audio.Media.IS_PENDING
|
||||
)
|
||||
}
|
||||
else -> ""
|
||||
}
|
||||
|
||||
if (mediaStoreFilePath.isNotEmpty()) {
|
||||
Log.i("$TAG Exported file path to MediaStore is: $mediaStoreFilePath")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private suspend fun addContentValuesToCollection(
|
||||
context: Context,
|
||||
filePath: String,
|
||||
collection: Uri,
|
||||
values: ContentValues,
|
||||
pendingKey: String
|
||||
): String {
|
||||
try {
|
||||
val fileUri = context.contentResolver.insert(collection, values)
|
||||
if (fileUri == null) {
|
||||
Log.e("$TAG Failed to get a URI to where store the file, aborting")
|
||||
return ""
|
||||
}
|
||||
|
||||
context.contentResolver.openOutputStream(fileUri).use { out ->
|
||||
if (FileUtils.copyFileTo(filePath, out)) {
|
||||
values.clear()
|
||||
values.put(pendingKey, 0)
|
||||
context.contentResolver.update(fileUri, values, null, null)
|
||||
|
||||
return fileUri.toString()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("$TAG Exception: $e")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import java.io.File
|
|||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -74,6 +75,13 @@ class FileUtils {
|
|||
return getMimeType(type) == MimeType.Video
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun isExtensionAudio(path: String): Boolean {
|
||||
val extension = getExtensionFromFileName(path)
|
||||
val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
return getMimeType(type) == MimeType.Audio
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
fun getExtensionFromFileName(fileName: String): String {
|
||||
var extension = MimeTypeMap.getFileExtensionFromUrl(fileName)
|
||||
|
|
@ -251,6 +259,34 @@ class FileUtils {
|
|||
return false
|
||||
}
|
||||
|
||||
suspend fun copyFileTo(filePath: String, outputStream: OutputStream?): Boolean {
|
||||
if (outputStream == null) {
|
||||
Log.e("$TAG Can't copy file $filePath to given null output stream")
|
||||
return false
|
||||
}
|
||||
|
||||
val file = File(filePath)
|
||||
if (!file.exists()) {
|
||||
Log.e("$TAG Can't copy file $filePath, it doesn't exists")
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
val inputStream = FileInputStream(file)
|
||||
val buffer = ByteArray(4096)
|
||||
var bytesRead: Int
|
||||
while (inputStream.read(buffer).also { bytesRead = it } >= 0) {
|
||||
outputStream.write(buffer, 0, bytesRead)
|
||||
}
|
||||
}
|
||||
return true
|
||||
} catch (e: IOException) {
|
||||
Log.e("$TAG copyFileTo exception: $e")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
suspend fun deleteFile(filePath: String) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val file = File(filePath)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@
|
|||
<variable
|
||||
name="shareClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="saveClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.viewer.viewmodel.FileViewModel" />
|
||||
|
|
@ -130,7 +127,7 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/save"
|
||||
android:onClick="@{saveClickListener}"
|
||||
android:onClick="@{() -> viewModel.exportToMediaStore()}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/white"
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@
|
|||
<variable
|
||||
name="shareClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="saveClickListener"
|
||||
type="View.OnClickListener" />
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="org.linphone.ui.main.viewer.viewmodel.FileViewModel" />
|
||||
|
|
@ -130,7 +127,7 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/save"
|
||||
android:onClick="@{saveClickListener}"
|
||||
android:onClick="@{() -> viewModel.exportToMediaStore()}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/white"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue