OtgAccess.kt 4.1 KB
Newer Older
Geoffrey Métais's avatar
Geoffrey Métais committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*****************************************************************************
 * OtgAccess.kt
 *****************************************************************************
 * Copyright © 2018 VLC authors and VideoLAN
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

package org.videolan.vlc.gui.helpers.hf

23
24
import android.annotation.TargetApi
import android.content.ActivityNotFoundException
Geoffrey Métais's avatar
Geoffrey Métais committed
25
26
27
import android.content.Context
import android.content.Intent
import android.net.Uri
28
import android.os.Build
Geoffrey Métais's avatar
Geoffrey Métais committed
29
30
import android.os.Bundle
import android.util.Log
31
import androidx.annotation.WorkerThread
32
33
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.FragmentActivity
34
import kotlinx.coroutines.flow.MutableStateFlow
35
import org.videolan.medialibrary.MLServiceLocator
36
import org.videolan.medialibrary.interfaces.media.MediaWrapper
Geoffrey Métais's avatar
Geoffrey Métais committed
37
38
39
40
41
42
43

const val SAF_REQUEST = 85
const val TAG = "OtgAccess"

const val OTG_CONTENT_AUTHORITY = "com.android.externalstorage.documents"
const val OTG_SCHEME = "otg"

44
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
Geoffrey Métais's avatar
Geoffrey Métais committed
45
46
47
48
class OtgAccess : BaseHeadlessFragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
Geoffrey Métais's avatar
Geoffrey Métais committed
49
50
51
52
53
54
55
        if (savedInstanceState == null) {
            val safIntent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            try {
                startActivityForResult(safIntent, SAF_REQUEST)
            } catch (e: ActivityNotFoundException) {
                exit()
            }
56
        }
Geoffrey Métais's avatar
Geoffrey Métais committed
57
58
59
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
60
        if (intent != null && requestCode == SAF_REQUEST) otgRoot.value = intent.data
Geoffrey Métais's avatar
Geoffrey Métais committed
61
62
63
64
65
        else super.onActivityResult(requestCode, resultCode, intent)
        exit()
    }

    companion object {
66
        val otgRoot = MutableStateFlow<Uri?>(null)
Geoffrey Métais's avatar
Geoffrey Métais committed
67
68
69
    }
}

Geoffrey Métais's avatar
Geoffrey Métais committed
70
71
72
73
fun FragmentActivity.requestOtgRoot() {
    supportFragmentManager.beginTransaction().add(OtgAccess(), TAG).commitAllowingStateLoss()
}

Geoffrey Métais's avatar
Geoffrey Métais committed
74
@WorkerThread
75
fun getDocumentFiles(context: Context, path: String) : List<MediaWrapper>? {
76
    val rootUri = OtgAccess.otgRoot.value ?: return null
Geoffrey Métais's avatar
Geoffrey Métais committed
77
78
79
80
//    else Uri.Builder().scheme("content")
//            .authority(OTG_CONTENT_AUTHORITY)
//            .path(path.substringBefore(':'))
//            .build()
81
    var documentFile = DocumentFile.fromTreeUri(context, rootUri)
Geoffrey Métais's avatar
Geoffrey Métais committed
82
83
84
85

    val parts = path.substringAfterLast(':').split("/".toRegex()).dropLastWhile { it.isEmpty() }
    for (part in parts) {
        if (part == "") continue
Geoffrey Métais's avatar
Geoffrey Métais committed
86
        documentFile = documentFile?.findFile(part)
Geoffrey Métais's avatar
Geoffrey Métais committed
87
88
89
90
91
92
93
94
    }

    if (documentFile == null) {
        Log.w(TAG, "Failed to find file")
        return null
    }

    // we have the end point DocumentFile, list the files inside it and return
95
    val list = mutableListOf<MediaWrapper>()
Geoffrey Métais's avatar
Geoffrey Métais committed
96
97
    for (file in documentFile.listFiles()) {
        if (file.exists() && file.canRead()) {
Geoffrey Métais's avatar
Geoffrey Métais committed
98
            if (file.name?.startsWith(".") == true) continue
99
            val mw = MLServiceLocator.getAbstractMediaWrapper(file.uri).apply {
Geoffrey Métais's avatar
Geoffrey Métais committed
100
                type = when {
101
102
103
                    file.isDirectory -> MediaWrapper.TYPE_DIR
                    file.type?.startsWith("video") == true -> MediaWrapper.TYPE_VIDEO
                    file.type?.startsWith("audio") == true -> MediaWrapper.TYPE_AUDIO
Geoffrey Métais's avatar
Geoffrey Métais committed
104
105
106
107
108
109
110
111
                    else -> type
                }
                title = file.name
            }
            list.add(mw)
        }
    }
    return list
Geoffrey Métais's avatar
Geoffrey Métais committed
112
}