From 3e02dfef634f516bc936541c392a320e04b41fd4 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 20 Jan 2024 16:40:12 +0300 Subject: [PATCH] Refactoring Android logging (#511) Refactoring Android logging --- client/amnezia_application.cpp | 14 +- client/android/AndroidManifest.xml | 11 +- client/android/build.gradle.kts | 1 - .../vpn/protocol/openvpn/OpenVpnClient.kt | 78 +++--- client/android/res/xml/backup_content.xml | 4 + .../android/res/xml/data_extraction_rules.xml | 9 + .../src/org/amnezia/vpn/AmneziaActivity.kt | 60 +++-- .../src/org/amnezia/vpn/AmneziaApplication.kt | 6 + .../src/org/amnezia/vpn/AmneziaVpnService.kt | 47 ++-- .../org/amnezia/vpn/ImportConfigActivity.kt | 10 +- .../android/src/org/amnezia/vpn/IpcMessage.kt | 3 +- client/android/src/org/amnezia/vpn/Prefs.kt | 25 -- .../src/org/amnezia/vpn/VpnRequestActivity.kt | 2 +- client/android/utils/build.gradle.kts | 4 + client/android/utils/src/main/kotlin/Log.kt | 249 ++++++++++++++++-- client/android/utils/src/main/kotlin/Prefs.kt | 58 ++++ .../utils/src/main/kotlin/net/NetworkState.kt | 4 +- .../platforms/android/android_controller.cpp | 115 +++++++- client/platforms/android/android_controller.h | 16 +- client/settings.cpp | 4 +- client/settings.h | 2 +- client/ui/controllers/settingsController.cpp | 9 + 22 files changed, 577 insertions(+), 154 deletions(-) create mode 100644 client/android/res/xml/backup_content.xml create mode 100644 client/android/res/xml/data_extraction_rules.xml delete mode 100644 client/android/src/org/amnezia/vpn/Prefs.kt create mode 100644 client/android/utils/src/main/kotlin/Prefs.kt diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 28b028ff..f0a7769a 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -91,6 +91,13 @@ void AmneziaApplication::init() initControllers(); #ifdef Q_OS_ANDROID + if(!AndroidController::initLogging()) { + qFatal("Android logging initialization failed"); + } + AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); + connect(m_settings.get(), &Settings::saveLogsChanged, + AndroidController::instance(), &AndroidController::setSaveLogs); + connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) { m_connectionController->onConnectionStateChanged(state); @@ -98,10 +105,7 @@ void AmneziaApplication::init() m_vpnConnection->restoreConnection(); }); if (!AndroidController::instance()->initialize()) { - qCritical() << QString("Init failed"); - if (m_vpnConnection) - emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); - return; + qFatal("Android controller initialization failed"); } connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { @@ -143,11 +147,13 @@ void AmneziaApplication::init() m_engine->load(url); m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); +#ifndef Q_OS_ANDROID if (m_settings->isSaveLogs()) { if (!Logger::init()) { qWarning() << "Initialization of debug subsystem failed"; } } +#endif #ifdef Q_OS_WIN if (m_parser.isSet("a")) diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 960dc87d..fb417f05 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -1,6 +1,8 @@ - + android:theme="@style/NoActionBar" + android:fullBackupContent="@xml/backup_content" + android:dataExtractionRules="@xml/data_extraction_rules" + tools:targetApi="s"> - + diff --git a/client/android/build.gradle.kts b/client/android/build.gradle.kts index 757989aa..0a1fe83f 100644 --- a/client/android/build.gradle.kts +++ b/client/android/build.gradle.kts @@ -108,7 +108,6 @@ dependencies { implementation(project(":cloak")) implementation(libs.androidx.core) implementation(libs.androidx.activity) - implementation(libs.androidx.security.crypto) implementation(libs.kotlinx.coroutines) implementation(libs.bundles.androidx.camera) implementation(libs.google.mlkit) diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt index c716a970..f489c980 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt @@ -52,7 +52,7 @@ class OpenVpnClient( // Callback to construct a new tun builder // Should be called first. override fun tun_builder_new(): Boolean { - Log.v(TAG, "tun_builder_new") + Log.d(TAG, "tun_builder_new") configBuilder.clearAddresses() return true } @@ -60,7 +60,7 @@ class OpenVpnClient( // Callback to set MTU of the VPN interface // Never called more than once per tun_builder session. override fun tun_builder_set_mtu(mtu: Int): Boolean { - Log.v(TAG, "tun_builder_set_mtu: $mtu") + Log.d(TAG, "tun_builder_set_mtu: $mtu") configBuilder.setMtu(mtu) return true } @@ -71,7 +71,7 @@ class OpenVpnClient( address: String, prefix_length: Int, gateway: String, ipv6: Boolean, net30: Boolean ): Boolean { - Log.v(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30") + Log.d(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30") configBuilder.addAddress(InetNetwork(address, prefix_length)) return true } @@ -80,7 +80,7 @@ class OpenVpnClient( // May be called more than once per tun_builder session // metric is optional and should be ignored if < 0 override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6") + Log.d(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6") if (address == "remote_host") return false configBuilder.addRoute(InetNetwork(address, prefix_length)) return true @@ -90,7 +90,7 @@ class OpenVpnClient( // May be called more than once per tun_builder session // metric is optional and should be ignored if < 0 override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") + Log.d(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { configBuilder.excludeRoute(InetNetwork(address, prefix_length)) } @@ -104,7 +104,7 @@ class OpenVpnClient( // domain should be routed. // Guaranteed to be called after tun_builder_reroute_gw. override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_add_dns_server: $address, $ipv6") + Log.d(TAG, "tun_builder_add_dns_server: $address, $ipv6") configBuilder.addDnsServer(parseInetAddress(address)) return true } @@ -119,28 +119,28 @@ class OpenVpnClient( // ignored for that family // See also Android's VPNService.Builder.allowFamily method /* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean { - Log.v(TAG, "tun_builder_set_allow_family: $af, $allow") + Log.d(TAG, "tun_builder_set_allow_family: $af, $allow") return true } */ // Callback to set address of remote server // Never called more than once per tun_builder session. override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean { - Log.v(TAG, "tun_builder_set_remote_address: $address, $ipv6") + Log.d(TAG, "tun_builder_set_remote_address: $address, $ipv6") return true } // Optional callback that indicates OSI layer, should be 2 or 3. // Defaults to 3. override fun tun_builder_set_layer(layer: Int): Boolean { - Log.v(TAG, "tun_builder_set_layer: $layer") + Log.d(TAG, "tun_builder_set_layer: $layer") return layer == 3 } // Callback to set the session name // Never called more than once per tun_builder session. override fun tun_builder_set_session_name(name: String): Boolean { - Log.v(TAG, "tun_builder_set_session_name: $name") + Log.d(TAG, "tun_builder_set_session_name: $name") return true } @@ -149,7 +149,7 @@ class OpenVpnClient( // if the tunnel could not be established. // Always called last after tun_builder session has been configured. override fun tun_builder_establish(): Int { - Log.v(TAG, "tun_builder_establish") + Log.d(TAG, "tun_builder_establish") return establish(configBuilder) } @@ -159,7 +159,7 @@ class OpenVpnClient( // flags are defined in RGWFlags (rgwflags.hpp). // Never called more than once per tun_builder session. override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean { - Log.v(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags") + Log.d(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags") if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true if (ipv4) { configBuilder.addRoute(InetNetwork("0.0.0.0", 0)) @@ -176,7 +176,7 @@ class OpenVpnClient( // reroute_dns parameter. // Guaranteed to be called after tun_builder_reroute_gw. override fun tun_builder_add_search_domain(domain: String): Boolean { - Log.v(TAG, "tun_builder_add_search_domain: $domain") + Log.d(TAG, "tun_builder_add_search_domain: $domain") configBuilder.setSearchDomain(domain) return true } @@ -184,7 +184,7 @@ class OpenVpnClient( // Callback to set the HTTP proxy // Never called more than once per tun_builder session. override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean { - Log.v(TAG, "tun_builder_set_proxy_http: $host, $port") + Log.d(TAG, "tun_builder_set_proxy_http: $host, $port") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { try { configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port)) @@ -199,7 +199,7 @@ class OpenVpnClient( // Callback to set the HTTPS proxy // Never called more than once per tun_builder session. override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean { - Log.v(TAG, "tun_builder_set_proxy_https: $host, $port") + Log.d(TAG, "tun_builder_set_proxy_https: $host, $port") return false } @@ -208,7 +208,7 @@ class OpenVpnClient( // to exclude them from the VPN network are generated // This should be a list of CIDR networks (e.g. 192.168.0.0/24) override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec { - Log.v(TAG, "tun_builder_get_local_networks: $ipv6") + Log.d(TAG, "tun_builder_get_local_networks: $ipv6") val networks = ClientAPI_StringVec() for (address in getLocalNetworks(ipv6)) { networks.add(address.toString()) @@ -222,21 +222,21 @@ class OpenVpnClient( // tun_builder_reroute_gw. Route metric is ignored // if < 0. /* override fun tun_builder_set_route_metric_default(metric: Int): Boolean { - Log.v(TAG, "tun_builder_set_route_metric_default: $metric") + Log.d(TAG, "tun_builder_set_route_metric_default: $metric") return super.tun_builder_set_route_metric_default(metric) } */ // Callback to add a host which should bypass the proxy // May be called more than once per tun_builder session /* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean { - Log.v(TAG, "tun_builder_add_proxy_bypass: $bypass_host") + Log.d(TAG, "tun_builder_add_proxy_bypass: $bypass_host") return super.tun_builder_add_proxy_bypass(bypass_host) } */ // Callback to set the proxy "Auto Config URL" // Never called more than once per tun_builder session. /* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean { - Log.v(TAG, "tun_builder_set_proxy_auto_config_url: $url") + Log.d(TAG, "tun_builder_set_proxy_auto_config_url: $url") return super.tun_builder_set_proxy_auto_config_url(url) } */ @@ -245,7 +245,7 @@ class OpenVpnClient( // May be called more than once per tun_builder session. // Guaranteed to be called after tun_builder_reroute_gw. /* override fun tun_builder_add_wins_server(address: String): Boolean { - Log.v(TAG, "tun_builder_add_wins_server: $address") + Log.d(TAG, "tun_builder_add_wins_server: $address") return super.tun_builder_add_wins_server(address) } */ @@ -254,7 +254,7 @@ class OpenVpnClient( // set the "Connection-specific DNS Suffix" property on // the TAP driver. /* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean { - Log.v(TAG, "tun_builder_set_adapter_domain_suffix: $name") + Log.d(TAG, "tun_builder_set_adapter_domain_suffix: $name") return super.tun_builder_set_adapter_domain_suffix(name) } */ @@ -266,13 +266,13 @@ class OpenVpnClient( // tun_builder_establish_lite() will be called. Otherwise, // tun_builder_establish() will be called. /* override fun tun_builder_persist(): Boolean { - Log.v(TAG, "tun_builder_persist") + Log.d(TAG, "tun_builder_persist") return super.tun_builder_persist() } */ // Indicates a reconnection with persisted tun state. /* override fun tun_builder_establish_lite() { - Log.v(TAG, "tun_builder_establish_lite") + Log.d(TAG, "tun_builder_establish_lite") super.tun_builder_establish_lite() } */ @@ -280,7 +280,7 @@ class OpenVpnClient( // If disconnect == true, then the teardown is occurring // prior to final disconnect. /* override fun tun_builder_teardown(disconnect: Boolean) { - Log.v(TAG, "tun_builder_teardown: $disconnect") + Log.d(TAG, "tun_builder_teardown: $disconnect") super.tun_builder_teardown(disconnect) } */ @@ -290,7 +290,7 @@ class OpenVpnClient( // Parse OpenVPN configuration file. override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig { - Log.v(TAG, "eval_config") + Log.d(TAG, "eval_config") return super.eval_config(arg0) } @@ -299,7 +299,7 @@ class OpenVpnClient( // to event() and log() functions. Make sure to call eval_config() // and possibly provide_creds() as well before this function. override fun connect(): ClientAPI_Status { - Log.v(TAG, "connect") + Log.d(TAG, "connect") return super.connect() } @@ -307,7 +307,7 @@ class OpenVpnClient( // Will be called from the thread executing connect(). // The remote and ipv6 are the remote host this socket will connect to override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean { - Log.v(TAG, "socket_protect: $socket, $remote, $ipv6") + Log.d(TAG, "socket_protect: $socket, $remote, $ipv6") return protect(socket) } @@ -315,7 +315,7 @@ class OpenVpnClient( // May be called asynchronously from a different thread // when connect() is running. override fun stop() { - Log.v(TAG, "stop") + Log.d(TAG, "stop") super.stop() } @@ -323,21 +323,21 @@ class OpenVpnClient( // when network is down. May be called from a different thread // when connect() is running. override fun pause(reason: String) { - Log.v(TAG, "pause: $reason") + Log.d(TAG, "pause: $reason") super.pause(reason) } // Resume the client after it has been paused. May be called from a // different thread when connect() is running. override fun resume() { - Log.v(TAG, "resume") + Log.d(TAG, "resume") super.resume() } // Do a disconnect/reconnect cycle n seconds from now. May be called // from a different thread when connect() is running. override fun reconnect(seconds: Int) { - Log.v(TAG, "reconnect") + Log.d(TAG, "reconnect: $seconds") super.reconnect(seconds) } @@ -346,14 +346,14 @@ class OpenVpnClient( // CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE // state. override fun pause_on_connection_timeout(): Boolean { - Log.v(TAG, "pause_on_connection_timeout") + Log.d(TAG, "pause_on_connection_timeout") return false } // Return information about the most recent connection. Should be called // after an event of type "CONNECTED". /* override fun connection_info(): ClientAPI_ConnectionInfo { - Log.v(TAG, "connection_info") + Log.d(TAG, "connection_info") return super.connection_info() } */ @@ -366,7 +366,7 @@ class OpenVpnClient( override fun event(event: ClientAPI_Event) { val name = event.name val info = event.info - Log.v(TAG, "OpenVpn event: $name: $info") + Log.d(TAG, "OpenVpn event: $name: $info") when (name) { "COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info") "CONNECTED" -> state.value = CONNECTED @@ -398,31 +398,31 @@ class OpenVpnClient( // return transport stats only override fun transport_stats(): ClientAPI_TransportStats { - Log.v(TAG, "transport_stats") + Log.d(TAG, "transport_stats") return super.transport_stats() } // return a stats value, index should be >= 0 and < stats_n() /* override fun stats_value(index: Int): Long { - Log.v(TAG, "stats_value: $index") + Log.d(TAG, "stats_value: $index") return super.stats_value(index) } */ // return all stats in a bundle /* override fun stats_bundle(): ClientAPI_LLVector { - Log.v(TAG, "stats_bundle") + Log.d(TAG, "stats_bundle") return super.stats_bundle() } */ // return tun stats only /* override fun tun_stats(): ClientAPI_InterfaceStats { - Log.v(TAG, "tun_stats") + Log.d(TAG, "tun_stats") return super.tun_stats() } */ // post control channel message /* override fun post_cc_msg(msg: String) { - Log.v(TAG, "post_cc_msg: $msg") + Log.d(TAG, "post_cc_msg: $msg") super.post_cc_msg(msg) } */ } diff --git a/client/android/res/xml/backup_content.xml b/client/android/res/xml/backup_content.xml new file mode 100644 index 00000000..33260809 --- /dev/null +++ b/client/android/res/xml/backup_content.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/client/android/res/xml/data_extraction_rules.xml b/client/android/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..f5f079cc --- /dev/null +++ b/client/android/res/xml/data_extraction_rules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 0c0ec0f9..2ed2c7f7 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -143,7 +143,7 @@ class AmneziaActivity : QtActivity() { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.v(TAG, "Create Amnezia activity: $intent") + Log.d(TAG, "Create Amnezia activity: $intent") mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) vpnServiceMessenger = IpcMessenger( onDeadObjectException = ::doUnbindService, @@ -154,7 +154,7 @@ class AmneziaActivity : QtActivity() { override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - Log.v(TAG, "onNewIntent: $intent") + Log.d(TAG, "onNewIntent: $intent") intent?.let(::processIntent) } @@ -174,7 +174,7 @@ class AmneziaActivity : QtActivity() { override fun onStart() { super.onStart() - Log.v(TAG, "Start Amnezia activity") + Log.d(TAG, "Start Amnezia activity") mainScope.launch { qtInitialized.await() doBindService() @@ -182,13 +182,13 @@ class AmneziaActivity : QtActivity() { } override fun onStop() { - Log.v(TAG, "Stop Amnezia activity") + Log.d(TAG, "Stop Amnezia activity") doUnbindService() super.onStop() } override fun onDestroy() { - Log.v(TAG, "Destroy Amnezia activity") + Log.d(TAG, "Destroy Amnezia activity") mainScope.cancel() super.onDestroy() } @@ -217,7 +217,7 @@ class AmneziaActivity : QtActivity() { CHECK_VPN_PERMISSION_ACTION_CODE -> { when (resultCode) { RESULT_OK -> { - Log.v(TAG, "Vpn permission granted") + Log.d(TAG, "Vpn permission granted") Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() checkVpnPermissionCallbacks?.run { onSuccess() } } @@ -240,7 +240,7 @@ class AmneziaActivity : QtActivity() { */ @MainThread private fun doBindService() { - Log.v(TAG, "Bind service") + Log.d(TAG, "Bind service") Intent(this, AmneziaVpnService::class.java).also { bindService(it, serviceConnection, BIND_ABOVE_CLIENT) } @@ -251,7 +251,7 @@ class AmneziaActivity : QtActivity() { @MainThread private fun doUnbindService() { if (isInBoundState) { - Log.v(TAG, "Unbind service") + Log.d(TAG, "Unbind service") isWaitingStatus = true QtAndroidController.onServiceDisconnected() vpnServiceMessenger.reset() @@ -286,7 +286,7 @@ class AmneziaActivity : QtActivity() { @MainThread private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) { - Log.v(TAG, "Check VPN permission") + Log.d(TAG, "Check VPN permission") VpnService.prepare(applicationContext)?.let { checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail) startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE) @@ -307,7 +307,7 @@ class AmneziaActivity : QtActivity() { } private fun connectToVpn(vpnConfig: String) { - Log.v(TAG, "Connect to VPN") + Log.d(TAG, "Connect to VPN") vpnServiceMessenger.send { Action.CONNECT.packToMessage { putString(VPN_CONFIG, vpnConfig) @@ -316,7 +316,7 @@ class AmneziaActivity : QtActivity() { } private fun startVpnService(vpnConfig: String) { - Log.v(TAG, "Start VPN service") + Log.d(TAG, "Start VPN service") Intent(this, AmneziaVpnService::class.java).apply { putExtra(VPN_CONFIG, vpnConfig) }.also { @@ -325,7 +325,7 @@ class AmneziaActivity : QtActivity() { } private fun disconnectFromVpn() { - Log.v(TAG, "Disconnect from VPN") + Log.d(TAG, "Disconnect from VPN") vpnServiceMessenger.send(Action.DISCONNECT) } @@ -369,7 +369,7 @@ class AmneziaActivity : QtActivity() { @Suppress("unused") fun saveFile(fileName: String, data: String) { - Log.v(TAG, "Save file $fileName") + Log.d(TAG, "Save file $fileName") mainScope.launch { tmpFileContentToSave = data @@ -397,7 +397,7 @@ class AmneziaActivity : QtActivity() { Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) - Log.d(TAG, "File mimyType filter: $mimeTypes") + Log.v(TAG, "File mimyType filter: $mimeTypes") when (mimeTypes.size) { 1 -> type = mimeTypes.first() @@ -416,13 +416,6 @@ class AmneziaActivity : QtActivity() { @Suppress("unused") fun setNotificationText(title: String, message: String, timerSec: Int) { Log.v(TAG, "Set notification text") - Log.w(TAG, "Not yet implemented") - } - - @Suppress("unused") - fun cleanupLogs() { - Log.v(TAG, "Cleanup logs") - Log.w(TAG, "Not yet implemented") } @Suppress("unused") @@ -432,4 +425,29 @@ class AmneziaActivity : QtActivity() { startActivity(it) } } + + @Suppress("unused") + fun setSaveLogs(enabled: Boolean) { + Log.d(TAG, "Set save logs: $enabled") + mainScope.launch { + Log.saveLogs = enabled + vpnServiceMessenger.send { + Action.SET_SAVE_LOGS.packToMessage { + putBoolean(SAVE_LOGS, enabled) + } + } + } + } + + @Suppress("unused") + fun exportLogsFile(fileName: String) { + Log.v(TAG, "Export logs file") + saveFile(fileName, Log.getLogs()) + } + + @Suppress("unused") + fun clearLogs() { + Log.v(TAG, "Clear logs") + Log.clearLogs() + } } diff --git a/client/android/src/org/amnezia/vpn/AmneziaApplication.kt b/client/android/src/org/amnezia/vpn/AmneziaApplication.kt index e9d8fdfb..33182887 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaApplication.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaApplication.kt @@ -5,14 +5,20 @@ import androidx.camera.core.CameraSelector import androidx.camera.core.CameraXConfig import androidx.core.app.NotificationChannelCompat.Builder import androidx.core.app.NotificationManagerCompat +import org.amnezia.vpn.util.Log +import org.amnezia.vpn.util.Prefs import org.qtproject.qt.android.bindings.QtApplication +private const val TAG = "AmneziaApplication" const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification" class AmneziaApplication : QtApplication(), CameraXConfig.Provider { override fun onCreate() { super.onCreate() + Prefs.init(this) + Log.init(this) + Log.d(TAG, "Create Amnezia application") createNotificationChannel() } diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt index 094874c7..ab753716 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt @@ -50,6 +50,7 @@ import org.amnezia.vpn.protocol.putStatistics import org.amnezia.vpn.protocol.putStatus import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.util.Log +import org.amnezia.vpn.util.Prefs import org.amnezia.vpn.util.net.NetworkState import org.json.JSONException import org.json.JSONObject @@ -58,6 +59,8 @@ private const val TAG = "AmneziaVpnService" const val VPN_CONFIG = "VPN_CONFIG" const val ERROR_MSG = "ERROR_MSG" +const val SAVE_LOGS = "SAVE_LOGS" + const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK" private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val NOTIFICATION_ID = 1337 @@ -118,7 +121,7 @@ class AmneziaVpnService : VpnService() { Action.CONNECT -> { val vpnConfig = msg.data.getString(VPN_CONFIG) - saveConfigToPrefs(vpnConfig) + Prefs.save(PREFS_CONFIG_KEY, vpnConfig) connect(vpnConfig) } @@ -135,6 +138,10 @@ class AmneziaVpnService : VpnService() { } } } + + Action.SET_SAVE_LOGS -> { + Log.saveLogs = msg.data.getBoolean(SAVE_LOGS) + } } } } @@ -179,7 +186,7 @@ class AmneziaVpnService : VpnService() { */ override fun onCreate() { super.onCreate() - Log.v(TAG, "Create Amnezia VPN service") + Log.d(TAG, "Create Amnezia VPN service") mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler) clientMessenger = IpcMessenger(messengerName = "Client") @@ -193,15 +200,15 @@ class AmneziaVpnService : VpnService() { else intent?.component?.packageName != packageName if (isAlwaysOnCompat) { - Log.v(TAG, "Start service via Always-on") - connect(loadConfigFromPrefs()) + Log.d(TAG, "Start service via Always-on") + connect(Prefs.load(PREFS_CONFIG_KEY)) } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { - Log.v(TAG, "Start service after permission check") - connect(loadConfigFromPrefs()) + Log.d(TAG, "Start service after permission check") + connect(Prefs.load(PREFS_CONFIG_KEY)) } else { - Log.v(TAG, "Start service") + Log.d(TAG, "Start service") val vpnConfig = intent?.getStringExtra(VPN_CONFIG) - saveConfigToPrefs(vpnConfig) + Prefs.save(PREFS_CONFIG_KEY, vpnConfig) connect(vpnConfig) } ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) @@ -237,7 +244,7 @@ class AmneziaVpnService : VpnService() { } override fun onRevoke() { - Log.v(TAG, "onRevoke") + Log.d(TAG, "onRevoke") // Calls to onRevoke() method may not happen on the main thread of the process mainScope.launch { disconnect() @@ -245,7 +252,7 @@ class AmneziaVpnService : VpnService() { } override fun onDestroy() { - Log.v(TAG, "Destroy service") + Log.d(TAG, "Destroy service") runBlocking { disconnect() disconnectionJob?.join() @@ -256,7 +263,7 @@ class AmneziaVpnService : VpnService() { } private fun stopService() { - Log.v(TAG, "Stop service") + Log.d(TAG, "Stop service") // the coroutine below will be canceled during the onDestroy call mainScope.launch { delay(STOP_SERVICE_TIMEOUT) @@ -272,7 +279,7 @@ class AmneziaVpnService : VpnService() { private fun launchProtocolStateHandler() { mainScope.launch { protocolState.collect { protocolState -> - Log.d(TAG, "Protocol state: $protocolState") + Log.d(TAG, "Protocol state changed: $protocolState") when (protocolState) { CONNECTED -> { clientMessenger.send(ServiceEvent.CONNECTED) @@ -305,7 +312,7 @@ class AmneziaVpnService : VpnService() { @MainThread private fun launchSendingStatistics() { - if (isServiceBound && isConnected) { + /* if (isServiceBound && isConnected) { statisticsSendingJob = mainScope.launch { while (true) { clientMessenger.send { @@ -316,7 +323,7 @@ class AmneziaVpnService : VpnService() { delay(STATISTICS_SENDING_TIMEOUT) } } - } + } */ } @MainThread @@ -328,7 +335,7 @@ class AmneziaVpnService : VpnService() { private fun connect(vpnConfig: String?) { if (isConnected || protocolState.value == CONNECTING) return - Log.v(TAG, "Start VPN connection") + Log.d(TAG, "Start VPN connection") protocolState.value = CONNECTING @@ -357,7 +364,7 @@ class AmneziaVpnService : VpnService() { private fun disconnect() { if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return - Log.v(TAG, "Stop VPN connection") + Log.d(TAG, "Stop VPN connection") protocolState.value = DISCONNECTING @@ -383,7 +390,7 @@ class AmneziaVpnService : VpnService() { private fun reconnect() { if (!isConnected) return - Log.v(TAG, "Reconnect VPN") + Log.d(TAG, "Reconnect VPN") protocolState.value = RECONNECTING @@ -439,10 +446,4 @@ class AmneziaVpnService : VpnService() { } else { true } - - private fun loadConfigFromPrefs(): String? = - Prefs.get(this).getString(PREFS_CONFIG_KEY, null) - - private fun saveConfigToPrefs(config: String?) = - Prefs.get(this).edit().putString(PREFS_CONFIG_KEY, config).apply() } diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt index 309333ef..cae7ab75 100644 --- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt +++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt @@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.v(TAG, "Create Import Config Activity: $intent") + Log.d(TAG, "Create Import Config Activity: $intent") intent?.let(::readConfig) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - Log.v(TAG, "onNewIntent: $intent") + Log.d(TAG, "onNewIntent: $intent") intent?.let(::readConfig) } private fun readConfig(intent: Intent) { when (intent.action) { ACTION_SEND -> { - Log.v(TAG, "Process SEND action, type: ${intent.type}") + Log.d(TAG, "Process SEND action, type: ${intent.type}") when (intent.type) { "application/octet-stream" -> { intent.getUriCompat()?.let { uri -> @@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() { } ACTION_VIEW -> { - Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}") + Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}") when (intent.scheme) { "file", "content" -> { intent.data?.let { uri -> @@ -128,7 +128,7 @@ class ImportConfigActivity : ComponentActivity() { private fun startMainActivity(config: String) { if (config.isNotBlank()) { - Log.v(TAG, "startMainActivity") + Log.d(TAG, "startMainActivity") Intent(applicationContext, AmneziaActivity::class.java).apply { action = ACTION_IMPORT_CONFIG addCategory(CATEGORY_DEFAULT) diff --git a/client/android/src/org/amnezia/vpn/IpcMessage.kt b/client/android/src/org/amnezia/vpn/IpcMessage.kt index c9d2bd3f..26c3b9de 100644 --- a/client/android/src/org/amnezia/vpn/IpcMessage.kt +++ b/client/android/src/org/amnezia/vpn/IpcMessage.kt @@ -32,7 +32,8 @@ enum class Action : IpcMessage { REGISTER_CLIENT, CONNECT, DISCONNECT, - REQUEST_STATUS + REQUEST_STATUS, + SET_SAVE_LOGS } fun T.packToMessage(): Message diff --git a/client/android/src/org/amnezia/vpn/Prefs.kt b/client/android/src/org/amnezia/vpn/Prefs.kt deleted file mode 100644 index 9b4cb2ba..00000000 --- a/client/android/src/org/amnezia/vpn/Prefs.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.amnezia.vpn - -import android.content.Context -import android.content.SharedPreferences -import androidx.security.crypto.EncryptedSharedPreferences -import androidx.security.crypto.MasterKey -import org.amnezia.vpn.util.Log - -private const val TAG = "Prefs" -private const val PREFS_FILE = "org.amnezia.vpn.prefs" -private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure" - -object Prefs { - fun get(context: Context, appContext: Context = context.applicationContext): SharedPreferences = - try { - EncryptedSharedPreferences( - appContext, - SECURE_PREFS_FILE, - MasterKey(appContext) - ) - } catch (e: Exception) { - Log.e(TAG, "Getting Encryption Storage failed: ${e.message}, plaintext fallback") - appContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) - } -} diff --git a/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt b/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt index 9dce3d78..c5abbc39 100644 --- a/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt +++ b/client/android/src/org/amnezia/vpn/VpnRequestActivity.kt @@ -25,7 +25,7 @@ class VpnRequestActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.v(TAG, "Start request activity") + Log.d(TAG, "Start request activity") val requestIntent = VpnService.prepare(applicationContext) if (requestIntent != null) { if (getSystemService()!!.isKeyguardLocked) { diff --git a/client/android/utils/build.gradle.kts b/client/android/utils/build.gradle.kts index ac5d1efb..2ad03d61 100644 --- a/client/android/utils/build.gradle.kts +++ b/client/android/utils/build.gradle.kts @@ -15,3 +15,7 @@ android { buildConfig = true } } + +dependencies { + implementation(libs.androidx.security.crypto) +} diff --git a/client/android/utils/src/main/kotlin/Log.kt b/client/android/utils/src/main/kotlin/Log.kt index 82180c3d..03da4507 100644 --- a/client/android/utils/src/main/kotlin/Log.kt +++ b/client/android/utils/src/main/kotlin/Log.kt @@ -1,33 +1,252 @@ package org.amnezia.vpn.util +import android.content.Context +import android.icu.text.DateFormat +import android.icu.text.SimpleDateFormat +import android.os.Build +import android.os.Process +import java.io.File +import java.io.IOException +import java.io.RandomAccessFile +import java.nio.channels.FileChannel +import java.nio.channels.FileLock +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.Date +import java.util.Locale +import java.util.concurrent.locks.ReentrantLock +import org.amnezia.vpn.util.Log.Priority.D +import org.amnezia.vpn.util.Log.Priority.E +import org.amnezia.vpn.util.Log.Priority.F +import org.amnezia.vpn.util.Log.Priority.I +import org.amnezia.vpn.util.Log.Priority.V +import org.amnezia.vpn.util.Log.Priority.W import android.util.Log as NativeLog -class Log { - companion object { - fun v(tag: String, msg: String) = debugLog(tag, msg, NativeLog::v) +private const val TAG = "Log" +private const val LOG_FILE_NAME = "amneziaVPN.log" +private const val ROTATE_LOG_FILE_NAME = "amneziaVPN.rotate.log" +private const val LOCK_FILE_NAME = ".lock" +private const val DATE_TIME_PATTERN = "MM-dd HH:mm:ss.SSS" +private const val PREFS_SAVE_LOGS_KEY = "SAVE_LOGS" +private const val LOG_MAX_FILE_SIZE = 1024 * 1024 - fun d(tag: String, msg: String) = debugLog(tag, msg, NativeLog::d) +/** + * | Priority | Save to file | Logcat logging | + * |-------------------|--------------|----------------------------------------------| + * | Verbose | Don't save | Only in Debug build | + * | Debug | Save | In Debug build or if log saving is enabled | + * | Info, Warn, Error | Save | Enabled | + * | Fatal (Assert) | Save | Enabled. Depending on system configuration, | + * | | | create a report and/or terminate the process | + */ +object Log { + private val dateTimeFormat: Any = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN) + else object : ThreadLocal() { + override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US) + } - fun i(tag: String, msg: String) = log(tag, msg, NativeLog::i) + private lateinit var logDir: File + private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) } + private val rotateLogFile: File by lazy { File(logDir, ROTATE_LOG_FILE_NAME) } - fun w(tag: String, msg: String) = log(tag, msg, NativeLog::w) + private val fileLock: FileChannel by lazy { RandomAccessFile(File(logDir, LOCK_FILE_NAME).path, "rw").channel } + private val threadLock: ReentrantLock by lazy { ReentrantLock() } - fun e(tag: String, msg: String) = log(tag, msg, NativeLog::e) + @Volatile + private var _saveLogs: Boolean = false + var saveLogs: Boolean + get() = _saveLogs + set(value) { + if (_saveLogs != value) { + if (value && !logDir.exists() && !logDir.mkdir()) { + NativeLog.e(TAG, "Failed to create dir: $logDir") + return + } + _saveLogs = value + Prefs.save(PREFS_SAVE_LOGS_KEY, value) + } + } - fun v(tag: String, msg: Any?) = v(tag, msg.toString()) + @JvmStatic + fun v(tag: String, msg: String) = log(tag, msg, V) - fun d(tag: String, msg: Any?) = d(tag, msg.toString()) + @JvmStatic + fun d(tag: String, msg: String) = log(tag, msg, D) - fun i(tag: String, msg: Any?) = i(tag, msg.toString()) + @JvmStatic + fun i(tag: String, msg: String) = log(tag, msg, I) - fun w(tag: String, msg: Any?) = w(tag, msg.toString()) + @JvmStatic + fun w(tag: String, msg: String) = log(tag, msg, W) - fun e(tag: String, msg: Any?) = e(tag, msg.toString()) + @JvmStatic + fun e(tag: String, msg: String) = log(tag, msg, E) - private inline fun log(tag: String, msg: String, delegate: (String, String) -> Unit) = delegate(tag, msg) + @JvmStatic + fun f(tag: String, msg: String) = log(tag, msg, F) - private inline fun debugLog(tag: String, msg: String, delegate: (String, String) -> Unit) { - if (BuildConfig.DEBUG) delegate(tag, msg) + fun v(tag: String, msg: Any?) = v(tag, msg.toString()) + + fun d(tag: String, msg: Any?) = d(tag, msg.toString()) + + fun i(tag: String, msg: Any?) = i(tag, msg.toString()) + + fun w(tag: String, msg: Any?) = w(tag, msg.toString()) + + fun e(tag: String, msg: Any?) = e(tag, msg.toString()) + + fun f(tag: String, msg: Any?) = f(tag, msg.toString()) + + fun init(context: Context) { + v(TAG, "Init Log") + logDir = File(context.cacheDir, "logs") + saveLogs = Prefs.load(PREFS_SAVE_LOGS_KEY) + } + + fun getLogs(): String = + "${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}" + + fun clearLogs() { + withLock { + logFile.delete() + rotateLogFile.delete() } } + + private fun log(tag: String, msg: String, priority: Priority) { + if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority)) + + if (priority == F) { + NativeLog.wtf(tag, msg) + } else if ( + (priority != V && priority != D) || + (priority == V && BuildConfig.DEBUG) || + (priority == D && (BuildConfig.DEBUG || saveLogs)) + ) { + NativeLog.println(priority.level, tag, msg) + } + } + + private fun saveLogMsg(msg: String) { + withTryLock(condition = { logFile.length() > LOG_MAX_FILE_SIZE }) { + logFile.renameTo(rotateLogFile) + } + try { + logFile.appendText(msg) + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to write log: $e") + } + } + + private fun formatLogMsg(tag: String, msg: String, priority: Priority): String { + val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter) + } else { + @Suppress("UNCHECKED_CAST") + (dateTimeFormat as ThreadLocal).get()?.format(Date()) + } + return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " + + "$tag: $msg\n" + } + + private fun deviceInfo(): String { + val sb = StringBuilder() + sb.append("Model: ").appendLine(Build.MODEL) + sb.append("Brand: ").appendLine(Build.BRAND) + sb.append("Product: ").appendLine(Build.PRODUCT) + sb.append("Device: ").appendLine(Build.DEVICE) + sb.append("Codename: ").appendLine(Build.VERSION.CODENAME) + sb.append("Release: ").appendLine(Build.VERSION.RELEASE) + sb.append("SDK: ").appendLine(Build.VERSION.SDK_INT) + sb.append("ABI: ").appendLine(Build.SUPPORTED_ABIS.joinToString()) + return sb.toString() + } + + private fun readLogs(): String { + var logText = "" + withLock { + try { + if (rotateLogFile.exists()) logText = rotateLogFile.readText() + if (logFile.exists()) logText += logFile.readText() + } catch (e: IOException) { + val errorMsg = "Failed to read log: $e" + NativeLog.e(TAG, errorMsg) + logText += errorMsg + } + } + return logText + } + + private fun getLogcat(): String { + try { + val process = ProcessBuilder("logcat", "-d").redirectErrorStream(true).start() + return process.inputStream.reader().readText() + } catch (e: IOException) { + val errorMsg = "Failed to get logcat log: $e" + NativeLog.e(TAG, errorMsg) + return errorMsg + } + } + + private fun withLock(block: () -> Unit) { + threadLock.lock() + try { + var l: FileLock? = null + try { + l = fileLock.lock() + block() + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to get file lock: $e") + } finally { + try { + l?.release() + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to release file lock: $e") + } + } + } finally { + threadLock.unlock() + } + } + + private fun withTryLock(condition: () -> Boolean, block: () -> Unit) { + if (condition()) { + if (threadLock.tryLock()) { + try { + if (condition()) { + var l: FileLock? = null + try { + l = fileLock.tryLock() + if (l != null) { + if (condition()) { + block() + } + } + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to get file tryLock: $e") + } finally { + try { + l?.release() + } catch (e: IOException) { + NativeLog.e(TAG, "Failed to release file tryLock: $e") + } + } + } + } finally { + threadLock.unlock() + } + } + } + } + + private enum class Priority(val level: Int) { + V(2), + D(3), + I(4), + W(5), + E(6), + F(7) + } } diff --git a/client/android/utils/src/main/kotlin/Prefs.kt b/client/android/utils/src/main/kotlin/Prefs.kt new file mode 100644 index 00000000..9afbc7be --- /dev/null +++ b/client/android/utils/src/main/kotlin/Prefs.kt @@ -0,0 +1,58 @@ +package org.amnezia.vpn.util + +import android.app.Application +import android.content.Context +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import kotlin.reflect.typeOf + +private const val TAG = "Prefs" +private const val PREFS_FILE = "org.amnezia.vpn.prefs" +private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure" + +object Prefs { + private lateinit var app: Application + val prefs: SharedPreferences + get() = try { + EncryptedSharedPreferences( + app, + SECURE_PREFS_FILE, + MasterKey(app) + ) + } catch (e: Exception) { + Log.e(TAG, "Getting Encryption Storage failed: $e, plaintext fallback") + app.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE) + } + + fun init(app: Application) { + Log.v(TAG, "Init Prefs") + this.app = app + } + + fun save(key: String, value: Boolean) = + prefs.edit().putBoolean(key, value).apply() + + fun save(key: String, value: String?) = + prefs.edit().putString(key, value).apply() + + fun save(key: String, value: Int) = + prefs.edit().putInt(key, value).apply() + + fun save(key: String, value: Long) = + prefs.edit().putLong(key, value).apply() + + fun save(key: String, value: Float) = + prefs.edit().putFloat(key, value).apply() + + inline fun load(key: String): T { + return when (typeOf()) { + typeOf() -> prefs.getBoolean(key, false) + typeOf() -> prefs.getString(key, "") + typeOf() -> prefs.getInt(key, 0) + typeOf() -> prefs.getLong(key, 0L) + typeOf() -> prefs.getFloat(key, 0f) + else -> throw IllegalArgumentException("SharedPreferences does not support type: ${typeOf()}") + } as T + } +} diff --git a/client/android/utils/src/main/kotlin/net/NetworkState.kt b/client/android/utils/src/main/kotlin/net/NetworkState.kt index 42d7baee..957fc3cb 100644 --- a/client/android/utils/src/main/kotlin/net/NetworkState.kt +++ b/client/android/utils/src/main/kotlin/net/NetworkState.kt @@ -82,7 +82,7 @@ class NetworkState( fun bindNetworkListener() { if (isListenerBound) return - Log.v(TAG, "Bind network listener") + Log.d(TAG, "Bind network listener") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -95,7 +95,7 @@ class NetworkState( fun unbindNetworkListener() { if (!isListenerBound) return - Log.v(TAG, "Unbind network listener") + Log.d(TAG, "Unbind network listener") connectivityManager.unregisterNetworkCallback(networkCallback) isListenerBound = false currentNetwork = null diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 225ceebe..5d442b81 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -12,13 +12,15 @@ namespace AndroidController *s_instance = nullptr; constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController"; + constexpr auto ANDROID_LOG_CLASS = "org/amnezia/vpn/util/Log"; + constexpr auto TAG = "AmneziaQt"; } // namespace AndroidController::AndroidController() : QObject() { connect(this, &AndroidController::status, this, [this](AndroidController::ConnectionState state) { - qDebug() << "Android event: status; state:" << textConnectionState(state); + qDebug() << "Android event: status =" << textConnectionState(state); if (isWaitingStatus) { qDebug() << "Initialization by service status"; isWaitingStatus = false; @@ -126,24 +128,19 @@ bool AndroidController::initialize() // static template -auto AndroidController::callActivityMethod(const char *methodName, const char *signature, - const std::function &defValue, Args &&...args) +auto AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) { qDebug() << "Call activity method:" << methodName; QJniObject activity = AndroidUtils::getActivity(); - if (activity.isValid()) { - return activity.callMethod(methodName, signature, std::forward(args)...); - } else { - qCritical() << "Activity is not valid"; - return defValue(); - } + Q_ASSERT(activity.isValid()); + return activity.callMethod(methodName, signature, std::forward(args)...); } // static template void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) { - callActivityMethod(methodName, signature, [] {}, std::forward(args)...); + callActivityMethod(methodName, signature, std::forward(args)...); } ErrorCode AndroidController::start(const QJsonObject &vpnConfig) @@ -199,6 +196,104 @@ void AndroidController::startQrReaderActivity() callActivityMethod("startQrCodeReader", "()V"); } +void AndroidController::setSaveLogs(bool enabled) +{ + callActivityMethod("setSaveLogs", "(Z)V", enabled); +} + +void AndroidController::exportLogsFile(const QString &fileName) +{ + callActivityMethod("exportLogsFile", "(Ljava/lang/String;)V", + QJniObject::fromString(fileName).object()); +} + +void AndroidController::clearLogs() +{ + callActivityMethod("clearLogs", "()V"); +} + +// Moving log processing to the Android side +jclass AndroidController::log; +jmethodID AndroidController::logDebug; +jmethodID AndroidController::logInfo; +jmethodID AndroidController::logWarning; +jmethodID AndroidController::logError; +jmethodID AndroidController::logFatal; + +// static +bool AndroidController::initLogging() +{ + QJniEnvironment env; + + log = env.findClass(ANDROID_LOG_CLASS); + if (log == nullptr) { + qCritical() << "Android log class" << ANDROID_LOG_CLASS << "not found"; + return false; + } + + auto logMethodSignature = "(Ljava/lang/String;Ljava/lang/String;)V"; + + logDebug = env.findStaticMethod(log, "d", logMethodSignature); + if (logDebug == nullptr) { + qCritical() << "Android debug log method not found"; + return false; + } + + logInfo = env.findStaticMethod(log, "i", logMethodSignature); + if (logInfo == nullptr) { + qCritical() << "Android info log method not found"; + return false; + } + + logWarning = env.findStaticMethod(log, "w", logMethodSignature); + if (logWarning == nullptr) { + qCritical() << "Android warning log method not found"; + return false; + } + + logError = env.findStaticMethod(log, "e", logMethodSignature); + if (logError == nullptr) { + qCritical() << "Android error log method not found"; + return false; + } + + logFatal = env.findStaticMethod(log, "f", logMethodSignature); + if (logFatal == nullptr) { + qCritical() << "Android fatal log method not found"; + return false; + } + + qInstallMessageHandler(messageHandler); + return true; +} + +// static +void AndroidController::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + jmethodID logMethod = logDebug; + switch (type) { + case QtDebugMsg: + logMethod = logDebug; + break; + case QtInfoMsg: + logMethod = logInfo; + break; + case QtWarningMsg: + logMethod = logWarning; + break; + case QtCriticalMsg: + logMethod = logError; + break; + case QtFatalMsg: + logMethod = logFatal; + break; + } + QString formattedMessage = qFormatLogMessage(type, context, message); + QJniObject::callStaticMethod(log, logMethod, + QJniObject::fromString(TAG).object(), + QJniObject::fromString(formattedMessage).object()); +} + void AndroidController::qtAndroidControllerInitialized() { callActivityMethod("qtAndroidControllerInitialized", "()V"); diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 481f4b49..48044544 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -34,6 +34,12 @@ public: void saveFile(const QString &fileName, const QString &data); QString openFile(const QString &filter); void startQrReaderActivity(); + void setSaveLogs(bool enabled); + void exportLogsFile(const QString &fileName); + void clearLogs(); + + static bool initLogging(); + static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); signals: void connectionStateChanged(Vpn::ConnectionState state); @@ -53,6 +59,13 @@ signals: private: bool isWaitingStatus = true; + static jclass log; + static jmethodID logDebug; + static jmethodID logInfo; + static jmethodID logWarning; + static jmethodID logError; + static jmethodID logFatal; + void qtAndroidControllerInitialized(); static Vpn::ConnectionState convertState(ConnectionState state); @@ -72,8 +85,7 @@ private: static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); template - static auto callActivityMethod(const char *methodName, const char *signature, - const std::function &defValue, Args &&...args); + static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); template static void callActivityMethod(const char *methodName, const char *signature, Args &&...args); }; diff --git a/client/settings.cpp b/client/settings.cpp index f3fb57a7..475c52e7 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -216,6 +216,7 @@ QString Settings::nextAvailableServerName() const void Settings::setSaveLogs(bool enabled) { setValue("Conf/saveLogs", enabled); +#ifndef Q_OS_ANDROID if (!isSaveLogs()) { Logger::deInit(); } else { @@ -223,7 +224,8 @@ void Settings::setSaveLogs(bool enabled) qWarning() << "Initialization of debug subsystem failed"; } } - emit saveLogsChanged(); +#endif + emit saveLogsChanged(enabled); } QString Settings::routeModeString(RouteMode mode) const diff --git a/client/settings.h b/client/settings.h index 50a28dc4..ed302653 100644 --- a/client/settings.h +++ b/client/settings.h @@ -190,7 +190,7 @@ public: void clearSettings(); signals: - void saveLogsChanged(); + void saveLogsChanged(bool enabled); private: QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index f7345608..8ac4cca0 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -8,6 +8,7 @@ #include "version.h" #ifdef Q_OS_ANDROID #include "platforms/android/android_utils.h" + #include "platforms/android/android_controller.h" #include #endif @@ -91,13 +92,21 @@ void SettingsController::openLogsFolder() void SettingsController::exportLogsFile(const QString &fileName) { +#ifdef Q_OS_ANDROID + AndroidController::instance()->exportLogsFile(fileName); +#else SystemController::saveFile(fileName, Logger::getLogFile()); +#endif } void SettingsController::clearLogs() { +#ifdef Q_OS_ANDROID + AndroidController::instance()->clearLogs(); +#else Logger::clearLogs(); Logger::clearServiceLogs(); +#endif } void SettingsController::backupAppConfig(const QString &fileName)