From 195bdb947ea8b27231bd37460c6e69b26a8b65d0 Mon Sep 17 00:00:00 2001 From: albexk Date: Mon, 11 Dec 2023 22:56:01 +0300 Subject: [PATCH] Refactor import config Remove the path filter, as the content path may not contain a filename. Disable import when viewing files. Config can be imported from: - shared file - shared text - vpn:// link --- client/android/AndroidManifest.xml | 55 ++---- .../res/layout/activity_import_config.xml | 5 - .../src/org/amnezia/vpn/AmneziaActivity.kt | 24 ++- .../org/amnezia/vpn/ImportConfigActivity.kt | 163 ++++++------------ .../org/amnezia/vpn/qt/QtAndroidController.kt | 2 +- .../platforms/android/android_controller.cpp | 25 +-- client/platforms/android/android_controller.h | 6 +- 7 files changed, 109 insertions(+), 171 deletions(-) delete mode 100644 client/android/res/layout/activity_import_config.xml diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 19e31c3c..5765167a 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -17,8 +17,6 @@ - - @@ -78,57 +76,26 @@ + android:excludeFromRecents="true" + android:launchMode="singleTask" + android:taskAffinity="" + android:exported="true" + android:theme="@style/Translucent"> - + - - - - - - - - - - - + + - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/client/android/res/layout/activity_import_config.xml b/client/android/res/layout/activity_import_config.xml deleted file mode 100644 index 2a1d3660..00000000 --- a/client/android/res/layout/activity_import_config.xml +++ /dev/null @@ -1,5 +0,0 @@ - - \ 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 cc2cee25..44f03e88 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -2,6 +2,7 @@ package org.amnezia.vpn import android.content.ComponentName import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY import android.content.ServiceConnection import android.net.Uri import android.net.VpnService @@ -140,12 +141,33 @@ class AmneziaActivity : QtActivity() { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.v(TAG, "Create Amnezia activity") + Log.v(TAG, "Create Amnezia activity: $intent") mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) vpnServiceMessenger = IpcMessenger( onDeadObjectException = ::doUnbindService, messengerName = "VpnService" ) + intent?.let(::processIntent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + Log.v(TAG, "onNewIntent: $intent") + intent?.let(::processIntent) + } + + private fun processIntent(intent: Intent) { + // disable config import when starting activity from history + if (intent.flags and FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == 0) { + if (intent.action == ACTION_IMPORT_CONFIG) { + intent.getStringExtra(EXTRA_CONFIG)?.let { + mainScope.launch { + qtInitialized.await() + QtAndroidController.onConfigImported(it) + } + } + } + } } override fun onStart() { diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt index bba744d6..2eaad731 100644 --- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt +++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt @@ -1,137 +1,88 @@ package org.amnezia.vpn -import android.Manifest -import android.app.Activity -import android.content.pm.PackageManager -import android.content.ContentResolver import android.content.Intent +import android.content.Intent.ACTION_SEND +import android.content.Intent.ACTION_VIEW +import android.content.Intent.CATEGORY_DEFAULT +import android.content.Intent.EXTRA_TEXT +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.net.Uri +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES import android.os.Bundle -import android.provider.MediaStore -import android.widget.Toast -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat +import androidx.activity.ComponentActivity +import java.io.BufferedReader +import org.amnezia.vpn.util.Log -import java.io.* +private const val TAG = "ImportConfigActivity" -const val INTENT_ACTION_IMPORT_CONFIG = "org.amnezia.vpn.IMPORT_CONFIG" +const val ACTION_IMPORT_CONFIG = "org.amnezia.vpn.IMPORT_CONFIG" +const val EXTRA_CONFIG = "CONFIG" -class ImportConfigActivity : Activity() { - - private val STORAGE_PERMISSION_CODE = 42 +class ImportConfigActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_import_config) - startReadConfig(intent) + Log.v(TAG, "Create Import Config Activity: $intent") + intent?.let(::readConfig) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - startReadConfig(intent) + Log.v(TAG, "onNewIntent: $intent") + intent?.let(::readConfig) } - private fun startMainActivity(config: String?) { + private fun readConfig(intent: Intent) { + when (intent.action) { + ACTION_SEND -> { + Log.v(TAG, "Process SEND action, type: ${intent.type}") + when (intent.type) { + "application/octet-stream" -> + processStream(intent) - if (config == null || config.length == 0) { - return + "text/plain" -> { + intent.getStringExtra(EXTRA_TEXT)?.let(::startMainActivity) + } + } + } + + ACTION_VIEW -> { + Log.v(TAG, "Process VIEW action") + intent.data?.toString()?.let(::startMainActivity) + } } - - val activityIntent = Intent(applicationContext, AmneziaActivity::class.java) - activityIntent.action = INTENT_ACTION_IMPORT_CONFIG - activityIntent.addCategory("android.intent.category.DEFAULT") - activityIntent.putExtra("CONFIG", config) - - startActivity(activityIntent) finish() } - private fun startReadConfig(intent: Intent?) { - val newIntent = intent - val newIntentAction: String = newIntent?.action ?: "" - - if (newIntent != null && newIntentAction == Intent.ACTION_VIEW) { - readConfig(newIntent, newIntentAction) + private fun processStream(intent: Intent) { + getUriCompat(intent)?.let { uri -> + contentResolver.openInputStream(uri)?.use { + it.bufferedReader().use(BufferedReader::readText).let(::startMainActivity) + } } } - private fun readConfig(newIntent: Intent, newIntentAction: String) { - if (isReadStorageAllowed()) { - val configString = processIntent(newIntent, newIntentAction) - startMainActivity(configString) + private fun getUriCompat(intent: Intent): Uri? = + if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java) } else { - requestStoragePermission() + @Suppress("DEPRECATION") + intent.getParcelableExtra(Intent.EXTRA_STREAM) } - } - private fun requestStoragePermission() { - ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE) - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (requestCode == STORAGE_PERMISSION_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - val configString = processIntent(intent, intent.action!!) - - if (configString != null) { - startMainActivity(configString) - } - } else { - Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show() + private fun startMainActivity(config: String) { + if (config.isNotBlank()) { + Log.v(TAG, "startMainActivity") + Intent(applicationContext, AmneziaActivity::class.java).apply { + action = ACTION_IMPORT_CONFIG + addCategory(CATEGORY_DEFAULT) + putExtra(EXTRA_CONFIG, config) + flags = FLAG_ACTIVITY_NEW_TASK + }.also { + startActivity(it) } } + finish() } - - private fun processIntent(intent: Intent, action: String): String? { - val scheme = intent.scheme - - if (scheme == null) { - return null - } - - if (action.compareTo(Intent.ACTION_VIEW) == 0) { - val resolver = contentResolver - - if (scheme.compareTo(ContentResolver.SCHEME_CONTENT) == 0) { - val uri = intent.data - val name: String? = getContentName(resolver, uri) - - println("Content intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) - - val input = resolver.openInputStream(uri!!) - - return input?.bufferedReader()?.use(BufferedReader::readText) - } else if (scheme.compareTo(ContentResolver.SCHEME_FILE) == 0) { - val uri = intent.data - val name = uri!!.lastPathSegment - - println("File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name) - - val input = resolver.openInputStream(uri) - - return input?.bufferedReader()?.use(BufferedReader::readText) - } - } - - return null - } - - private fun isReadStorageAllowed(): Boolean { - val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - return permissionStatus == PackageManager.PERMISSION_GRANTED - } - - private fun getContentName(resolver: ContentResolver?, uri: Uri?): String? { - val cursor = resolver!!.query(uri!!, null, null, null, null) - - cursor.use { - cursor!!.moveToFirst() - val nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) - return if (nameIndex >= 0) { - return cursor.getString(nameIndex) - } else { - null - } - } - } -} \ No newline at end of file +} diff --git a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt index 30102a4a..bc8cc425 100644 --- a/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt +++ b/client/android/src/org/amnezia/vpn/qt/QtAndroidController.kt @@ -15,7 +15,7 @@ object QtAndroidController { external fun onVpnReconnecting() external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long) - external fun onConfigImported() + external fun onConfigImported(data: String) external fun decodeQrCode(data: String): Boolean } \ No newline at end of file diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 384ef131..a739bee3 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -77,14 +77,9 @@ AndroidController::AndroidController() : QObject() connect( this, &AndroidController::configImported, this, - []() { - // todo: not yet implemented - qDebug() << "Transact: config import"; - /*auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - - QString buffer = doc.object()["config"].toString(); - qDebug() << "Transact: config string" << buffer; - importConfigFromOutside(buffer);*/ + [this](const QString& config) { + qDebug() << "Android event: config import"; + emit importConfigFromOutside(config); }, Qt::QueuedConnection); } @@ -111,7 +106,7 @@ bool AndroidController::initialize() {"onVpnDisconnected", "()V", reinterpret_cast(onVpnDisconnected)}, {"onVpnReconnecting", "()V", reinterpret_cast(onVpnReconnecting)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast(onStatisticsUpdate)}, - {"onConfigImported", "()V", reinterpret_cast(onConfigImported)}, + {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast(onConfigImported)}, {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast(decodeQrCode)} }; @@ -290,12 +285,20 @@ void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBy } // static -void AndroidController::onConfigImported(JNIEnv *env, jobject thiz) +void AndroidController::onConfigImported(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(env); Q_UNUSED(thiz); - emit AndroidController::instance()->configImported(); + const char *buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + return; + } + + QString config(buffer); + env->ReleaseStringUTFChars(data, buffer); + + emit AndroidController::instance()->configImported(config); } // static diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index d902398d..4e72cbdf 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -43,8 +43,8 @@ signals: void vpnDisconnected(); void vpnReconnecting(); void statisticsUpdated(quint64 rxBytes, quint64 txBytes); - void configImported(); - void importConfigFromOutside(QString &data); + void configImported(QString config); + void importConfigFromOutside(QString config); void initConnectionState(Vpn::ConnectionState state); private: @@ -64,7 +64,7 @@ private: static void onVpnDisconnected(JNIEnv *env, jobject thiz); static void onVpnReconnecting(JNIEnv *env, jobject thiz); static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes); - static void onConfigImported(JNIEnv *env, jobject thiz); + static void onConfigImported(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); template