Fixed import from external applications

This commit is contained in:
Dmitriy Karpushin 2023-03-29 16:09:46 +03:00
parent 7e663b05d5
commit 9e461ef6e1
8 changed files with 256 additions and 154 deletions

View file

@ -51,50 +51,10 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.vpn"/>
<data android:pathPattern=".*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.vpn"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.cfg"/>
<data android:pathPattern=".*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.cfg"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.conf"/>
<data android:pathPattern=".*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.conf"/>
<intent-filter>
<action android:name="org.amnezia.vpn.qt.IMPORT_CONFIG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
@ -118,6 +78,56 @@
<activity
android:name=".qt.CameraActivity"
android:exported="false" />
<activity
android:name=".qt.ImportConfigActivity"
android:exported="true" >
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.vpn"/>
<data android:pathPattern=".*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.vpn"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.vpn"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.cfg"/>
<data android:pathPattern=".*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.cfg"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.cfg"/>
</intent-filter>
<intent-filter android:label="AmneziaVPN">
<action android:name="android.intent.action.SEND"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="*/*"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.conf"/>
<data android:pathPattern=".*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.conf"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.conf"/>
</intent-filter>
</activity>
<service
android:name=".VPNService"

View file

@ -75,7 +75,7 @@ android {
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qt5AndroidDir - holds the path to qt android files
* - qtAndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
@ -106,7 +106,7 @@ android {
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']
jniLibs.srcDirs = ['libs']
androidTest.assets.srcDirs += files("${qt5AndroidDir}/schemas".toString())
androidTest.assets.srcDirs += files("${qtAndroidDir}/schemas".toString())
}
}
@ -140,7 +140,7 @@ android {
versionName "2.0.10" // Change to a higher number
javaCompileOptions.annotationProcessorOptions.arguments = [
"room.schemaLocation": "${qt5AndroidDir}/schemas".toString()
"room.schemaLocation": "${qtAndroidDir}/schemas".toString()
]
}

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View file

@ -0,0 +1,140 @@
package org.amnezia.vpn.qt
import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.content.ContentResolver
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.io.*
import org.amnezia.vpn.R
const val INTENT_ACTION_IMPORT_CONFIG = "org.amnezia.vpn.qt.IMPORT_CONFIG"
class ImportConfigActivity : Activity() {
private val STORAGE_PERMISSION_CODE = 42
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_import_config)
startReadConfig(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
startReadConfig(intent)
}
private fun startMainActivity(config: String?) {
if (config == null || config.length == 0) {
return
}
val activityIntent = Intent(applicationContext, VPNActivity::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 readConfig(newIntent: Intent, newIntentAction: String) {
if (isReadStorageAllowed()) {
val configString = processIntent(newIntent, newIntentAction)
startMainActivity(configString)
} else {
requestStoragePermission()
}
}
private fun requestStoragePermission() {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, 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 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
}
}
}
}

View file

@ -34,15 +34,23 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
private var configString: String? = null
private var vpnServiceBinder: IBinder? = null
private var isBound = false
set(value) {
field = value
if (value && configString != null) {
sendImportConfigCommand()
}
}
private val TAG = "VPNActivity"
private val STORAGE_PERMISSION_CODE = 42
private val CAMERA_ACTION_CODE = 101
private val CREATE_FILE_ACTION_CODE = 102
private var tmpFileContentToSave: String = ""
private val delayedCommands: ArrayList<Pair<Int, String>> = ArrayList()
companion object {
private lateinit var instance: VPNActivity
@ -72,16 +80,16 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
val newIntent = intent
val newIntentAction = newIntent.action
if (newIntent != null && newIntentAction != null) {
configString = processIntent(newIntent, newIntentAction)
}
super.onCreate(savedInstanceState)
instance = this
val newIntent = intent
val newIntentAction: String? = newIntent.action
if (newIntent != null && newIntentAction != null && newIntentAction == "org.amnezia.vpn.qt.IMPORT_CONFIG") {
configString = newIntent.getStringExtra("CONFIG")
}
}
private fun startQrCodeActivity() {
@ -123,9 +131,22 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
private fun dispatchParcel(actionCode: Int, body: String) {
if (!isBound) {
Log.d(TAG, "dispatchParcel: not bound")
delayedCommands.add(Pair(actionCode, body))
return
}
if (delayedCommands.size > 0) {
for (command in delayedCommands) {
processCommand(command.first, command.second)
}
delayedCommands.clear()
}
processCommand(actionCode, body)
}
private fun processCommand(actionCode: Int, body: String) {
val out: Parcel = Parcel.obtain()
out.writeByteArray(body.toByteArray())
@ -141,19 +162,15 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
}
override fun onNewIntent(newIntent: Intent) {
intent = newIntent
super.onNewIntent(intent)
setIntent(newIntent)
val newIntentAction = newIntent.action
if (newIntent != null && newIntentAction != null && newIntentAction != Intent.ACTION_MAIN) {
if (isReadStorageAllowed()) {
configString = processIntent(newIntent, newIntentAction)
} else {
requestStoragePermission()
}
}
super.onNewIntent(intent)
if (newIntent != null && newIntentAction != null && newIntentAction == INTENT_ACTION_IMPORT_CONFIG) {
configString = newIntent.getStringExtra("CONFIG")
}
}
override fun onResume() {
@ -164,84 +181,6 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
}
}
private fun isReadStorageAllowed(): Boolean {
val permissionStatus = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
return permissionStatus == PackageManager.PERMISSION_GRANTED
}
private fun requestStoragePermission() {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), STORAGE_PERMISSION_CODE)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) {
if (requestCode == STORAGE_PERMISSION_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Storage read permission granted")
if (configString == null) {
configString = processIntent(intent, intent.action!!)
}
if (configString != null) {
Log.d(TAG, "not empty")
sendImportConfigCommand()
} else {
Log.d(TAG, "empty")
}
} else {
Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show()
}
}
}
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)
Log.d(TAG, "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
Log.d(TAG, "File intent detected: " + action + " : " + intent.dataString + " : " + intent.type + " : " + name)
val input = resolver.openInputStream(uri)
return input?.bufferedReader()?.use(BufferedReader::readText)
}
}
return null
}
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
}
}
}
private fun sendImportConfigCommand() {
if (configString != null) {
val msg: Parcel = Parcel.obtain()
@ -257,7 +196,7 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
}
}
private var connection: ServiceConnection = object : ServiceConnection {
private fun createConnection() = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
vpnServiceBinder = binder
@ -283,6 +222,8 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
}
}
private var connection: ServiceConnection = createConnection()
private fun registerBinder(): Boolean {
val binder = VPNClientBinder()
val out: Parcel = Parcel.obtain()