diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06e439b..0924736 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,31 @@ jobs: flags: unittests fail_ci_if_error: false + test-android: + runs-on: ubuntu-latest + needs: [lint, test] + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Install JDK + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + distribution: 'zulu' + java-version: '17' + + - name: Finalize Android SDK + run: | + /bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null" + + - name: Run Android unit tests + run: ./gradlew testDebugUnitTest + working-directory: android + build-library: runs-on: ubuntu-latest @@ -129,7 +154,7 @@ jobs: runs-on: macos-latest env: - XCODE_VERSION: 16.4 + XCODE_VERSION: 26.0.1 TURBO_CACHE_DIR: .turbo/ios RCT_USE_RN_DEP: 1 RCT_USE_PREBUILT_RNCORE: 1 diff --git a/BackgroundLocation.podspec b/BackgroundLocation.podspec index ce381c6..5a1718c 100644 --- a/BackgroundLocation.podspec +++ b/BackgroundLocation.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.source_files = "ios/**/*.{h,m,mm,cpp,swift}" s.private_header_files = "ios/**/*.h" - s.frameworks = "CoreLocation", "CoreData" + s.frameworks = "CoreLocation", "CoreData", "CoreMotion" s.resource_bundles = { 'BackgroundLocationPrivacy' => ['ios/PrivacyInfo.xcprivacy'], diff --git a/CHANGELOG.md b/CHANGELOG.md index 54dad53..bd8840d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## [1.1.0] - 2026-06-22 + +### Added + +- **Activity Recognition — Android** (`ActivityProvider.kt`, `ActivityProviderFactory.kt`, `ActivityRecognitionProvider.kt`): Implemented a battery-efficient activity recognition architecture using Google Play Services `ActivityRecognitionClient`. Uses polling-based `requestActivityUpdates` with confidence-based filtering. +- **Activity Recognition — iOS** (`ActivityProvider.swift`): Equivalent `CMMotionActivityManager` wrapper for iOS. Detects `stationary`, `walking`, `running`, `automotive`, and `cycling` states via `CoreMotion` framework. Runs on a dedicated serial queue with confidence-based filtering. +- **`ActivityReceiver.kt`**: Manifest-registered `BroadcastReceiver` that captures activity recognition `PendingIntent` events from Play Services and routes them safely to the `LocationService` singleton via a thread-safe `handleActivityStateChanged` method. +- **Dynamic GPS throttling**: `LocationService.kt` now pauses `requestLocationUpdates` when the device is detected as `STILL` with high confidence (if `pauseLocationWhenStill` is enabled) and resumes when polling indicates movement, cutting GPS battery drain to near-zero while stationary. +- **iOS dynamic GPS throttling**: `LocationManagerWrapper.swift` implements the same pause/resume logic driven by `ActivityProvider`'s `onActivityStateChanged` delegate callback. +- **Three new `TrackingOptions` fields** (Android, iOS, TypeScript — all platforms): + - `activityTrackingEnabled: boolean` — opt-in to activity recognition (default: `false`). + - `pauseLocationWhenStill: boolean` — pause GPS when device is stationary (requires `activityTrackingEnabled`, default: `false`). + - `activityUpdateInterval: number` — polling interval in milliseconds for Android `requestActivityUpdates` (default: `60000`). +- **TurboModule Codegen spec** (`src/NativeBackgroundLocation.ts`): Added the three new fields to `TrackingOptionsSpec` so Codegen generates the correct C++ struct accessors on both platforms. +- **TypeScript types** (`src/types/tracking.ts`): Full JSDoc-annotated `TrackingOptions` fields with platform tags. +- **TS options mapper** (`src/utils/trackingOptionsMapper.ts`): `toTrackingOptionsSpec()` now forwards the three new fields across the TurboModule bridge. +- **`BackgroundLocation.mm`** (iOS Objective-C++ bridge): `transportDictFromCodegenSpec:` maps `activityTrackingEnabled`, `pauseLocationWhenStill`, and `activityUpdateInterval` from the C++ struct into the `NSDictionary` delivered to Swift. +- **`CoreMotion` framework** added to `BackgroundLocation.podspec` (`s.frameworks`): CocoaPods now auto-links `CoreMotion` for consumers without any manual Xcode project changes. +- **Manifest permissions** (`android/src/main/AndroidManifest.xml`): Added `android.permission.ACTIVITY_RECOGNITION` (API 29+) and `com.google.android.gms.permission.ACTIVITY_RECOGNITION` (legacy Play Services). +- **Example app permissions** (`example/android/app/src/main/AndroidManifest.xml`): Mirrored same permissions for the bundled demo application. +- **Example app `Info.plist`** (`example/ios/BackgroundLocationExample/Info.plist`): Added `NSMotionUsageDescription` so the demo app can request CoreMotion access without crashing. +- **Android unit tests**: + - `ActivityRecognitionProviderTest.kt` — MockK-based tests validating client registration, `PendingIntent` delivery, and `cleanup()` resource release. + - `TrackingOptionsTest.kt` — Validates all new option fields parse correctly with expected defaults and custom values. + +### Changed + +- `LocationService.kt`: Integrated `ActivityProvider` lifecycle (start on `onStartCommand`, cleanup on `onDestroy`). Added `instanceLock`-guarded `handleActivityStateChanged` for safe cross-thread state transitions. +- `LocationManagerWrapper.swift`: Conforms to `ActivityProviderDelegate`; mounts/unmounts `ActivityProvider` alongside the `CLLocationManager` session. +- `BackgroundLocationModule.kt`: Parses `activityTrackingEnabled`, `pauseLocationWhenStill`, and `activityUpdateInterval` from the incoming `ReadableMap`. +- `TrackingOptions.kt` / `TrackingOptions.swift`: Extended with new fields, safe defaults, and computed boolean accessors (`isActivityTrackingEnabled`, `shouldPauseLocationWhenStill`). + +### Notes + +- **Non-breaking release.** All new `TrackingOptions` fields are optional and default to `false`/`0`, so existing call sites compile and behave identically without changes. +- **No new Android dependency required.** `ActivityRecognitionClient` is included in the existing `com.google.android.gms:play-services-location:21.3.0` dependency. +- **iOS requires `NSMotionUsageDescription`** in the host app's `Info.plist` when `activityTrackingEnabled: true` is used. Omitting it causes a runtime crash on iOS. +- Runtime permission for `ACTIVITY_RECOGNITION` must be requested on Android 10+ (API 29) before enabling activity tracking. The library manifest declares the permission; the host app is responsible for requesting it at runtime. + ## [1.0.0-rc] - 2026-05-27 > **First release candidate for the 1.x line.** From `1.0.0` forward, the library follows strict semver: breaking changes ship only on a major version bump. Three surfaces are explicitly frozen for the 1.x line: (1) the public TypeScript surface (named exports from `src/index.tsx`), (2) the TurboModule Codegen spec (`src/NativeBackgroundLocation.ts`), and (3) the native event names emitted via `NativeEventEmitter` (`onLocationUpdate`, `onLocationError`, `onLocationWarning`, `onNotificationAction`, `onGeofenceTransition`). No native (Android/iOS) code changes and no public TypeScript API changes since `0.17.0` this release candidate exists to declare API stability, not to introduce behavior. diff --git a/README.md b/README.md index cca0461..b5d97d3 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ A TurboModule for the React Native New Architecture. Drives a foreground service - Native geofencing (GeofencingClient on Android, CLCircularRegion on iOS) - Persistent location storage (Room on Android, Core Data on iOS) - Crash recovery via WorkManager and significant location monitoring +- **Battery-efficient Activity Recognition** — pauses GPS when device is `STILL` (high confidence), resumes on motion (Android: Play Services `ActivityRecognitionClient`; iOS: `CoreMotion CMMotionActivityManager`) - React hooks: `useBackgroundLocation`, `useLocationPermissions`, `useLocationUpdates`, `useLocationTracking` - Expo config plugin for managed workflows @@ -49,6 +50,22 @@ cd ios && pod install Autolinking handles Android manifest merging and iOS pod registration. Bare iOS apps must still add `NSLocationWhenInUseUsageDescription`, `NSLocationAlwaysAndWhenInUseUsageDescription`, `NSLocationAlwaysUsageDescription`, and a `UIBackgroundModes` entry containing `location` to their `Info.plist`. See the [iOS setup guide](https://gabriel-sisjr.github.io/react-native-background-location/docs/getting-started/ios-setup) for full details. +> **Activity Recognition on iOS:** If you enable `activityTrackingEnabled: true`, you must also add `NSMotionUsageDescription` to your `Info.plist`. Without it, activity tracking will gracefully fall back to standard GPS (no battery optimization) instead of crashing. +> +> ```xml +> NSMotionUsageDescription +> This app requires motion data to optimize location tracking battery usage. +> ``` +> +> **App Store disclosure:** Apps using CoreMotion must disclose motion data collection in their App Store privacy nutrition labels. This library does not persist motion data — it is processed in-memory only — but the consuming app is responsible for accurate privacy disclosure. + +> **Activity Recognition on Android:** On Android 10+ (API 29), you must add `android.permission.ACTIVITY_RECOGNITION` to your app's `AndroidManifest.xml` and request it at runtime before enabling activity tracking: +> ```xml +> +> ``` + +> **Note:** On iOS, activity tracking state is not persisted across crash recovery. If the app is killed and restarted by the system, activity-based GPS pausing will be re-enabled only if `activityTrackingEnabled: true` is passed again. On Android, this state is persisted in Room and survives crash recovery automatically. + ## Quick Start ```tsx @@ -63,7 +80,11 @@ function App() { return (