aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-android
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-android')
-rw-r--r--packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js3
-rw-r--r--packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt13
-rw-r--r--packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/phonesync/DocChannels.kt14
-rw-r--r--packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/photos/PhotoChannels.kt14
4 files changed, 35 insertions, 9 deletions
diff --git a/packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js b/packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js
index 22fbe3d..a07399f 100644
--- a/packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js
+++ b/packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js
@@ -238,6 +238,7 @@
sent: (token, dir, name) => call('contactsync:sent', token, dir, name),
completed: () => call('contactsync:completed'),
failed: (code, text) => call('contactsync:failed', code, text),
+ openSettings: () => call('contactsync:open-settings'),
},
// Calendar backup (§9.13), the same way: one iCalendar file of every
@@ -250,6 +251,7 @@
sent: (token, dir, name) => call('calendarsync:sent', token, dir, name),
completed: () => call('calendarsync:completed'),
failed: (code, text) => call('calendarsync:failed', code, text),
+ openSettings: () => call('calendarsync:open-settings'),
},
// Files backup (§9.13): folders the person chose through the system's
@@ -295,6 +297,7 @@
sent: (token, dir, name) => call('messagesync:sent', token, dir, name),
completed: () => call('messagesync:completed'),
failed: (code, text) => call('messagesync:failed', code, text),
+ openSettings: () => call('messagesync:open-settings'),
} } : {}),
// Where downloads go, chosen once. A display name comes back, never a URI.
diff --git a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt
index 5cf06fc..699e0c3 100644
--- a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt
+++ b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/MainActivity.kt
@@ -187,13 +187,11 @@ class MainActivity : Activity() {
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
val url = request.url
if (url.host == UiAssets.HOST && url.path?.startsWith(PhotoChannels.PATH) == true) {
- val range = request.requestHeaders.entries.firstOrNull { it.key.equals("Range", true) }?.value
- return photos.takeIf { backups }?.serve(url.path ?: "", range) ?: refused()
+ return photos.takeIf { backups }?.serve(url.path ?: "", rangeOf(request)) ?: refused()
}
val driveKind = drives.firstOrNull { url.path?.startsWith(it.path) == true }
if (url.host == UiAssets.HOST && driveKind != null) {
- val range = request.requestHeaders.entries.firstOrNull { it.key.equals("Range", true) }?.value
- return driveKind.serve(url.path ?: "", range) ?: refused()
+ return driveKind.serve(url.path ?: "", rangeOf(request)) ?: refused()
}
if (url.host == UiAssets.HOST && url.path?.startsWith(DocChannels.PATH) == true) {
val path = url.path ?: ""
@@ -383,6 +381,13 @@ class MainActivity : Activity() {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
+ /**
+ * The slice a backup asks for, `?range=a-b` (platform.js): the WebView does
+ * not hand a fetch's Range header over to the application that answers.
+ */
+ private fun rangeOf(request: WebResourceRequest): String? =
+ request.url.getQueryParameter("range")?.let { "bytes=$it" }
+
private fun hasAsset(path: String) = try { assets.open(path).close(); true } catch (e: java.io.IOException) { false }
private fun refused() = WebResourceResponse("text/plain", "utf-8", 403, "Forbidden", emptyMap(), "".byteInputStream())
diff --git a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/phonesync/DocChannels.kt b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/phonesync/DocChannels.kt
index 2efe558..f4da64f 100644
--- a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/phonesync/DocChannels.kt
+++ b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/phonesync/DocChannels.kt
@@ -64,6 +64,7 @@ class DocChannels(
"sent" -> { sent(args.optString(0, ""), args.optString(1, ""), args.optString(2, "")); true }
"completed" -> { completed(); status() }
"failed" -> failed(args.optString(0, ""), args.optString(1, ""))
+ "open-settings" -> { openSettings(); true }
else -> throw Refused("Refused: no such channel")
}
@@ -167,6 +168,19 @@ class DocChannels(
permission = null
}
+ /**
+ * The application's page in the system's settings. Android keeps some
+ * permissions (text messages) behind "restricted settings" for an
+ * application installed outside a store; that page is where the person
+ * lifts it (⋮ > Allow restricted settings), and nowhere in here can.
+ */
+ private fun openSettings() {
+ val intent = android.content.Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ android.net.Uri.fromParts("package", activity.packageName, null))
+ .addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
+ activity.runOnUiThread { activity.startActivity(intent) }
+ }
+
/** From Activity.onRequestPermissionsResult; true when the request was ours. */
fun deliverPermission(requestCode: Int): Boolean {
if (requestCode != permissionRequest) return false
diff --git a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/photos/PhotoChannels.kt b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/photos/PhotoChannels.kt
index 2e7c519..c1de859 100644
--- a/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/photos/PhotoChannels.kt
+++ b/packages/meshbay-android/app/src/main/kotlin/org/meshbay/client/photos/PhotoChannels.kt
@@ -135,9 +135,11 @@ class PhotoChannels(
if (destinations.get() == null) throw Refused("Refused: no destination")
val next = SyncConfig.fromJson(o, System.currentTimeMillis(), previous)
val edit = prefs.edit().putString(CONFIG, next.toJson().toString())
- // A different scope is a different backup: due at once, and whatever
- // the last one was refused for is not this one's problem.
- if (previous == null || previous.since != next.since) {
+ // Anything that asks for more (another scope, an album, the videos)
+ // is due at once rather than tomorrow: the person just asked for it,
+ // and waits to see it go. Whatever the last run was refused for is
+ // not this one's problem.
+ if (previous == null || previous != next) {
edit.remove(LAST).remove(FAILURE).remove(FAILURE_AT).remove(NOTIFIED)
}
edit.apply()
@@ -367,7 +369,7 @@ class PhotoChannels(
.setContentIntent(pending).setAutoCancel(true).build())
}
- /** `range` of `stream`, `size` bytes long, as a 206 (or a 416 for a range that cannot be served). */
+ /** `range` of `stream`, `size` bytes long (or a 416 for a range that cannot be served). */
fun serveRange(stream: InputStream, size: Long, mime: String, range: String?): WebResourceResponse {
val r = ByteRange.parse(range, size) ?: run {
stream.close()
@@ -377,7 +379,9 @@ class PhotoChannels(
val headers = mapOf("Cache-Control" to "no-store", "X-Content-Type-Options" to "nosniff",
"Content-Range" to "bytes ${r.first}-${r.last}/$size",
"Content-Length" to (r.last - r.first + 1).toString())
- return WebResourceResponse(mime, null, 206, "Partial Content", headers, ByteRange.slice(stream, r))
+ // 200, not 206: what was asked is in the address, and a 206 to a
+ // request that carried no Range is not something to rely on.
+ return WebResourceResponse(mime, null, 200, "OK", headers, ByteRange.slice(stream, r))
}
fun digestOf(s: InputStream): ByteArray {