mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-03 08:06:59 +00:00
feat: register a bulwark account with android's contacts and calendar sync #34
Android syncs the contacts and calendars of other services through
accounts and sync adapters. This adds the Android side of device sync:
a "Bulwark" account type (`${applicationId}.account`, set in
build.gradle), an authenticator that sends Settings → Add account to
the app instead of asking for credentials, and one sync adapter each
for the contacts and the calendar provider.
An adapter does no syncing itself. It starts the BulwarkDeviceSync
headless JS task from the main thread, through the helper the push job
uses, waits for the task's report and hands it to SyncManager as the
SyncResult. The engine that will write the report runs in JS, so the
existing client stays the only owner of token refresh, mTLS and
session discovery; for now the task only reports that device sync is
off. Nothing in the app creates an account yet, so the feature stays
unreachable until its settings land. Debug builds get an adb hook for
the device tests (DeviceSyncDebugReceiver, shell only).
Hard errors are reported without progress: SyncManager retries a
failed sync at once when it made progress, which would loop an auth
failure.
Checked on the API 34 and API 32 emulators with the debug build, from
logcat and `dumpsys content`:
- app in the foreground: report after 319-334 ms
- app in the background: 320-327 ms
- process killed: SyncManager starts it for the sync service, React
Native starts headless, and the report arrives after 16.2 s (API 34)
and 8.5 s (API 32), most of it the debug bundle download from Metro
- an io report is retried with back-off and an auth report is not
retried; too many deletions shows the system's "Attempted to delete
too many Contacts." notification
- with Metro unreachable the adapter gives up after 2 s instead of
waiting for its deadline
- `gradlew :app:testDebugUnitTest`: 10 JVM tests for the report mapping
This commit is contained in:
+58
@@ -0,0 +1,58 @@
|
||||
package com.anonymous.bulwarkmobile.sync
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
|
||||
/**
|
||||
* Debug builds only: lets adb set up device sync accounts without the settings
|
||||
* UI, for the device tests in docs/device-sync.md ("Testing"). Protected by
|
||||
* android.permission.DUMP, which only the shell and the system hold. Syncs
|
||||
* themselves are triggered with `adb shell requestsync`.
|
||||
*
|
||||
* adb shell am broadcast -n com.anonymous.bulwarkmobile/.sync.DeviceSyncDebugReceiver \
|
||||
* --es cmd ensure --es name usera@example.org --es registry usera@example.org@10.0.2.2 \
|
||||
* --es enable contacts,calendar
|
||||
*
|
||||
* Commands: `ensure` (name, registry, optional enable), `enable` (name,
|
||||
* authority, optional --ez on false), `remove` (name) and `list`.
|
||||
*/
|
||||
class DeviceSyncDebugReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val result = try {
|
||||
val name = intent.getStringExtra("name")
|
||||
when (intent.getStringExtra("cmd")) {
|
||||
"ensure" -> {
|
||||
val created = DeviceSyncAccounts.ensure(context, name!!, intent.getStringExtra("registry")!!)
|
||||
for (which in intent.getStringExtra("enable").orEmpty().split(',').filter { it.isNotBlank() }) {
|
||||
DeviceSyncAccounts.setSyncEnabled(context, name, authority(which), true)
|
||||
}
|
||||
"created=$created"
|
||||
}
|
||||
"enable" -> {
|
||||
val on = intent.getBooleanExtra("on", true)
|
||||
DeviceSyncAccounts.setSyncEnabled(context, name!!, authority(intent.getStringExtra("authority")!!), on)
|
||||
"enabled=$on"
|
||||
}
|
||||
"remove" -> "removed=${DeviceSyncAccounts.remove(context, name!!)}"
|
||||
"list" -> DeviceSyncAccounts.list(context).joinToString(";") { (account, registryId) -> "${account.name}=$registryId" }
|
||||
else -> "unknown cmd"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
"error: ${e.javaClass.simpleName}: ${e.message}"
|
||||
}
|
||||
Log.i(TAG, "debug command: $result")
|
||||
resultData = result
|
||||
}
|
||||
|
||||
private fun authority(which: String) = when (which) {
|
||||
"contacts" -> DeviceSyncAccounts.CONTACTS_AUTHORITY
|
||||
"calendar" -> DeviceSyncAccounts.CALENDAR_AUTHORITY
|
||||
else -> which
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "BulwarkDeviceSync"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user