diff --git a/packages/activity_recognition_flutter/CHANGELOG.md b/packages/activity_recognition_flutter/CHANGELOG.md
index d28e96bed..80b5232f7 100644
--- a/packages/activity_recognition_flutter/CHANGELOG.md
+++ b/packages/activity_recognition_flutter/CHANGELOG.md
@@ -1,3 +1,24 @@
+## 6.0.0
+
+Breaking changes:
+
+- Requires Flutter 3.44 and Dart 3.10
+- Android package has been renamed to `dk.carp.activity_recognition_flutter`.
+- Apps that referenced the old package in their manifest or ProGuard rules need to update those references
+- `ActivityEvent` now takes the timestamp (optional)
+- Minimum iOS deployment target is 15.0 and the Android `compileSdk` is 36
+- CocoaPods is no longer supported on iOS
+- Added Swift Package Manager (SPM)
+- Moved Android impls to Kotlin
+- Plugin now declares its own permissions, broadcast receiver and foreground
+ service in its manifest
+- Removed `ActivityRecognizedService`
+- Removed `ActivityType.INVALID`
+- Removed `ActivityEvent.fromString`
+- Fixed activity timestamps source to the platform's `CMMotionActivity.startDate` on
+ iOS, `ActivityRecognitionResult.time` on Android
+- Fixed Android events deregister on stream cancel
+
## 5.0.0
- upgraded Android SDK level
diff --git a/packages/activity_recognition_flutter/README.md b/packages/activity_recognition_flutter/README.md
index a82ba6b0f..a42642038 100644
--- a/packages/activity_recognition_flutter/README.md
+++ b/packages/activity_recognition_flutter/README.md
@@ -4,29 +4,28 @@
Activity recognition plugin for Android and iOS. Only working while App is running (= not terminated by the user or OS).
+The communication with the native platforms is defined in [`pigeons/messages.dart`](pigeons/messages.dart)
+and generated with [Pigeon](https://pub.dev/packages/pigeon). Regenerate it after
+changing that file with:
-## Configuration
+```sh
+dart run pigeon --input pigeons/messages.dart
+```
-### Android
+The iOS implementation is a Swift package (`ios/activity_recognition_flutter/`).
+CocoaPods is no longer supported, so consuming apps must use Swift Package
+Manager. See [iOS](#ios) below.
-Add the following entries inside the `` tag:
-```xml
-
-
-
-```
+## Configuration
-Next, add the plugin's service inside the `` tag:
+### Android
-```xml
-
-
-
-```
+The permissions the plugin requires, and the broadcast receiver and
+foreground service it relies on, are declared by the plugin.
+
+You still have to request the `ACTIVITY_RECOGNITION` runtime permission before
+listening to the stream -- see [Usage](#usage) below.
#### Known Android quirks
@@ -37,7 +36,19 @@ This package uses the Android Embedding API v2. In order to use this in pre-Flut
### iOS
-An iOS app linked on or after iOS 10.0 must include usage description keys in its `Info.plist` file for the types of data it needs. Failure to include these keys will cause the app to crash.
+This plugin ships only as a Swift package, so your app must use Swift Package
+Manager. It is enabled by default on Flutter 3.44 and later; if you turned it
+off, re-enable it with:
+
+```sh
+flutter config --enable-swift-package-manager
+```
+
+If your app still has CocoaPods integration, see
+[Flutter Swift Package Manager guide](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers)
+for details.
+
+An iOS app linked on must include usage description keys in its `Info.plist` file for the types of data it needs. Failure to include these keys will cause the app to crash.
To access motion and fitness data specifically, it must include `NSMotionUsageDescription`, like this:
```xml
@@ -63,7 +74,6 @@ Each detected activity will have an activity type, which is one of the following
* TILTING
* UNKNOWN
* WALKING
-* INVALID (used for parsing errors)
-
-As well as a confidence expressed in percentages (i.e. a value from 0-100).
+As well as a confidence expressed in percentages (i.e. a value from 0-100), and
+the timestamp of the detection as reported by the platform.
diff --git a/packages/activity_recognition_flutter/android/build.gradle b/packages/activity_recognition_flutter/android/build.gradle
deleted file mode 100644
index c1103af3a..000000000
--- a/packages/activity_recognition_flutter/android/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-group 'dk.cachet.activity_recognition_flutter'
-version '1.0'
-
-buildscript {
- repositories {
- google()
- mavenCentral()
- }
-
- dependencies {
- classpath 'com.android.tools.build:gradle:7.3.0'
- }
-}
-
-rootProject.allprojects {
- repositories {
- google()
- mavenCentral()
- }
-}
-
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 33
-
- defaultConfig {
- minSdkVersion 26
- }
- lintOptions {
- disable 'InvalidPackage'
- }
- namespace "dk.cachet.activity_recognition_flutter"
-}
-
-dependencies {
- implementation 'com.google.android.gms:play-services-location:19.0.1'
-}
diff --git a/packages/activity_recognition_flutter/android/build.gradle.kts b/packages/activity_recognition_flutter/android/build.gradle.kts
new file mode 100644
index 000000000..c2ff7c1a3
--- /dev/null
+++ b/packages/activity_recognition_flutter/android/build.gradle.kts
@@ -0,0 +1,63 @@
+group = "dk.carp.activity_recognition_flutter"
+version = "1.0"
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+plugins {
+ id("com.android.library")
+}
+
+// AGP 9+ has built-in Kotlin support; on older AGP the Kotlin Gradle plugin
+// must be applied explicitly. Version is supplied by the consuming app.
+// See https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors
+val agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.substringBefore('.').toInt()
+if (agpMajor < 9) {
+ apply(plugin = "org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "dk.carp.activity_recognition_flutter"
+
+ // Kept at the lowest currently supported level on purpose: a library forces
+ // every app that depends on it to compile against at least this SDK.
+ compileSdk = 36
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ sourceSets {
+ getByName("main") {
+ java.srcDirs("src/main/kotlin")
+ }
+ }
+
+ defaultConfig {
+ // API 26 is required by the plugin itself: notification channels and
+ // Context.startForegroundService were both added in Android 8.
+ minSdk = 26
+ }
+
+ lint {
+ disable += "InvalidPackage"
+ }
+}
+
+// Configured reflectively rather than through the `kotlin { }` accessor: the
+// Kotlin plugin is applied after this script is compiled (by Flutter, or
+// conditionally above), so the type-safe accessor does not exist here.
+project.extensions.configure(org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension::class.java) {
+ compilerOptions {
+ jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
+ }
+}
+
+dependencies {
+ implementation("com.google.android.gms:play-services-location:21.3.0")
+}
diff --git a/packages/activity_recognition_flutter/android/gradle.properties b/packages/activity_recognition_flutter/android/gradle.properties
deleted file mode 100644
index 94adc3a3f..000000000
--- a/packages/activity_recognition_flutter/android/gradle.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-org.gradle.jvmargs=-Xmx1536M
-android.useAndroidX=true
-android.enableJetifier=true
diff --git a/packages/activity_recognition_flutter/android/gradle/wrapper/gradle-wrapper.properties b/packages/activity_recognition_flutter/android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 02767eb1c..000000000
--- a/packages/activity_recognition_flutter/android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
diff --git a/packages/activity_recognition_flutter/android/settings.gradle b/packages/activity_recognition_flutter/android/settings.gradle
deleted file mode 100644
index f7af86bb7..000000000
--- a/packages/activity_recognition_flutter/android/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'activity_recognition_flutter'
diff --git a/packages/activity_recognition_flutter/android/settings.gradle.kts b/packages/activity_recognition_flutter/android/settings.gradle.kts
new file mode 100644
index 000000000..9939b49d6
--- /dev/null
+++ b/packages/activity_recognition_flutter/android/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "activity_recognition_flutter"
diff --git a/packages/activity_recognition_flutter/android/src/main/AndroidManifest.xml b/packages/activity_recognition_flutter/android/src/main/AndroidManifest.xml
index 1601def3a..33c2c53f3 100644
--- a/packages/activity_recognition_flutter/android/src/main/AndroidManifest.xml
+++ b/packages/activity_recognition_flutter/android/src/main/AndroidManifest.xml
@@ -1,5 +1,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
deleted file mode 100644
index 6df5f1cd3..000000000
--- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package dk.cachet.activity_recognition_flutter;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-
-import com.google.android.gms.location.ActivityRecognition;
-import com.google.android.gms.tasks.OnFailureListener;
-import com.google.android.gms.tasks.OnSuccessListener;
-import com.google.android.gms.tasks.Task;
-
-import java.util.HashMap;
-
-import io.flutter.embedding.engine.plugins.FlutterPlugin;
-import io.flutter.embedding.engine.plugins.activity.ActivityAware;
-import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
-import io.flutter.plugin.common.EventChannel;
-
-/**
- * ActivityRecognitionFlutterPlugin
- */
-@SuppressLint("LongLogTag")
-public class ActivityRecognitionFlutterPlugin implements FlutterPlugin, EventChannel.StreamHandler, ActivityAware, SharedPreferences.OnSharedPreferenceChangeListener {
- private EventChannel channel;
- private EventChannel.EventSink eventSink;
- private Activity androidActivity;
- private Context androidContext;
- public static final String DETECTED_ACTIVITY = "detected_activity";
- public static final String ACTIVITY_RECOGNITION = "activity_recognition_flutter";
-
- private final String TAG = "activity_recognition_flutter";
-
- /**
- * The main function for starting activity tracking.
- * Handling events is done inside [ActivityRecognizedService]
- */
- private void startActivityTracking() {
- // Start the service
- Intent intent = new Intent(androidActivity, ActivityRecognizedBroadcastReceiver.class);
-
- Log.d(TAG, "SDK = " + Build.VERSION.SDK_INT);
- int flags = PendingIntent.FLAG_UPDATE_CURRENT;
- if (Build.VERSION.SDK_INT >= 31) {
- flags |= PendingIntent.FLAG_IMMUTABLE;
- }
- PendingIntent pendingIntent = PendingIntent.getBroadcast(androidActivity, 0, intent, flags);
-
- // Frequency in milliseconds
- long frequency = 5 * 1000;
- Task task = ActivityRecognition.getClient(androidContext)
- .requestActivityUpdates(frequency, pendingIntent);
-
- task.addOnSuccessListener(new OnSuccessListener() {
- @Override
- public void onSuccess(Void e) {
- Log.d(TAG, "Successfully registered ActivityRecognition listener.");
- }
- });
- task.addOnFailureListener(new OnFailureListener() {
- @Override
- public void onFailure(@NonNull Exception e) {
- Log.d(TAG, "Failed to registered ActivityRecognition listener.");
- }
- });
- }
-
- /**
- * EventChannel.StreamHandler interface below
- */
-
- @Override
- public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
- channel = new EventChannel(flutterPluginBinding.getBinaryMessenger(), ACTIVITY_RECOGNITION);
- channel.setStreamHandler(this);
- }
-
- // Unchecked HashMap cast. Using instanceof does not clear the warning.
- @SuppressWarnings("unchecked")
- @RequiresApi(api = Build.VERSION_CODES.O)
- @Override
- public void onListen(Object arguments, EventChannel.EventSink events) {
- HashMap args = (HashMap) arguments;
- boolean fg = (boolean) args.get("foreground");
- if(fg) {
- startForegroundService();
- }
- Log.d(TAG, "Foreground mode: " + fg);
-
- eventSink = events;
- startActivityTracking();
- }
-
- @RequiresApi(api = Build.VERSION_CODES.O)
- void startForegroundService() {
- Intent intent = new Intent(androidActivity, ForegroundService.class);
-
- // Tell the service we want to start it
- intent.setAction("start");
-
- // Pass the notification title/text/icon to the service
- intent.putExtra("title", "MonsensoMonitor")
- .putExtra("text", "Monsenso Foreground Service")
- .putExtra("icon", R.drawable.common_full_open_on_phone)
- .putExtra("importance", 3)
- .putExtra("id", 10);
-
- // Start the service
- androidContext.startForegroundService(intent);
- }
-
- @Override
- public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
- channel.setStreamHandler(null);
- }
-
- @Override
- public void onCancel(Object arguments) {
- channel.setStreamHandler(null);
- }
-
- /**
- * ActivityAware interface below
- */
- @Override
- public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) {
- androidActivity = binding.getActivity();
- androidContext = binding.getActivity().getApplicationContext();
-
- SharedPreferences prefs = androidContext.getSharedPreferences(ACTIVITY_RECOGNITION, Context.MODE_PRIVATE);
- prefs.registerOnSharedPreferenceChangeListener(this);
- // Log.d(TAG, "onAttachedToActivity");
- }
-
- @Override
- public void onDetachedFromActivityForConfigChanges() {
- androidActivity = null;
- androidContext = null;
- }
-
- @Override
- public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) {
- androidActivity = binding.getActivity();
- androidContext = binding.getActivity().getApplicationContext();
-
- }
-
- @Override
- public void onDetachedFromActivity() {
- androidActivity = null;
- androidContext = null;
- }
-
- /**
- * Shared preferences changed, i.e. latest activity
- */
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- String result = sharedPreferences
- .getString(DETECTED_ACTIVITY, "error");
- // Log.d("onSharedPreferenceChange", result);
- if (key!= null && key.equals(DETECTED_ACTIVITY)) {
- // Log.d(TAG, "Detected activity: " + result);
- eventSink.success(result);
- }
- }
- }
diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.java
deleted file mode 100644
index 5c7433148..000000000
--- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package dk.cachet.activity_recognition_flutter;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class ActivityRecognizedBroadcastReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- ActivityRecognizedService.enqueueWork(context, intent);
- }
-}
diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedService.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedService.java
deleted file mode 100644
index be4e3b273..000000000
--- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedService.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package dk.cachet.activity_recognition_flutter;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.core.app.JobIntentService;
-
-import com.google.android.gms.location.ActivityRecognitionResult;
-import com.google.android.gms.location.DetectedActivity;
-
-import java.util.List;
-
-public class ActivityRecognizedService extends JobIntentService {
-
- static void enqueueWork(Context context, Intent work) {
- enqueueWork(context, ActivityRecognizedService.class, 1, work);
- }
-
- @Override
- public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
- return super.onStartCommand(intent, flags, startId);
- }
-
- // This method is called when service starts instead of onHandleIntent
- protected void onHandleWork(@Nullable Intent intent) {
- onHandleIntent(intent);
- }
-
- // remove override and make onHandleIntent private.
- private void onHandleIntent(@Nullable Intent intent) {
- ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent);
- List activities = result.getProbableActivities();
-
- DetectedActivity mostLikely = activities.get(0);
-
- for (DetectedActivity a : activities) {
- if (a.getConfidence() > mostLikely.getConfidence()) {
- mostLikely = a;
- }
- }
-
- String type = getActivityString(mostLikely.getType());
- int confidence = mostLikely.getConfidence();
-
- String data = type + "," + confidence;
-
- Log.d("onHandleIntent", data);
-
- // Same preferences as in ActivityRecognitionFlutterPlugin.java
- SharedPreferences preferences =
- getApplicationContext().getSharedPreferences(
- ActivityRecognitionFlutterPlugin.ACTIVITY_RECOGNITION, MODE_PRIVATE);
-
- preferences.edit().clear()
- .putString(
- ActivityRecognitionFlutterPlugin.DETECTED_ACTIVITY,
- data
- )
- .apply();
- }
-
- public static String getActivityString(int type) {
- if (type == DetectedActivity.IN_VEHICLE) return "IN_VEHICLE";
- if (type == DetectedActivity.ON_BICYCLE) return "ON_BICYCLE";
- if (type == DetectedActivity.ON_FOOT) return "ON_FOOT";
- if (type == DetectedActivity.RUNNING) return "RUNNING";
- if (type == DetectedActivity.STILL) return "STILL";
- if (type == DetectedActivity.TILTING) return "TILTING";
- if (type == DetectedActivity.WALKING) return "WALKING";
-
- // Default case
- return "UNKNOWN";
- }
-}
-
diff --git a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ForegroundService.java b/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ForegroundService.java
deleted file mode 100644
index 05b9a07dc..000000000
--- a/packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ForegroundService.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package dk.cachet.activity_recognition_flutter;
-
-import android.content.Intent;
-import android.content.Context;
-import android.app.Service;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.os.IBinder;
-import android.os.Bundle;
-import android.annotation.TargetApi;
-
-public class ForegroundService extends Service {
-
- public ForegroundService() {
- super();
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- Bundle bundle = new Bundle();
- bundle.putString("title", "Foreground service");
- bundle.putString("text", "Foreground monitoring service");
- startPluginForegroundService(bundle);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- return START_STICKY;
- }
-
- @TargetApi(26)
- private void startPluginForegroundService(Bundle extras) {
- Context context = getApplicationContext();
-
- // Delete notification channel if it already exists
- NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- manager.deleteNotificationChannel("foreground.service.channel");
-
- // Get notification channel importance
- Integer importance;
-
- try {
- importance = Integer.parseInt((String) extras.get("importance"));
- } catch (NumberFormatException e) {
- importance = 1;
- }
-
- switch (importance) {
- case 2:
- importance = NotificationManager.IMPORTANCE_DEFAULT;
- break;
- case 3:
- importance = NotificationManager.IMPORTANCE_HIGH;
- break;
- default:
- importance = NotificationManager.IMPORTANCE_LOW;
- // We are not using IMPORTANCE_MIN because we want the notification to be visible
- }
-
- // Create notification channel
- NotificationChannel channel = new NotificationChannel("foreground.service.channel", "Background Services", importance);
- channel.setDescription("Enables background processing.");
- getSystemService(NotificationManager.class).createNotificationChannel(channel);
-
- // Get notification icon
-// int icon = getResources().getIdentifier((String) extras.get("icon"), "drawable", context.getPackageName());
- int icon = R.drawable.common_full_open_on_phone;
-
- // Make notification
- Notification notification = new Notification.Builder(context, "foreground.service.channel")
- .setContentTitle((CharSequence) extras.get("title"))
- .setContentText((CharSequence) extras.get("text"))
- .setOngoing(true)
- .setSmallIcon(icon == 0 ? 17301514 : icon) // Default is the star icon
- .build();
-
- // Get notification ID
- Integer id;
- try {
- id = Integer.parseInt((String) extras.get("id"));
- } catch (NumberFormatException e) {
- id = 0;
- }
-
- // Put service in foreground and show notification (id of 0 is not allowed)
- startForeground(id != 0 ? id : 197812504, notification);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- throw new UnsupportedOperationException("Not yet implemented");
- }
-}
diff --git a/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.kt b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.kt
new file mode 100644
index 000000000..61372ceac
--- /dev/null
+++ b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.kt
@@ -0,0 +1,136 @@
+package dk.carp.activity_recognition_flutter
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import com.google.android.gms.location.ActivityRecognition
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+
+/**
+ * Activity recognition for Android, backed by the Google Play services
+ * Activity Recognition API.
+ *
+ * The Dart <-> native contract is generated by Pigeon from `pigeons/messages.dart`;
+ * see `Messages.g.kt`. Configuration arrives over a host API and detections are
+ * pushed back over an event channel.
+ *
+ * Detections are delivered by the system to [ActivityRecognizedBroadcastReceiver],
+ * which may run without a Flutter engine attached, so they reach this plugin
+ * through [DetectedActivityStore].
+ */
+class ActivityRecognitionFlutterPlugin :
+ FlutterPlugin,
+ ActivityRecognitionHostApi,
+ SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private companion object {
+ const val TAG = "ActivityRecognition"
+
+ /** How often the system is asked for a new detection. */
+ const val DETECTION_INTERVAL_MS = 5_000L
+ }
+
+ private val mainHandler = Handler(Looper.getMainLooper())
+ private val streamHandler = ActivityStreamHandler()
+
+ private var applicationContext: Context? = null
+ private var preferences: SharedPreferences? = null
+ private var eventSink: PigeonEventSink? = null
+ private var runForegroundService = false
+
+ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ val context = binding.applicationContext
+ applicationContext = context
+
+ ActivityRecognitionHostApi.setUp(binding.binaryMessenger, this)
+ StreamActivitiesStreamHandler.register(binding.binaryMessenger, streamHandler)
+
+ preferences = DetectedActivityStore.preferences(context).also {
+ it.registerOnSharedPreferenceChangeListener(this)
+ }
+ }
+
+ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
+ stopTracking()
+
+ preferences?.unregisterOnSharedPreferenceChangeListener(this)
+ preferences = null
+
+ ActivityRecognitionHostApi.setUp(binding.binaryMessenger, null)
+ applicationContext = null
+ }
+
+ // MARK: - ActivityRecognitionHostApi
+
+ override fun configure(configuration: TrackingConfiguration) {
+ runForegroundService = configuration.runForegroundService
+ }
+
+ // MARK: - Tracking
+
+ private fun startTracking() {
+ val context = applicationContext ?: return
+
+ if (runForegroundService) {
+ context.startForegroundService(Intent(context, ForegroundService::class.java))
+ }
+
+ ActivityRecognition.getClient(context)
+ .requestActivityUpdates(DETECTION_INTERVAL_MS, detectionIntent(context))
+ .addOnSuccessListener { Log.d(TAG, "Registered for activity updates.") }
+ .addOnFailureListener { Log.e(TAG, "Could not register for activity updates.", it) }
+ }
+
+ private fun stopTracking() {
+ val context = applicationContext ?: return
+
+ ActivityRecognition.getClient(context).removeActivityUpdates(detectionIntent(context))
+
+ if (runForegroundService) {
+ context.stopService(Intent(context, ForegroundService::class.java))
+ }
+ }
+
+ private fun detectionIntent(context: Context): PendingIntent {
+ val intent = Intent(context, ActivityRecognizedBroadcastReceiver::class.java)
+
+ var flags = PendingIntent.FLAG_UPDATE_CURRENT
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ // The Activity Recognition API writes the detection result into this
+ // intent, so it has to stay mutable on API 31 and above.
+ flags = flags or PendingIntent.FLAG_MUTABLE
+ }
+
+ return PendingIntent.getBroadcast(context, 0, intent, flags)
+ }
+
+ // MARK: - Detections arriving from the broadcast receiver
+
+ override fun onSharedPreferenceChanged(preferences: SharedPreferences?, key: String?) {
+ if (key != DetectedActivityStore.KEY_DETECTED_ACTIVITY) return
+
+ val sink = eventSink ?: return
+ val activity = preferences?.let(DetectedActivityStore::read) ?: return
+
+ // Event sinks have to be driven from the main thread, and preference
+ // changes are published on whichever thread wrote them.
+ mainHandler.post { sink.success(activity) }
+ }
+
+ private inner class ActivityStreamHandler : StreamActivitiesStreamHandler() {
+ override fun onListen(p0: Any?, sink: PigeonEventSink) {
+ eventSink = sink
+ startTracking()
+ }
+
+ override fun onCancel(p0: Any?) {
+ stopTracking()
+ eventSink = null
+ }
+ }
+}
diff --git a/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.kt b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.kt
new file mode 100644
index 000000000..4de3ee847
--- /dev/null
+++ b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.kt
@@ -0,0 +1,41 @@
+package dk.carp.activity_recognition_flutter
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.google.android.gms.location.ActivityRecognitionResult
+import com.google.android.gms.location.DetectedActivity
+
+/**
+ * Receives detections from the Activity Recognition API and hands the most
+ * probable one to [DetectedActivityStore].
+ */
+class ActivityRecognizedBroadcastReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ if (!ActivityRecognitionResult.hasResult(intent)) return
+
+ val result = ActivityRecognitionResult.extractResult(intent) ?: return
+ val mostProbable = result.probableActivities.maxByOrNull { it.confidence } ?: return
+
+ DetectedActivityStore.write(
+ context,
+ PlatformActivity(
+ type = mostProbable.toPlatformActivityType(),
+ confidence = mostProbable.confidence.toLong(),
+ timestamp = result.time,
+ ),
+ )
+ }
+}
+
+private fun DetectedActivity.toPlatformActivityType(): PlatformActivityType = when (type) {
+ DetectedActivity.IN_VEHICLE -> PlatformActivityType.IN_VEHICLE
+ DetectedActivity.ON_BICYCLE -> PlatformActivityType.ON_BICYCLE
+ DetectedActivity.ON_FOOT -> PlatformActivityType.ON_FOOT
+ DetectedActivity.RUNNING -> PlatformActivityType.RUNNING
+ DetectedActivity.STILL -> PlatformActivityType.STILL
+ DetectedActivity.TILTING -> PlatformActivityType.TILTING
+ DetectedActivity.WALKING -> PlatformActivityType.WALKING
+ else -> PlatformActivityType.UNKNOWN
+}
diff --git a/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/DetectedActivityStore.kt b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/DetectedActivityStore.kt
new file mode 100644
index 000000000..b2d1a0474
--- /dev/null
+++ b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/DetectedActivityStore.kt
@@ -0,0 +1,48 @@
+package dk.carp.activity_recognition_flutter
+
+import android.content.Context
+import android.content.SharedPreferences
+
+/**
+ * Carries detections from [ActivityRecognizedBroadcastReceiver] to
+ * [ActivityRecognitionFlutterPlugin].
+ *
+ * The receiver is started by the system and may run while no Flutter engine is
+ * attached, so the two sides cannot hold references to each other. Writing the
+ * latest detection to shared preferences lets the plugin observe it whenever it
+ * happens to be listening.
+ */
+internal object DetectedActivityStore {
+ private const val PREFERENCES_NAME = "activity_recognition_flutter"
+
+ const val KEY_DETECTED_ACTIVITY = "detected_activity"
+
+ fun preferences(context: Context): SharedPreferences =
+ context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+ fun write(context: Context, activity: PlatformActivity) {
+ preferences(context).edit()
+ // Clearing first guarantees a change notification even when the same
+ // activity is detected twice in a row.
+ .clear()
+ .putString(KEY_DETECTED_ACTIVITY, encode(activity))
+ .apply()
+ }
+
+ fun read(preferences: SharedPreferences): PlatformActivity? =
+ preferences.getString(KEY_DETECTED_ACTIVITY, null)?.let(::decode)
+
+ private fun encode(activity: PlatformActivity): String =
+ "${activity.type.raw},${activity.confidence},${activity.timestamp}"
+
+ private fun decode(value: String): PlatformActivity? {
+ val parts = value.split(",")
+ if (parts.size != 3) return null
+
+ val type = parts[0].toIntOrNull()?.let(PlatformActivityType::ofRaw) ?: return null
+ val confidence = parts[1].toLongOrNull() ?: return null
+ val timestamp = parts[2].toLongOrNull() ?: return null
+
+ return PlatformActivity(type = type, confidence = confidence, timestamp = timestamp)
+ }
+}
diff --git a/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ForegroundService.kt b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ForegroundService.kt
new file mode 100644
index 000000000..5ec8831ad
--- /dev/null
+++ b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ForegroundService.kt
@@ -0,0 +1,59 @@
+package dk.carp.activity_recognition_flutter
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+
+/**
+ * Keeps the process alive so that detections keep arriving while the app is in
+ * the background.
+ *
+ * Started by [ActivityRecognitionFlutterPlugin] only when the Dart side asks for
+ * it through `runForegroundService`. The notification can be customised through
+ * the intent extras below.
+ */
+class ForegroundService : Service() {
+
+ companion object {
+ const val EXTRA_TITLE = "title"
+ const val EXTRA_TEXT = "text"
+ const val EXTRA_ICON = "icon"
+ const val EXTRA_ID = "id"
+
+ private const val CHANNEL_ID = "activity_recognition_flutter.foreground"
+ private const val DEFAULT_NOTIFICATION_ID = 197812504
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ val manager = getSystemService(NotificationManager::class.java)
+ manager.createNotificationChannel(
+ NotificationChannel(
+ CHANNEL_ID,
+ "Activity recognition",
+ // Low importance keeps the notification silent while still visible,
+ // which is what a background monitoring service should be.
+ NotificationManager.IMPORTANCE_LOW,
+ ).apply {
+ description = "Keeps detecting your activity while the app is in the background."
+ },
+ )
+
+ val icon = intent?.getIntExtra(EXTRA_ICON, 0) ?: 0
+ val notification = Notification.Builder(this, CHANNEL_ID)
+ .setContentTitle(intent?.getStringExtra(EXTRA_TITLE) ?: "Activity recognition")
+ .setContentText(intent?.getStringExtra(EXTRA_TEXT) ?: "Detecting your activity.")
+ .setSmallIcon(if (icon != 0) icon else android.R.drawable.ic_menu_compass)
+ .setOngoing(true)
+ .build()
+
+ val notificationId = intent?.getIntExtra(EXTRA_ID, 0) ?: 0
+ startForeground(if (notificationId != 0) notificationId else DEFAULT_NOTIFICATION_ID, notification)
+
+ return START_STICKY
+ }
+
+ override fun onBind(intent: Intent?): IBinder? = null
+}
diff --git a/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/Messages.g.kt b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/Messages.g.kt
new file mode 100644
index 000000000..16e58f6fd
--- /dev/null
+++ b/packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/Messages.g.kt
@@ -0,0 +1,458 @@
+// Autogenerated from Pigeon (v27.3.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
+
+package dk.carp.activity_recognition_flutter
+
+import android.util.Log
+import io.flutter.plugin.common.BasicMessageChannel
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.EventChannel
+import io.flutter.plugin.common.MessageCodec
+import io.flutter.plugin.common.StandardMethodCodec
+import io.flutter.plugin.common.StandardMessageCodec
+import java.io.ByteArrayOutputStream
+import java.nio.ByteBuffer
+private object MessagesPigeonUtils {
+
+ fun wrapResult(result: Any?): List {
+ return listOf(result)
+ }
+
+ fun wrapError(exception: Throwable): List {
+ return if (exception is FlutterError) {
+ listOf(
+ exception.code,
+ exception.message,
+ exception.details
+ )
+ } else {
+ listOf(
+ exception.javaClass.simpleName,
+ exception.toString(),
+ "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
+ )
+ }
+ }
+ fun doubleEquals(a: Double, b: Double): Boolean {
+ // Normalize -0.0 to 0.0 and handle NaN equality.
+ return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
+ }
+
+ fun floatEquals(a: Float, b: Float): Boolean {
+ // Normalize -0.0 to 0.0 and handle NaN equality.
+ return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
+ }
+
+ fun doubleHash(d: Double): Int {
+ // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
+ val normalized = if (d == 0.0) 0.0 else d
+ val bits = java.lang.Double.doubleToLongBits(normalized)
+ return (bits xor (bits ushr 32)).toInt()
+ }
+
+ fun floatHash(f: Float): Int {
+ // Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
+ val normalized = if (f == 0.0f) 0.0f else f
+ return java.lang.Float.floatToIntBits(normalized)
+ }
+
+ fun deepEquals(a: Any?, b: Any?): Boolean {
+ if (a === b) {
+ return true
+ }
+ if (a == null || b == null) {
+ return false
+ }
+ if (a is ByteArray && b is ByteArray) {
+ return a.contentEquals(b)
+ }
+ if (a is IntArray && b is IntArray) {
+ return a.contentEquals(b)
+ }
+ if (a is LongArray && b is LongArray) {
+ return a.contentEquals(b)
+ }
+ if (a is DoubleArray && b is DoubleArray) {
+ if (a.size != b.size) return false
+ for (i in a.indices) {
+ if (!doubleEquals(a[i], b[i])) return false
+ }
+ return true
+ }
+ if (a is FloatArray && b is FloatArray) {
+ if (a.size != b.size) return false
+ for (i in a.indices) {
+ if (!floatEquals(a[i], b[i])) return false
+ }
+ return true
+ }
+ if (a is Array<*> && b is Array<*>) {
+ if (a.size != b.size) return false
+ for (i in a.indices) {
+ if (!deepEquals(a[i], b[i])) return false
+ }
+ return true
+ }
+ if (a is List<*> && b is List<*>) {
+ if (a.size != b.size) return false
+ val iterA = a.iterator()
+ val iterB = b.iterator()
+ while (iterA.hasNext() && iterB.hasNext()) {
+ if (!deepEquals(iterA.next(), iterB.next())) return false
+ }
+ return true
+ }
+ if (a is Map<*, *> && b is Map<*, *>) {
+ if (a.size != b.size) return false
+ for (entry in a) {
+ val key = entry.key
+ var found = false
+ for (bEntry in b) {
+ if (deepEquals(key, bEntry.key)) {
+ if (deepEquals(entry.value, bEntry.value)) {
+ found = true
+ break
+ } else {
+ return false
+ }
+ }
+ }
+ if (!found) return false
+ }
+ return true
+ }
+ if (a is Double && b is Double) {
+ return doubleEquals(a, b)
+ }
+ if (a is Float && b is Float) {
+ return floatEquals(a, b)
+ }
+ return a == b
+ }
+
+ fun deepHash(value: Any?): Int {
+ return when (value) {
+ null -> 0
+ is ByteArray -> value.contentHashCode()
+ is IntArray -> value.contentHashCode()
+ is LongArray -> value.contentHashCode()
+ is DoubleArray -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + doubleHash(item)
+ }
+ result
+ }
+ is FloatArray -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + floatHash(item)
+ }
+ result
+ }
+ is Array<*> -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + deepHash(item)
+ }
+ result
+ }
+ is List<*> -> {
+ var result = 1
+ for (item in value) {
+ result = 31 * result + deepHash(item)
+ }
+ result
+ }
+ is Map<*, *> -> {
+ var result = 0
+ for (entry in value) {
+ result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
+ }
+ result
+ }
+ is Double -> doubleHash(value)
+ is Float -> floatHash(value)
+ else -> value.hashCode()
+ }
+ }
+
+}
+
+/**
+ * Error class for passing custom error details to Flutter via a thrown PlatformException.
+ * @property code The error code.
+ * @property message The error message.
+ * @property details The error details. Must be a datatype supported by the api codec.
+ */
+class FlutterError (
+ val code: String,
+ override val message: String? = null,
+ val details: Any? = null
+) : RuntimeException()
+
+/**
+ * The activity types that can be reported by the native platforms.
+ *
+ * Android reports these directly; the smaller set of iOS `CMMotionActivity`
+ * flags is mapped onto them natively, so the wire format is identical on
+ * both platforms.
+ */
+enum class PlatformActivityType(val raw: Int) {
+ IN_VEHICLE(0),
+ ON_BICYCLE(1),
+ ON_FOOT(2),
+ RUNNING(3),
+ STILL(4),
+ TILTING(5),
+ UNKNOWN(6),
+ WALKING(7);
+
+ companion object {
+ fun ofRaw(raw: Int): PlatformActivityType? {
+ return values().firstOrNull { it.raw == raw }
+ }
+ }
+}
+
+/**
+ * A single activity detection as reported by the platform.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class PlatformActivity (
+ /** The detected activity. */
+ val type: PlatformActivityType,
+ /** Confidence of the detection, in percent (0-100). */
+ val confidence: Long,
+ /**
+ * When the activity was detected, in milliseconds since the Unix epoch.
+ *
+ * Produced natively so the timestamp reflects the detection itself rather
+ * than the moment the event happened to arrive in Dart.
+ */
+ val timestamp: Long
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): PlatformActivity {
+ val type = pigeonVar_list[0] as PlatformActivityType
+ val confidence = pigeonVar_list[1] as Long
+ val timestamp = pigeonVar_list[2] as Long
+ return PlatformActivity(type, confidence, timestamp)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ type,
+ confidence,
+ timestamp,
+ )
+ }
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other.javaClass != javaClass) {
+ return false
+ }
+ if (this === other) {
+ return true
+ }
+ val other = other as PlatformActivity
+ return MessagesPigeonUtils.deepEquals(this.type, other.type) && MessagesPigeonUtils.deepEquals(this.confidence, other.confidence) && MessagesPigeonUtils.deepEquals(this.timestamp, other.timestamp)
+ }
+
+ override fun hashCode(): Int {
+ var result = javaClass.hashCode()
+ result = 31 * result + MessagesPigeonUtils.deepHash(this.type)
+ result = 31 * result + MessagesPigeonUtils.deepHash(this.confidence)
+ result = 31 * result + MessagesPigeonUtils.deepHash(this.timestamp)
+ return result
+ }
+ override fun toString(): String {
+ return "PlatformActivity(type=$type, confidence=$confidence, timestamp=$timestamp)"
+ }
+}
+
+/**
+ * Options applied to activity tracking before the event stream is started.
+ *
+ * Generated class from Pigeon that represents data sent in messages.
+ */
+data class TrackingConfiguration (
+ /**
+ * Whether Android should run a foreground service so that detections keep
+ * arriving while the app is backgrounded.
+ *
+ * Ignored on iOS, where `CMMotionActivityManager` already delivers updates
+ * in the background.
+ */
+ val runForegroundService: Boolean
+)
+ {
+ companion object {
+ fun fromList(pigeonVar_list: List): TrackingConfiguration {
+ val runForegroundService = pigeonVar_list[0] as Boolean
+ return TrackingConfiguration(runForegroundService)
+ }
+ }
+ fun toList(): List {
+ return listOf(
+ runForegroundService,
+ )
+ }
+ override fun equals(other: Any?): Boolean {
+ if (other == null || other.javaClass != javaClass) {
+ return false
+ }
+ if (this === other) {
+ return true
+ }
+ val other = other as TrackingConfiguration
+ return MessagesPigeonUtils.deepEquals(this.runForegroundService, other.runForegroundService)
+ }
+
+ override fun hashCode(): Int {
+ var result = javaClass.hashCode()
+ result = 31 * result + MessagesPigeonUtils.deepHash(this.runForegroundService)
+ return result
+ }
+ override fun toString(): String {
+ return "TrackingConfiguration(runForegroundService=$runForegroundService)"
+ }
+}
+private open class MessagesPigeonCodec : StandardMessageCodec() {
+ override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
+ return when (type) {
+ 129.toByte() -> {
+ return (readValue(buffer) as Long?)?.let {
+ PlatformActivityType.ofRaw(it.toInt())
+ }
+ }
+ 130.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ PlatformActivity.fromList(it)
+ }
+ }
+ 131.toByte() -> {
+ return (readValue(buffer) as? List)?.let {
+ TrackingConfiguration.fromList(it)
+ }
+ }
+ else -> super.readValueOfType(type, buffer)
+ }
+ }
+ override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
+ when (value) {
+ is PlatformActivityType -> {
+ stream.write(129)
+ writeValue(stream, value.raw.toLong())
+ }
+ is PlatformActivity -> {
+ stream.write(130)
+ writeValue(stream, value.toList())
+ }
+ is TrackingConfiguration -> {
+ stream.write(131)
+ writeValue(stream, value.toList())
+ }
+ else -> super.writeValue(stream, value)
+ }
+ }
+}
+
+val MessagesPigeonMethodCodec = StandardMethodCodec(MessagesPigeonCodec())
+
+/**
+ * Control channel. Called before listening to [ActivityStreamApi].
+ *
+ * Generated interface from Pigeon that represents a handler of messages from Flutter.
+ */
+interface ActivityRecognitionHostApi {
+ /** Applies [configuration] to the next tracking session. */
+ fun configure(configuration: TrackingConfiguration)
+
+ companion object {
+ /** The codec used by ActivityRecognitionHostApi. */
+ val codec: MessageCodec by lazy {
+ MessagesPigeonCodec()
+ }
+ /** Sets up an instance of `ActivityRecognitionHostApi` to handle messages through the `binaryMessenger`. */
+ @JvmOverloads
+ fun setUp(binaryMessenger: BinaryMessenger, api: ActivityRecognitionHostApi?, messageChannelSuffix: String = "") {
+ val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
+ run {
+ val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.activity_recognition_flutter.ActivityRecognitionHostApi.configure$separatedMessageChannelSuffix", codec)
+ if (api != null) {
+ channel.setMessageHandler { message, reply ->
+ val args = message as List
+ val configurationArg = args[0] as TrackingConfiguration
+ val wrapped: List = try {
+ api.configure(configurationArg)
+ listOf(null)
+ } catch (exception: Throwable) {
+ MessagesPigeonUtils.wrapError(exception)
+ }
+ reply.reply(wrapped)
+ }
+ } else {
+ channel.setMessageHandler(null)
+ }
+ }
+ }
+ }
+}
+
+private class MessagesPigeonStreamHandler(
+ val wrapper: MessagesPigeonEventChannelWrapper
+) : EventChannel.StreamHandler {
+ var pigeonSink: PigeonEventSink? = null
+
+ override fun onListen(p0: Any?, sink: EventChannel.EventSink) {
+ pigeonSink = PigeonEventSink(sink)
+ wrapper.onListen(p0, pigeonSink!!)
+ }
+
+ override fun onCancel(p0: Any?) {
+ pigeonSink = null
+ wrapper.onCancel(p0)
+ }
+}
+
+interface MessagesPigeonEventChannelWrapper {
+ open fun onListen(p0: Any?, sink: PigeonEventSink) {}
+
+ open fun onCancel(p0: Any?) {}
+}
+
+class PigeonEventSink(private val sink: EventChannel.EventSink) {
+ fun success(value: T) {
+ sink.success(value)
+ }
+
+ fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
+ sink.error(errorCode, errorMessage, errorDetails)
+ }
+
+ fun endOfStream() {
+ sink.endOfStream()
+ }
+}
+
+/** Data channel carrying the detected activities. */
+abstract class StreamActivitiesStreamHandler : MessagesPigeonEventChannelWrapper {
+ companion object {
+ fun register(messenger: BinaryMessenger, streamHandler: StreamActivitiesStreamHandler, instanceName: String = "") {
+ var channelName: String = "dev.flutter.pigeon.activity_recognition_flutter.ActivityStreamApi.streamActivities"
+ if (instanceName.isNotEmpty()) {
+ channelName += ".$instanceName"
+ }
+ val internalStreamHandler = MessagesPigeonStreamHandler(streamHandler)
+ EventChannel(messenger, channelName, MessagesPigeonMethodCodec).setStreamHandler(internalStreamHandler)
+ }
+ }
+// Implement methods from MessagesPigeonEventChannelWrapper
+override fun onListen(p0: Any?, sink: PigeonEventSink) {}
+
+override fun onCancel(p0: Any?) {}
+}
+
diff --git a/packages/activity_recognition_flutter/example/android/app/build.gradle b/packages/activity_recognition_flutter/example/android/app/build.gradle
deleted file mode 100644
index 432eb5a42..000000000
--- a/packages/activity_recognition_flutter/example/android/app/build.gradle
+++ /dev/null
@@ -1,67 +0,0 @@
-plugins {
- id "com.android.application"
- id "kotlin-android"
- id "dev.flutter.flutter-gradle-plugin"
-}
-
-def localProperties = new Properties()
-def localPropertiesFile = rootProject.file('local.properties')
-if (localPropertiesFile.exists()) {
- localPropertiesFile.withReader('UTF-8') { reader ->
- localProperties.load(reader)
- }
-}
-
-def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
-if (flutterVersionCode == null) {
- flutterVersionCode = '1'
-}
-
-def flutterVersionName = localProperties.getProperty('flutter.versionName')
-if (flutterVersionName == null) {
- flutterVersionName = '1.0'
-}
-
-android {
- namespace "dk.cachet.activity_recognition_flutter_example"
- testNamespace "dk.cachet.activity_recognition_flutter_example.test"
- compileSdkVersion flutter.compileSdkVersion
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-
- kotlinOptions {
- jvmTarget = '1.8'
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
- }
-
- lintOptions {
- disable 'InvalidPackage'
- }
-
- defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId "dk.cachet.activity_recognition_flutter_example"
- minSdkVersion 26
- targetSdkVersion flutter.targetSdkVersion
- versionCode flutterVersionCode.toInteger()
- versionName flutterVersionName
- }
-
- buildTypes {
- release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
- signingConfig signingConfigs.debug
- }
- }
-}
-
-flutter {
- source '../..'
-}
diff --git a/packages/activity_recognition_flutter/example/android/app/build.gradle.kts b/packages/activity_recognition_flutter/example/android/app/build.gradle.kts
new file mode 100644
index 000000000..e58ee42ab
--- /dev/null
+++ b/packages/activity_recognition_flutter/example/android/app/build.gradle.kts
@@ -0,0 +1,48 @@
+plugins {
+ id("com.android.application")
+ // The Flutter Gradle Plugin must be applied after the Android Gradle Plugin.
+ id("dev.flutter.flutter-gradle-plugin")
+}
+
+android {
+ namespace = "dk.carp.activity_recognition_flutter_example"
+ testNamespace = "dk.carp.activity_recognition_flutter_example.test"
+
+ // permission_handler_android requires compiling against SDK 37.
+ compileSdk = 37
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ defaultConfig {
+ applicationId = "dk.carp.activity_recognition_flutter_example"
+ // The plugin needs notification channels and startForegroundService.
+ minSdk = 26
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutter.versionCode
+ versionName = flutter.versionName
+ }
+
+ buildTypes {
+ release {
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("debug")
+ }
+ }
+
+ lint {
+ disable += "InvalidPackage"
+ }
+}
+
+kotlin {
+ compilerOptions {
+ jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
+ }
+}
+
+flutter {
+ source = "../.."
+}
diff --git a/packages/activity_recognition_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/activity_recognition_flutter/example/android/app/src/main/AndroidManifest.xml
index 23d7a4b6b..33bc229c9 100644
--- a/packages/activity_recognition_flutter/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/activity_recognition_flutter/example/android/app/src/main/AndroidManifest.xml
@@ -42,17 +42,8 @@
android:name="flutterEmbedding"
android:value="2" />
-
-
-
-
+
-
-
-
-
diff --git a/packages/activity_recognition_flutter/example/android/app/src/main/java/dk/cachet/activity_recognition_flutter_example/MainActivity.java b/packages/activity_recognition_flutter/example/android/app/src/main/java/dk/cachet/activity_recognition_flutter_example/MainActivity.java
deleted file mode 100644
index 57917d8d1..000000000
--- a/packages/activity_recognition_flutter/example/android/app/src/main/java/dk/cachet/activity_recognition_flutter_example/MainActivity.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package dk.cachet.activity_recognition_flutter_example;
-
-import io.flutter.embedding.android.FlutterActivity;
-
-public class MainActivity extends FlutterActivity {
-}
diff --git a/packages/activity_recognition_flutter/example/android/app/src/main/kotlin/dk/carp/activity_recognition_flutter_example/MainActivity.kt b/packages/activity_recognition_flutter/example/android/app/src/main/kotlin/dk/carp/activity_recognition_flutter_example/MainActivity.kt
new file mode 100644
index 000000000..a3ea46619
--- /dev/null
+++ b/packages/activity_recognition_flutter/example/android/app/src/main/kotlin/dk/carp/activity_recognition_flutter_example/MainActivity.kt
@@ -0,0 +1,5 @@
+package dk.carp.activity_recognition_flutter_example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity : FlutterActivity()
diff --git a/packages/activity_recognition_flutter/example/android/build.gradle b/packages/activity_recognition_flutter/example/android/build.gradle
deleted file mode 100644
index bc157bd1a..000000000
--- a/packages/activity_recognition_flutter/example/android/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-allprojects {
- repositories {
- google()
- mavenCentral()
- }
-}
-
-rootProject.buildDir = '../build'
-subprojects {
- project.buildDir = "${rootProject.buildDir}/${project.name}"
-}
-subprojects {
- project.evaluationDependsOn(':app')
-}
-
-tasks.register("clean", Delete) {
- delete rootProject.buildDir
-}
diff --git a/packages/activity_recognition_flutter/example/android/build.gradle.kts b/packages/activity_recognition_flutter/example/android/build.gradle.kts
new file mode 100644
index 000000000..dbee657bb
--- /dev/null
+++ b/packages/activity_recognition_flutter/example/android/build.gradle.kts
@@ -0,0 +1,24 @@
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+val newBuildDir: Directory =
+ rootProject.layout.buildDirectory
+ .dir("../../build")
+ .get()
+rootProject.layout.buildDirectory.value(newBuildDir)
+
+subprojects {
+ val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
+ project.layout.buildDirectory.value(newSubprojectBuildDir)
+}
+subprojects {
+ project.evaluationDependsOn(":app")
+}
+
+tasks.register("clean") {
+ delete(rootProject.layout.buildDirectory)
+}
diff --git a/packages/activity_recognition_flutter/example/android/gradle.properties b/packages/activity_recognition_flutter/example/android/gradle.properties
index a6738207f..442df403d 100644
--- a/packages/activity_recognition_flutter/example/android/gradle.properties
+++ b/packages/activity_recognition_flutter/example/android/gradle.properties
@@ -1,4 +1,8 @@
-org.gradle.jvmargs=-Xmx1536M
+org.gradle.jvmargs=-Xmx4096M
android.useAndroidX=true
-android.enableJetifier=true
-android.enableR8=true
+# This builtInKotlin flag was added automatically by Flutter migrator
+# Kept false: AGP 9's built-in Kotlin is Kotlin 2.2.10, below Flutter's minimum
+# of 2.2.20, so the Kotlin Gradle plugin is declared in settings.gradle.kts.
+android.builtInKotlin=false
+# This newDsl flag was added automatically by Flutter migrator
+android.newDsl=false
diff --git a/packages/activity_recognition_flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/activity_recognition_flutter/example/android/gradle/wrapper/gradle-wrapper.properties
index 8f3239ab6..a97e89ca1 100644
--- a/packages/activity_recognition_flutter/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/activity_recognition_flutter/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
diff --git a/packages/activity_recognition_flutter/example/android/settings.gradle b/packages/activity_recognition_flutter/example/android/settings.gradle
deleted file mode 100644
index 19494fa21..000000000
--- a/packages/activity_recognition_flutter/example/android/settings.gradle
+++ /dev/null
@@ -1,25 +0,0 @@
-pluginManagement {
- def flutterSdkPath = {
- def properties = new Properties()
- file("local.properties").withInputStream { properties.load(it) }
- def flutterSdkPath = properties.getProperty("flutter.sdk")
- assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
- return flutterSdkPath
- }()
-
- includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
-
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-plugins {
- id "dev.flutter.flutter-plugin-loader" version "1.0.2"
- id "com.android.application" version '8.9.0' apply false
- id "org.jetbrains.kotlin.android" version "1.9.20" apply false
-}
-
-include ":app"
\ No newline at end of file
diff --git a/packages/activity_recognition_flutter/example/android/settings.gradle.kts b/packages/activity_recognition_flutter/example/android/settings.gradle.kts
new file mode 100644
index 000000000..ef9c87aae
--- /dev/null
+++ b/packages/activity_recognition_flutter/example/android/settings.gradle.kts
@@ -0,0 +1,26 @@
+pluginManagement {
+ val flutterSdkPath =
+ run {
+ val properties = java.util.Properties()
+ file("local.properties").inputStream().use { properties.load(it) }
+ val flutterSdkPath = properties.getProperty("flutter.sdk")
+ require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
+ flutterSdkPath
+ }
+
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id("dev.flutter.flutter-plugin-loader") version "1.0.2"
+ id("com.android.application") version "9.0.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.3.20" apply false
+}
+
+include(":app")
diff --git a/packages/activity_recognition_flutter/example/ios/Flutter/AppFrameworkInfo.plist b/packages/activity_recognition_flutter/example/ios/Flutter/AppFrameworkInfo.plist
index 8c6e56146..ab8e063fe 100644
--- a/packages/activity_recognition_flutter/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/packages/activity_recognition_flutter/example/ios/Flutter/AppFrameworkInfo.plist
@@ -20,7 +20,5 @@
????
CFBundleVersion
1.0
- MinimumOSVersion
- 12.0
diff --git a/packages/activity_recognition_flutter/example/ios/Flutter/Debug.xcconfig b/packages/activity_recognition_flutter/example/ios/Flutter/Debug.xcconfig
index e8efba114..592ceee85 100644
--- a/packages/activity_recognition_flutter/example/ios/Flutter/Debug.xcconfig
+++ b/packages/activity_recognition_flutter/example/ios/Flutter/Debug.xcconfig
@@ -1,2 +1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
diff --git a/packages/activity_recognition_flutter/example/ios/Flutter/Release.xcconfig b/packages/activity_recognition_flutter/example/ios/Flutter/Release.xcconfig
index 399e9340e..592ceee85 100644
--- a/packages/activity_recognition_flutter/example/ios/Flutter/Release.xcconfig
+++ b/packages/activity_recognition_flutter/example/ios/Flutter/Release.xcconfig
@@ -1,2 +1 @@
-#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
diff --git a/packages/activity_recognition_flutter/example/ios/Podfile b/packages/activity_recognition_flutter/example/ios/Podfile
deleted file mode 100644
index 279576f38..000000000
--- a/packages/activity_recognition_flutter/example/ios/Podfile
+++ /dev/null
@@ -1,41 +0,0 @@
-# Uncomment this line to define a global platform for your project
-# platform :ios, '12.0'
-
-# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
-ENV['COCOAPODS_DISABLE_STATS'] = 'true'
-
-project 'Runner', {
- 'Debug' => :debug,
- 'Profile' => :release,
- 'Release' => :release,
-}
-
-def flutter_root
- generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
- unless File.exist?(generated_xcode_build_settings_path)
- raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
- end
-
- File.foreach(generated_xcode_build_settings_path) do |line|
- matches = line.match(/FLUTTER_ROOT\=(.*)/)
- return matches[1].strip if matches
- end
- raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
-end
-
-require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
-
-flutter_ios_podfile_setup
-
-target 'Runner' do
- use_frameworks!
- use_modular_headers!
-
- flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
-end
-
-post_install do |installer|
- installer.pods_project.targets.each do |target|
- flutter_additional_ios_build_settings(target)
- end
-end
diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj
index 19babf78e..1af15e909 100644
--- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,14 +3,14 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 54;
+ objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
- 24A7651E5407004ACA8F4574 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9D3861AE45E9E4A0F6101B5C /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@@ -33,11 +33,12 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 55DAE46F7EFFFC65CAD56BA0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = ""; };
+ 78DABEA22ED26510000E7860 /* activity_recognition_flutter */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = activity_recognition_flutter; path = ../../ios/activity_recognition_flutter; sourceTree = ""; };
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 7BA3D2FF28AEF196044136AD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -45,8 +46,6 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 9D3861AE45E9E4A0F6101B5C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- B82047B9D55539FAB3D824E9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -54,26 +53,19 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 24A7651E5407004ACA8F4574 /* Pods_Runner.framework in Frameworks */,
+ 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 2BBABEDF8625AFA845500189 /* Pods */ = {
- isa = PBXGroup;
- children = (
- B82047B9D55539FAB3D824E9 /* Pods-Runner.debug.xcconfig */,
- 7BA3D2FF28AEF196044136AD /* Pods-Runner.release.xcconfig */,
- 55DAE46F7EFFFC65CAD56BA0 /* Pods-Runner.profile.xcconfig */,
- );
- path = Pods;
- sourceTree = "";
- };
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
+ 78DABEA22ED26510000E7860 /* activity_recognition_flutter */,
+ 784666492D4C4C64000A1A5F /* FlutterFramework */,
+ 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@@ -88,8 +80,6 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
- 2BBABEDF8625AFA845500189 /* Pods */,
- A12F4221223302A8D533B1EC /* Frameworks */,
);
sourceTree = "";
};
@@ -116,14 +106,6 @@
path = Runner;
sourceTree = "";
};
- A12F4221223302A8D533B1EC /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 9D3861AE45E9E4A0F6101B5C /* Pods_Runner.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -131,20 +113,21 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 388E0730D4C55E1ABD759B49 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 5A4B0B838E074EF09BAFC73E /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
+ packageProductDependencies = (
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
+ );
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
@@ -173,6 +156,9 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
+ packageReferences = (
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
+ );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -197,28 +183,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 388E0730D4C55E1ABD759B49 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -235,23 +199,6 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 5A4B0B838E074EF09BAFC73E /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
- );
- name = "[CP] Embed Pods Frameworks";
- outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@@ -343,7 +290,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -374,7 +321,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.activityRecognitionFlutterExample;
+ PRODUCT_BUNDLE_IDENTIFIER = dk.carp.activityRecognitionFlutterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -430,7 +377,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -480,7 +427,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -513,7 +460,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.activityRecognitionFlutterExample;
+ PRODUCT_BUNDLE_IDENTIFIER = dk.carp.activityRecognitionFlutterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -544,7 +491,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
- PRODUCT_BUNDLE_IDENTIFIER = dk.cachet.activityRecognitionFlutterExample;
+ PRODUCT_BUNDLE_IDENTIFIER = dk.carp.activityRecognitionFlutterExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@@ -576,6 +523,20 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
+
+/* Begin XCLocalSwiftPackageReference section */
+ 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
+ isa = XCLocalSwiftPackageReference;
+ relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCLocalSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = FlutterGeneratedPluginSwiftPackage;
+ };
+/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
diff --git a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index c53e2b314..5db441f58 100644
--- a/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/packages/activity_recognition_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -5,6 +5,24 @@
+
+
+
+
+
+
+
+
+
+
-
-
diff --git a/packages/activity_recognition_flutter/example/ios/Runner/AppDelegate.swift b/packages/activity_recognition_flutter/example/ios/Runner/AppDelegate.swift
index b63630348..c30b367ec 100644
--- a/packages/activity_recognition_flutter/example/ios/Runner/AppDelegate.swift
+++ b/packages/activity_recognition_flutter/example/ios/Runner/AppDelegate.swift
@@ -1,13 +1,16 @@
-import UIKit
import Flutter
+import UIKit
@main
-@objc class AppDelegate: FlutterAppDelegate {
+@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
- GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
+
+ func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
+ GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
+ }
}
diff --git a/packages/activity_recognition_flutter/example/ios/Runner/Info.plist b/packages/activity_recognition_flutter/example/ios/Runner/Info.plist
index a8a3846a2..491b7da34 100644
--- a/packages/activity_recognition_flutter/example/ios/Runner/Info.plist
+++ b/packages/activity_recognition_flutter/example/ios/Runner/Info.plist
@@ -2,6 +2,8 @@
+ CADisableMinimumFrameDurationOnPhone
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
@@ -24,6 +26,29 @@
NSMotionUsageDescription
Tracks the user activity
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneClassName
+ UIWindowScene
+ UISceneConfigurationName
+ flutter
+ UISceneDelegateClassName
+ FlutterSceneDelegate
+ UISceneStoryboardFile
+ Main
+
+
+
+
+ UIApplicationSupportsIndirectInputEvents
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -41,9 +66,5 @@
UIViewControllerBasedStatusBarAppearance
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
diff --git a/packages/activity_recognition_flutter/example/pubspec.yaml b/packages/activity_recognition_flutter/example/pubspec.yaml
index 479b39522..caf70b110 100644
--- a/packages/activity_recognition_flutter/example/pubspec.yaml
+++ b/packages/activity_recognition_flutter/example/pubspec.yaml
@@ -7,7 +7,7 @@ description: Demonstrates how to use the activity_recognition_flutter plugin.
publish_to: "none" # Remove this line if you wish to publish to pub.dev
environment:
- sdk: '>=2.12.0 <3.0.0'
+ sdk: ">=3.8.0 <4.0.0"
dependencies:
flutter:
@@ -23,12 +23,20 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^1.0.2
+ cupertino_icons: ^1.0.8
+
+ # Used to request the runtime activity recognition permission on Android.
+ permission_handler: ^13.0.0
dev_dependencies:
flutter_test:
sdk: flutter
- permission_handler: ^7.0.0
flutter:
+ # The plugin ships only as a Swift package, so pin the example to Swift
+ # Package Manager instead of relying on the global `flutter config` value.
+ # Without this, a machine that has SPM disabled would fall back to CocoaPods
+ # and re-add the Pods include to ios/Flutter/{Debug,Release}.xcconfig.
+ config:
+ enable-swift-package-manager: true
uses-material-design: true
diff --git a/packages/activity_recognition_flutter/ios/Assets/.gitkeep b/packages/activity_recognition_flutter/ios/Assets/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.h b/packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.h
deleted file mode 100644
index 4642b53c6..000000000
--- a/packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#import
-
-@interface ActivityRecognitionFlutterPlugin : NSObject
-@end
diff --git a/packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.m b/packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.m
deleted file mode 100644
index 9f649c299..000000000
--- a/packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.m
+++ /dev/null
@@ -1,15 +0,0 @@
-#import "ActivityRecognitionFlutterPlugin.h"
-#if __has_include()
-#import
-#else
-// Support project import fallback if the generated compatibility header
-// is not copied when this plugin is created as a library.
-// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
-#import "activity_recognition_flutter-Swift.h"
-#endif
-
-@implementation ActivityRecognitionFlutterPlugin
-+ (void)registerWithRegistrar:(NSObject*)registrar {
- [SwiftActivityRecognitionFlutterPlugin registerWithRegistrar:registrar];
-}
-@end
diff --git a/packages/activity_recognition_flutter/ios/Classes/SwiftActivityRecognitionFlutterPlugin.swift b/packages/activity_recognition_flutter/ios/Classes/SwiftActivityRecognitionFlutterPlugin.swift
deleted file mode 100644
index 5eacf8eb7..000000000
--- a/packages/activity_recognition_flutter/ios/Classes/SwiftActivityRecognitionFlutterPlugin.swift
+++ /dev/null
@@ -1,73 +0,0 @@
-import Flutter
-import UIKit
-import CoreMotion
-
-
-public class SwiftActivityRecognitionFlutterPlugin: NSObject, FlutterPlugin {
-
- public static func register(with registrar: FlutterPluginRegistrar) {
- let handler = ActivityStreamHandler()
- let channel = FlutterEventChannel(name: "activity_recognition_flutter", binaryMessenger: registrar.messenger())
- channel.setStreamHandler(handler)
- }
-}
-public class ActivityStreamHandler: NSObject, FlutterStreamHandler {
-
- private let activityManager = CMMotionActivityManager()
-
- public func onListen(withArguments arguments: Any?, eventSink: @escaping FlutterEventSink) -> FlutterError? {
- activityManager.startActivityUpdates(to: OperationQueue.init()) { (activity) in
- if let a = activity {
-
- let type = self.extractActivityType(a: a)
- let confidence = self.extractActivityConfidence(a: a)
- let data = "\(type),\(confidence)"
-
- /// Send event to flutter
- eventSink(data)
- }
- }
- return nil
- }
-
- public func onCancel(withArguments arguments: Any?) -> FlutterError? {
- activityManager.stopActivityUpdates()
- return nil
- }
-
- func extractActivityType(a: CMMotionActivity) -> String {
- var type = "UNKNOWN"
- switch true {
- case a.stationary:
- type = "STILL"
- case a.walking:
- type = "WALKING"
- case a.running:
- type = "RUNNING"
- case a.automotive:
- type = "IN_VEHICLE"
- case a.cycling:
- type = "ON_BICYCLE"
- default:
- type = "UNKNOWN"
- }
- return type
- }
-
- func extractActivityConfidence(a: CMMotionActivity) -> Int {
- var conf = -1
-
- switch a.confidence {
- case CMMotionActivityConfidence.low:
- conf = 10
- case CMMotionActivityConfidence.medium:
- conf = 50
- case CMMotionActivityConfidence.high:
- conf = 100
- default:
- conf = -1
- }
- return conf
- }
-
-}
diff --git a/packages/activity_recognition_flutter/ios/activity_recognition_flutter.podspec b/packages/activity_recognition_flutter/ios/activity_recognition_flutter.podspec
deleted file mode 100644
index c2773180d..000000000
--- a/packages/activity_recognition_flutter/ios/activity_recognition_flutter.podspec
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
-# Run `pod lib lint activity_recognition_flutter.podspec' to validate before publishing.
-#
-Pod::Spec.new do |s|
- s.name = 'activity_recognition_flutter'
- s.version = '0.0.1'
- s.summary = 'A new flutter plugin project.'
- s.description = <<-DESC
-A new flutter plugin project.
- DESC
- s.homepage = 'http://example.com'
- s.license = { :file => '../LICENSE' }
- s.author = { 'Your Company' => 'email@example.com' }
- s.source = { :path => '.' }
- s.source_files = 'Classes/**/*'
- s.dependency 'Flutter'
- s.platform = :ios, '8.0'
-
- # Flutter.framework does not contain a i386 slice.
- s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
- s.swift_version = '5.0'
-end
diff --git a/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Package.swift b/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Package.swift
new file mode 100644
index 000000000..12001c609
--- /dev/null
+++ b/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Package.swift
@@ -0,0 +1,36 @@
+// swift-tools-version: 5.9
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+ name: "activity_recognition_flutter",
+ platforms: [
+ .iOS("15.0")
+ ],
+ products: [
+ .library(name: "activity-recognition-flutter", targets: ["activity_recognition_flutter"])
+ ],
+ dependencies: [
+ .package(name: "FlutterFramework", path: "../FlutterFramework")
+ ],
+ targets: [
+ .target(
+ name: "activity_recognition_flutter",
+ dependencies: [
+ .product(name: "FlutterFramework", package: "FlutterFramework")
+ ],
+ resources: [
+ // If your plugin requires a privacy manifest, for example if it uses any required
+ // reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's
+ // privacy impact, and then uncomment these lines. For more information, see
+ // https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
+ // .process("PrivacyInfo.xcprivacy"),
+
+ // If you have other resources that need to be bundled with your plugin, refer to
+ // the following instructions to add them:
+ // https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
+ ]
+ )
+ ]
+)
diff --git a/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.swift b/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.swift
new file mode 100644
index 000000000..6e6860a71
--- /dev/null
+++ b/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.swift
@@ -0,0 +1,90 @@
+import CoreMotion
+import Flutter
+import UIKit
+
+/// Activity recognition for iOS, backed by `CMMotionActivityManager`.
+///
+/// The Dart <-> native contract is generated by Pigeon from `pigeons/messages.dart`;
+/// see `Messages.g.swift`. Configuration arrives over a host API and detections are
+/// pushed back over an event channel.
+public class ActivityRecognitionFlutterPlugin: NSObject, FlutterPlugin, ActivityRecognitionHostApi {
+ private let streamHandler = ActivityStreamHandler()
+
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let plugin = ActivityRecognitionFlutterPlugin()
+
+ ActivityRecognitionHostApiSetup.setUp(
+ binaryMessenger: registrar.messenger(),
+ api: plugin
+ )
+ StreamActivitiesStreamHandler.register(
+ with: registrar.messenger(),
+ streamHandler: plugin.streamHandler
+ )
+
+ registrar.publish(plugin)
+ }
+
+ // MARK: - ActivityRecognitionHostApi
+
+ /// `runForegroundService` is Android-only: `CMMotionActivityManager` already
+ /// delivers updates while the app is in the background, so there is nothing
+ /// to configure on iOS.
+ func configure(configuration: TrackingConfiguration) throws {}
+}
+
+/// Bridges `CMMotionActivityManager` updates onto the Pigeon event channel.
+final class ActivityStreamHandler: StreamActivitiesStreamHandler {
+ private let activityManager = CMMotionActivityManager()
+ private let updateQueue = OperationQueue()
+
+ override func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {
+ guard CMMotionActivityManager.isActivityAvailable() else {
+ sink.error(
+ code: "UNAVAILABLE",
+ message: "Motion activity is not available on this device.",
+ details: nil
+ )
+ sink.endOfStream()
+ return
+ }
+
+ activityManager.startActivityUpdates(to: updateQueue) { activity in
+ guard let activity else { return }
+
+ let event = PlatformActivity(
+ type: Self.activityType(of: activity),
+ confidence: Self.confidence(of: activity),
+ timestamp: Int64(activity.startDate.timeIntervalSince1970 * 1000)
+ )
+
+ // Event sinks must be driven from the platform thread.
+ DispatchQueue.main.async { sink.success(event) }
+ }
+ }
+
+ override func onCancel(withArguments arguments: Any?) {
+ activityManager.stopActivityUpdates()
+ }
+
+ /// `CMMotionActivity` exposes independent flags that may be set at the same
+ /// time, so they are checked in priority order and the first match wins.
+ private static func activityType(of activity: CMMotionActivity) -> PlatformActivityType {
+ if activity.stationary { return .still }
+ if activity.walking { return .walking }
+ if activity.running { return .running }
+ if activity.automotive { return .inVehicle }
+ if activity.cycling { return .onBicycle }
+ return .unknown
+ }
+
+ /// Maps the three-level iOS confidence onto the 0-100 scale Android reports.
+ private static func confidence(of activity: CMMotionActivity) -> Int64 {
+ switch activity.confidence {
+ case .low: return 10
+ case .medium: return 50
+ case .high: return 100
+ @unknown default: return 0
+ }
+ }
+}
diff --git a/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/Messages.g.swift b/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/Messages.g.swift
new file mode 100644
index 000000000..f47783ec0
--- /dev/null
+++ b/packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/Messages.g.swift
@@ -0,0 +1,446 @@
+// Autogenerated from Pigeon (v27.3.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+
+import Foundation
+
+#if os(iOS)
+ import Flutter
+#elseif os(macOS)
+ import FlutterMacOS
+#else
+ #error("Unsupported platform.")
+#endif
+
+/// Error class for passing custom error details to Dart side.
+final class PigeonError: Error {
+ let code: String
+ let message: String?
+ let details: Sendable?
+
+ init(code: String, message: String?, details: Sendable?) {
+ self.code = code
+ self.message = message
+ self.details = details
+ }
+
+ var localizedDescription: String {
+ return
+ "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")"
+ }
+}
+
+private func wrapResult(_ result: Any?) -> [Any?] {
+ return [result]
+}
+
+private func wrapError(_ error: Any) -> [Any?] {
+ if let pigeonError = error as? PigeonError {
+ return [
+ pigeonError.code,
+ pigeonError.message,
+ pigeonError.details,
+ ]
+ }
+ if let flutterError = error as? FlutterError {
+ return [
+ flutterError.code,
+ flutterError.message,
+ flutterError.details,
+ ]
+ }
+ return [
+ "\(error)",
+ "\(Swift.type(of: error))",
+ "Stacktrace: \(Thread.callStackSymbols)",
+ ]
+}
+
+enum MessagesPigeonInternal {
+ static func isNullish(_ value: Any?) -> Bool {
+ guard let innerValue = value else {
+ return true
+ }
+
+ if case Optional.some(Optional.none) = value {
+ return true
+ }
+
+ return innerValue is NSNull
+ }
+ static func doubleEquals(_ lhs: Double, _ rhs: Double) -> Bool {
+ return (lhs.isNaN && rhs.isNaN) || lhs == rhs
+ }
+
+ static func doubleHash(_ value: Double, _ hasher: inout Hasher) {
+ if value.isNaN {
+ hasher.combine(0x7FF8000000000000)
+ } else {
+ // Normalize -0.0 to 0.0
+ hasher.combine(value == 0 ? 0 : value)
+ }
+ }
+
+ static func deepEquals(_ lhs: Any?, _ rhs: Any?) -> Bool {
+ let cleanLhs = nilOrValue(lhs) as Any?
+ let cleanRhs = nilOrValue(rhs) as Any?
+ switch (cleanLhs, cleanRhs) {
+ case (nil, nil):
+ return true
+
+ case (nil, _), (_, nil):
+ return false
+
+ case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs:
+ return true
+
+ case is (Void, Void):
+ return true
+
+ case (let lhsArray, let rhsArray) as ([Any?], [Any?]):
+ guard lhsArray.count == rhsArray.count else { return false }
+ for (index, element) in lhsArray.enumerated() {
+ if !deepEquals(element, rhsArray[index]) {
+ return false
+ }
+ }
+ return true
+
+ case (let lhsArray, let rhsArray) as ([Double], [Double]):
+ guard lhsArray.count == rhsArray.count else { return false }
+ for (index, element) in lhsArray.enumerated() {
+ if !doubleEquals(element, rhsArray[index]) {
+ return false
+ }
+ }
+ return true
+
+ case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
+ guard lhsDictionary.count == rhsDictionary.count else { return false }
+ for (lhsKey, lhsValue) in lhsDictionary {
+ var found = false
+ for (rhsKey, rhsValue) in rhsDictionary {
+ if deepEquals(lhsKey, rhsKey) {
+ if deepEquals(lhsValue, rhsValue) {
+ found = true
+ break
+ } else {
+ return false
+ }
+ }
+ }
+ if !found { return false }
+ }
+ return true
+
+ case (let lhs as Double, let rhs as Double):
+ return doubleEquals(lhs, rhs)
+
+ case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable):
+ return lhsHashable == rhsHashable
+
+ default:
+ return false
+ }
+ }
+
+ static func deepHash(value: Any?, hasher: inout Hasher) {
+ let cleanValue = nilOrValue(value) as Any?
+ if let cleanValue = cleanValue {
+ if let doubleValue = cleanValue as? Double {
+ doubleHash(doubleValue, &hasher)
+ } else if let valueList = cleanValue as? [Any?] {
+ for item in valueList {
+ deepHash(value: item, hasher: &hasher)
+ }
+ } else if let valueList = cleanValue as? [Double] {
+ for item in valueList {
+ doubleHash(item, &hasher)
+ }
+ } else if let valueDict = cleanValue as? [AnyHashable: Any?] {
+ var result = 0
+ for (key, value) in valueDict {
+ var entryKeyHasher = Hasher()
+ deepHash(value: key, hasher: &entryKeyHasher)
+ var entryValueHasher = Hasher()
+ deepHash(value: value, hasher: &entryValueHasher)
+ result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize())
+ }
+ hasher.combine(result)
+ } else if let hashableValue = cleanValue as? AnyHashable {
+ hasher.combine(hashableValue)
+ } else {
+ hasher.combine(String(describing: cleanValue))
+ }
+ } else {
+ hasher.combine(0)
+ }
+ }
+
+}
+
+private func nilOrValue(_ value: Any?) -> T? {
+ if value is NSNull { return nil }
+ return value as! T?
+}
+
+
+/// The activity types that can be reported by the native platforms.
+///
+/// Android reports these directly; the smaller set of iOS `CMMotionActivity`
+/// flags is mapped onto them natively, so the wire format is identical on
+/// both platforms.
+enum PlatformActivityType: Int, CaseIterable {
+ case inVehicle = 0
+ case onBicycle = 1
+ case onFoot = 2
+ case running = 3
+ case still = 4
+ case tilting = 5
+ case unknown = 6
+ case walking = 7
+}
+
+/// A single activity detection as reported by the platform.
+///
+/// Generated class from Pigeon that represents data sent in messages.
+struct PlatformActivity: Hashable, CustomStringConvertible {
+ /// The detected activity.
+ var type: PlatformActivityType
+ /// Confidence of the detection, in percent (0-100).
+ var confidence: Int64
+ /// When the activity was detected, in milliseconds since the Unix epoch.
+ ///
+ /// Produced natively so the timestamp reflects the detection itself rather
+ /// than the moment the event happened to arrive in Dart.
+ var timestamp: Int64
+
+
+ // swift-format-ignore: AlwaysUseLowerCamelCase
+ static func fromList(_ pigeonVar_list: [Any?]) -> PlatformActivity? {
+ let type = pigeonVar_list[0] as! PlatformActivityType
+ let confidence = pigeonVar_list[1] as! Int64
+ let timestamp = pigeonVar_list[2] as! Int64
+
+ return PlatformActivity(
+ type: type,
+ confidence: confidence,
+ timestamp: timestamp
+ )
+ }
+ func toList() -> [Any?] {
+ return [
+ type,
+ confidence,
+ timestamp,
+ ]
+ }
+ static func == (lhs: PlatformActivity, rhs: PlatformActivity) -> Bool {
+ if Swift.type(of: lhs) != Swift.type(of: rhs) {
+ return false
+ }
+ return MessagesPigeonInternal.deepEquals(lhs.type, rhs.type) && MessagesPigeonInternal.deepEquals(lhs.confidence, rhs.confidence) && MessagesPigeonInternal.deepEquals(lhs.timestamp, rhs.timestamp)
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine("PlatformActivity")
+ MessagesPigeonInternal.deepHash(value: type, hasher: &hasher)
+ MessagesPigeonInternal.deepHash(value: confidence, hasher: &hasher)
+ MessagesPigeonInternal.deepHash(value: timestamp, hasher: &hasher)
+ }
+
+ public var description: String {
+ return "PlatformActivity(type: \(String(describing: type)), confidence: \(String(describing: confidence)), timestamp: \(String(describing: timestamp)))"
+ }
+}
+
+/// Options applied to activity tracking before the event stream is started.
+///
+/// Generated class from Pigeon that represents data sent in messages.
+struct TrackingConfiguration: Hashable, CustomStringConvertible {
+ /// Whether Android should run a foreground service so that detections keep
+ /// arriving while the app is backgrounded.
+ ///
+ /// Ignored on iOS, where `CMMotionActivityManager` already delivers updates
+ /// in the background.
+ var runForegroundService: Bool
+
+
+ // swift-format-ignore: AlwaysUseLowerCamelCase
+ static func fromList(_ pigeonVar_list: [Any?]) -> TrackingConfiguration? {
+ let runForegroundService = pigeonVar_list[0] as! Bool
+
+ return TrackingConfiguration(
+ runForegroundService: runForegroundService
+ )
+ }
+ func toList() -> [Any?] {
+ return [
+ runForegroundService
+ ]
+ }
+ static func == (lhs: TrackingConfiguration, rhs: TrackingConfiguration) -> Bool {
+ if Swift.type(of: lhs) != Swift.type(of: rhs) {
+ return false
+ }
+ return MessagesPigeonInternal.deepEquals(lhs.runForegroundService, rhs.runForegroundService)
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine("TrackingConfiguration")
+ MessagesPigeonInternal.deepHash(value: runForegroundService, hasher: &hasher)
+ }
+
+ public var description: String {
+ return "TrackingConfiguration(runForegroundService: \(String(describing: runForegroundService)))"
+ }
+}
+
+private class MessagesPigeonCodecReader: FlutterStandardReader {
+ override func readValue(ofType type: UInt8) -> Any? {
+ switch type {
+ case 129:
+ let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
+ if let enumResultAsInt = enumResultAsInt {
+ return PlatformActivityType(rawValue: enumResultAsInt)
+ }
+ return nil
+ case 130:
+ return PlatformActivity.fromList(self.readValue() as! [Any?])
+ case 131:
+ return TrackingConfiguration.fromList(self.readValue() as! [Any?])
+ default:
+ return super.readValue(ofType: type)
+ }
+ }
+}
+
+private class MessagesPigeonCodecWriter: FlutterStandardWriter {
+ override func writeValue(_ value: Any) {
+ if let value = value as? PlatformActivityType {
+ super.writeByte(129)
+ super.writeValue(value.rawValue)
+ } else if let value = value as? PlatformActivity {
+ super.writeByte(130)
+ super.writeValue(value.toList())
+ } else if let value = value as? TrackingConfiguration {
+ super.writeByte(131)
+ super.writeValue(value.toList())
+ } else {
+ super.writeValue(value)
+ }
+ }
+}
+
+private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter {
+ override func reader(with data: Data) -> FlutterStandardReader {
+ return MessagesPigeonCodecReader(data: data)
+ }
+
+ override func writer(with data: NSMutableData) -> FlutterStandardWriter {
+ return MessagesPigeonCodecWriter(data: data)
+ }
+}
+
+class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
+ static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter())
+}
+
+var messagesPigeonMethodCodec = FlutterStandardMethodCodec(readerWriter: MessagesPigeonCodecReaderWriter());
+
+/// Control channel. Called before listening to [ActivityStreamApi].
+///
+/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
+protocol ActivityRecognitionHostApi {
+ /// Applies [configuration] to the next tracking session.
+ func configure(configuration: TrackingConfiguration) throws
+}
+
+/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
+class ActivityRecognitionHostApiSetup {
+ static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared }
+ /// Sets up an instance of `ActivityRecognitionHostApi` to handle messages through the `binaryMessenger`.
+ static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ActivityRecognitionHostApi?, messageChannelSuffix: String = "") {
+ let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
+ /// Applies [configuration] to the next tracking session.
+ let configureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.activity_recognition_flutter.ActivityRecognitionHostApi.configure\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
+ if let api = api {
+ configureChannel.setMessageHandler { message, reply in
+ let args = message as! [Any?]
+ let configurationArg = args[0] as! TrackingConfiguration
+ do {
+ try api.configure(configuration: configurationArg)
+ reply(wrapResult(nil))
+ } catch {
+ reply(wrapError(error))
+ }
+ }
+ } else {
+ configureChannel.setMessageHandler(nil)
+ }
+ }
+}
+
+private class PigeonStreamHandler: NSObject, FlutterStreamHandler {
+ private let wrapper: PigeonEventChannelWrapper
+ private var pigeonSink: PigeonEventSink? = nil
+
+ init(wrapper: PigeonEventChannelWrapper) {
+ self.wrapper = wrapper
+ }
+
+ func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink)
+ -> FlutterError?
+ {
+ pigeonSink = PigeonEventSink(events)
+ wrapper.onListen(withArguments: arguments, sink: pigeonSink!)
+ return nil
+ }
+
+ func onCancel(withArguments arguments: Any?) -> FlutterError? {
+ pigeonSink = nil
+ wrapper.onCancel(withArguments: arguments)
+ return nil
+ }
+}
+
+class PigeonEventChannelWrapper {
+ func onListen(withArguments arguments: Any?, sink: PigeonEventSink) {}
+ func onCancel(withArguments arguments: Any?) {}
+}
+
+class PigeonEventSink {
+ private let sink: FlutterEventSink
+
+ init(_ sink: @escaping FlutterEventSink) {
+ self.sink = sink
+ }
+
+ func success(_ value: ReturnType) {
+ sink(value)
+ }
+
+ func error(code: String, message: String?, details: Any?) {
+ sink(FlutterError(code: code, message: message, details: details))
+ }
+
+ func endOfStream() {
+ sink(FlutterEndOfEventStream)
+ }
+
+}
+
+/// Data channel carrying the detected activities.
+class StreamActivitiesStreamHandler: PigeonEventChannelWrapper {
+ static func register(with messenger: FlutterBinaryMessenger,
+ instanceName: String = "",
+ streamHandler: StreamActivitiesStreamHandler) {
+ var channelName = "dev.flutter.pigeon.activity_recognition_flutter.ActivityStreamApi.streamActivities"
+ if !instanceName.isEmpty {
+ channelName += ".\(instanceName)"
+ }
+ let internalStreamHandler = PigeonStreamHandler(wrapper: streamHandler)
+ let channel = FlutterEventChannel(name: channelName, binaryMessenger: messenger, codec: messagesPigeonMethodCodec)
+ channel.setStreamHandler(internalStreamHandler)
+ }
+}
+
diff --git a/packages/activity_recognition_flutter/lib/activity_recognition_domain.dart b/packages/activity_recognition_flutter/lib/activity_recognition_domain.dart
index db65d7620..acc0d5b75 100644
--- a/packages/activity_recognition_flutter/lib/activity_recognition_domain.dart
+++ b/packages/activity_recognition_flutter/lib/activity_recognition_domain.dart
@@ -1,8 +1,9 @@
part of activity_recognition;
/// The different types of activities which can be detected.
-/// These types is identical to the types detected on Android
-/// and iOS types are mapped to these.
+///
+/// These types are identical to the ones detected on Android; the smaller set
+/// of activities iOS reports is mapped onto them natively.
enum ActivityType {
IN_VEHICLE,
ON_BICYCLE,
@@ -12,62 +13,45 @@ enum ActivityType {
TILTING,
UNKNOWN,
WALKING,
- INVALID // Used for parsing errors
}
-Map _activityMap = {
- // Android
- 'IN_VEHICLE': ActivityType.IN_VEHICLE,
- 'ON_BICYCLE': ActivityType.ON_BICYCLE,
- 'ON_FOOT': ActivityType.ON_FOOT,
- 'RUNNING': ActivityType.RUNNING,
- 'STILL': ActivityType.STILL,
- 'TILTING': ActivityType.TILTING,
- 'UNKNOWN': ActivityType.UNKNOWN,
- 'WALKING': ActivityType.WALKING,
-
- // iOS
- 'automotive': ActivityType.IN_VEHICLE,
- 'cycling': ActivityType.ON_BICYCLE,
- 'running': ActivityType.RUNNING,
- 'stationary': ActivityType.STILL,
- 'unknown': ActivityType.UNKNOWN,
- 'walking': ActivityType.WALKING,
+const Map _activityTypes =
+ {
+ PlatformActivityType.inVehicle: ActivityType.IN_VEHICLE,
+ PlatformActivityType.onBicycle: ActivityType.ON_BICYCLE,
+ PlatformActivityType.onFoot: ActivityType.ON_FOOT,
+ PlatformActivityType.running: ActivityType.RUNNING,
+ PlatformActivityType.still: ActivityType.STILL,
+ PlatformActivityType.tilting: ActivityType.TILTING,
+ PlatformActivityType.unknown: ActivityType.UNKNOWN,
+ PlatformActivityType.walking: ActivityType.WALKING,
};
/// Represents an activity event detected on the phone.
class ActivityEvent {
/// The type of activity.
- ActivityType type;
+ final ActivityType type;
- /// The confidence of the dection in percentage.
- int confidence;
+ /// The confidence of the detection in percent (0-100).
+ final int confidence;
- /// The timestamp when detected.
- late DateTime timeStamp;
+ /// The timestamp of the detection, as reported by the platform.
+ final DateTime timeStamp;
/// The type of activity as a String.
- String get typeString => type.toString().split('.').last;
+ String get typeString => type.name;
- ActivityEvent(this.type, this.confidence) {
- this.timeStamp = DateTime.now();
- }
+ ActivityEvent(this.type, this.confidence, [DateTime? timeStamp])
+ : timeStamp = timeStamp ?? DateTime.now();
factory ActivityEvent.unknown() => ActivityEvent(ActivityType.UNKNOWN, 100);
- /// Create an [ActivityEvent] based on the string format `type,confidence`.
- factory ActivityEvent.fromString(String string) {
- List tokens = string.split(",");
- if (tokens.length < 2) return ActivityEvent.unknown();
-
- ActivityType type = ActivityType.UNKNOWN;
- if (_activityMap.containsKey(tokens.first)) {
- type = _activityMap[tokens.first]!;
- }
- int conf = int.tryParse(tokens.last)!;
-
- return ActivityEvent(type, conf);
- }
+ factory ActivityEvent._fromPlatform(PlatformActivity activity) =>
+ ActivityEvent(
+ _activityTypes[activity.type] ?? ActivityType.UNKNOWN,
+ activity.confidence,
+ DateTime.fromMillisecondsSinceEpoch(activity.timestamp),
+ );
@override
String toString() => 'Activity - type: $typeString, confidence: $confidence%';
diff --git a/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart b/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart
index 4158d59c9..1867aa322 100644
--- a/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart
+++ b/packages/activity_recognition_flutter/lib/activity_recognition_flutter.dart
@@ -1,36 +1,73 @@
library activity_recognition;
import 'dart:async';
-import 'package:flutter/services.dart';
+
+import 'src/messages.g.dart';
part 'activity_recognition_domain.dart';
-/// Main entry to activity recognition API. Use as a singleton like
+/// Main entry to the activity recognition API. Use as a singleton like
///
/// `ActivityRecognition()`
///
class ActivityRecognition {
- static const EventChannel _eventChannel =
- const EventChannel('activity_recognition_flutter');
- Stream? _stream;
- static ActivityRecognition _instance = ActivityRecognition._();
+ static final ActivityRecognition _instance = ActivityRecognition._();
+
ActivityRecognition._();
/// Get the [ActivityRecognition] singleton.
factory ActivityRecognition() => _instance;
+ final ActivityRecognitionHostApi _hostApi = ActivityRecognitionHostApi();
+
+ Stream? _stream;
+
/// Requests continuous [ActivityEvent] updates.
///
- /// The Stream will output the *most probable* [ActivityEvent].
- /// By default the foreground service is enabled, which allows the
- /// updates to be streamed while the app runs in the background.
- /// The programmer can choose to not enable to foreground service.
- Stream activityStream({bool runForegroundService = true}) {
- if (_stream == null) {
- _stream = _eventChannel
- .receiveBroadcastStream({"foreground": runForegroundService}).map(
- (json) => ActivityEvent.fromString(json));
- }
- return _stream!;
+ /// The stream emits the *most probable* [ActivityEvent] detected by the phone.
+ ///
+ /// On Android a foreground service is started by default, which allows the
+ /// updates to keep arriving while the app runs in the background. Pass
+ /// `runForegroundService: false` to opt out. The flag is ignored on iOS,
+ /// where updates are delivered in the background regardless.
+ ///
+ /// The returned stream is a broadcast stream and is built once; later calls
+ /// return the same stream and do not re-apply [runForegroundService].
+ Stream activityStream({bool runForegroundService = true}) =>
+ _stream ??= _createStream(runForegroundService);
+
+ Stream _createStream(bool runForegroundService) {
+ // `streamActivities` opens a new event channel on every call, so it is
+ // invoked exactly once and the resulting stream reused.
+ final Stream events =
+ streamActivities().map(ActivityEvent._fromPlatform);
+
+ late final StreamController controller;
+ StreamSubscription? subscription;
+
+ controller = StreamController.broadcast(
+ onListen: () async {
+ // The platform starts tracking as soon as the event channel is listened
+ // to, so the configuration has to be applied before subscribing.
+ await _hostApi.configure(
+ TrackingConfiguration(runForegroundService: runForegroundService),
+ );
+
+ // The listener may have cancelled while the configuration was in flight.
+ if (!controller.hasListener) return;
+
+ subscription = events.listen(
+ controller.add,
+ onError: controller.addError,
+ onDone: controller.close,
+ );
+ },
+ onCancel: () async {
+ await subscription?.cancel();
+ subscription = null;
+ },
+ );
+
+ return controller.stream;
}
}
diff --git a/packages/activity_recognition_flutter/lib/src/messages.g.dart b/packages/activity_recognition_flutter/lib/src/messages.g.dart
new file mode 100644
index 000000000..1b59bd5d9
--- /dev/null
+++ b/packages/activity_recognition_flutter/lib/src/messages.g.dart
@@ -0,0 +1,320 @@
+// Autogenerated from Pigeon (v27.3.2), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: unused_import, unused_shown_name
+// ignore_for_file: type=lint
+
+import 'dart:async';
+import 'dart:typed_data' show Float64List, Int32List, Int64List;
+
+import 'package:flutter/services.dart';
+import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
+
+Object? _extractReplyValueOrThrow(
+ List