WriteExternalDelegate.kt 4.97 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11
package org.videolan.vlc.gui.helpers.hf

import android.annotation.TargetApi
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.DocumentsContract
import android.support.v4.app.FragmentActivity
import android.support.v4.provider.DocumentFile
12
import android.support.v7.app.AlertDialog
13 14
import android.support.v7.preference.PreferenceManager
import android.text.TextUtils
15
import kotlinx.coroutines.experimental.channels.Channel
16
import org.videolan.libvlc.util.AndroidUtil
17
import org.videolan.vlc.R
18 19 20
import org.videolan.vlc.VLCApplication
import org.videolan.vlc.util.AndroidDevices
import org.videolan.vlc.util.FileUtils
21
import org.videolan.vlc.util.Settings
22 23 24 25 26 27 28


class WriteExternalDelegate : BaseHeadlessFragment() {

    @TargetApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
29 30 31 32 33
        showDialog()
    }

    private fun showDialog() {
        if (!isAdded) return
34
        val builder = AlertDialog.Builder(activity!!)
35 36
        builder.setMessage(R.string.sdcard_permission_dialog_message)
                .setTitle(R.string.sdcard_permission_dialog_title)
37
                .setPositiveButton(R.string.ok) { _, _ ->
38 39 40
                    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
                    arguments?.getString(KEY_STORAGE_PATH)?.let { intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse(it)) }
                    startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCES)
41 42
                }
                .setNeutralButton(getString(R.string.dialog_sd_wizard)) { _, _ -> showHelpDialog() }.create().show()
43 44 45 46
    }

    private fun showHelpDialog() {
        if (!isAdded) return
47 48 49 50 51 52
        activity?.let {
            val inflater = it.layoutInflater
            AlertDialog.Builder(it).setView(inflater.inflate(R.layout.dialog_sd_write, null))
                    .setOnDismissListener { showDialog() }
                    .create().show()
        }
53 54
    }

55
    @TargetApi(Build.VERSION_CODES.KITKAT)
56 57 58 59
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (data !== null && requestCode == REQUEST_CODE_STORAGE_ACCES) {
            if (resultCode == Activity.RESULT_OK) {
60
                val context = context ?: return
61
                val treeUri = data.data
62 63 64
                Settings.getInstance(context).edit()
                        .putString("tree_uri_$storage", treeUri.toString())
                        .apply()
65 66 67 68 69 70 71
                val treeFile = DocumentFile.fromTreeUri(context, treeUri)
                val contentResolver = context.contentResolver

                // revoke access if a permission already exists
                val persistedUriPermissions = contentResolver.persistedUriPermissions
                for (uriPermission in persistedUriPermissions) {
                    val file = DocumentFile.fromTreeUri(context, uriPermission.uri)
Geoffrey Métais's avatar
Geoffrey Métais committed
72
                    if (treeFile?.name == file?.name) {
73 74 75 76 77 78 79 80
                        contentResolver.releasePersistableUriPermission(uriPermission.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                        return
                    }
                }

                // else set permission
                contentResolver.takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                permissions = contentResolver.persistedUriPermissions
81
                executePendingAction()
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
                return
            }
        }
    }

    companion object {
        internal const val TAG = "VLC/WriteExternal"
        internal const val KEY_STORAGE_PATH = "VLC/storage_path"
        private const val REQUEST_CODE_STORAGE_ACCES = 42
        private var permissions = VLCApplication.getAppContext().contentResolver.persistedUriPermissions
        private lateinit var storage: String

        fun askForExtWrite(activity: FragmentActivity?, uri: Uri, cb: Runnable? = null) {
            if (activity === null) return
            val fragment = WriteExternalDelegate()
97
            val channel = if (cb != null) Channel<Unit>(1) else null
98
            storage = FileUtils.getMediaStorage(uri) ?: return
99
            fragment.arguments = Bundle(1).apply { putString(KEY_STORAGE_PATH, storage) }
100
            channel?.let { fragment.channel = it }
101
            activity.supportFragmentManager.beginTransaction().add(fragment, TAG).commitAllowingStateLoss()
102
            channel?.let { waitForIt(it, cb!!) }
103 104 105 106 107 108 109 110 111 112 113
        }

        fun needsWritePermission(uri: Uri) : Boolean {
            val path = uri.path
            return AndroidUtil.isLolliPopOrLater && "file" == uri.scheme
                    && !TextUtils.isEmpty(path) && path.startsWith('/')
                    && !path.startsWith(AndroidDevices.EXTERNAL_PUBLIC_DIRECTORY)
                    && !(FileUtils.findFile(uri)?.canWrite() ?: false)
        }
    }
}