Skip to content

Commit

Permalink
fix: add a workaround to open files on Android TV due to lack of SAF
Browse files Browse the repository at this point in the history
  • Loading branch information
albexk committed Nov 14, 2024
1 parent e663fe9 commit 1d1ddd9
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 50 deletions.
7 changes: 7 additions & 0 deletions client/android/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@
android:exported="false"
android:theme="@style/Translucent" />

<activity android:name=".TvFilePicker"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />

<activity
android:name=".ImportConfigActivity"
android:excludeFromRecents="true"
Expand Down
1 change: 1 addition & 0 deletions client/android/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@
<string name="openNotificationSettings">Открыть настройки уведомлений</string>

<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
<string name="tvChooserTitle">Выберете файловый менеджер</string>
</resources>
1 change: 1 addition & 0 deletions client/android/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@
<string name="openNotificationSettings">Open notification settings</string>

<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
<string name="tvChooserTitle">Choose a file browser</string>
</resources>
122 changes: 72 additions & 50 deletions client/android/src/org/amnezia/vpn/AmneziaActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -520,21 +520,25 @@ class AmneziaActivity : QtActivity() {
type = "text/*"
putExtra(Intent.EXTRA_TITLE, fileName)
}.also {
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = {
it?.data?.let { uri ->
Log.v(TAG, "Save file to $uri")
try {
contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) }
try {
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = {
it?.data?.let { uri ->
Log.v(TAG, "Save file to $uri")
try {
contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) }
}
} catch (e: IOException) {
Log.e(TAG, "Failed to save file $uri: $e")
// todo: send error to Qt
}
} catch (e: IOException) {
Log.e(TAG, "Failed to save file $uri: $e")
// todo: send error to Qt
}
}
}
))
))
} catch (_: ActivityNotFoundException) {
Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
}
}
}
}
Expand All @@ -543,55 +547,73 @@ class AmneziaActivity : QtActivity() {
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
mainScope.launch {
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map {
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
}.toSet()
} else emptySet()

Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes")
if ("*/*" in mimeTypes) {
type = "*/*"
} else {
when (mimeTypes.size) {
1 -> type = mimeTypes.first()
val intent = if (!isOnTv()) {
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map {
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
}.toSet()
} else emptySet()

Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes")
if ("*/*" in mimeTypes) {
type = "*/*"
} else {
when (mimeTypes.size) {
1 -> type = mimeTypes.first()

in 2..Int.MAX_VALUE -> {
type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}
in 2..Int.MAX_VALUE -> {
type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}

else -> type = "*/*"
else -> type = "*/*"
}
}
}
}.also {
if (packageManager.resolveActivity(it, PackageManager.MATCH_DEFAULT_ONLY) == null) {
Log.w(TAG, "Not found activity for ACTION_OPEN_DOCUMENT intent")
it.action = Intent.ACTION_GET_CONTENT
}
} else {
Intent(this@AmneziaActivity, TvFilePicker::class.java)
}

try {
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = {
val uri = it?.data?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
try {
startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = {
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
showNoFileBrowserAlertDialog()
}
))
} catch (_: ActivityNotFoundException) {
Toast.makeText(this@AmneziaActivity, resources.getText(R.string.tvNoFileBrowser), Toast.LENGTH_LONG).show()
val uri = it?.data?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
))
} catch (_: ActivityNotFoundException) {
showNoFileBrowserAlertDialog()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened("")
}
}
}
}

private fun showNoFileBrowserAlertDialog() {
AlertDialog.Builder(this)
.setMessage(R.string.tvNoFileBrowser)
.setCancelable(false)
.setPositiveButton(android.R.string.ok) { _, _ ->
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
} catch (_: Throwable) {}
}
.show()
}

@Suppress("unused")
fun getFd(fileName: String): Int = try {
Log.v(TAG, "Get fd for $fileName")
Expand Down
41 changes: 41 additions & 0 deletions client/android/src/org/amnezia/vpn/TvFilePicker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.amnezia.vpn

import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import org.amnezia.vpn.util.Log

private const val TAG = "TvFilePicker"

class TvFilePicker : ComponentActivity() {

private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
setResult(RESULT_OK, Intent().apply { data = it })
finish()
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "onCreate")
getFile()
}

override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent")
getFile()
}

private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch("*/*")
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
finish()
}
}
}

0 comments on commit 1d1ddd9

Please sign in to comment.