Android project restructuring

This commit is contained in:
albexk 2023-11-16 20:16:28 +03:00
parent bc68c487ee
commit 5a5ea4a018
17 changed files with 46 additions and 48 deletions

View file

@ -0,0 +1,301 @@
/* 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
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.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.qt.android.bindings.QtActivity
import java.io.*
class AmneziaActivity : 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 = "AmneziaActivity"
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: AmneziaActivity
@JvmStatic fun getInstance(): AmneziaActivity {
return instance
}
@JvmStatic fun connectService() {
getInstance().initServiceConnection()
}
@JvmStatic fun startQrCodeReader() {
getInstance().startQrCodeActivity()
}
@JvmStatic fun sendToService(actionCode: Int, body: String) {
getInstance().dispatchParcel(actionCode, body)
}
@JvmStatic fun saveFileAs(fileContent: String, suggestedName: String) {
getInstance().saveFile(fileContent, suggestedName)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
instance = this
val newIntent = intent
val newIntentAction: String? = newIntent.action
if (newIntent != null && newIntentAction != null && newIntentAction == "org.amnezia.vpn.IMPORT_CONFIG") {
configString = newIntent.getStringExtra("CONFIG")
}
}
private fun startQrCodeActivity() {
val intent = Intent(this, CameraActivity::class.java)
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)
}
external fun handleBackButton(): Boolean
external fun onServiceMessage(actionCode: Int, body: String?)
external fun qtOnServiceConnected()
external fun qtOnServiceDisconnected()
external fun onActivityMessage(actionCode: Int, body: String?)
private fun dispatchParcel(actionCode: Int, body: String) {
if (!isBound) {
Log.d(TAG, "dispatchParcel: not bound")
delayedCommands.add(Pair(actionCode, body))
return
}
if (delayedCommands.size > 0) {
for (command in delayedCommands) {
processCommand(command.first, command.second)
}
delayedCommands.clear()
}
processCommand(actionCode, body)
}
private fun processCommand(actionCode: Int, body: String) {
val out: Parcel = Parcel.obtain()
out.writeByteArray(body.toByteArray())
try {
vpnServiceBinder?.transact(actionCode, out, Parcel.obtain(), 0)
} catch (e: DeadObjectException) {
isBound = false
vpnServiceBinder = null
qtOnServiceDisconnected()
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onNewIntent(newIntent: Intent) {
super.onNewIntent(intent)
setIntent(newIntent)
val newIntentAction = newIntent.action
if (newIntent != null && newIntentAction != null && newIntentAction == INTENT_ACTION_IMPORT_CONFIG) {
configString = newIntent.getStringExtra("CONFIG")
}
}
override fun onResume() {
super.onResume()
if (configString != null && isBound) {
sendImportConfigCommand()
}
}
private fun sendImportConfigCommand() {
if (configString != null) {
val msg: Parcel = Parcel.obtain()
msg.writeString(configString!!)
try {
vpnServiceBinder?.transact(ACTION_IMPORT_CONFIG, msg, Parcel.obtain(), 0)
} catch (e: RemoteException) {
e.printStackTrace()
}
configString = null
}
}
private fun createConnection() = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
vpnServiceBinder = binder
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
if (registerBinder()){
qtOnServiceConnected();
} else {
qtOnServiceDisconnected();
return
}
isBound = true
}
override fun onServiceDisconnected(className: ComponentName) {
vpnServiceBinder = null
isBound = false
qtOnServiceDisconnected();
}
}
private var connection: ServiceConnection = createConnection()
private fun registerBinder(): Boolean {
val binder = VPNClientBinder()
val out: Parcel = Parcel.obtain()
out.writeStrongBinder(binder)
try {
// Register our IBinder Listener
vpnServiceBinder?.transact(ACTION_REGISTER_LISTENER, out, Parcel.obtain(), 0)
return true
} catch (e: DeadObjectException) {
isBound = false
vpnServiceBinder = null
} catch (e: RemoteException) {
e.printStackTrace()
}
return false
}
private fun initServiceConnection() {
// We already have a connection to the service,
// just need to re-register the binder
if (isBound && vpnServiceBinder!!.isBinderAlive() && registerBinder()) {
qtOnServiceConnected()
return
}
bindService(Intent(this, AmneziaVpnService::class.java), connection, Context.BIND_AUTO_CREATE)
}
// TODO: Move all ipc codes into a shared lib.
// this is getting out of hand.
private val PERMISSION_TRANSACTION = 1337
private val ACTION_REGISTER_LISTENER = 3
private val ACTION_RESUME_ACTIVATE = 7
private val ACTION_IMPORT_CONFIG = 11
private val EVENT_PERMISSION_REQUIRED = 6
private val EVENT_DISCONNECTED = 2
private val UI_EVENT_QR_CODE_RECEIVED = 0
fun onPermissionRequest(code: Int, data: Parcel?) {
if (code != EVENT_PERMISSION_REQUIRED) {
return
}
val x = Intent()
x.readFromParcel(data)
startActivityForResult(x, PERMISSION_TRANSACTION)
}
override protected fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == PERMISSION_TRANSACTION) {
// THATS US!
if (resultCode == RESULT_OK) {
// Prompt accepted, tell service to retry.
dispatchParcel(ACTION_RESUME_ACTIVATE, "")
} else {
// Tell the Client we've disconnected
onServiceMessage(EVENT_DISCONNECTED, "")
}
return
}
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == CAMERA_ACTION_CODE && resultCode == RESULT_OK) {
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)
}
}
}
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 = ""
}
}