@tokamakdev/plugin-location 0.1.0-beta.132 → 0.1.0-beta.134

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ import com.tokamak.runtime.TokamakHost
7
7
  import com.tokamak.runtime.TokamakPluginBytesReply
8
8
  import com.tokamak.runtime.TokamakPluginError
9
9
  import com.tokamak.runtime.TokamakValue
10
+ import com.tokamak.runtime.jsonName
10
11
  import kotlin.reflect.KClass
11
12
  import org.json.JSONArray
12
13
  import org.json.JSONObject
@@ -142,9 +143,9 @@ class TokamakLocationPluginDispatch(host: TokamakHost) : TokamakDispatch<Tokamak
142
143
 
143
144
  private fun json(value: LocationPermission): JSONObject =
144
145
  JSONObject()
145
- .put("inUse", value.inUse.name)
146
- .put("background", value.background.name)
147
- .put("precision", value.precision.name)
146
+ .put("inUse", jsonName(value.inUse.name))
147
+ .put("background", jsonName(value.background.name))
148
+ .put("precision", jsonName(value.precision.name))
148
149
 
149
150
  private fun TokamakValue.asLocationUpdate(): LocationUpdate =
150
151
  LocationUpdate(
@@ -183,7 +184,7 @@ class TokamakLocationPluginDispatch(host: TokamakHost) : TokamakDispatch<Tokamak
183
184
  private fun json(value: PermissionRequest): JSONObject =
184
185
  JSONObject()
185
186
  .putOpt("background", value.background)
186
- .putOpt("precision", value.precision?.name)
187
+ .putOpt("precision", value.precision?.let { jsonName(it.name) })
187
188
 
188
189
  private fun TokamakValue.asPosition(): Position =
189
190
  Position(
@@ -225,7 +226,7 @@ class TokamakLocationPluginDispatch(host: TokamakHost) : TokamakDispatch<Tokamak
225
226
  private fun json(value: RegionTransition): JSONObject =
226
227
  JSONObject()
227
228
  .put("id", value.id)
228
- .put("transition", value.transition.name)
229
+ .put("transition", jsonName(value.transition.name))
229
230
  .put("timestamp", value.timestamp)
230
231
 
231
232
  private fun TokamakValue.asServiceNotification(): ServiceNotification =
@@ -260,7 +261,7 @@ class TokamakLocationPluginDispatch(host: TokamakHost) : TokamakDispatch<Tokamak
260
261
 
261
262
  private fun json(value: UpdateOptions): JSONObject =
262
263
  JSONObject()
263
- .putOpt("accuracy", value.accuracy?.name)
264
+ .putOpt("accuracy", value.accuracy?.let { jsonName(it.name) })
264
265
  .putOpt("distance", value.distance)
265
266
  .putOpt("interval", value.interval)
266
267
  .putOpt("foregroundService", value.foregroundService?.let { json(it) })
@@ -36,7 +36,7 @@ internal class LocationEvents(private val host: TokamakHost) {
36
36
  }
37
37
 
38
38
  fun transition(id: String, entered: Boolean, finish: () -> Unit) {
39
- val transition = RegionTransition(id, if (entered) Transition.enter else Transition.exit, System.currentTimeMillis())
39
+ val transition = RegionTransition(id, if (entered) Transition.Enter else Transition.Exit, System.currentTimeMillis())
40
40
  transitions.send(transition)
41
41
  send("region", transition, finish)
42
42
  }
@@ -41,9 +41,9 @@ class TokamakLocationPlugin(
41
41
  /** Foreground location permissions, which the `precision` build setting declares. */
42
42
  private val foreground: Set<String> by lazy { setOf(COARSE, FINE).filter(declared::contains).toSet() }
43
43
 
44
- private val updates = LocationUpdates(context, LocationUpdates.UPDATES)
45
- private val significantChanges = LocationUpdates(context, LocationUpdates.SIGNIFICANT_CHANGES)
46
- private val regions = LocationRegions(context)
44
+ private val updates = LocationUpdates(host, LocationUpdates.UPDATES)
45
+ private val significantChanges = LocationUpdates(host, LocationUpdates.SIGNIFICANT_CHANGES)
46
+ private val regions = LocationRegions(host)
47
47
  private val events = LocationEvents(host)
48
48
  private val positions = LocationPositions(context, manager)
49
49
 
@@ -89,10 +89,10 @@ class TokamakLocationPlugin(
89
89
 
90
90
  /** The app's in-use and background permission, and the precision it gets, or would get once allowed. */
91
91
  fun permission(): LocationPermission {
92
- val inUse = if (hasForegroundPermission()) Permission.granted else state(COARSE)
93
- val background = if (BACKGROUND in declared) state(BACKGROUND) else Permission.denied
92
+ val inUse = if (hasForegroundPermission()) Permission.Granted else state(COARSE)
93
+ val background = if (BACKGROUND in declared) state(BACKGROUND) else Permission.Denied
94
94
  val precise = granted(FINE) || (!granted(COARSE) && FINE in declared)
95
- return LocationPermission(inUse, background, if (precise) Precision.precise else Precision.approximate)
95
+ return LocationPermission(inUse, background, if (precise) Precision.Precise else Precision.Approximate)
96
96
  }
97
97
 
98
98
  /**
@@ -104,7 +104,7 @@ class TokamakLocationPlugin(
104
104
  if (background && BACKGROUND !in declared) {
105
105
  throw TokamakPluginError.notSupported("Background location needs the location plugin's background build setting")
106
106
  }
107
- val asked = if (request?.precision == Precision.approximate) setOf(COARSE) else foreground
107
+ val asked = if (request?.precision == Precision.Approximate) setOf(COARSE) else foreground
108
108
  val allowed = ask(asked).containsValue(true)
109
109
  if (allowed) restore(updatesRemoved = false)
110
110
  if (background && allowed) ask(setOf(BACKGROUND))
@@ -121,10 +121,11 @@ class TokamakLocationPlugin(
121
121
  /** Starts updates in the foreground service when [options] name one, which stops otherwise. On failure, none run. */
122
122
  fun startUpdates(options: UpdateOptions?) {
123
123
  val running = options ?: UpdateOptions(null, null, null, null)
124
+ val notification = running.foregroundService
124
125
  requirePermission()
125
- if (running.foregroundService != null) requireService()
126
+ if (notification != null) requireService()
126
127
  try {
127
- if (running.foregroundService == null) TokamakLocationService.stop(context) else TokamakLocationService.start(context, running)
128
+ if (notification == null) TokamakLocationService.stop(context) else TokamakLocationService.start(context, notification)
128
129
  updates.start(running)
129
130
  } catch (error: Exception) {
130
131
  stopUpdates()
@@ -167,9 +168,9 @@ class TokamakLocationPlugin(
167
168
 
168
169
  private fun state(permission: String): Permission =
169
170
  when {
170
- granted(permission) -> Permission.granted
171
- host.wouldPrompt(permission) -> Permission.prompt
172
- else -> Permission.denied
171
+ granted(permission) -> Permission.Granted
172
+ host.wouldPrompt(permission) -> Permission.Prompt
173
+ else -> Permission.Denied
173
174
  }
174
175
 
175
176
  private fun requirePermission() {
@@ -177,7 +178,7 @@ class TokamakLocationPlugin(
177
178
  }
178
179
 
179
180
  private fun startService() {
180
- updates.options?.takeIf { it.foregroundService != null }?.let { TokamakLocationService.start(context, it) }
181
+ updates.options?.foregroundService?.let { TokamakLocationService.start(context, it) }
181
182
  }
182
183
 
183
184
  /**
@@ -260,6 +261,6 @@ class TokamakLocationPlugin(
260
261
  const val BACKGROUND = Manifest.permission.ACCESS_BACKGROUND_LOCATION
261
262
 
262
263
  /** Apple's significant-change service's thresholds: 500 metres or more, at most every 5 minutes, without GPS. */
263
- val SIGNIFICANT_CHANGES = UpdateOptions(Accuracy.low, distance = 500.0, interval = 300_000, foregroundService = null)
264
+ val SIGNIFICANT_CHANGES = UpdateOptions(Accuracy.Low, distance = 500.0, interval = 300_000, foregroundService = null)
264
265
  }
265
266
  }
@@ -4,6 +4,8 @@ import android.app.PendingIntent
4
4
  import android.content.Context
5
5
  import android.content.Intent
6
6
  import android.location.LocationManager
7
+ import com.tokamak.runtime.TokamakHost
8
+ import org.json.JSONObject
7
9
 
8
10
  /** The JSON of each monitored region, by id. */
9
11
  private const val PREFERENCES = "tokamak.location.regions"
@@ -14,13 +16,14 @@ private const val PREFERENCES = "tokamak.location.regions"
14
16
  * persist until [remove], and [restore] adds them again where Android removed them. Use it on the
15
17
  * main thread.
16
18
  */
17
- internal class LocationRegions(private val context: Context) {
19
+ internal class LocationRegions(private val host: TokamakHost) {
20
+ private val context = host.context
18
21
  private val manager = context.getSystemService(LocationManager::class.java)
19
22
  private val preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE)
20
23
 
21
- fun all(): List<Region> = preferences.all.values.map { Region.from(it as String) }
24
+ fun all(): List<Region> = preferences.all.values.map { region(it as String) }
22
25
 
23
- fun get(id: String): Region? = preferences.getString(id, null)?.let(Region::from)
26
+ fun get(id: String): Region? = preferences.getString(id, null)?.let(::region)
24
27
 
25
28
  /**
26
29
  * Monitors [region], replacing one with the same id. It starts in an unknown state, so a device
@@ -30,7 +33,7 @@ internal class LocationRegions(private val context: Context) {
30
33
  val alert = checkNotNull(pendingIntent(region.id, PendingIntent.FLAG_UPDATE_CURRENT))
31
34
  manager.removeProximityAlert(alert)
32
35
  manager.addProximityAlert(region.latitude, region.longitude, region.radius.toFloat(), -1, alert)
33
- preferences.edit().putString(region.id, region.json().toString()).apply()
36
+ preferences.edit().putString(region.id, host.json(region).toString()).apply()
34
37
  }
35
38
 
36
39
  fun remove(id: String) {
@@ -50,6 +53,8 @@ internal class LocationRegions(private val context: Context) {
50
53
  all().filter { pendingIntent(it.id, PendingIntent.FLAG_NO_CREATE) == null }.forEach(::add)
51
54
  }
52
55
 
56
+ private fun region(json: String): Region = host.fromJson(JSONObject(json), Region::class)
57
+
53
58
  /** The region [id]'s `PendingIntent`, mutable so `LocationManager` adds whether the device entered. */
54
59
  private fun pendingIntent(id: String, flags: Int): PendingIntent? =
55
60
  PendingIntent.getBroadcast(
@@ -9,6 +9,7 @@ import android.content.Context
9
9
  import android.content.Intent
10
10
  import android.content.pm.ServiceInfo
11
11
  import android.os.IBinder
12
+ import com.tokamak.runtime.tokamakPlugin
12
13
 
13
14
  private const val CHANNEL = "tokamak.location"
14
15
  private const val NOTIFICATION_ID = 1
@@ -24,8 +25,8 @@ class TokamakLocationService : Service() {
24
25
  /** Shows the notification the start [intent] carries, or on a restart, the running updates'. */
25
26
  override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
26
27
  val content =
27
- intent?.getStringExtra(NOTIFICATION_EXTRA)?.let { UpdateOptions.from(it).foregroundService }
28
- ?: LocationUpdates(this, LocationUpdates.UPDATES).options?.foregroundService
28
+ intent?.getStringArrayExtra(NOTIFICATION_EXTRA)?.let { (title, body, channel) -> ServiceNotification(title, body, channel) }
29
+ ?: (tokamakPlugin("location") as? TokamakLocationPlugin)?.getUpdates()?.foregroundService
29
30
  if (content == null) {
30
31
  stopSelf()
31
32
  return START_NOT_STICKY
@@ -58,13 +59,13 @@ class TokamakLocationService : Service() {
58
59
  ?: android.R.drawable.sym_def_app_icon
59
60
 
60
61
  companion object {
61
- /** The start intent's updates' options, as JSON. */
62
+ /** The start intent's notification: its title, body and channel. */
62
63
  private const val NOTIFICATION_EXTRA = "com.tokamak.plugins.location.NOTIFICATION"
63
64
 
64
- /** Starts the service showing the notification of [options], or shows it in the running service. */
65
- fun start(context: Context, options: UpdateOptions) {
66
- val start = Intent(context, TokamakLocationService::class.java).putExtra(NOTIFICATION_EXTRA, options.json().toString())
67
- context.startForegroundService(start)
65
+ /** Starts the service showing [notification], or shows it in the running service. */
66
+ fun start(context: Context, notification: ServiceNotification) {
67
+ val content = arrayOf(notification.title, notification.body, notification.channel)
68
+ context.startForegroundService(Intent(context, TokamakLocationService::class.java).putExtra(NOTIFICATION_EXTRA, content))
68
69
  }
69
70
 
70
71
  fun stop(context: Context) {
@@ -1,8 +1,6 @@
1
1
  package com.tokamak.plugins.location
2
2
 
3
3
  import android.location.Location
4
- import com.tokamak.runtime.TokamakValue
5
- import org.json.JSONObject
6
4
 
7
5
  data class Coordinates(
8
6
  val latitude: Double,
@@ -31,15 +29,15 @@ data class Position(val coords: Coordinates, val timestamp: Long) {
31
29
 
32
30
  data class LocationUpdate(val positions: List<Position>)
33
31
 
34
- enum class Transition { enter, exit }
32
+ enum class Transition { Enter, Exit }
35
33
 
36
34
  data class RegionTransition(val id: String, val transition: Transition, val timestamp: Long)
37
35
 
38
36
  data class LocationErrorEvent(val name: String, val message: String)
39
37
 
40
- enum class Permission { granted, denied, prompt }
38
+ enum class Permission { Granted, Denied, Prompt }
41
39
 
42
- enum class Precision { precise, approximate }
40
+ enum class Precision { Precise, Approximate }
43
41
 
44
42
  data class LocationPermission(val inUse: Permission, val background: Permission, val precision: Precision)
45
43
 
@@ -48,7 +46,7 @@ data class PermissionRequest(val background: Boolean?, val precision: Precision?
48
46
  data class SetupConfig(val significantChanges: Boolean?)
49
47
 
50
48
  /** The request's quality, precision traded against battery use. */
51
- enum class Accuracy { high, balanced, low }
49
+ enum class Accuracy { High, Balanced, Low }
52
50
 
53
51
  /** The foreground service's notification, set from JavaScript so apps can translate it. */
54
52
  data class ServiceNotification(val title: String, val body: String, val channel: String)
@@ -60,33 +58,7 @@ data class UpdateOptions(
60
58
  /** Milliseconds between updates. */
61
59
  val interval: Long?,
62
60
  val foregroundService: ServiceNotification?,
63
- ) {
64
- fun json(): JSONObject {
65
- val notification =
66
- foregroundService?.let { JSONObject().put("title", it.title).put("body", it.body).put("channel", it.channel) }
67
- return JSONObject()
68
- .putOpt("accuracy", accuracy?.name)
69
- .putOpt("distance", distance)
70
- .putOpt("interval", interval)
71
- .putOpt("foregroundService", notification)
72
- }
73
-
74
- companion object {
75
- /** The options [json] holds, as [json] wrote them. */
76
- fun from(json: String): UpdateOptions {
77
- val options = TokamakValue(JSONObject(json), "")
78
- return UpdateOptions(
79
- accuracy = options["accuracy"].nullable { it.enum(Accuracy.entries) },
80
- distance = options["distance"].nullable { it.double() },
81
- interval = options["interval"].nullable { it.long() },
82
- foregroundService =
83
- options["foregroundService"].nullable {
84
- ServiceNotification(it["title"].string(), it["body"].string(), it["channel"].string())
85
- },
86
- )
87
- }
88
- }
89
- }
61
+ )
90
62
 
91
63
  data class Region(
92
64
  val id: String,
@@ -96,31 +68,7 @@ data class Region(
96
68
  val radius: Double,
97
69
  val onEntry: Boolean?,
98
70
  val onExit: Boolean?,
99
- ) {
100
- fun json(): JSONObject =
101
- JSONObject()
102
- .put("id", id)
103
- .put("latitude", latitude)
104
- .put("longitude", longitude)
105
- .put("radius", radius)
106
- .putOpt("onEntry", onEntry)
107
- .putOpt("onExit", onExit)
108
-
109
- companion object {
110
- /** The region [json] holds, as [json] wrote it. */
111
- fun from(json: String): Region {
112
- val region = TokamakValue(JSONObject(json), "")
113
- return Region(
114
- id = region["id"].string(),
115
- latitude = region["latitude"].double(),
116
- longitude = region["longitude"].double(),
117
- radius = region["radius"].double(),
118
- onEntry = region["onEntry"].nullable { it.boolean() },
119
- onExit = region["onExit"].nullable { it.boolean() },
120
- )
121
- }
122
- }
123
- }
71
+ )
124
72
 
125
73
  data class MonitoredRegion(
126
74
  val id: String,
@@ -7,6 +7,8 @@ import android.content.Context
7
7
  import android.content.Intent
8
8
  import android.location.LocationManager
9
9
  import android.location.LocationRequest
10
+ import com.tokamak.runtime.TokamakHost
11
+ import org.json.JSONObject
10
12
 
11
13
  /** The JSON of each kind of updates' options, by name. */
12
14
  private const val PREFERENCES = "tokamak.location"
@@ -14,9 +16,9 @@ private const val DEFAULT_INTERVAL_MILLIS = 10_000L
14
16
 
15
17
  private val QUALITIES =
16
18
  mapOf(
17
- Accuracy.high to LocationRequest.QUALITY_HIGH_ACCURACY,
18
- Accuracy.balanced to LocationRequest.QUALITY_BALANCED_POWER_ACCURACY,
19
- Accuracy.low to LocationRequest.QUALITY_LOW_POWER,
19
+ Accuracy.High to LocationRequest.QUALITY_HIGH_ACCURACY,
20
+ Accuracy.Balanced to LocationRequest.QUALITY_BALANCED_POWER_ACCURACY,
21
+ Accuracy.Low to LocationRequest.QUALITY_LOW_POWER,
20
22
  )
21
23
 
22
24
  /**
@@ -25,17 +27,18 @@ private val QUALITIES =
25
27
  * it has ended. Their options persist until [stop], and [restore] requests them where they are not
26
28
  * running. Use it on the main thread.
27
29
  */
28
- internal class LocationUpdates(private val context: Context, private val name: String) {
30
+ internal class LocationUpdates(private val host: TokamakHost, private val name: String) {
31
+ private val context = host.context
29
32
  private val manager = context.getSystemService(LocationManager::class.java)
30
33
  private val preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE)
31
34
 
32
35
  /** The options of the updates, or null. */
33
36
  val options: UpdateOptions?
34
- get() = preferences.getString(name, null)?.let(UpdateOptions::from)
37
+ get() = preferences.getString(name, null)?.let { host.fromJson(JSONObject(it), UpdateOptions::class) }
35
38
 
36
39
  /** Keeps [options], which [restore] requests. */
37
40
  fun keep(options: UpdateOptions) {
38
- preferences.edit().putString(name, options.json().toString()).apply()
41
+ preferences.edit().putString(name, host.json(options).toString()).apply()
39
42
  }
40
43
 
41
44
  /**
@@ -76,7 +79,7 @@ internal class LocationUpdates(private val context: Context, private val name: S
76
79
  ?.reason == ApplicationExitInfo.REASON_USER_REQUESTED
77
80
 
78
81
  private fun request(options: UpdateOptions) {
79
- val accuracy = options.accuracy ?: Accuracy.high
82
+ val accuracy = options.accuracy ?: Accuracy.High
80
83
  val request =
81
84
  LocationRequest.Builder(options.interval ?: DEFAULT_INTERVAL_MILLIS)
82
85
  .setQuality(QUALITIES.getValue(accuracy))
@@ -96,7 +99,7 @@ internal class LocationUpdates(private val context: Context, private val name: S
96
99
  private fun provider(accuracy: Accuracy): String =
97
100
  when {
98
101
  manager.hasProvider(LocationManager.FUSED_PROVIDER) -> LocationManager.FUSED_PROVIDER
99
- accuracy == Accuracy.high -> LocationManager.GPS_PROVIDER
102
+ accuracy == Accuracy.High -> LocationManager.GPS_PROVIDER
100
103
  else -> LocationManager.NETWORK_PROVIDER
101
104
  }
102
105
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/plugin-location",
3
- "version": "0.1.0-beta.132",
3
+ "version": "0.1.0-beta.134",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/mantty/tokamak.git",
@@ -14,7 +14,7 @@
14
14
  "./events": "./src/events.ts"
15
15
  },
16
16
  "peerDependencies": {
17
- "@tokamakdev/plugin": "0.1.0-beta.132"
17
+ "@tokamakdev/plugin": "0.1.0-beta.134"
18
18
  },
19
19
  "files": [
20
20
  "android",