Removes deprecated file management API

blinds
Laurent 5 years ago
parent 21d700db1e
commit f874f35ad9
  1. 18
      app/src/main/AndroidManifest.xml
  2. 191
      app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/replayer/ReplayExportService.kt
  3. 2
      app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt

@ -6,26 +6,10 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--
android:requestLegacyExternalStorage="true"
has been added due to a crash:
Fatal Exception: java.io.FileNotFoundException
/storage/emulated/0/Movies/gif_20_09_18_09_59_41.gif: open failed: EACCES (Permission denied)
happening here:
net.pokeranalytics.android.ui.modules.handhistory.replayer.ReplayExportService$startGIFExport$1$c$1.invokeSuspend (ReplayExportService.java:88)
The temporary fix comes from here:
https://medium.com/@sriramaripirala/android-10-open-failed-eacces-permission-denied-da8b630a89df
-->
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->
<application
android:name=".PokerAnalyticsApplication"
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

@ -2,19 +2,19 @@ package net.pokeranalytics.android.ui.modules.handhistory.replayer
import android.app.PendingIntent
import android.app.Service
import android.content.ContentValues
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.Build
import android.os.Environment
import android.os.IBinder
import androidx.core.content.FileProvider
import android.provider.MediaStore
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS
import com.arthenica.mobileffmpeg.FFmpeg
import io.realm.Realm
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
@ -25,10 +25,13 @@ import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.video.AnimatedGIFWriter
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.coroutines.CoroutineContext
enum class FileType(var value: String) {
IMAGE("image/gif"),
VIDEO("video/*")
}
class ReplayExportService : Service() {
@ -39,7 +42,7 @@ class ReplayExportService : Service() {
private val coroutineContext: CoroutineContext
get() = Dispatchers.Main
override fun onBind(intent: Intent?): IBinder? {
override fun onBind(intent: Intent?): IBinder {
return binder
}
@ -81,40 +84,57 @@ class ReplayExportService : Service() {
animator.configure(width.toFloat(), height.toFloat(), context)
val formattedDate = Date().dateTimeFileFormatted
val path = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"gif_${formattedDate}.gif"
).toString()
val fileName = "hand_${formattedDate}.gif"
val writer = AnimatedGIFWriter(false)
val os = FileOutputStream(path)
writer.prepareForWrite(os, width, height)
// Add a specific media item.
val resolver = applicationContext.contentResolver
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
// Find all video files on the primary external storage device.
val videoCollection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
}
var animationCount = 0
animator.frames(context) { bitmap, count ->
val gifDetails = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
put(MediaStore.Images.Media.MIME_TYPE, "image/gif")
}
when {
count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
val uri = resolver.insert(videoCollection, gifDetails)
if (uri != null) {
val os = resolver.openOutputStream(uri)
val writer = AnimatedGIFWriter(false)
writer.prepareForWrite(os, width, height)
val drawer = TableDrawer()
drawer.configurePaints(context, animator)
var animationCount = 0
animator.frames(context) { bitmap, count ->
when {
count > 10 -> {
writer.writeFrame(os, bitmap, count * 8)
animationCount = 0
}
else -> {
if (animationCount % 2 == 0) {
writer.writeFrame(os, bitmap)
}
animationCount++
}
animationCount++
}
}
writer.finishWrite(os)
realm.close()
notifyUser(uri, FileType.IMAGE)
}
writer.finishWrite(os)
realm.close()
notifyUser(path)
}
c.await()
}
@ -148,30 +168,79 @@ class ReplayExportService : Service() {
val tmpDir = animator.generateVideoContent(this@ReplayExportService)
val dpath = "${tmpDir.path}/$FFMPEG_DESCRIPTOR_FILE"
// val directory = context.getExternalFilesDir(null) ?: throw PAIllegalStateException("File is invalid")
// val output = "${directory.path}/video_${Date().dateTimeFileFormatted}.mp4"
val formattedDate = Date().dateTimeFileFormatted
val output = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
"video_${formattedDate}.mp4"
).path
val fileName = "hand_${formattedDate}.mp4"
// val output = File(
// Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES),
// fileName
// ).path
val outputDirectory = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES) ?: throw PAIllegalStateException("File is invalid")
val output = "${outputDirectory.path}/$fileName"
Environment.getExternalStorageState(tmpDir)
// Environment.getExternalStorageState(tmpDir)
Timber.d("Assembling images for video...")
FFmpeg.executeAsync("-f concat -safe 0 -i $dpath -vb 20M -vsync vfr -s ${width}x${height} -pix_fmt yuv420p $output") { id, rc ->
if (rc == RETURN_CODE_SUCCESS) {
Timber.d("FFMPEG command execution completed successfully")
} else if (rc == RETURN_CODE_CANCEL) {
Timber.d("Command execution cancelled by user.")
} else {
Timber.d(String.format("Command execution failed with rc=%d and the output below.", rc))
when (rc) {
RETURN_CODE_SUCCESS -> {
Timber.d("FFMPEG command execution completed successfully")
}
RETURN_CODE_CANCEL -> {
Timber.d("Command execution cancelled by user.")
}
else -> {
Timber.d(String.format("Command execution failed with rc=%d and the output below.", rc))
}
}
tmpDir.delete()
notifyUser(output)
val file = File(output)
val resolver = applicationContext.contentResolver
val videoCollection =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else {
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
}
val fileDetails = ContentValues().apply {
Timber.d("set file details = $fileName")
put(MediaStore.Video.Media.DISPLAY_NAME, fileName)
// put(MediaStore.Video.Media.CONTENT_TYPE, "video/mp4")
}
resolver.insert(videoCollection, fileDetails)?.let { uri ->
Timber.d("copy file at uri = $uri")
val os = resolver.openOutputStream(uri)
os?.write(file.readBytes())
os?.close()
file.delete()
notifyUser(uri, FileType.VIDEO)
} ?: run {
Timber.w("Resolver insert ended without uri...")
}
// val uri = FileProvider.getUriForFile(
// applicationContext,
// applicationContext.packageName.toString() + ".fileprovider",
// File(output)
// )
// Timber.d("File exported at $output")
// notifyUser(output)
}
}
@ -226,26 +295,26 @@ class ReplayExportService : Service() {
//
// }
private fun notifyUser(path: String) {
Timber.d("Show local notification")
private fun notifyUser(uri: Uri, type: FileType) {
val title = getString(R.string.video_available)
val body = getString(R.string.video_retrieval_message) + ": " + path
val body = getString(R.string.video_retrieval_message) + ": " + uri.path
val uri = FileProvider.getUriForFile(
this,
this.applicationContext.packageName.toString() + ".fileprovider",
File(path)
)
Timber.d("Show local notification, path of file: ${uri.path}")
val type = when {
path.contains("gif") -> "image/gif"
else -> "video/*"
}
// val uri = FileProvider.getUriForFile(
// this,
// this.applicationContext.packageName.toString() + ".fileprovider",
// File(path)
// )
//
// val type = when {
// path.contains("gif") -> "image/gif"
// else -> "video/*"
// }
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(uri, type)
intent.setDataAndType(uri, type.value)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
val chooser = Intent.createChooser(intent, getString(R.string.open_file_with))

@ -120,7 +120,7 @@ fun Date.getDayMonthYear(): String {
// Returns a file friendly date time string
val Date.dateTimeFileFormatted: String
get() {
return SimpleDateFormat("yy_MM_dd_hh_mm_ss", Locale.getDefault()).format(this)
return SimpleDateFormat("yy_MM_dd-hh_mm_ss", Locale.getDefault()).format(this)
}
// Return the netDuration between two dates

Loading…
Cancel
Save