diff --git a/PR-DESCRIPTION.md b/PR-DESCRIPTION.md new file mode 100644 index 00000000..0ba8584e --- /dev/null +++ b/PR-DESCRIPTION.md @@ -0,0 +1,39 @@ +## Add configurable network access setting + +I needed to access the ActivityWatch API from other devices on my network (via Tailscale) to sync data to a central server, so I figured I'd contribute a proper implementation rather than just hardcoding `0.0.0.0`. + +### What this does + +Adds a "Allow network access" toggle in a new native Settings screen, accessible from the action bar settings button (which previously showed a "not yet implemented" snackbar). + +- **Off by default** — server binds to `127.0.0.1` (unchanged behavior) +- **When enabled** — server binds to `0.0.0.0`, allowing connections from other devices on the local network +- Enabling shows a **security warning dialog** explaining that the API has no authentication and activity data will be exposed to the network +- Displays a toast noting the change takes effect on app restart + +### Changes + +**aw-android:** +- `AWPreferences.kt` — new `isNetworkAccessEnabled` / `setNetworkAccessEnabled` preference +- `RustInterface.kt` — `startServer()` and `startServerTask()` now accept a `host` parameter +- `MainActivity.kt` — reads the preference and passes the host to the server; wires the Settings action bar button to the new `SettingsActivity` +- `SettingsActivity.kt` (new) — simple settings screen with the network access toggle + confirmation dialog +- `activity_settings.xml` (new) — layout for the settings screen +- `strings.xml` — added setting labels and warning text +- `AndroidManifest.xml` — registered `SettingsActivity` + +**aw-server-rust** (submodule, [see commit](https://github.com/lucletoffe/aw-server-rust/commit/7a5c14841ee7b33b29576d4b2cc2003cacd52ac2)): +- `android/mod.rs` — `startServer` JNI function now accepts a `host` string parameter and passes it to `AWConfig` instead of relying on the default + +### Security considerations + +The toggle is opt-in with a clear warning. The dialog text mentions: +- No authentication on the API +- Anyone on the same network can read/modify data +- Recommendation to only enable on trusted networks (e.g. VPN) + +### Screenshots + +_Not available yet — will add after building the APK._ + +Closes #121, closes #107 diff --git a/aw-server-rust b/aw-server-rust index dc70318e..7a5c1484 160000 --- a/aw-server-rust +++ b/aw-server-rust @@ -1 +1 @@ -Subproject commit dc70318e819efc0d0535a5d7bd35a0c7ab8e9106 +Subproject commit 7a5c14841ee7b33b29576d4b2cc2003cacd52ac2 diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index c983de3a..6fe919b2 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -48,6 +48,12 @@ android:theme="@style/AppTheme.NoActionBar" android:exported="true"/> + + { - Snackbar.make(binding.coordinatorLayout, "The settings button was clicked, but it's not yet implemented!", Snackbar.LENGTH_LONG) - .setAction("Action", null).show() + val intent = Intent(this, SettingsActivity::class.java) + startActivity(intent) true } else -> super.onOptionsItemSelected(item) diff --git a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt index 01b68c50..1fc1d8bd 100644 --- a/mobile/src/main/java/net/activitywatch/android/RustInterface.kt +++ b/mobile/src/main/java/net/activitywatch/android/RustInterface.kt @@ -40,7 +40,7 @@ class RustInterface constructor(context: Context? = null) { private external fun initialize(): String private external fun greeting(pattern: String): String - private external fun startServer() + private external fun startServer(host: String) private external fun setDataDir(path: String) external fun getBuckets(): String external fun createBucket(bucket: String): String @@ -51,7 +51,7 @@ class RustInterface constructor(context: Context? = null) { return greeting(to) } - fun startServerTask(context: Context) { + fun startServerTask(context: Context, host: String = "127.0.0.1") { if(!serverStarted) { // check if port 5600 is already in use try { @@ -69,8 +69,8 @@ class RustInterface constructor(context: Context? = null) { // will not block the UI thread // Start server - Log.w(TAG, "Starting server...") - startServer() + Log.w(TAG, "Starting server on $host...") + startServer(host) handler.post { // will run on UI thread after the task is done @@ -78,7 +78,7 @@ class RustInterface constructor(context: Context? = null) { serverStarted = false } } - Log.w(TAG, "Server started") + Log.w(TAG, "Server started on $host") } } diff --git a/mobile/src/main/java/net/activitywatch/android/SettingsActivity.kt b/mobile/src/main/java/net/activitywatch/android/SettingsActivity.kt new file mode 100644 index 00000000..6c9dd453 --- /dev/null +++ b/mobile/src/main/java/net/activitywatch/android/SettingsActivity.kt @@ -0,0 +1,54 @@ +package net.activitywatch.android + +import android.os.Bundle +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.google.android.material.switchmaterial.SwitchMaterial + +class SettingsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_settings) + + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.title = getString(R.string.settings_title) + + val prefs = AWPreferences(this) + val switchNetworkAccess = findViewById(R.id.switchNetworkAccess) + switchNetworkAccess.isChecked = prefs.isNetworkAccessEnabled() + + switchNetworkAccess.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + // Show security warning before enabling + AlertDialog.Builder(this) + .setTitle(R.string.network_access_warning_title) + .setMessage(R.string.network_access_warning_message) + .setPositiveButton(R.string.network_access_warning_enable) { _, _ -> + prefs.setNetworkAccessEnabled(true) + showRestartNotice() + } + .setNegativeButton(android.R.string.cancel) { _, _ -> + switchNetworkAccess.isChecked = false + } + .setOnCancelListener { + switchNetworkAccess.isChecked = false + } + .show() + } else { + prefs.setNetworkAccessEnabled(false) + showRestartNotice() + } + } + } + + private fun showRestartNotice() { + Toast.makeText(this, R.string.network_access_restart_notice, Toast.LENGTH_LONG).show() + } + + override fun onSupportNavigateUp(): Boolean { + finish() + return true + } +} diff --git a/mobile/src/main/res/layout/activity_settings.xml b/mobile/src/main/res/layout/activity_settings.xml new file mode 100644 index 00000000..055c9af6 --- /dev/null +++ b/mobile/src/main/res/layout/activity_settings.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index fd66293d..82d2e656 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -26,4 +26,13 @@ Hello blank fragment + + + Settings + Allow network access + Allow other devices on the local network to connect to the ActivityWatch server. When disabled, only this device can access the server. + Security warning + Enabling network access will allow any device on the same network to access your ActivityWatch data.\n\nThe ActivityWatch API does not have authentication, so anyone on your network will be able to read and modify your activity data.\n\nOnly enable this if you trust your network (e.g. a VPN like Tailscale). + Enable + Restart the app for this change to take effect.