Merge pull request #138 from amnezia-vpn/feature/android_config_export_import

Export/import of configuration files on Android
This commit is contained in:
pokamest 2022-12-12 16:48:23 +01:00 committed by GitHub
commit 7345f464a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 568 additions and 147 deletions

View file

@ -1,5 +1,10 @@
<?xml version="1.0"?>
<manifest package="org.amnezia.vpn" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
<manifest
package="org.amnezia.vpn"
xmlns:android="http://schemas.android.com/apk/res/android"
android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
@ -18,12 +23,72 @@
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:name=".qt.AmneziaApp" android:hardwareAccelerated="true" android:label="-- %%INSERT_APP_NAME%% --" android:extractNativeLibs="true" android:icon="@drawable/icon">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name=".qt.VPNActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleInstance" android:taskAffinity="" android:theme="@style/splashScreenTheme">
<application
android:name=".qt.AmneziaApp"
android:hardwareAccelerated="true"
android:label="-- %%INSERT_APP_NAME%% --"
android:extractNativeLibs="true"
android:icon="@drawable/icon">
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
android:name=".qt.VPNActivity"
android:label="-- %%INSERT_APP_NAME%% --"
android:screenOrientation="unspecified"
android:launchMode="singleInstance"
android:taskAffinity=""
android:theme="@style/splashScreenTheme">
<intent-filter>
<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>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
@ -79,10 +144,17 @@
<!-- extract android style -->
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
</activity>
<service android:name=".VPNService" android:permission="android.permission.BIND_VPN_SERVICE" android:process=":QtOnlyProcess">
<service
android:name=".VPNService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:process=":QtOnlyProcess"
android:exported="true">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
@ -95,10 +167,24 @@
<meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
</service>
<service android:name="org.amnezia.vpn.qt.VPNPermissionHelper" android:permission="android.permission.BIND_VPN_SERVICE">
<service
android:name=".qt.VPNPermissionHelper"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true">
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
</service>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="org.amnezia.vpn.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/fileprovider"/>
</provider>
</application>
</manifest>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="/" />
</paths>

View file

@ -0,0 +1,5 @@
package org.amnezia.vpn
const val IMPORT_COMMAND_CODE = 1
const val IMPORT_ACTION_CODE = "import_action"
const val IMPORT_CONFIG_KEY = "CONFIG_DATA_KEY"

View file

@ -15,6 +15,8 @@ import android.os.*
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.text.TextUtils
import androidx.core.content.FileProvider
import com.wireguard.android.util.SharedLibraryLoader
import com.wireguard.config.*
import com.wireguard.crypto.Key
@ -151,6 +153,31 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
private var flags = 0
private var startId = 0
private lateinit var mMessenger: Messenger
internal class ExternalConfigImportHandler(
context: Context,
private val serviceBinder: VPNServiceBinder,
private val applicationContext: Context = context.applicationContext
) : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
IMPORT_COMMAND_CODE -> {
val data = msg.data.getString(IMPORT_CONFIG_KEY)
if (data != null) {
serviceBinder.importConfig(data)
}
}
else -> {
super.handleMessage(msg)
}
}
}
}
fun init() {
if (mAlreadyInitialised) {
return
@ -188,6 +215,14 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
*/
override fun onBind(intent: Intent): IBinder {
Log.v(tag, "Aman: onBind....................")
if (intent.action != null && intent.action == IMPORT_ACTION_CODE) {
Log.v(tag, "Service bind for import of config")
mMessenger = Messenger(ExternalConfigImportHandler(this, mBinder))
return mMessenger.binder
}
Log.v(tag, "Regular service bind")
when (mProtocol) {
"shadowsocks" -> {
when (intent.action) {
@ -840,4 +875,44 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface {
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())
}
}
}

View file

@ -17,6 +17,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
private val tag = "VPNServiceBinder"
private var mListener: IBinder? = null
private var mResumeConfig: JSONObject? = null
private var mImportedConfig: String? = null
/**
* The codes this Binder does accept in [onTransact]
@ -31,6 +32,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val resumeActivate = 7
const val setNotificationText = 8
const val setFallBackNotification = 9
const val shareConfig = 10
}
/**
@ -95,6 +97,16 @@ class VPNServiceBinder(service: VPNService) : Binder() {
obj.put("connected", mService.isUp)
obj.put("time", mService.connectionTime)
dispatchEvent(EVENTS.init, obj.toString())
////
if (mImportedConfig != null) {
Log.i(tag, "register: config not null")
dispatchEvent(EVENTS.configImport, mImportedConfig)
mImportedConfig = null
} else {
Log.i(tag, "register: config is null")
}
return true
}
@ -108,18 +120,36 @@ class VPNServiceBinder(service: VPNService) : Binder() {
dispatchEvent(EVENTS.backendLogs, Log.getContent())
return true
}
ACTIONS.requestCleanupLog -> {
Log.clearFile()
return true
}
ACTIONS.setNotificationText -> {
NotificationUtil.update(data)
return true
}
ACTIONS.setFallBackNotification -> {
NotificationUtil.saveFallBackMessage(data, mService)
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
}
IBinder.LAST_CALL_TRANSACTION -> {
Log.e(tag, "The OS Requested to shut down the VPN")
this.mService.turnOff()
@ -132,6 +162,7 @@ class VPNServiceBinder(service: VPNService) : Binder() {
return false
}
}
return false
}
@ -166,5 +197,23 @@ class VPNServiceBinder(service: VPNService) : Binder() {
const val statisticUpdate = 3
const val backendLogs = 4
const val activationError = 5
const val configImport = 6
}
fun importConfig(config: String) {
val obj = JSONObject()
obj.put("config", config)
val resultString = obj.toString()
Log.i(tag, "Transact import config request")
if (mListener != null) {
Log.i(tag, "binder alive")
dispatchEvent(EVENTS.configImport, resultString)
} else {
Log.i(tag, "binder NOT alive")
mImportedConfig = resultString
}
}
}

View file

@ -1,37 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn.qt;
import android.view.KeyEvent;
public class VPNActivity extends org.qtproject.qt5.android.bindings.QtActivity {
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
onBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}
// TODO finalize
// https://github.com/mozilla-mobile/mozilla-vpn-client/blob/6acff5dd9f072380a04c3fa12e9f3c98dbdd7a26/src/platforms/android/androidvpnactivity.h
@Override
public void onBackPressed() {
// super.onBackPressed();
try {
if (!handleBackButton()) {
// Move the activity into paused state if back button was pressed
moveTaskToBack(true);
// finish();
}
} catch (Exception e) {
}
}
// Returns true if MVPN has handled the back button
native boolean handleBackButton();
}

View file

@ -0,0 +1,196 @@
package org.amnezia.vpn.qt;
import android.Manifest
import android.content.ComponentName
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri
import android.os.*
import android.provider.MediaStore
import android.util.Log
import android.view.KeyEvent
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import org.amnezia.vpn.VPNService
import org.amnezia.vpn.VPNServiceBinder
import org.amnezia.vpn.IMPORT_COMMAND_CODE
import org.amnezia.vpn.IMPORT_ACTION_CODE
import org.amnezia.vpn.IMPORT_CONFIG_KEY
import org.qtproject.qt5.android.bindings.QtActivity
import java.io.*
class VPNActivity : org.qtproject.qt5.android.bindings.QtActivity() {
private var configString: String? = null
private var vpnServiceBinder: Messenger? = null
private var isBound = false
private val TAG = "VPNActivity"
private val STORAGE_PERMISSION_CODE = 42
override fun onCreate(savedInstanceState: Bundle?) {
val newIntent = intent
val newIntentAction = newIntent.action
if (newIntent != null && newIntentAction != null) {
configString = processIntent(newIntent, newIntentAction)
}
super.onCreate(savedInstanceState)
}
override fun onNewIntent(newIntent: Intent) {
intent = 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)
}
override fun onResume() {
super.onResume()
if (configString != null && !isBound) {
bindVpnService()
}
}
override fun onPause() {
if (vpnServiceBinder != null && isBound) {
unbindService(connection)
isBound = false
}
super.onPause()
}
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) {
bindVpnService()
}
} else {
Toast.makeText(this, "Oops you just denied the permission", Toast.LENGTH_LONG).show()
}
}
}
private fun bindVpnService() {
try {
val intent = Intent(this, VPNService::class.java)
intent.action = IMPORT_ACTION_CODE
bindService(intent, connection, Context.BIND_AUTO_CREATE)
} catch (e: Exception) {
e.printStackTrace()
}
}
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 var connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
vpnServiceBinder = Messenger(binder)
if (configString != null) {
val msg: Message = Message.obtain(null, IMPORT_COMMAND_CODE, 0, 0)
val bundle = Bundle()
bundle.putString(IMPORT_CONFIG_KEY, configString!!)
msg.data = bundle
try {
vpnServiceBinder?.send(msg)
} catch (e: RemoteException) {
e.printStackTrace()
}
configString = null
}
isBound = true
}
override fun onServiceDisconnected(className: ComponentName) {
vpnServiceBinder = null
isBound = false
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0) {
onBackPressed()
return true
}
return super.onKeyDown(keyCode, event)
}
}

View file

@ -263,9 +263,19 @@ android {
android/gradlew.bat \
android/gradle.properties \
android/res/values/libs.xml \
android/res/xml/fileprovider.xml \
android/src/org/amnezia/vpn/AuthHelper.java \
android/src/org/amnezia/vpn/IPCContract.kt \
android/src/org/amnezia/vpn/NotificationUtil.kt \
android/src/org/amnezia/vpn/OpenVPNThreadv3.kt \
android/src/org/amnezia/vpn/Prefs.kt \
android/src/org/amnezia/vpn/VpnLogger.kt \
android/src/org/amnezia/vpn/VpnService.kt \
android/src/org/amnezia/vpn/VpnServiceBinder.kt \
android/src/org/amnezia/vpn/qt/AmneziaApp.kt \
android/src/org/amnezia/vpn/qt/PackageManagerHelper.java \
android/src/org/amnezia/vpn/qt/VPNActivity.kt \
android/src/org/amnezia/vpn/qt/VPNApplication.java \
android/src/org/amnezia/vpn/qt/VPNPermissionHelper.kt
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android

View file

@ -16,19 +16,21 @@
#include "android_controller.h"
#include "core/errorstrings.h"
#include "ui/pages_logic/StartPageLogic.h"
// Binder Codes for VPNServiceBinder
// See also - VPNServiceBinder.kt
// Actions that are Requestable
const int ACTION_ACTIVATE = 1;
const int ACTION_DEACTIVATE = 2;
const int ACTION_REGISTERLISTENER = 3;
const int ACTION_REGISTER_LISTENER = 3;
const int ACTION_REQUEST_STATISTIC = 4;
const int ACTION_REQUEST_GET_LOG = 5;
const int ACTION_REQUEST_CLEANUP_LOG = 6;
const int ACTION_RESUME_ACTIVATE = 7;
const int ACTION_SET_NOTIFICATION_TEXT = 8;
const int ACTION_SET_NOTIFICATION_FALLBACK = 9;
const int ACTION_SHARE_CONFIG = 10;
// Event Types that will be Dispatched after registration
const int EVENT_INIT = 0;
@ -37,6 +39,7 @@ const int EVENT_DISCONNECTED = 2;
const int EVENT_STATISTIC_UPDATE = 3;
const int EVENT_BACKEND_LOGS = 4;
const int EVENT_ACTIVATION_ERROR = 5;
const int EVENT_CONFIG_IMPORT = 6;
namespace {
AndroidController* s_instance = nullptr;
@ -57,10 +60,12 @@ AndroidController* AndroidController::instance() {
return s_instance;
}
bool AndroidController::initialize()
bool AndroidController::initialize(StartPageLogic *startPageLogic)
{
qDebug() << "Initializing";
m_startPageLogic = startPageLogic;
// Hook in the native implementation for startActivityForResult into the JNI
JNINativeMethod methods[]{{"startActivityForResult",
"(Landroid/content/Intent;)V",
@ -148,6 +153,16 @@ void AndroidController::setNotificationText(const QString& title,
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr);
}
void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) {
QJsonObject rootObject;
rootObject["data"] = configContent;
rootObject["suggestedName"] = suggestedName;
QJsonDocument doc(rootObject);
QAndroidParcel parcel;
parcel.writeData(doc.toJson());
m_serviceBinder.transact(ACTION_SHARE_CONFIG, parcel, nullptr);
}
/*
* Sets fallback Notification text that should be shown in case the VPN
* switches into the Connected state without the app open
@ -187,6 +202,10 @@ void AndroidController::cleanupBackendLogs() {
m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr);
}
void AndroidController::importConfig(const QString& data){
m_startPageLogic->importConnectionFromCode(data);
}
void AndroidController::onServiceConnected(
const QString& name, const QAndroidBinder& serviceBinder) {
qDebug() << "Server " + name + " connected";
@ -198,7 +217,7 @@ void AndroidController::onServiceConnected(
// Send the Service our Binder to recive incoming Events
QAndroidParcel binderParcel;
binderParcel.writeBinder(m_binder);
m_serviceBinder.transact(ACTION_REGISTERLISTENER, binderParcel, nullptr);
m_serviceBinder.transact(ACTION_REGISTER_LISTENER, binderParcel, nullptr);
}
void AndroidController::onServiceDisconnected(const QString& name) {
@ -279,7 +298,14 @@ bool AndroidController::VPNBinder::onTransact(int code,
case EVENT_ACTIVATION_ERROR:
qDebug() << "Transact: error";
emit m_controller->connectionStateChanged(VpnProtocol::Error);
break;
case EVENT_CONFIG_IMPORT:
qDebug() << "Transact: config import";
doc = QJsonDocument::fromJson(data.readData());
buffer = doc.object()["config"].toString();
qDebug() << "Transact: config string" << buffer;
m_controller->importConfig(buffer);
break;
default:
qWarning() << "Transact: Invalid!";
break;

View file

@ -4,11 +4,13 @@
#include <QAndroidBinder>
#include <QAndroidServiceConnection>
#include "ui/uilogic.h"
#include "ui/pages_logic/StartPageLogic.h"
#include "protocols/vpnprotocol.h"
using namespace amnezia;
class AndroidController : public QObject, public QAndroidServiceConnection
{
Q_OBJECT
@ -19,7 +21,7 @@ public:
virtual ~AndroidController() override = default;
bool initialize();
bool initialize(StartPageLogic *startPageLogic);
ErrorCode start();
void stop();
@ -27,9 +29,11 @@ public:
void checkStatus();
void setNotificationText(const QString& title, const QString& message, int timerSec);
void shareConfig(const QString& data, const QString& suggestedName);
void setFallbackConnectedNotification();
void getBackendLogs(std::function<void(const QString&)>&& callback);
void cleanupBackendLogs();
void importConfig(const QString& data);
// from QAndroidServiceConnection
void onServiceConnected(const QString& name, const QAndroidBinder& serviceBinder) override;
@ -58,6 +62,8 @@ private:
//Protocol m_protocol;
QJsonObject m_vpnConfig;
StartPageLogic *m_startPageLogic;
bool m_serviceConnected = false;
std::function<void(const QString&)> m_logCallback;

View file

@ -112,7 +112,7 @@ New encryption keys pair will be generated.")
Layout.bottomMargin: 10
Layout.fillWidth: true
Layout.preferredHeight: 40
text: qsTr("Save to file")
text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file")
enabled: tfShareCode.textArea.length > 0
visible: tfShareCode.textArea.length > 0

View file

@ -94,7 +94,7 @@ PageShareProtocolBase {
Layout.fillWidth: true
Layout.preferredHeight: 40
text: qsTr("Save to file")
text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file")
enabled: tfShareCode.textArea.length > 0
visible: tfShareCode.textArea.length > 0

View file

@ -93,7 +93,7 @@ PageShareProtocolBase {
Layout.preferredHeight: 40
width: parent.width - 60
text: qsTr("Save to file")
text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file")
enabled: tfShareCode.textArea.length > 0
visible: tfShareCode.textArea.length > 0

View file

@ -91,7 +91,7 @@ PageShareProtocolBase {
Layout.preferredHeight: 40
Layout.fillWidth: true
text: qsTr("Save to file")
text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file")
enabled: tfShareCode.textArea.length > 0
visible: tfShareCode.textArea.length > 0

View file

@ -134,7 +134,7 @@ void UiLogic::initalizeUiLogic()
pageLogic<VpnLogic>()->onConnectionStateChanged(VpnProtocol::Connected);
}
});
if (!AndroidController::instance()->initialize()) {
if (!AndroidController::instance()->initialize(pageLogic<StartPageLogic>())) {
qCritical() << QString("Init failed") ;
emit VpnProtocol::Error;
return;
@ -593,8 +593,9 @@ void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QS
if (fileName.isEmpty()) return;
if (!fileName.toString().endsWith(ext)) fileName = QUrl(fileName.toString() + ext);
#elif defined Q_OS_ANDROID
fileName = QFileDialog::getSaveFileUrl(nullptr, suggestedName,
QUrl::fromLocalFile(docDir), "*" + ext);
qDebug() << "UiLogic::shareConfig" << data;
AndroidController::instance()->shareConfig(data, suggestedName);
return;
#endif
if (fileName.isEmpty()) return;