Skip to content

Commit 5199460

Browse files
committed
Open file:// Uris as documents from Android N+
Because file:// can't be used with Scoped Storage anymore.
1 parent fd040e1 commit 5199460

File tree

2 files changed

+88
-7
lines changed

2 files changed

+88
-7
lines changed

app/src/main/kotlin/de/markusfisch/android/binaryeye/content/Share.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,15 @@ fun Context.startIntent(intent: Intent): Boolean = try {
3838
}
3939

4040
fun Context.openUrl(url: String, silent: Boolean = false): Boolean {
41-
val intent = Intent(Intent.ACTION_VIEW, url.parseAndNormalizeUri())
41+
return openUri(url.parseAndNormalizeUri(), silent)
42+
}
43+
44+
fun Context.openUri(uri: Uri, silent: Boolean = false): Boolean {
45+
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
46+
if (uri.scheme == "content") {
47+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
48+
}
49+
}
4250
return if (silent) {
4351
startIntent(intent)
4452
} else {

app/src/main/kotlin/de/markusfisch/android/binaryeye/fragment/DecodeFragment.kt

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package de.markusfisch.android.binaryeye.fragment
22

3+
import android.app.Activity
4+
import android.content.Intent
35
import android.net.Uri
46
import android.os.Build
57
import android.os.Bundle
8+
import android.os.Environment
9+
import android.provider.DocumentsContract
10+
import android.support.annotation.RequiresApi
611
import android.support.design.widget.FloatingActionButton
712
import android.support.v4.app.Fragment
813
import android.text.Editable
@@ -64,6 +69,7 @@ import kotlinx.coroutines.Dispatchers
6469
import kotlinx.coroutines.Job
6570
import kotlinx.coroutines.launch
6671
import kotlinx.coroutines.withContext
72+
import java.io.File
6773
import java.security.MessageDigest
6874
import kotlin.math.roundToInt
6975

@@ -96,6 +102,21 @@ class DecodeFragment : Fragment() {
96102
private var label: String? = null
97103
private var recreationSize = 0
98104

105+
override fun onActivityResult(
106+
requestCode: Int,
107+
resultCode: Int,
108+
resultData: Intent?
109+
) {
110+
when (requestCode) {
111+
OPEN_DOCUMENT -> {
112+
if (resultCode == Activity.RESULT_OK) {
113+
val uri = resultData?.data ?: return
114+
activity?.openPickedFile(uri)
115+
}
116+
}
117+
}
118+
}
119+
99120
override fun onCreate(state: Bundle?) {
100121
super.onCreate(state)
101122
setHasOptionsMenu(true)
@@ -216,11 +237,11 @@ class DecodeFragment : Fragment() {
216237
) = Unit
217238
})
218239
fab.setOnClickListener {
219-
executeAction(this.content)
240+
executeAction(content)
220241
}
221242
if (justScanned && prefs.openImmediately) {
222243
closeAutomatically = true
223-
executeAction(this.content)
244+
executeAction(content)
224245
}
225246
}
226247
}
@@ -575,22 +596,53 @@ class DecodeFragment : Fragment() {
575596
}
576597
}
577598

578-
private fun executeAction(content: String) {
599+
private fun executeAction(str: String) {
579600
val ac = activity ?: return
580-
if (content.isEmpty()) {
601+
if (str.isEmpty() || openLocalDocument(str)) {
581602
return
582603
}
583604
if (action is WifiAction &&
584605
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
585-
!ac.hasLocationPermission { executeAction(content) }
606+
!ac.hasLocationPermission { executeAction(str) }
586607
) {
587608
return
588609
}
589610
scope.launch {
590-
action.execute(ac, content.toByteArray())
611+
action.execute(ac, str.toByteArray())
591612
}
592613
}
593614

615+
private fun openLocalDocument(str: String): Boolean {
616+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N ||
617+
!str.startsWith(SCHEME_FILE)
618+
) {
619+
return false
620+
}
621+
// Needs to be disabled so we get the result.
622+
closeAutomatically = false
623+
// file:// can't be used with Scoped Storage anymore.
624+
startActivityForResult(
625+
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
626+
addCategory(Intent.CATEGORY_OPENABLE)
627+
type = "*/*"
628+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
629+
val primary = str.substring(
630+
SCHEME_FILE.length
631+
).localDirName() ?: Environment.getExternalStorageDirectory().path
632+
putExtra(
633+
DocumentsContract.EXTRA_INITIAL_URI,
634+
DocumentsContract.buildDocumentUri(
635+
"com.android.externalstorage.documents",
636+
"primary:$primary"
637+
)
638+
)
639+
}
640+
},
641+
OPEN_DOCUMENT
642+
)
643+
return true
644+
}
645+
594646
private fun askForFileNameAndSave(raw: ByteArray) {
595647
val ac = activity ?: return
596648
// Write permission is only required before Android Q.
@@ -617,6 +669,8 @@ class DecodeFragment : Fragment() {
617669

618670
companion object {
619671
private const val SCAN = "scan"
672+
private const val OPEN_DOCUMENT = 1
673+
private const val SCHEME_FILE = "file://"
620674

621675
fun newInstance(scan: Scan): Fragment {
622676
val args = Bundle()
@@ -810,3 +864,22 @@ private fun String.fold(): CharSequence {
810864
this
811865
}
812866
}
867+
868+
private fun String.localDirName(): String? = when {
869+
endsWith("/") -> this
870+
else -> File(this).parentFile?.absolutePath
871+
}?.replaceFirst(Regex("^/storage/emulated/0/"), "")
872+
?.replaceFirst(Regex("^/sdcard/"), "")
873+
?.trim('/')
874+
875+
private fun Activity.openPickedFile(uri: Uri) {
876+
startActivity(
877+
Intent.createChooser(
878+
Intent(Intent.ACTION_VIEW).apply {
879+
setDataAndType(uri, contentResolver.getType(uri) ?: "*/*")
880+
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
881+
},
882+
getString(R.string.open_url)
883+
)
884+
)
885+
}

0 commit comments

Comments
 (0)