Refactoring Android logging (#511)

Refactoring Android logging
This commit is contained in:
albexk 2024-01-20 16:40:12 +03:00 committed by GitHub
parent f6175c2c69
commit 3e02dfef63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 577 additions and 154 deletions

View file

@ -91,6 +91,13 @@ void AmneziaApplication::init()
initControllers(); initControllers();
#ifdef Q_OS_ANDROID #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, connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) { [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state); m_connectionController->onConnectionStateChanged(state);
@ -98,10 +105,7 @@ void AmneziaApplication::init()
m_vpnConnection->restoreConnection(); m_vpnConnection->restoreConnection();
}); });
if (!AndroidController::instance()->initialize()) { if (!AndroidController::instance()->initialize()) {
qCritical() << QString("Init failed"); qFatal("Android controller initialization failed");
if (m_vpnConnection)
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error);
return;
} }
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
@ -143,11 +147,13 @@ void AmneziaApplication::init()
m_engine->load(url); m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
#ifndef Q_OS_ANDROID
if (m_settings->isSaveLogs()) { if (m_settings->isSaveLogs()) {
if (!Logger::init()) { if (!Logger::init()) {
qWarning() << "Initialization of debug subsystem failed"; qWarning() << "Initialization of debug subsystem failed";
} }
} }
#endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
if (m_parser.isSet("a")) if (m_parser.isSet("a"))

View file

@ -1,6 +1,8 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- Leave package attribute for androiddeployqt --> <!-- Leave package attribute for androiddeployqt -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn" package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --"
@ -31,7 +33,10 @@
android:label="-- %%INSERT_APP_NAME%% --" android:label="-- %%INSERT_APP_NAME%% --"
android:icon="@mipmap/icon" android:icon="@mipmap/icon"
android:roundIcon="@mipmap/icon_round" android:roundIcon="@mipmap/icon_round"
android:theme="@style/NoActionBar"> android:theme="@style/NoActionBar"
android:fullBackupContent="@xml/backup_content"
android:dataExtractionRules="@xml/data_extraction_rules"
tools:targetApi="s">
<activity <activity
android:name=".AmneziaActivity" android:name=".AmneziaActivity"
@ -147,7 +152,7 @@
android:authorities="org.amnezia.vpn.qtprovider" android:authorities="org.amnezia.vpn.qtprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" /> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>

View file

@ -108,7 +108,6 @@ dependencies {
implementation(project(":cloak")) implementation(project(":cloak"))
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.activity) implementation(libs.androidx.activity)
implementation(libs.androidx.security.crypto)
implementation(libs.kotlinx.coroutines) implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera) implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit) implementation(libs.google.mlkit)

View file

@ -52,7 +52,7 @@ class OpenVpnClient(
// Callback to construct a new tun builder // Callback to construct a new tun builder
// Should be called first. // Should be called first.
override fun tun_builder_new(): Boolean { override fun tun_builder_new(): Boolean {
Log.v(TAG, "tun_builder_new") Log.d(TAG, "tun_builder_new")
configBuilder.clearAddresses() configBuilder.clearAddresses()
return true return true
} }
@ -60,7 +60,7 @@ class OpenVpnClient(
// Callback to set MTU of the VPN interface // Callback to set MTU of the VPN interface
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_mtu(mtu: Int): Boolean { 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) configBuilder.setMtu(mtu)
return true return true
} }
@ -71,7 +71,7 @@ class OpenVpnClient(
address: String, prefix_length: Int, address: String, prefix_length: Int,
gateway: String, ipv6: Boolean, net30: Boolean gateway: String, ipv6: Boolean, net30: Boolean
): 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)) configBuilder.addAddress(InetNetwork(address, prefix_length))
return true return true
} }
@ -80,7 +80,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // 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 { 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 if (address == "remote_host") return false
configBuilder.addRoute(InetNetwork(address, prefix_length)) configBuilder.addRoute(InetNetwork(address, prefix_length))
return true return true
@ -90,7 +90,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // 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 { 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
configBuilder.excludeRoute(InetNetwork(address, prefix_length)) configBuilder.excludeRoute(InetNetwork(address, prefix_length))
} }
@ -104,7 +104,7 @@ class OpenVpnClient(
// domain should be routed. // domain should be routed.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean { 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)) configBuilder.addDnsServer(parseInetAddress(address))
return true return true
} }
@ -119,28 +119,28 @@ class OpenVpnClient(
// ignored for that family // ignored for that family
// See also Android's VPNService.Builder.allowFamily method // See also Android's VPNService.Builder.allowFamily method
/* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean { /* 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 return true
} */ } */
// Callback to set address of remote server // Callback to set address of remote server
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean { 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 return true
} }
// Optional callback that indicates OSI layer, should be 2 or 3. // Optional callback that indicates OSI layer, should be 2 or 3.
// Defaults to 3. // Defaults to 3.
override fun tun_builder_set_layer(layer: Int): Boolean { 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 return layer == 3
} }
// Callback to set the session name // Callback to set the session name
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_session_name(name: String): Boolean { 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 return true
} }
@ -149,7 +149,7 @@ class OpenVpnClient(
// if the tunnel could not be established. // if the tunnel could not be established.
// Always called last after tun_builder session has been configured. // Always called last after tun_builder session has been configured.
override fun tun_builder_establish(): Int { override fun tun_builder_establish(): Int {
Log.v(TAG, "tun_builder_establish") Log.d(TAG, "tun_builder_establish")
return establish(configBuilder) return establish(configBuilder)
} }
@ -159,7 +159,7 @@ class OpenVpnClient(
// flags are defined in RGWFlags (rgwflags.hpp). // flags are defined in RGWFlags (rgwflags.hpp).
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean { 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 ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true
if (ipv4) { if (ipv4) {
configBuilder.addRoute(InetNetwork("0.0.0.0", 0)) configBuilder.addRoute(InetNetwork("0.0.0.0", 0))
@ -176,7 +176,7 @@ class OpenVpnClient(
// reroute_dns parameter. // reroute_dns parameter.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_search_domain(domain: String): Boolean { 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) configBuilder.setSearchDomain(domain)
return true return true
} }
@ -184,7 +184,7 @@ class OpenVpnClient(
// Callback to set the HTTP proxy // Callback to set the HTTP proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean { 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) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try { try {
configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port)) configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port))
@ -199,7 +199,7 @@ class OpenVpnClient(
// Callback to set the HTTPS proxy // Callback to set the HTTPS proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean { 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 return false
} }
@ -208,7 +208,7 @@ class OpenVpnClient(
// to exclude them from the VPN network are generated // to exclude them from the VPN network are generated
// This should be a list of CIDR networks (e.g. 192.168.0.0/24) // 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 { 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() val networks = ClientAPI_StringVec()
for (address in getLocalNetworks(ipv6)) { for (address in getLocalNetworks(ipv6)) {
networks.add(address.toString()) networks.add(address.toString())
@ -222,21 +222,21 @@ class OpenVpnClient(
// tun_builder_reroute_gw. Route metric is ignored // tun_builder_reroute_gw. Route metric is ignored
// if < 0. // if < 0.
/* override fun tun_builder_set_route_metric_default(metric: Int): Boolean { /* 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) return super.tun_builder_set_route_metric_default(metric)
} */ } */
// Callback to add a host which should bypass the proxy // Callback to add a host which should bypass the proxy
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
/* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean { /* 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) return super.tun_builder_add_proxy_bypass(bypass_host)
} */ } */
// Callback to set the proxy "Auto Config URL" // Callback to set the proxy "Auto Config URL"
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
/* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean { /* 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) 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. // May be called more than once per tun_builder session.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
/* override fun tun_builder_add_wins_server(address: String): Boolean { /* 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) return super.tun_builder_add_wins_server(address)
} */ } */
@ -254,7 +254,7 @@ class OpenVpnClient(
// set the "Connection-specific DNS Suffix" property on // set the "Connection-specific DNS Suffix" property on
// the TAP driver. // the TAP driver.
/* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean { /* 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) 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_lite() will be called. Otherwise,
// tun_builder_establish() will be called. // tun_builder_establish() will be called.
/* override fun tun_builder_persist(): Boolean { /* override fun tun_builder_persist(): Boolean {
Log.v(TAG, "tun_builder_persist") Log.d(TAG, "tun_builder_persist")
return super.tun_builder_persist() return super.tun_builder_persist()
} */ } */
// Indicates a reconnection with persisted tun state. // Indicates a reconnection with persisted tun state.
/* override fun tun_builder_establish_lite() { /* 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() super.tun_builder_establish_lite()
} */ } */
@ -280,7 +280,7 @@ class OpenVpnClient(
// If disconnect == true, then the teardown is occurring // If disconnect == true, then the teardown is occurring
// prior to final disconnect. // prior to final disconnect.
/* override fun tun_builder_teardown(disconnect: Boolean) { /* 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) super.tun_builder_teardown(disconnect)
} */ } */
@ -290,7 +290,7 @@ class OpenVpnClient(
// Parse OpenVPN configuration file. // Parse OpenVPN configuration file.
override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig { override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig {
Log.v(TAG, "eval_config") Log.d(TAG, "eval_config")
return super.eval_config(arg0) return super.eval_config(arg0)
} }
@ -299,7 +299,7 @@ class OpenVpnClient(
// to event() and log() functions. Make sure to call eval_config() // to event() and log() functions. Make sure to call eval_config()
// and possibly provide_creds() as well before this function. // and possibly provide_creds() as well before this function.
override fun connect(): ClientAPI_Status { override fun connect(): ClientAPI_Status {
Log.v(TAG, "connect") Log.d(TAG, "connect")
return super.connect() return super.connect()
} }
@ -307,7 +307,7 @@ class OpenVpnClient(
// Will be called from the thread executing connect(). // Will be called from the thread executing connect().
// The remote and ipv6 are the remote host this socket will connect to // The remote and ipv6 are the remote host this socket will connect to
override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean { 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) return protect(socket)
} }
@ -315,7 +315,7 @@ class OpenVpnClient(
// May be called asynchronously from a different thread // May be called asynchronously from a different thread
// when connect() is running. // when connect() is running.
override fun stop() { override fun stop() {
Log.v(TAG, "stop") Log.d(TAG, "stop")
super.stop() super.stop()
} }
@ -323,21 +323,21 @@ class OpenVpnClient(
// when network is down. May be called from a different thread // when network is down. May be called from a different thread
// when connect() is running. // when connect() is running.
override fun pause(reason: String) { override fun pause(reason: String) {
Log.v(TAG, "pause: $reason") Log.d(TAG, "pause: $reason")
super.pause(reason) super.pause(reason)
} }
// Resume the client after it has been paused. May be called from a // Resume the client after it has been paused. May be called from a
// different thread when connect() is running. // different thread when connect() is running.
override fun resume() { override fun resume() {
Log.v(TAG, "resume") Log.d(TAG, "resume")
super.resume() super.resume()
} }
// Do a disconnect/reconnect cycle n seconds from now. May be called // Do a disconnect/reconnect cycle n seconds from now. May be called
// from a different thread when connect() is running. // from a different thread when connect() is running.
override fun reconnect(seconds: Int) { override fun reconnect(seconds: Int) {
Log.v(TAG, "reconnect") Log.d(TAG, "reconnect: $seconds")
super.reconnect(seconds) super.reconnect(seconds)
} }
@ -346,14 +346,14 @@ class OpenVpnClient(
// CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE // CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE
// state. // state.
override fun pause_on_connection_timeout(): Boolean { override fun pause_on_connection_timeout(): Boolean {
Log.v(TAG, "pause_on_connection_timeout") Log.d(TAG, "pause_on_connection_timeout")
return false return false
} }
// Return information about the most recent connection. Should be called // Return information about the most recent connection. Should be called
// after an event of type "CONNECTED". // after an event of type "CONNECTED".
/* override fun connection_info(): ClientAPI_ConnectionInfo { /* override fun connection_info(): ClientAPI_ConnectionInfo {
Log.v(TAG, "connection_info") Log.d(TAG, "connection_info")
return super.connection_info() return super.connection_info()
} */ } */
@ -366,7 +366,7 @@ class OpenVpnClient(
override fun event(event: ClientAPI_Event) { override fun event(event: ClientAPI_Event) {
val name = event.name val name = event.name
val info = event.info val info = event.info
Log.v(TAG, "OpenVpn event: $name: $info") Log.d(TAG, "OpenVpn event: $name: $info")
when (name) { when (name) {
"COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info") "COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info")
"CONNECTED" -> state.value = CONNECTED "CONNECTED" -> state.value = CONNECTED
@ -398,31 +398,31 @@ class OpenVpnClient(
// return transport stats only // return transport stats only
override fun transport_stats(): ClientAPI_TransportStats { override fun transport_stats(): ClientAPI_TransportStats {
Log.v(TAG, "transport_stats") Log.d(TAG, "transport_stats")
return super.transport_stats() return super.transport_stats()
} }
// return a stats value, index should be >= 0 and < stats_n() // return a stats value, index should be >= 0 and < stats_n()
/* override fun stats_value(index: Int): Long { /* 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 super.stats_value(index)
} */ } */
// return all stats in a bundle // return all stats in a bundle
/* override fun stats_bundle(): ClientAPI_LLVector { /* override fun stats_bundle(): ClientAPI_LLVector {
Log.v(TAG, "stats_bundle") Log.d(TAG, "stats_bundle")
return super.stats_bundle() return super.stats_bundle()
} */ } */
// return tun stats only // return tun stats only
/* override fun tun_stats(): ClientAPI_InterfaceStats { /* override fun tun_stats(): ClientAPI_InterfaceStats {
Log.v(TAG, "tun_stats") Log.d(TAG, "tun_stats")
return super.tun_stats() return super.tun_stats()
} */ } */
// post control channel message // post control channel message
/* override fun post_cc_msg(msg: String) { /* 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) super.post_cc_msg(msg)
} */ } */
} }

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="." />
</full-backup-content>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="." />
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="." />
</device-transfer>
</data-extraction-rules>

View file

@ -143,7 +143,7 @@ class AmneziaActivity : QtActivity() {
*/ */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.v(TAG, "Create Amnezia activity: $intent") Log.d(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger( vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService, onDeadObjectException = ::doUnbindService,
@ -154,7 +154,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent") Log.d(TAG, "onNewIntent: $intent")
intent?.let(::processIntent) intent?.let(::processIntent)
} }
@ -174,7 +174,7 @@ class AmneziaActivity : QtActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
Log.v(TAG, "Start Amnezia activity") Log.d(TAG, "Start Amnezia activity")
mainScope.launch { mainScope.launch {
qtInitialized.await() qtInitialized.await()
doBindService() doBindService()
@ -182,13 +182,13 @@ class AmneziaActivity : QtActivity() {
} }
override fun onStop() { override fun onStop() {
Log.v(TAG, "Stop Amnezia activity") Log.d(TAG, "Stop Amnezia activity")
doUnbindService() doUnbindService()
super.onStop() super.onStop()
} }
override fun onDestroy() { override fun onDestroy() {
Log.v(TAG, "Destroy Amnezia activity") Log.d(TAG, "Destroy Amnezia activity")
mainScope.cancel() mainScope.cancel()
super.onDestroy() super.onDestroy()
} }
@ -217,7 +217,7 @@ class AmneziaActivity : QtActivity() {
CHECK_VPN_PERMISSION_ACTION_CODE -> { CHECK_VPN_PERMISSION_ACTION_CODE -> {
when (resultCode) { when (resultCode) {
RESULT_OK -> { RESULT_OK -> {
Log.v(TAG, "Vpn permission granted") Log.d(TAG, "Vpn permission granted")
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
checkVpnPermissionCallbacks?.run { onSuccess() } checkVpnPermissionCallbacks?.run { onSuccess() }
} }
@ -240,7 +240,7 @@ class AmneziaActivity : QtActivity() {
*/ */
@MainThread @MainThread
private fun doBindService() { private fun doBindService() {
Log.v(TAG, "Bind service") Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also { Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT) bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
} }
@ -251,7 +251,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun doUnbindService() { private fun doUnbindService() {
if (isInBoundState) { if (isInBoundState) {
Log.v(TAG, "Unbind service") Log.d(TAG, "Unbind service")
isWaitingStatus = true isWaitingStatus = true
QtAndroidController.onServiceDisconnected() QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset() vpnServiceMessenger.reset()
@ -286,7 +286,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) { private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) {
Log.v(TAG, "Check VPN permission") Log.d(TAG, "Check VPN permission")
VpnService.prepare(applicationContext)?.let { VpnService.prepare(applicationContext)?.let {
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail) checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail)
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE) startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE)
@ -307,7 +307,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun connectToVpn(vpnConfig: String) { private fun connectToVpn(vpnConfig: String) {
Log.v(TAG, "Connect to VPN") Log.d(TAG, "Connect to VPN")
vpnServiceMessenger.send { vpnServiceMessenger.send {
Action.CONNECT.packToMessage { Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig) putString(VPN_CONFIG, vpnConfig)
@ -316,7 +316,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun startVpnService(vpnConfig: String) { private fun startVpnService(vpnConfig: String) {
Log.v(TAG, "Start VPN service") Log.d(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply { Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig) putExtra(VPN_CONFIG, vpnConfig)
}.also { }.also {
@ -325,7 +325,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun disconnectFromVpn() { private fun disconnectFromVpn() {
Log.v(TAG, "Disconnect from VPN") Log.d(TAG, "Disconnect from VPN")
vpnServiceMessenger.send(Action.DISCONNECT) vpnServiceMessenger.send(Action.DISCONNECT)
} }
@ -369,7 +369,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun saveFile(fileName: String, data: String) { fun saveFile(fileName: String, data: String) {
Log.v(TAG, "Save file $fileName") Log.d(TAG, "Save file $fileName")
mainScope.launch { mainScope.launch {
tmpFileContentToSave = data tmpFileContentToSave = data
@ -397,7 +397,7 @@ class AmneziaActivity : QtActivity() {
Intent(Intent.ACTION_OPEN_DOCUMENT).apply { Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
Log.d(TAG, "File mimyType filter: $mimeTypes") Log.v(TAG, "File mimyType filter: $mimeTypes")
when (mimeTypes.size) { when (mimeTypes.size) {
1 -> type = mimeTypes.first() 1 -> type = mimeTypes.first()
@ -416,13 +416,6 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun setNotificationText(title: String, message: String, timerSec: Int) { fun setNotificationText(title: String, message: String, timerSec: Int) {
Log.v(TAG, "Set notification text") 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") @Suppress("unused")
@ -432,4 +425,29 @@ class AmneziaActivity : QtActivity() {
startActivity(it) 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()
}
} }

View file

@ -5,14 +5,20 @@ import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig import androidx.camera.core.CameraXConfig
import androidx.core.app.NotificationChannelCompat.Builder import androidx.core.app.NotificationChannelCompat.Builder
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.qtproject.qt.android.bindings.QtApplication import org.qtproject.qt.android.bindings.QtApplication
private const val TAG = "AmneziaApplication"
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification" const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
class AmneziaApplication : QtApplication(), CameraXConfig.Provider { class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Prefs.init(this)
Log.init(this)
Log.d(TAG, "Create Amnezia application")
createNotificationChannel() createNotificationChannel()
} }

View file

@ -50,6 +50,7 @@ import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.amnezia.vpn.util.net.NetworkState import org.amnezia.vpn.util.net.NetworkState
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@ -58,6 +59,8 @@ private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG" const val VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG" const val ERROR_MSG = "ERROR_MSG"
const val SAVE_LOGS = "SAVE_LOGS"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK" const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val NOTIFICATION_ID = 1337 private const val NOTIFICATION_ID = 1337
@ -118,7 +121,7 @@ class AmneziaVpnService : VpnService() {
Action.CONNECT -> { Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG) val vpnConfig = msg.data.getString(VPN_CONFIG)
saveConfigToPrefs(vpnConfig) Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(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() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.v(TAG, "Create Amnezia VPN service") Log.d(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler) connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client") clientMessenger = IpcMessenger(messengerName = "Client")
@ -193,15 +200,15 @@ class AmneziaVpnService : VpnService() {
else intent?.component?.packageName != packageName else intent?.component?.packageName != packageName
if (isAlwaysOnCompat) { if (isAlwaysOnCompat) {
Log.v(TAG, "Start service via Always-on") Log.d(TAG, "Start service via Always-on")
connect(loadConfigFromPrefs()) connect(Prefs.load(PREFS_CONFIG_KEY))
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.v(TAG, "Start service after permission check") Log.d(TAG, "Start service after permission check")
connect(loadConfigFromPrefs()) connect(Prefs.load(PREFS_CONFIG_KEY))
} else { } else {
Log.v(TAG, "Start service") Log.d(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG) val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
saveConfigToPrefs(vpnConfig) Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig) connect(vpnConfig)
} }
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
@ -237,7 +244,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onRevoke() { 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 // Calls to onRevoke() method may not happen on the main thread of the process
mainScope.launch { mainScope.launch {
disconnect() disconnect()
@ -245,7 +252,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onDestroy() { override fun onDestroy() {
Log.v(TAG, "Destroy service") Log.d(TAG, "Destroy service")
runBlocking { runBlocking {
disconnect() disconnect()
disconnectionJob?.join() disconnectionJob?.join()
@ -256,7 +263,7 @@ class AmneziaVpnService : VpnService() {
} }
private fun stopService() { private fun stopService() {
Log.v(TAG, "Stop service") Log.d(TAG, "Stop service")
// the coroutine below will be canceled during the onDestroy call // the coroutine below will be canceled during the onDestroy call
mainScope.launch { mainScope.launch {
delay(STOP_SERVICE_TIMEOUT) delay(STOP_SERVICE_TIMEOUT)
@ -272,7 +279,7 @@ class AmneziaVpnService : VpnService() {
private fun launchProtocolStateHandler() { private fun launchProtocolStateHandler() {
mainScope.launch { mainScope.launch {
protocolState.collect { protocolState -> protocolState.collect { protocolState ->
Log.d(TAG, "Protocol state: $protocolState") Log.d(TAG, "Protocol state changed: $protocolState")
when (protocolState) { when (protocolState) {
CONNECTED -> { CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED) clientMessenger.send(ServiceEvent.CONNECTED)
@ -305,7 +312,7 @@ class AmneziaVpnService : VpnService() {
@MainThread @MainThread
private fun launchSendingStatistics() { private fun launchSendingStatistics() {
if (isServiceBound && isConnected) { /* if (isServiceBound && isConnected) {
statisticsSendingJob = mainScope.launch { statisticsSendingJob = mainScope.launch {
while (true) { while (true) {
clientMessenger.send { clientMessenger.send {
@ -316,7 +323,7 @@ class AmneziaVpnService : VpnService() {
delay(STATISTICS_SENDING_TIMEOUT) delay(STATISTICS_SENDING_TIMEOUT)
} }
} }
} } */
} }
@MainThread @MainThread
@ -328,7 +335,7 @@ class AmneziaVpnService : VpnService() {
private fun connect(vpnConfig: String?) { private fun connect(vpnConfig: String?) {
if (isConnected || protocolState.value == CONNECTING) return if (isConnected || protocolState.value == CONNECTING) return
Log.v(TAG, "Start VPN connection") Log.d(TAG, "Start VPN connection")
protocolState.value = CONNECTING protocolState.value = CONNECTING
@ -357,7 +364,7 @@ class AmneziaVpnService : VpnService() {
private fun disconnect() { private fun disconnect() {
if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return
Log.v(TAG, "Stop VPN connection") Log.d(TAG, "Stop VPN connection")
protocolState.value = DISCONNECTING protocolState.value = DISCONNECTING
@ -383,7 +390,7 @@ class AmneziaVpnService : VpnService() {
private fun reconnect() { private fun reconnect() {
if (!isConnected) return if (!isConnected) return
Log.v(TAG, "Reconnect VPN") Log.d(TAG, "Reconnect VPN")
protocolState.value = RECONNECTING protocolState.value = RECONNECTING
@ -439,10 +446,4 @@ class AmneziaVpnService : VpnService() {
} else { } else {
true 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()
} }

View file

@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.v(TAG, "Create Import Config Activity: $intent") Log.d(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent") Log.d(TAG, "onNewIntent: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
private fun readConfig(intent: Intent) { private fun readConfig(intent: Intent) {
when (intent.action) { when (intent.action) {
ACTION_SEND -> { ACTION_SEND -> {
Log.v(TAG, "Process SEND action, type: ${intent.type}") Log.d(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) { when (intent.type) {
"application/octet-stream" -> { "application/octet-stream" -> {
intent.getUriCompat()?.let { uri -> intent.getUriCompat()?.let { uri ->
@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
} }
ACTION_VIEW -> { ACTION_VIEW -> {
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}") Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) { when (intent.scheme) {
"file", "content" -> { "file", "content" -> {
intent.data?.let { uri -> intent.data?.let { uri ->
@ -128,7 +128,7 @@ class ImportConfigActivity : ComponentActivity() {
private fun startMainActivity(config: String) { private fun startMainActivity(config: String) {
if (config.isNotBlank()) { if (config.isNotBlank()) {
Log.v(TAG, "startMainActivity") Log.d(TAG, "startMainActivity")
Intent(applicationContext, AmneziaActivity::class.java).apply { Intent(applicationContext, AmneziaActivity::class.java).apply {
action = ACTION_IMPORT_CONFIG action = ACTION_IMPORT_CONFIG
addCategory(CATEGORY_DEFAULT) addCategory(CATEGORY_DEFAULT)

View file

@ -32,7 +32,8 @@ enum class Action : IpcMessage {
REGISTER_CLIENT, REGISTER_CLIENT,
CONNECT, CONNECT,
DISCONNECT, DISCONNECT,
REQUEST_STATUS REQUEST_STATUS,
SET_SAVE_LOGS
} }
fun <T> T.packToMessage(): Message fun <T> T.packToMessage(): Message

View file

@ -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)
}
}

View file

@ -25,7 +25,7 @@ class VpnRequestActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.v(TAG, "Start request activity") Log.d(TAG, "Start request activity")
val requestIntent = VpnService.prepare(applicationContext) val requestIntent = VpnService.prepare(applicationContext)
if (requestIntent != null) { if (requestIntent != null) {
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) { if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {

View file

@ -15,3 +15,7 @@ android {
buildConfig = true buildConfig = true
} }
} }
dependencies {
implementation(libs.androidx.security.crypto)
}

View file

@ -1,33 +1,252 @@
package org.amnezia.vpn.util 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 import android.util.Log as NativeLog
class Log { private const val TAG = "Log"
companion object { private const val LOG_FILE_NAME = "amneziaVPN.log"
fun v(tag: String, msg: String) = debugLog(tag, msg, NativeLog::v) 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<DateFormat>() {
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) { fun v(tag: String, msg: Any?) = v(tag, msg.toString())
if (BuildConfig.DEBUG) delegate(tag, msg)
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<DateFormat>).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)
}
} }

View file

@ -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 <reified T> load(key: String): T {
return when (typeOf<T>()) {
typeOf<Boolean>() -> prefs.getBoolean(key, false)
typeOf<String>() -> prefs.getString(key, "")
typeOf<Int>() -> prefs.getInt(key, 0)
typeOf<Long>() -> prefs.getLong(key, 0L)
typeOf<Float>() -> prefs.getFloat(key, 0f)
else -> throw IllegalArgumentException("SharedPreferences does not support type: ${typeOf<T>()}")
} as T
}
}

View file

@ -82,7 +82,7 @@ class NetworkState(
fun bindNetworkListener() { fun bindNetworkListener() {
if (isListenerBound) return if (isListenerBound) return
Log.v(TAG, "Bind network listener") Log.d(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -95,7 +95,7 @@ class NetworkState(
fun unbindNetworkListener() { fun unbindNetworkListener() {
if (!isListenerBound) return if (!isListenerBound) return
Log.v(TAG, "Unbind network listener") Log.d(TAG, "Unbind network listener")
connectivityManager.unregisterNetworkCallback(networkCallback) connectivityManager.unregisterNetworkCallback(networkCallback)
isListenerBound = false isListenerBound = false
currentNetwork = null currentNetwork = null

View file

@ -12,13 +12,15 @@ namespace
AndroidController *s_instance = nullptr; AndroidController *s_instance = nullptr;
constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController"; 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 } // namespace
AndroidController::AndroidController() : QObject() AndroidController::AndroidController() : QObject()
{ {
connect(this, &AndroidController::status, this, connect(this, &AndroidController::status, this,
[this](AndroidController::ConnectionState state) { [this](AndroidController::ConnectionState state) {
qDebug() << "Android event: status; state:" << textConnectionState(state); qDebug() << "Android event: status =" << textConnectionState(state);
if (isWaitingStatus) { if (isWaitingStatus) {
qDebug() << "Initialization by service status"; qDebug() << "Initialization by service status";
isWaitingStatus = false; isWaitingStatus = false;
@ -126,24 +128,19 @@ bool AndroidController::initialize()
// static // static
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
auto AndroidController::callActivityMethod(const char *methodName, const char *signature, auto AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
const std::function<Ret()> &defValue, Args &&...args)
{ {
qDebug() << "Call activity method:" << methodName; qDebug() << "Call activity method:" << methodName;
QJniObject activity = AndroidUtils::getActivity(); QJniObject activity = AndroidUtils::getActivity();
if (activity.isValid()) { Q_ASSERT(activity.isValid());
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...); return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
} else {
qCritical() << "Activity is not valid";
return defValue();
}
} }
// static // static
template <typename ...Args> template <typename ...Args>
void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
{ {
callActivityMethod<void>(methodName, signature, [] {}, std::forward<Args>(args)...); callActivityMethod<void>(methodName, signature, std::forward<Args>(args)...);
} }
ErrorCode AndroidController::start(const QJsonObject &vpnConfig) ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
@ -199,6 +196,104 @@ void AndroidController::startQrReaderActivity()
callActivityMethod("startQrCodeReader", "()V"); 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<jstring>());
}
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<void>(log, logMethod,
QJniObject::fromString(TAG).object<jstring>(),
QJniObject::fromString(formattedMessage).object<jstring>());
}
void AndroidController::qtAndroidControllerInitialized() void AndroidController::qtAndroidControllerInitialized()
{ {
callActivityMethod("qtAndroidControllerInitialized", "()V"); callActivityMethod("qtAndroidControllerInitialized", "()V");

View file

@ -34,6 +34,12 @@ public:
void saveFile(const QString &fileName, const QString &data); void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter); QString openFile(const QString &filter);
void startQrReaderActivity(); 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: signals:
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
@ -53,6 +59,13 @@ signals:
private: private:
bool isWaitingStatus = true; bool isWaitingStatus = true;
static jclass log;
static jmethodID logDebug;
static jmethodID logInfo;
static jmethodID logWarning;
static jmethodID logError;
static jmethodID logFatal;
void qtAndroidControllerInitialized(); void qtAndroidControllerInitialized();
static Vpn::ConnectionState convertState(ConnectionState state); static Vpn::ConnectionState convertState(ConnectionState state);
@ -72,8 +85,7 @@ private:
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
const std::function<Ret()> &defValue, Args &&...args);
template <typename ...Args> template <typename ...Args>
static void callActivityMethod(const char *methodName, const char *signature, Args &&...args); static void callActivityMethod(const char *methodName, const char *signature, Args &&...args);
}; };

View file

@ -216,6 +216,7 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled) void Settings::setSaveLogs(bool enabled)
{ {
setValue("Conf/saveLogs", enabled); setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID
if (!isSaveLogs()) { if (!isSaveLogs()) {
Logger::deInit(); Logger::deInit();
} else { } else {
@ -223,7 +224,8 @@ void Settings::setSaveLogs(bool enabled)
qWarning() << "Initialization of debug subsystem failed"; qWarning() << "Initialization of debug subsystem failed";
} }
} }
emit saveLogsChanged(); #endif
emit saveLogsChanged(enabled);
} }
QString Settings::routeModeString(RouteMode mode) const QString Settings::routeModeString(RouteMode mode) const

View file

@ -190,7 +190,7 @@ public:
void clearSettings(); void clearSettings();
signals: signals:
void saveLogsChanged(); void saveLogsChanged(bool enabled);
private: private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;

View file

@ -8,6 +8,7 @@
#include "version.h" #include "version.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h" #include "platforms/android/android_utils.h"
#include "platforms/android/android_controller.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
@ -91,13 +92,21 @@ void SettingsController::openLogsFolder()
void SettingsController::exportLogsFile(const QString &fileName) void SettingsController::exportLogsFile(const QString &fileName)
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->exportLogsFile(fileName);
#else
SystemController::saveFile(fileName, Logger::getLogFile()); SystemController::saveFile(fileName, Logger::getLogFile());
#endif
} }
void SettingsController::clearLogs() void SettingsController::clearLogs()
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs(); Logger::clearLogs();
Logger::clearServiceLogs(); Logger::clearServiceLogs();
#endif
} }
void SettingsController::backupAppConfig(const QString &fileName) void SettingsController::backupAppConfig(const QString &fileName)