diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 6dfd09831..96f60f539 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -91,6 +91,13 @@ android:exported="false" android:theme="@style/Translucent" /> + + Открыть настройки уведомлений Пожалуйста, установите приложение для просмотра файлов + Выберете файловый менеджер \ No newline at end of file diff --git a/client/android/res/values/strings.xml b/client/android/res/values/strings.xml index bf8d76d1d..9a8ecce02 100644 --- a/client/android/res/values/strings.xml +++ b/client/android/res/values/strings.xml @@ -25,4 +25,5 @@ Open notification settings Please install a file management utility to browse files + Choose a file browser \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 29a6d06ba..0dd3d3192 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -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() + } } } } @@ -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") diff --git a/client/android/src/org/amnezia/vpn/TvFilePicker.kt b/client/android/src/org/amnezia/vpn/TvFilePicker.kt new file mode 100644 index 000000000..f3048509e --- /dev/null +++ b/client/android/src/org/amnezia/vpn/TvFilePicker.kt @@ -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() + } + } +}