Save files to app external dirs

This commit is contained in:
dece 2020-05-01 21:06:02 +02:00
parent 97c3f59741
commit 1a925bd070
4 changed files with 132 additions and 19 deletions

View file

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.lowrespalmtree.zmingz">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"

View file

@ -1,28 +1,68 @@
package dev.lowrespalmtree.zmingz
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.os.AsyncTask
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.PopupMenu
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS
import com.arthenica.mobileffmpeg.FFmpeg
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.lang.ref.WeakReference
class MainActivity: AppCompatActivity() {
/** Path for first image/video. */
private var path1 = ""
/** Path for second image/video. */
private var path2 = ""
/** Temporary path for the media to save, to use after perms being granted. */
private var savingPath = ""
/** Temporary type for the media to save, to use after perms being granted. */
private var savingType = MediaType.IMAGE
private enum class MediaType { IMAGE, VIDEO }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQ_PICK_IMG, REQ_PICK_VID -> handlePickResult(requestCode, resultCode, data)
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == REQ_WRITE_PERM) {
if (permissions[0] == Manifest.permission.WRITE_EXTERNAL_STORAGE) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
saveWithPerm(savingPath, savingType)
return
}
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
fun openFile(v: View) {
val req: Int
val pickIntent = when (v.id) {
@ -39,17 +79,36 @@ class MainActivity: AppCompatActivity() {
startActivityForResult(pickIntent, req)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQ_PICK_IMG, REQ_PICK_VID -> handlePickResult(requestCode, resultCode, data)
else -> super.onActivityResult(requestCode, resultCode, data)
fun openViewMenu(v: View) {
val menu = PopupMenu(this, v)
val saveItem = menu.menu.add(R.string.save)
val shareItem = menu.menu.add(R.string.share)
menu.setOnMenuItemClickListener { item ->
val path = when (v.id) {
imageView1.id, videoView1.id -> path1
imageView2.id, videoView2.id -> path2
else -> return@setOnMenuItemClickListener false
}
when (item.itemId) {
saveItem.itemId -> {
val type = when (v.id) {
imageView1.id, imageView2.id -> MediaType.IMAGE
videoView1.id, videoView2.id -> MediaType.VIDEO
else -> return@setOnMenuItemClickListener false
}
save(path, type)
}
else -> {}
}
true
}
menu.show()
}
@Suppress("UNUSED_PARAMETER")
private fun handlePickResult(requestCode: Int, resultCode: Int, data: Intent?) {
val uri = data?.data
?: return Unit .also { Log.e(TAG, "No intent or data.") }
?: return Unit .also { Log.e(TAG, "No intent or data") }
val uriPath = uri.path
?: return Unit .also { Log.e(TAG, "No path in URI") }
val extension = getFileExtension(uriPath)
@ -64,6 +123,10 @@ class MainActivity: AppCompatActivity() {
}
}
imageLayout.visibility = View.GONE
videoLayout.visibility = View.GONE
Toast.makeText(this, R.string.please_wait, Toast.LENGTH_SHORT).show()
val outputFile1 = File.createTempFile("output1", ".$extension", cacheDir)
MirrorTask(WeakReference(this), requestCode, 1)
.execute(inputFile.canonicalPath, outputFile1.canonicalPath, VF1)
@ -85,9 +148,9 @@ class MainActivity: AppCompatActivity() {
val vf = params[2]!!
val command = "-i $inputPath -vf \"$vf\" -y $outputPath"
return when (val rc = FFmpeg.execute(command)) {
RETURN_CODE_SUCCESS -> true. also { Log.d(TAG, "ffmpeg success") }
RETURN_CODE_CANCEL -> false .also { Log.d(TAG, "ffmpeg canceled") }
else -> false .also { Log.d(TAG, "ffmpeg failed with rc $rc") }
RETURN_CODE_SUCCESS -> true. also { Log.d(TAG, "FFmpeg success") }
RETURN_CODE_CANCEL -> false .also { Log.d(TAG, "FFmpeg canceled") }
else -> false .also { Log.d(TAG, "FFmpeg failed with rc $rc") }
}
}
@ -101,25 +164,21 @@ class MainActivity: AppCompatActivity() {
internal fun updateViews(requestCode: Int, viewIndex: Int, outputPath: String) {
if (requestCode == REQ_PICK_IMG) {
if (imageLayout.visibility != View.VISIBLE) {
videoLayout.visibility = View.GONE
if (imageLayout.visibility != View.VISIBLE)
imageLayout.visibility = View.VISIBLE
}
val mirrored = BitmapFactory.decodeFile(outputPath)
val view = when (viewIndex) {
1 -> imageView1
2 -> imageView2
1 -> { path1 = outputPath; imageView1 }
2 -> { path2 = outputPath; imageView2 }
else -> return
}
view.setImageBitmap(mirrored)
} else if (requestCode == REQ_PICK_VID) {
if (videoLayout.visibility != View.VISIBLE) {
imageLayout.visibility = View.GONE
if (videoLayout.visibility != View.VISIBLE)
videoLayout.visibility = View.VISIBLE
}
val view = when (viewIndex) {
1 -> videoView1
2 -> videoView2
1 -> { path1 = outputPath; videoView1 }
2 -> { path2 = outputPath; videoView2 }
else -> return
}
view.setVideoPath(outputPath)
@ -128,10 +187,54 @@ class MainActivity: AppCompatActivity() {
}
}
private fun save(path: String, type: MediaType) {
val writePermission = Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissionStatus = checkSelfPermission(writePermission)
if (permissionStatus == PackageManager.PERMISSION_GRANTED) {
saveWithPerm(path, type)
} else {
savingPath = path
savingType = type
requestPermissions(arrayOf(writePermission), REQ_WRITE_PERM)
}
}
private fun saveWithPerm(path: String, type: MediaType) {
// Ensure media directories for the app exist.
val envDirId = when (type) {
MediaType.IMAGE -> Environment.DIRECTORY_PICTURES
MediaType.VIDEO -> Environment.DIRECTORY_MOVIES
}
val externalMediaDir = getExternalFilesDir(envDirId)
?: return Unit .also { Log.e(TAG, "Can't find an external dir") }
val mediaDir = File(externalMediaDir, getString(R.string.app_name))
if (!mediaDir.exists() && !mediaDir.mkdir()) {
Log.e(TAG, "Failed to create app media dir: $mediaDir")
Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
return
}
val extension = getFileExtension(path).let { if (it.isEmpty()) "xxx" else it }
val outputFile = File(mediaDir.canonicalPath, "${System.currentTimeMillis()}.$extension")
if (!outputFile.createNewFile()) {
Log.e(TAG, "Failed to create new file: $outputFile")
Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
return
}
FileInputStream(path).use { fis ->
FileOutputStream(outputFile).use { fos ->
fos.buffered().write(fis.buffered().readBytes())
}
}
Log.i(TAG, "File saved at $outputFile")
}
companion object {
private const val TAG = "ZMINGZ"
private const val REQ_PICK_IMG = 1
private const val REQ_PICK_VID = 2
private const val REQ_WRITE_PERM = 3
private const val VF1 = "crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left][right] hstack"
private const val VF2 = "crop=iw/2:ih:iw/2:0,split[left][tmp];[tmp]hflip[right];[right][left] hstack"
@ -142,4 +245,4 @@ class MainActivity: AppCompatActivity() {
}
}
}
}

View file

@ -23,6 +23,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:onClick="openViewMenu"
android:contentDescription="@android:string/untitled" />
<ImageView
@ -30,6 +31,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.5"
android:onClick="openViewMenu"
android:contentDescription="@android:string/untitled" />
</LinearLayout>
@ -50,6 +52,7 @@
android:id="@+id/videoView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="openViewMenu"
app:layout_constraintBottom_toTopOf="@id/videoView2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
@ -60,6 +63,7 @@
android:id="@+id/videoView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="openViewMenu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"

View file

@ -3,4 +3,8 @@
<string name="open_image">Image</string>
<string name="open_video">Video</string>
<string name="please_wait">Please wait...</string>
<string name="save">Save</string>
<string name="share">Share</string>
<string name="write_error">Failed to create file.</string>
</resources>