-
Notifications
You must be signed in to change notification settings - Fork 50
Add Tasker action to buzz the whoop, and ability to subscribe to double-tap from Tasker #89
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| .fvm | ||
| .fvmrc | ||
| # Miscellaneous | ||
| *.class | ||
| *.log | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package wtf.openstrap.openstrap_edge | ||
|
|
||
| import android.content.BroadcastReceiver | ||
| import android.content.Context | ||
| import android.content.Intent | ||
| import android.os.Build | ||
| import android.util.Log | ||
| import io.flutter.embedding.engine.FlutterEngineCache | ||
| import io.flutter.plugin.common.MethodChannel | ||
|
|
||
| class TaskerReceiver : BroadcastReceiver() { | ||
| override fun onReceive(context: Context, intent: Intent) { | ||
| val action = intent.action | ||
| Log.i(TAG, "onReceive: action=$action") | ||
| if (action != ACTION_BUZZ_STRAP) return | ||
|
|
||
| val pattern = intent.getIntExtra(EXTRA_PATTERN, DEFAULT_PATTERN) | ||
| Log.i(TAG, "pattern=$pattern") | ||
|
|
||
| val engine = FlutterEngineCache.getInstance() | ||
| .get(EdgeApplication.ENGINE_ID) | ||
|
|
||
| if (engine != null) { | ||
| Log.i(TAG, "engine alive, invoking method channel") | ||
| val args = java.util.HashMap<String, Any>() | ||
| args["pattern"] = pattern | ||
| MethodChannel( | ||
| engine.dartExecutor.binaryMessenger, | ||
| CHANNEL | ||
| ).invokeMethod("buzz_strap", args) | ||
| return | ||
| } | ||
|
|
||
| Log.i(TAG, "engine dead, persisting pending flag") | ||
| val prefs = context.getSharedPreferences( | ||
| "openstrap_runtime", | ||
| Context.MODE_PRIVATE | ||
| ) | ||
| prefs.edit() | ||
| .putBoolean(PENDING_BUZZ_KEY, true) | ||
| .putInt(PENDING_PATTERN_KEY, pattern) | ||
| .apply() | ||
|
Comment on lines
+35
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: For the shared_preferences plugin (version 2.3.3), the SharedPreferences.getInstance method interacts with a default, plugin-managed SharedPreferences file on Android, not an arbitrary file named "openstrap_runtime" [1][2][3]. By default, the plugin uses a specific SharedPreferences file internally [4]. It does not automatically target or read from other arbitrary native preference files like "openstrap_runtime". Regarding key-prefix behavior: 1. Default Prefix: By default, the plugin only reads and writes preferences that begin with the prefix "flutter." [5][3]. This filtering is handled automatically by the plugin [5][3]. 2. Custom Prefixes: You can change this behavior by calling setPrefix before instantiating any SharedPreferences object [5][3]. If you set the prefix to an empty string (""), the plugin will read all keys within that SharedPreferences file, which is often used for migrating existing native Android preferences to Flutter [5][3]. 3. Interaction with Async APIs: While newer APIs like SharedPreferencesAsync (available in later versions) allow specifying custom file names via SharedPreferencesAsyncAndroidOptions (e.g., using AndroidSharedPreferencesStoreOptions(fileName: '...')), the standard SharedPreferences.getInstance API in version 2.3.3 remains tied to the plugin's default storage configuration [6][1][2]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Map the relevant files first
ast-grep outline android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt --view expanded || true
ast-grep outline lib/platform/tasker_bridge.dart --view expanded || true
# Read the relevant slices with line numbers
echo '--- TaskerReceiver.kt ---'
cat -n android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt | sed -n '1,220p'
echo '--- tasker_bridge.dart ---'
cat -n lib/platform/tasker_bridge.dart | sed -n '1,220p'Repository: OpenStrap/edge Length of output: 4691 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate any shared_preferences usage and any native bridge for pending buzz
rg -n "SharedPreferences\.getInstance|openstrap_runtime|PENDING_BUZZ_KEY|PENDING_PATTERN_KEY|consumePendingBuzz|setPrefix|flutter\." android libRepository: OpenStrap/edge Length of output: 8442 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find where shared_preferences plugin behavior is configured, if at all
rg -n "SharedPreferences\.setPrefix|SharedPreferencesAsync|AndroidSharedPreferencesStoreOptions|SharedPreferencesWithCache|shared_preferences" lib androidRepository: OpenStrap/edge Length of output: 2622 Use the same store for deferred Tasker requests.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
|
|
||
| val svcIntent = Intent(context, EdgeTrackingService::class.java) | ||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||
| context.startForegroundService(svcIntent) | ||
| } else { | ||
| context.startService(svcIntent) | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| const val TAG = "TaskerReceiver" | ||
| const val ACTION_BUZZ_STRAP = | ||
| "wtf.openstrap.openstrap_edge.BUZZ_STRAP" | ||
| const val EXTRA_PATTERN = "pattern" | ||
| const val PENDING_BUZZ_KEY = "pending_tasker_buzz" | ||
| const val PENDING_PATTERN_KEY = "pending_tasker_buzz_pattern" | ||
| const val DEFAULT_PATTERN = 2 | ||
| private const val CHANNEL = "openstrap/tasker" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| * `0`: A quick buzz of the device | ||
| * `1`: An alarm, buzzes until acked by tapping the device | ||
| * `2`: A quick double buzz | ||
| * `3`: Nothing | ||
| * `4`: Nothing | ||
| * `5`: Nothing | ||
| * `6`: Nothing | ||
| * `7`: Nothing |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import 'package:flutter/foundation.dart'; | ||
| import 'package:shared_preferences/shared_preferences.dart'; | ||
| import 'package:flutter/services.dart'; | ||
|
|
||
| class TaskerBridge { | ||
| static const _ch = MethodChannel('openstrap/tasker'); | ||
| static const _pendingKey = 'pending_tasker_buzz'; | ||
| static const _pendingPatternKey = 'pending_tasker_buzz_pattern'; | ||
|
|
||
| final Future<void> Function(int pattern) buzzPattern; | ||
|
|
||
| TaskerBridge({required this.buzzPattern}) { | ||
| _ch.setMethodCallHandler(_onMethodCall); | ||
| } | ||
|
|
||
| Future<void> _onMethodCall(MethodCall call) async { | ||
| if (call.method == 'buzz_strap') { | ||
| try { | ||
| final args = call.arguments; | ||
| debugPrint('[tasker] _onMethodCall args=$args (${args.runtimeType})'); | ||
| final map = args is Map ? args : <dynamic, dynamic>{}; | ||
| final pattern = (map['pattern'] as int?) ?? 2; | ||
| debugPrint('[tasker] buzz pattern=$pattern'); | ||
| await buzzPattern(pattern); | ||
| } catch (e, st) { | ||
| debugPrint('[tasker] buzz failed: $e\n$st'); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| static Future<int?> consumePendingBuzz() async { | ||
| final prefs = await SharedPreferences.getInstance(); | ||
| final pending = prefs.getBool(_pendingKey) ?? false; | ||
| if (!pending) return null; | ||
| final pattern = prefs.getInt(_pendingPatternKey) ?? 2; | ||
| await prefs.remove(_pendingKey); | ||
| await prefs.remove(_pendingPatternKey); | ||
| return pattern; | ||
|
Comment on lines
+31
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Do not acknowledge a pending buzz before BLE is ready.
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Protect the exported buzz endpoint.
Any installed app can explicitly target this exported receiver with the documented action, repeatedly wake the app/service, and trigger strap haptics. Keep it exported for Tasker, but require a user-configured capability/token and apply a rate limit before persisting or invoking the channel.
android/app/src/main/AndroidManifest.xml#L149-L156: document and enforce the chosen external-caller protection model.android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt#L12-L50: validate the capability and pattern range before executing or starting the service.📍 Affects 2 files
android/app/src/main/AndroidManifest.xml#L149-L156(this comment)android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt#L12-L50🤖 Prompt for AI Agents