diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 50d6d0f7..605a4d09 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -59,6 +59,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
@@ -85,6 +86,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
diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml
index 22c828fb..be67c8ba 100644
--- a/client/android/AndroidManifest.xml
+++ b/client/android/AndroidManifest.xml
@@ -51,50 +51,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/android/shadowsocks/build.gradle b/client/android/shadowsocks/build.gradle
index 69ebd0bd..f1d738b2 100644
--- a/client/android/shadowsocks/build.gradle
+++ b/client/android/shadowsocks/build.gradle
@@ -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"
}
diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt
index 6ca99e87..412b1e45 100644
--- a/client/android/src/org/amnezia/vpn/VPNService.kt
+++ b/client/android/src/org/amnezia/vpn/VPNService.kt
@@ -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())
- }
- }
}
diff --git a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt
index 239269a5..bc44217e 100644
--- a/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt
+++ b/client/android/src/org/amnezia/vpn/VPNServiceBinder.kt
@@ -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)
diff --git a/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt
new file mode 100644
index 00000000..de175a98
--- /dev/null
+++ b/client/android/src/org/amnezia/vpn/qt/ImportConfigActivity.kt
@@ -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, 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
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt
index b5e8d5fb..d2b5b7ab 100644
--- a/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt
+++ b/client/android/src/org/amnezia/vpn/qt/VPNActivity.kt
@@ -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> = 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, 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)
}
}
diff --git a/client/main.cpp b/client/main.cpp
index 62685953..f20c5dd1 100644
--- a/client/main.cpp
+++ b/client/main.cpp
@@ -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);
diff --git a/client/migrations.cpp b/client/migrations.cpp
new file mode 100644
index 00000000..6fac6be7
--- /dev/null
+++ b/client/migrations.cpp
@@ -0,0 +1,86 @@
+#include "migrations.h"
+
+#include
+#include
+#include
+#include
+
+#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
+}
diff --git a/client/migrations.h b/client/migrations.h
new file mode 100644
index 00000000..ea6bae92
--- /dev/null
+++ b/client/migrations.h
@@ -0,0 +1,24 @@
+#ifndef MIGRATIONS_H
+#define MIGRATIONS_H
+
+#include
+
+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
diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp
index a86ab7ca..2e5641c9 100644
--- a/client/platforms/android/android_controller.cpp
+++ b/client/platforms/android/android_controller.cpp
@@ -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]() {
diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h
index 59ecd9b3..00b37225 100644
--- a/client/platforms/android/android_controller.h
+++ b/client/platforms/android/android_controller.h
@@ -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);
diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp
index 17639023..9431597b 100644
--- a/client/platforms/android/androidvpnactivity.cpp
+++ b/client/platforms/android/androidvpnactivity.cpp
@@ -57,6 +57,22 @@ void AndroidVPNActivity::startQrCodeReader()
QJniObject::callStaticMethod(CLASSNAME, "startQrCodeReader", "()V");
}
+void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) {
+ QJniObject::callStaticMethod(
+ CLASSNAME,
+ "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V",
+ QJniObject::fromString(fileContent).object(),
+ QJniObject::fromString(suggestedFilename).object());
+}
+
+void AndroidVPNActivity::copyTextToClipboard(QString text)
+{
+ QJniObject::callStaticMethod(
+ CLASSNAME,
+ "putTextToClipboard", "(Ljava/lang/String;)V",
+ QJniObject::fromString(text).object());
+}
+
// 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(
- CLASSNAME, "sendToService", "(ILjava/lang/String;)V",
+ CLASSNAME,
+ "sendToService", "(ILjava/lang/String;)V",
static_cast(messageType),
QJniObject::fromString(data).object());
}
diff --git a/client/platforms/android/androidvpnactivity.h b/client/platforms/android/androidvpnactivity.h
index 1bc1a522..8eeb5598 100644
--- a/client/platforms/android/androidvpnactivity.h
+++ b/client/platforms/android/androidvpnactivity.h
@@ -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();
diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp
index 8c78667d..7eb67adf 100644
--- a/client/ui/pages_logic/StartPageLogic.cpp
+++ b/client/ui/pages_logic/StartPageLogic.cpp
@@ -175,14 +175,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
@@ -192,6 +185,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;
diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h
index b3dea002..6f21c105 100644
--- a/client/ui/pages_logic/StartPageLogic.h
+++ b/client/ui/pages_logic/StartPageLogic.h
@@ -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);
diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml
index b8758384..d91c013f 100644
--- a/client/ui/qml/main.qml
+++ b/client/ui/qml/main.qml
@@ -61,8 +61,12 @@ Window {
function close_page() {
if (pageLoader.depth <= 1) {
+ if (GC.isMobile()) {
+ root.close()
+ }
return
}
+
pageLoader.currentItem.deactivated()
pageLoader.pop()
}
diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp
index 895a526c..cdfd1833 100644
--- a/client/ui/uilogic.cpp
+++ b/client/ui/uilogic.cpp
@@ -133,6 +133,7 @@ void UiLogic::initalizeUiLogic()
connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) {
if (connected) {
pageLogic()->onConnectionStateChanged(VpnProtocol::Connected);
+ m_vpnConnection->restoreConnection();
}
});
if (!AndroidController::instance()->initialize(pageLogic())) {
@@ -224,9 +225,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) {
@@ -243,10 +245,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)
@@ -466,7 +474,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) {
diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp
index e01f7e61..6d8aa493 100644
--- a/client/vpnconnection.cpp
+++ b/client/vpnconnection.cpp
@@ -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,9 +370,7 @@ 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)));
+ createProtocolConnections();
m_serverController->disconnectFromHost(credentials);
@@ -383,6 +378,46 @@ void VpnConnection::connectToVpn(int serverIndex,
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 +436,6 @@ void VpnConnection::disconnectFromVpn()
}
#endif
-
-
if (!m_vpnProtocol.data()) {
emit connectionStateChanged(VpnProtocol::Disconnected);
#ifdef Q_OS_ANDROID
@@ -415,11 +448,8 @@ void VpnConnection::disconnectFromVpn()
VpnProtocol::VpnConnectionState VpnConnection::connectionState()
{
-
-
if (!m_vpnProtocol) return VpnProtocol::Disconnected;
return m_vpnProtocol->connectionState();
-
}
bool VpnConnection::isConnected() const
diff --git a/client/vpnconnection.h b/client/vpnconnection.h
index 3a0d4064..ab8f2de0 100644
--- a/client/vpnconnection.h
+++ b/client/vpnconnection.h
@@ -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