From cf72a99cd94c86074806f738b249f31718e464df Mon Sep 17 00:00:00 2001
From: Alireza Hajebrahimi <6937697+iarata@users.noreply.github.com>
Date: Mon, 31 Aug 2026 15:03:09 +0200
Subject: [PATCH] Upgrade activity recognition plugin to v6.0.0
Migrate Android implementation to Kotlin and typed Pigeon APIs, add
native timestamps, and move iOS integration to Swift Package Manager.
Update platform requirements, manifests, example projects,
documentation, and stream cancellation handling.
---
.../activity_recognition_flutter/CHANGELOG.md | 21 +
.../activity_recognition_flutter/README.md | 52 +-
.../android/build.gradle | 38 --
.../android/build.gradle.kts | 63 +++
.../android/gradle.properties | 3 -
.../gradle/wrapper/gradle-wrapper.properties | 5 -
.../android/settings.gradle | 1 -
.../android/settings.gradle.kts | 1 +
.../android/src/main/AndroidManifest.xml | 19 +-
.../ActivityRecognitionFlutterPlugin.java | 174 -------
.../ActivityRecognizedBroadcastReceiver.java | 14 -
.../ActivityRecognizedService.java | 79 ---
.../ForegroundService.java | 95 ----
.../ActivityRecognitionFlutterPlugin.kt | 136 ++++++
.../ActivityRecognizedBroadcastReceiver.kt | 41 ++
.../DetectedActivityStore.kt | 48 ++
.../ForegroundService.kt | 59 +++
.../Messages.g.kt | 458 ++++++++++++++++++
.../example/android/app/build.gradle | 67 ---
.../example/android/app/build.gradle.kts | 48 ++
.../android/app/src/main/AndroidManifest.xml | 13 +-
.../MainActivity.java | 6 -
.../MainActivity.kt | 5 +
.../example/android/build.gradle | 18 -
.../example/android/build.gradle.kts | 24 +
.../example/android/gradle.properties | 10 +-
.../gradle/wrapper/gradle-wrapper.properties | 2 +-
.../example/android/settings.gradle | 25 -
.../example/android/settings.gradle.kts | 26 +
.../ios/Flutter/AppFrameworkInfo.plist | 2 -
.../example/ios/Flutter/Debug.xcconfig | 1 -
.../example/ios/Flutter/Release.xcconfig | 1 -
.../example/ios/Podfile | 41 --
.../ios/Runner.xcodeproj/project.pbxproj | 109 ++---
.../xcshareddata/xcschemes/Runner.xcscheme | 20 +
.../contents.xcworkspacedata | 3 -
.../example/ios/Runner/AppDelegate.swift | 9 +-
.../example/ios/Runner/Info.plist | 29 +-
.../example/pubspec.yaml | 14 +-
.../ios/Assets/.gitkeep | 0
.../ActivityRecognitionFlutterPlugin.h | 4 -
.../ActivityRecognitionFlutterPlugin.m | 15 -
...wiftActivityRecognitionFlutterPlugin.swift | 73 ---
.../ios/activity_recognition_flutter.podspec | 23 -
.../Package.swift | 36 ++
.../ActivityRecognitionFlutterPlugin.swift | 90 ++++
.../Messages.g.swift | 446 +++++++++++++++++
.../lib/activity_recognition_domain.dart | 70 ++-
.../lib/activity_recognition_flutter.dart | 71 ++-
.../lib/src/messages.g.dart | 320 ++++++++++++
.../pigeons/messages.dart | 90 ++++
.../activity_recognition_flutter/pubspec.yaml | 11 +-
.../activity_recognition_flutter_test.dart | 107 +++-
53 files changed, 2253 insertions(+), 883 deletions(-)
delete mode 100644 packages/activity_recognition_flutter/android/build.gradle
create mode 100644 packages/activity_recognition_flutter/android/build.gradle.kts
delete mode 100644 packages/activity_recognition_flutter/android/gradle.properties
delete mode 100644 packages/activity_recognition_flutter/android/gradle/wrapper/gradle-wrapper.properties
delete mode 100644 packages/activity_recognition_flutter/android/settings.gradle
create mode 100644 packages/activity_recognition_flutter/android/settings.gradle.kts
delete mode 100644 packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.java
delete mode 100644 packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.java
delete mode 100644 packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ActivityRecognizedService.java
delete mode 100644 packages/activity_recognition_flutter/android/src/main/java/dk/cachet/activity_recognition_flutter/ForegroundService.java
create mode 100644 packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.kt
create mode 100644 packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ActivityRecognizedBroadcastReceiver.kt
create mode 100644 packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/DetectedActivityStore.kt
create mode 100644 packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/ForegroundService.kt
create mode 100644 packages/activity_recognition_flutter/android/src/main/kotlin/dk/carp/activity_recognition_flutter/Messages.g.kt
delete mode 100644 packages/activity_recognition_flutter/example/android/app/build.gradle
create mode 100644 packages/activity_recognition_flutter/example/android/app/build.gradle.kts
delete mode 100644 packages/activity_recognition_flutter/example/android/app/src/main/java/dk/cachet/activity_recognition_flutter_example/MainActivity.java
create mode 100644 packages/activity_recognition_flutter/example/android/app/src/main/kotlin/dk/carp/activity_recognition_flutter_example/MainActivity.kt
delete mode 100644 packages/activity_recognition_flutter/example/android/build.gradle
create mode 100644 packages/activity_recognition_flutter/example/android/build.gradle.kts
delete mode 100644 packages/activity_recognition_flutter/example/android/settings.gradle
create mode 100644 packages/activity_recognition_flutter/example/android/settings.gradle.kts
delete mode 100644 packages/activity_recognition_flutter/example/ios/Podfile
delete mode 100644 packages/activity_recognition_flutter/ios/Assets/.gitkeep
delete mode 100644 packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.h
delete mode 100644 packages/activity_recognition_flutter/ios/Classes/ActivityRecognitionFlutterPlugin.m
delete mode 100644 packages/activity_recognition_flutter/ios/Classes/SwiftActivityRecognitionFlutterPlugin.swift
delete mode 100644 packages/activity_recognition_flutter/ios/activity_recognition_flutter.podspec
create mode 100644 packages/activity_recognition_flutter/ios/activity_recognition_flutter/Package.swift
create mode 100644 packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/ActivityRecognitionFlutterPlugin.swift
create mode 100644 packages/activity_recognition_flutter/ios/activity_recognition_flutter/Sources/activity_recognition_flutter/Messages.g.swift
create mode 100644 packages/activity_recognition_flutter/lib/src/messages.g.dart
create mode 100644 packages/activity_recognition_flutter/pigeons/messages.dart
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