Merge remote-tracking branch 'remotes/origin/dev' into feature/qt6-libssh-support
This commit is contained in:
commit
4aba34c18b
28 changed files with 594 additions and 264 deletions
25
README.md
25
README.md
|
@ -96,6 +96,31 @@ Build might fail with "source files not found" error the first time you try it,
|
|||
dependencies in parallel, and some dependencies end up being built after the ones that
|
||||
require them. In this case simply restart the build.
|
||||
|
||||
## How to build the Android app
|
||||
_tested on Mac OS_
|
||||
|
||||
The Android app has the following requirements:
|
||||
* JDK 11
|
||||
* Android platform SDK 33
|
||||
* cmake 3.25.0
|
||||
|
||||
After you have installed QT, QT Creator and Android Studio installed, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||
* set path to jdk 11
|
||||
* set path to Android SDK ($ANDROID_HOME)
|
||||
|
||||
In case you get errors regarding missing SDK or 'sdkmanager not running', you cannot fix them by correcting the paths and you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and click on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
||||
|
||||
Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake.
|
||||
|
||||
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platfrom SDK`.
|
||||
|
||||
That's it you should be ready to compile the project from QT Creator!
|
||||
|
||||
### Development flow
|
||||
After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
|
||||
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated proejct, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes!
|
||||
|
||||
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the geneated project's root directory (`<path>/client/android-build/.`) and you should be good to continue.
|
||||
|
||||
## License
|
||||
GPL v.3
|
||||
|
|
|
@ -65,6 +65,7 @@ include_directories(
|
|||
)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
|
||||
|
@ -92,6 +93,7 @@ if(NOT IOS)
|
|||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
]
|
||||
}
|
||||
|
||||
|
|
5
client/android/res/layout/activity_import_config.xml
Normal file
5
client/android/res/layout/activity_import_config.xml
Normal 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" />
|
|
@ -35,7 +35,7 @@ androidExtensions {
|
|||
}
|
||||
|
||||
//def lifecycleVersion = '2.0.0'
|
||||
//def roomVersion = '2.0.0'
|
||||
def roomVersion = "2.4.3"
|
||||
//def preferencexVersion = '1.0.0'
|
||||
dependencies {
|
||||
|
||||
|
@ -45,13 +45,12 @@ dependencies {
|
|||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
|
||||
|
||||
|
||||
implementation "androidx.core:core-ktx:1.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:2.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
|
||||
implementation "androidx.room:room-runtime:2.2.5" // runtime
|
||||
implementation "androidx.room:room-runtime:$roomVersion" // runtime
|
||||
implementation "androidx.preference:preference:1.1.0"
|
||||
implementation "androidx.work:work-runtime-ktx:2.7.1"
|
||||
implementation "androidx.browser:browser:1.3.0-alpha01"
|
||||
|
@ -65,6 +64,7 @@ dependencies {
|
|||
// api "com.takisoft.preferencex:preferencex:1.0.0"
|
||||
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
|
||||
api 'org.connectbot.jsocks:jsocks:1.0.0'
|
||||
kapt "androidx.room:room-compiler:2.2.5"
|
||||
|
||||
kapt "androidx.room:room-compiler:$roomVersion"
|
||||
kapt "androidx.lifecycle:lifecycle-compiler:2.4.0"
|
||||
}
|
||||
|
|
|
@ -287,6 +287,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
|||
}
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
if (value) {
|
||||
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "")
|
||||
mConnectionTime = System.currentTimeMillis()
|
||||
|
@ -886,45 +888,4 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
|
|||
class CloseableFd(val fd: FileDescriptor) : Closeable {
|
||||
override fun close() = Os.close(fd)
|
||||
}
|
||||
|
||||
fun saveAsFile(configContent: String?, suggestedFileName: String): String {
|
||||
val rootDirPath = cacheDir.absolutePath
|
||||
val rootDir = File(rootDirPath)
|
||||
|
||||
if (!rootDir.exists()) {
|
||||
rootDir.mkdirs()
|
||||
}
|
||||
|
||||
val fileName = if (!TextUtils.isEmpty(suggestedFileName)) suggestedFileName else "amnezia.cfg"
|
||||
|
||||
val file = File(rootDir, fileName)
|
||||
|
||||
try {
|
||||
file.bufferedWriter().use { out -> out.write(configContent) }
|
||||
return file.toString()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
fun shareFile(attachmentFile: String?) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_SEND)
|
||||
intent.type = "text/*"
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
val file = File(attachmentFile)
|
||||
val uri = FileProvider.getUriForFile(this, "${BuildConfig.APPLICATION_ID}.fileprovider", file)
|
||||
intent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
val createChooser = Intent.createChooser(intent, "Config sharing")
|
||||
createChooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
startActivity(createChooser)
|
||||
} catch (e: Exception) {
|
||||
Log.i(tag, e.message.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ class VPNServiceBinder(service: VPNService) : Binder() {
|
|||
const val resumeActivate = 7
|
||||
const val setNotificationText = 8
|
||||
const val setFallBackNotification = 9
|
||||
const val shareConfig = 10
|
||||
const val importConfig = 11
|
||||
}
|
||||
|
||||
|
@ -139,20 +138,6 @@ class VPNServiceBinder(service: VPNService) : Binder() {
|
|||
return true
|
||||
}
|
||||
|
||||
ACTIONS.shareConfig -> {
|
||||
val byteArray = data.createByteArray()
|
||||
val json = byteArray?.let { String(it) }
|
||||
val config = JSONObject(json)
|
||||
val configContent = config.getString("data")
|
||||
val suggestedName = config.getString("suggestedName")
|
||||
|
||||
val filePath = mService.saveAsFile(configContent, suggestedName)
|
||||
Log.i(tag, "save file: $filePath")
|
||||
|
||||
mService.shareFile(filePath)
|
||||
return true
|
||||
}
|
||||
|
||||
ACTIONS.importConfig -> {
|
||||
val buffer = data.readString()
|
||||
|
||||
|
@ -196,7 +181,6 @@ class VPNServiceBinder(service: VPNService) : Binder() {
|
|||
try {
|
||||
mListener?.let {
|
||||
if (it.isBinderAlive) {
|
||||
Log.i(tag, "Dispatching event: binder alive")
|
||||
val data = Parcel.obtain()
|
||||
data.writeByteArray(payload?.toByteArray(charset("UTF-8")))
|
||||
it.transact(code, data, Parcel.obtain(), 0)
|
||||
|
|
|
@ -2,11 +2,11 @@ package org.amnezia.vpn.qt
|
|||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.*
|
||||
|
@ -17,9 +17,9 @@ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import org.amnezia.vpn.R
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import org.amnezia.vpn.R
|
||||
|
||||
|
||||
class CameraActivity : AppCompatActivity() {
|
||||
|
@ -78,7 +78,7 @@ class CameraActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
@SuppressLint("UnsafeOptInUsageError", "ClickableViewAccessibility")
|
||||
private fun configureVideoPreview() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||
val imageCapture = ImageCapture.Builder().build()
|
||||
|
@ -101,7 +101,20 @@ class CameraActivity : AppCompatActivity() {
|
|||
try {
|
||||
preview.setSurfaceProvider(viewFinder.surfaceProvider)
|
||||
cameraProvider.unbindAll()
|
||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase)
|
||||
val camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture, analysisUseCase)
|
||||
viewFinder.setOnTouchListener(View.OnTouchListener { view: View, motionEvent: MotionEvent ->
|
||||
when (motionEvent.action) {
|
||||
MotionEvent.ACTION_DOWN -> return@OnTouchListener true
|
||||
MotionEvent.ACTION_UP -> {
|
||||
val factory = viewFinder.meteringPointFactory
|
||||
val point = factory.createPoint(motionEvent.x, motionEvent.y)
|
||||
val action = FocusMeteringAction.Builder(point).build()
|
||||
camera.cameraControl.startFocusAndMetering(action)
|
||||
return@OnTouchListener true
|
||||
}
|
||||
else -> return@OnTouchListener false
|
||||
}
|
||||
})
|
||||
} catch(exc: Exception) {
|
||||
Log.e("WUTT", "Use case binding failed", exc)
|
||||
}
|
||||
|
@ -146,7 +159,6 @@ class CameraActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
imageProxy.close()
|
||||
}
|
||||
.addOnFailureListener {
|
||||
|
|
140
client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt
Normal file
140
client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
package org.amnezia.vpn.qt;
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
|
@ -32,11 +34,22 @@ 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
|
||||
|
@ -56,19 +69,27 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
@JvmStatic fun sendToService(actionCode: Int, body: String) {
|
||||
VPNActivity.getInstance().dispatchParcel(actionCode, body)
|
||||
}
|
||||
|
||||
@JvmStatic fun saveFileAs(fileContent: String, suggestedName: String) {
|
||||
VPNActivity.getInstance().saveFile(fileContent, suggestedName)
|
||||
}
|
||||
|
||||
@JvmStatic fun putTextToClipboard(text: String) {
|
||||
VPNActivity.getInstance().putToClipboard(text)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -76,6 +97,18 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
startActivityForResult(intent, CAMERA_ACTION_CODE)
|
||||
}
|
||||
|
||||
private fun saveFile(fileContent: String, suggestedName: String) {
|
||||
tmpFileContentToSave = fileContent
|
||||
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "text/*"
|
||||
putExtra(Intent.EXTRA_TITLE, suggestedName)
|
||||
}
|
||||
|
||||
startActivityForResult(intent, CREATE_FILE_ACTION_CODE)
|
||||
}
|
||||
|
||||
override fun getSystemService(name: String): Any? {
|
||||
return if (Build.VERSION.SDK_INT >= 29 && name == "clipboard") {
|
||||
// QT will always attempt to read the clipboard if content is there.
|
||||
|
@ -98,11 +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
|
||||
} else {
|
||||
Log.d(TAG, "dispatchParcel: bound")
|
||||
}
|
||||
|
||||
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())
|
||||
|
||||
|
@ -118,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() {
|
||||
|
@ -141,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()
|
||||
|
@ -234,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
|
||||
|
||||
|
@ -260,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()
|
||||
|
@ -330,13 +294,38 @@ class VPNActivity : org.qtproject.qt.android.bindings.QtActivity() {
|
|||
val extra = data?.getStringExtra("result") ?: ""
|
||||
onActivityMessage(UI_EVENT_QR_CODE_RECEIVED, extra)
|
||||
}
|
||||
|
||||
if (requestCode == CREATE_FILE_ACTION_CODE && resultCode == RESULT_OK) {
|
||||
data?.data?.also { uri ->
|
||||
alterDocument(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) {
|
||||
onBackPressed()
|
||||
return true
|
||||
private fun alterDocument(uri: Uri) {
|
||||
try {
|
||||
applicationContext.contentResolver.openFileDescriptor(uri, "w")?.use { fd ->
|
||||
FileOutputStream(fd.fileDescriptor).use { fos ->
|
||||
fos.write(tmpFileContentToSave.toByteArray())
|
||||
}
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
tmpFileContentToSave = ""
|
||||
}
|
||||
|
||||
private fun putToClipboard(text: String) {
|
||||
this.runOnUiThread {
|
||||
val clipboard = applicationContext.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager?
|
||||
|
||||
if (clipboard != null) {
|
||||
val clip: ClipData = ClipData.newPlainText("", text)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
}
|
||||
return super.onKeyDown(keyCode, event)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "amnezia_application.h"
|
||||
#include "defines.h"
|
||||
#include "migrations.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
|
@ -16,6 +17,9 @@
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Migrations migrationsManager;
|
||||
migrationsManager.doMigrations();
|
||||
|
||||
QLoggingCategory::setFilterRules(QStringLiteral("qtc.ssh=false"));
|
||||
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling, true);
|
||||
|
||||
|
|
86
client/migrations.cpp
Normal file
86
client/migrations.cpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#include "migrations.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "defines.h"
|
||||
|
||||
Migrations::Migrations(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
QString version(APP_MAJOR_VERSION);
|
||||
|
||||
QStringList versionDigits = version.split(".");
|
||||
|
||||
if (versionDigits.size() >= 3) {
|
||||
currentMajor = versionDigits[0].toInt();
|
||||
currentMinor = versionDigits[1].toInt();
|
||||
currentMicro = versionDigits[2].toInt();
|
||||
}
|
||||
|
||||
if (versionDigits.size() == 4) {
|
||||
currentPatch = versionDigits[3].toInt();
|
||||
}
|
||||
}
|
||||
|
||||
void Migrations::doMigrations()
|
||||
{
|
||||
if (currentMajor == 3) {
|
||||
migrateV3();
|
||||
}
|
||||
}
|
||||
|
||||
void Migrations::migrateV3()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
qDebug() << "Migration to V3 on Android...";
|
||||
|
||||
QString packageName = "org.amnezia.vpn";
|
||||
|
||||
QDir dir(".");
|
||||
QString currentDir = dir.absolutePath();
|
||||
|
||||
int packageNameIndex = currentDir.indexOf(packageName);
|
||||
|
||||
if (packageNameIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString rootLocation = currentDir.left(packageNameIndex + packageName.size());
|
||||
|
||||
if (rootLocation.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString location = rootLocation + "/files/.config/AmneziaVPN.ORG/AmneziaVPN.conf";
|
||||
|
||||
QFile oldConfig(location);
|
||||
|
||||
if (oldConfig.exists()) {
|
||||
QString newConfigPath = rootLocation + "/files/settings";
|
||||
|
||||
QDir newConfigDir(newConfigPath);
|
||||
|
||||
newConfigPath += "/AmneziaVPN.ORG";
|
||||
|
||||
bool mkPathRes = newConfigDir.mkpath(newConfigPath);
|
||||
|
||||
if (!mkPathRes) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile newConfigFile(newConfigPath + "/AmneziaVPN.conf");
|
||||
|
||||
if (!newConfigFile.exists()) {
|
||||
bool cpResult = QFile::copy(oldConfig.fileName(), newConfigFile.fileName());
|
||||
if (cpResult) {
|
||||
oldConfig.remove();
|
||||
QDir oldConfigDir(rootLocation + "/files/.config");
|
||||
oldConfigDir.rmdir("AmneziaVPN.ORG");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
24
client/migrations.h
Normal file
24
client/migrations.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef MIGRATIONS_H
|
||||
#define MIGRATIONS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class Migrations : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Migrations(QObject *parent = nullptr);
|
||||
|
||||
void doMigrations();
|
||||
|
||||
private:
|
||||
void migrateV3();
|
||||
|
||||
private:
|
||||
int currentMajor = 0;
|
||||
int currentMinor = 0;
|
||||
int currentMicro = 0;
|
||||
int currentPatch = 0;
|
||||
};
|
||||
|
||||
#endif // MIGRATIONS_H
|
|
@ -15,6 +15,7 @@
|
|||
#include "private/qandroidextras_p.h"
|
||||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
|
||||
#include "androidvpnactivity.h"
|
||||
#include "androidutils.h"
|
||||
|
||||
namespace {
|
||||
|
@ -54,6 +55,10 @@ AndroidController::AndroidController() : QObject()
|
|||
|
||||
isConnected = doc.object()["connected"].toBool();
|
||||
|
||||
if (isConnected) {
|
||||
emit scheduleStatusCheckSignal();
|
||||
}
|
||||
|
||||
emit initialized(
|
||||
true, isConnected,
|
||||
time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime());
|
||||
|
@ -66,9 +71,11 @@ AndroidController::AndroidController() : QObject()
|
|||
Q_UNUSED(parcelBody);
|
||||
qDebug() << "Transact: connected";
|
||||
|
||||
isConnected = true;
|
||||
if (!isConnected) {
|
||||
emit scheduleStatusCheckSignal();
|
||||
}
|
||||
|
||||
emit scheduleStatusCheckSignal();
|
||||
isConnected = true;
|
||||
|
||||
emit connectionStateChanged(VpnProtocol::Connected);
|
||||
}, Qt::QueuedConnection);
|
||||
|
@ -85,7 +92,6 @@ AndroidController::AndroidController() : QObject()
|
|||
connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this,
|
||||
[this](const QString& parcelBody) {
|
||||
qDebug() << "Transact: update";
|
||||
|
||||
auto doc = QJsonDocument::fromJson(parcelBody.toUtf8());
|
||||
|
||||
QString rx = doc.object()["rx_bytes"].toString();
|
||||
|
@ -203,12 +209,7 @@ void AndroidController::setNotificationText(const QString& title,
|
|||
}
|
||||
|
||||
void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) {
|
||||
QJsonObject rootObject;
|
||||
rootObject["data"] = configContent;
|
||||
rootObject["suggestedName"] = suggestedName;
|
||||
QJsonDocument doc(rootObject);
|
||||
|
||||
AndroidVPNActivity::sendToService(ServiceAction::ACTION_SHARE_CONFIG, doc.toJson());
|
||||
AndroidVPNActivity::saveFileAs(configContent, suggestedName);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -248,7 +249,7 @@ void AndroidController::cleanupBackendLogs() {
|
|||
}
|
||||
|
||||
void AndroidController::importConfig(const QString& data){
|
||||
m_startPageLogic->importConnectionFromCode(data);
|
||||
m_startPageLogic->selectConfigFormat(data);
|
||||
}
|
||||
|
||||
const QJsonObject &AndroidController::vpnConfig() const
|
||||
|
@ -266,6 +267,11 @@ void AndroidController::startQrReaderActivity()
|
|||
AndroidVPNActivity::instance()->startQrCodeReader();
|
||||
}
|
||||
|
||||
void AndroidController::copyTextToClipboard(QString text)
|
||||
{
|
||||
AndroidVPNActivity::instance()->copyTextToClipboard(text);
|
||||
}
|
||||
|
||||
void AndroidController::scheduleStatusCheckSlot()
|
||||
{
|
||||
QTimer::singleShot(1000, [this]() {
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "ui/pages_logic/StartPageLogic.h"
|
||||
|
||||
#include "protocols/vpnprotocol.h"
|
||||
#include "androidvpnactivity.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
|
@ -44,6 +43,7 @@ public:
|
|||
void setVpnConfig(const QJsonObject &newVpnConfig);
|
||||
|
||||
void startQrReaderActivity();
|
||||
void copyTextToClipboard(QString text);
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(VpnProtocol::VpnConnectionState state);
|
||||
|
|
|
@ -57,6 +57,22 @@ void AndroidVPNActivity::startQrCodeReader()
|
|||
QJniObject::callStaticMethod<void>(CLASSNAME, "startQrCodeReader", "()V");
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) {
|
||||
QJniObject::callStaticMethod<void>(
|
||||
CLASSNAME,
|
||||
"saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V",
|
||||
QJniObject::fromString(fileContent).object<jstring>(),
|
||||
QJniObject::fromString(suggestedFilename).object<jstring>());
|
||||
}
|
||||
|
||||
void AndroidVPNActivity::copyTextToClipboard(QString text)
|
||||
{
|
||||
QJniObject::callStaticMethod<void>(
|
||||
CLASSNAME,
|
||||
"putTextToClipboard", "(Ljava/lang/String;)V",
|
||||
QJniObject::fromString(text).object<jstring>());
|
||||
}
|
||||
|
||||
// static
|
||||
AndroidVPNActivity* AndroidVPNActivity::instance() {
|
||||
if (s_instance == nullptr) {
|
||||
|
@ -70,9 +86,9 @@ AndroidVPNActivity* AndroidVPNActivity::instance() {
|
|||
void AndroidVPNActivity::sendToService(ServiceAction type, const QString& data) {
|
||||
int messageType = (int)type;
|
||||
|
||||
QJniEnvironment env;
|
||||
QJniObject::callStaticMethod<void>(
|
||||
CLASSNAME, "sendToService", "(ILjava/lang/String;)V",
|
||||
CLASSNAME,
|
||||
"sendToService", "(ILjava/lang/String;)V",
|
||||
static_cast<int>(messageType),
|
||||
QJniObject::fromString(data).object<jstring>());
|
||||
}
|
||||
|
|
|
@ -75,6 +75,8 @@ public:
|
|||
static void sendToService(ServiceAction type, const QString& data);
|
||||
static void connectService();
|
||||
static void startQrCodeReader();
|
||||
static void saveFileAs(QString fileContent, QString suggestedFilename);
|
||||
static void copyTextToClipboard(QString text);
|
||||
|
||||
signals:
|
||||
void serviceConnected();
|
||||
|
|
|
@ -45,7 +45,6 @@ void AdvancedServerSettingsLogic::onPushButtonClearServerClicked()
|
|||
}
|
||||
|
||||
ErrorCode e = m_serverController->removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex));
|
||||
m_serverController->disconnectFromHost(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex));
|
||||
if (e) {
|
||||
emit uiLogic()->showWarningMessage(tr("Error occurred while cleaning the server.") + "\n" +
|
||||
tr("Error message: ") + errorString(e) + "\n" +
|
||||
|
|
|
@ -13,7 +13,7 @@ GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent):
|
|||
void GeneralSettingsLogic::onUpdatePage()
|
||||
{
|
||||
uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex();
|
||||
set_existsAnyServer(uiLogic()->m_selectedServerIndex >= 0);
|
||||
set_existsAnyServer(m_settings->serversCount() > 0);
|
||||
uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex());
|
||||
|
||||
set_pushButtonGeneralSettingsShareConnectionEnable(m_settings->haveAuthData(m_settings->defaultServerIndex()));
|
||||
|
|
|
@ -31,6 +31,7 @@ void ServerContainersLogic::onUpdatePage()
|
|||
p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex);
|
||||
|
||||
set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex));
|
||||
uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex);
|
||||
emit updatePage();
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ void ServerListLogic::onServerListPushbuttonDefaultClicked(int index)
|
|||
void ServerListLogic::onServerListPushbuttonSettingsClicked(int index)
|
||||
{
|
||||
uiLogic()->m_selectedServerIndex = index;
|
||||
uiLogic()->m_installCredentials = m_settings->serverCredentials(index);
|
||||
uiLogic()->goToPage(Page::ServerSettings);
|
||||
}
|
||||
|
||||
|
|
|
@ -181,14 +181,7 @@ void StartPageLogic::onPushButtonImportOpenFile()
|
|||
file.open(QIODevice::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
|
||||
auto configFormat = checkConfigFormat(QString(data));
|
||||
if (configFormat == ConfigTypes::OpenVpn) {
|
||||
importConnectionFromOpenVpnConfig(QString(data));
|
||||
} else if (configFormat == ConfigTypes::WireGuard) {
|
||||
importConnectionFromWireguardConfig(QString(data));
|
||||
} else {
|
||||
importConnectionFromCode(QString(data));
|
||||
}
|
||||
selectConfigFormat(QString(data));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
@ -198,6 +191,18 @@ void StartPageLogic::startQrDecoder()
|
|||
}
|
||||
#endif
|
||||
|
||||
void StartPageLogic::selectConfigFormat(QString configData)
|
||||
{
|
||||
auto configFormat = checkConfigFormat(configData);
|
||||
if (configFormat == ConfigTypes::OpenVpn) {
|
||||
importConnectionFromOpenVpnConfig(configData);
|
||||
} else if (configFormat == ConfigTypes::WireGuard) {
|
||||
importConnectionFromWireguardConfig(configData);
|
||||
} else {
|
||||
importConnectionFromCode(configData);
|
||||
}
|
||||
}
|
||||
|
||||
bool StartPageLogic::importConnection(const QJsonObject &profile)
|
||||
{
|
||||
ServerCredentials credentials;
|
||||
|
|
|
@ -35,6 +35,8 @@ public:
|
|||
Q_INVOKABLE void startQrDecoder();
|
||||
#endif
|
||||
|
||||
void selectConfigFormat(QString configData);
|
||||
|
||||
bool importConnection(const QJsonObject &profile);
|
||||
bool importConnectionFromCode(QString code);
|
||||
bool importConnectionFromQr(const QByteArray &data);
|
||||
|
|
|
@ -61,8 +61,12 @@ Window {
|
|||
|
||||
function close_page() {
|
||||
if (pageLoader.depth <= 1) {
|
||||
if (GC.isMobile()) {
|
||||
root.close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
pageLoader.currentItem.deactivated()
|
||||
pageLoader.pop()
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ void UiLogic::initalizeUiLogic()
|
|||
connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) {
|
||||
if (connected) {
|
||||
pageLogic<VpnLogic>()->onConnectionStateChanged(VpnProtocol::Connected);
|
||||
m_vpnConnection->restoreConnection();
|
||||
}
|
||||
});
|
||||
if (!AndroidController::instance()->initialize(pageLogic<StartPageLogic>())) {
|
||||
|
@ -234,9 +235,10 @@ void UiLogic::keyPressEvent(Qt::Key key)
|
|||
m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex()));
|
||||
break;
|
||||
case Qt::Key_Escape:
|
||||
case Qt::Key_Back:
|
||||
if (currentPage() == Page::Vpn) break;
|
||||
if (currentPage() == Page::ServerConfiguringProgress) break;
|
||||
case Qt::Key_Back:
|
||||
|
||||
// if (currentPage() == Page::Start && pagesStack.size() < 2) break;
|
||||
// if (currentPage() == Page::Sites &&
|
||||
// ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) {
|
||||
|
@ -253,10 +255,16 @@ void UiLogic::keyPressEvent(Qt::Key key)
|
|||
|
||||
void UiLogic::onCloseWindow()
|
||||
{
|
||||
if (m_settings->serversCount() == 0) qApp->quit();
|
||||
else {
|
||||
hide();
|
||||
#ifdef Q_OS_ANDROID
|
||||
qApp->quit();
|
||||
#else
|
||||
if (m_settings->serversCount() == 0)
|
||||
{
|
||||
qApp->quit();
|
||||
} else {
|
||||
emit hide();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QString UiLogic::containerName(int container)
|
||||
|
@ -344,8 +352,6 @@ void UiLogic::installServer(QPair<DockerContainer, QJsonObject> &container)
|
|||
errorCode = pageLogic<ServerConfiguringProgressLogic>()->doInstallAction(installAction, pageFunc, progressBarFunc,
|
||||
noButton, waitInfoFunc,
|
||||
busyInfoFunc, cancelButtonFunc);
|
||||
m_serverController->disconnectFromHost(m_installCredentials);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (!isServerCreated) {
|
||||
QJsonObject server;
|
||||
|
@ -476,7 +482,11 @@ void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &da
|
|||
|
||||
void UiLogic::copyToClipboard(const QString &text)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
AndroidController::instance()->copyTextToClipboard(text);
|
||||
#else
|
||||
qApp->clipboard()->setText(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) {
|
||||
|
@ -532,7 +542,6 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool createNewServer, bool &
|
|||
|
||||
QMap<DockerContainer, QJsonObject> installedContainers;
|
||||
ErrorCode errorCode = m_serverController->getAlreadyInstalledContainers(credentials, installedContainers);
|
||||
m_serverController->disconnectFromHost(credentials);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "../../platforms/android/android_controller.h"
|
||||
#include "protocols/android_vpnprotocol.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
|
@ -353,10 +352,8 @@ void VpnConnection::connectToVpn(int serverIndex,
|
|||
}
|
||||
m_vpnProtocol->prepare();
|
||||
#elif defined Q_OS_ANDROID
|
||||
Proto proto = ContainerProps::defaultProtocol(container);
|
||||
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
|
||||
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState);
|
||||
connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated);
|
||||
androidVpnProtocol = createDefaultAndroidVpnProtocol(container);
|
||||
createAndroidConnections(container);
|
||||
|
||||
m_vpnProtocol.reset(androidVpnProtocol);
|
||||
#elif defined Q_OS_IOS
|
||||
|
@ -373,16 +370,52 @@ void VpnConnection::connectToVpn(int serverIndex,
|
|||
m_vpnProtocol.reset(iosVpnProtocol);
|
||||
#endif
|
||||
|
||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState)));
|
||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||
|
||||
m_serverController->disconnectFromHost(credentials);
|
||||
createProtocolConnections();
|
||||
|
||||
e = m_vpnProtocol.data()->start();
|
||||
if (e) emit VpnProtocol::Error;
|
||||
}
|
||||
|
||||
void VpnConnection::createProtocolConnections() {
|
||||
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
|
||||
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState)));
|
||||
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void VpnConnection::restoreConnection() {
|
||||
createAndroidConnections();
|
||||
|
||||
m_vpnProtocol.reset(androidVpnProtocol);
|
||||
|
||||
createProtocolConnections();
|
||||
}
|
||||
|
||||
void VpnConnection::createAndroidConnections()
|
||||
{
|
||||
int serverIndex = m_settings->defaultServerIndex();
|
||||
DockerContainer container = m_settings->defaultContainer(serverIndex);
|
||||
|
||||
createAndroidConnections(container);
|
||||
}
|
||||
|
||||
void VpnConnection::createAndroidConnections(DockerContainer container)
|
||||
{
|
||||
androidVpnProtocol = createDefaultAndroidVpnProtocol(container);
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState);
|
||||
connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated);
|
||||
}
|
||||
|
||||
AndroidVpnProtocol* VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container)
|
||||
{
|
||||
Proto proto = ContainerProps::defaultProtocol(container);
|
||||
AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration);
|
||||
|
||||
return androidVpnProtocol;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString VpnConnection::bytesPerSecToText(quint64 bytes)
|
||||
{
|
||||
double mbps = bytes * 8 / 1e6;
|
||||
|
@ -401,8 +434,6 @@ void VpnConnection::disconnectFromVpn()
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
if (!m_vpnProtocol.data()) {
|
||||
emit connectionStateChanged(VpnProtocol::Disconnected);
|
||||
#ifdef Q_OS_ANDROID
|
||||
|
@ -415,11 +446,8 @@ void VpnConnection::disconnectFromVpn()
|
|||
|
||||
VpnProtocol::VpnConnectionState VpnConnection::connectionState()
|
||||
{
|
||||
|
||||
|
||||
if (!m_vpnProtocol) return VpnProtocol::Disconnected;
|
||||
return m_vpnProtocol->connectionState();
|
||||
|
||||
}
|
||||
|
||||
bool VpnConnection::isConnected() const
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
#include "core/ipcclient.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "protocols/android_vpnprotocol.h"
|
||||
#endif
|
||||
|
||||
class VpnConfigurator;
|
||||
class ServerController;
|
||||
|
||||
|
@ -61,6 +65,10 @@ public:
|
|||
const QString &remoteAddress() const;
|
||||
void addSitesRoutes(const QString &gw, Settings::RouteMode mode);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnection();
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
void connectToVpn(int serverIndex,
|
||||
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig);
|
||||
|
@ -101,6 +109,15 @@ private:
|
|||
#ifdef Q_OS_IOS
|
||||
IOSVpnProtocol * iosVpnProtocol{nullptr};
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID
|
||||
AndroidVpnProtocol* androidVpnProtocol = nullptr;
|
||||
|
||||
AndroidVpnProtocol* createDefaultAndroidVpnProtocol(DockerContainer container);
|
||||
void createAndroidConnections();
|
||||
void createAndroidConnections(DockerContainer container);
|
||||
#endif
|
||||
|
||||
void createProtocolConnections();
|
||||
};
|
||||
|
||||
#endif // VPNCONNECTION_H
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue