├── .circleci └── config.yml ├── .github └── stale.yml ├── .gitignore ├── .gitmodules ├── .npmignore ├── @mauron85_react-native-background-geolocation.podspec ├── CHANGES.md ├── CHANGES_zh-Hans.md ├── CONTRIBUTORS.md ├── DISTANCE_FILTER_PROVIDER.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── PROVIDERS.md ├── README.md ├── android ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── lib │ ├── build.gradle │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── marianhello │ │ │ └── bgloc │ │ │ └── react │ │ │ └── ConfigMapperTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ ├── iodine │ │ │ │ └── start │ │ │ │ │ ├── ArrayUtil.java │ │ │ │ │ └── MapUtil.java │ │ │ │ └── marianhello │ │ │ │ └── bgloc │ │ │ │ └── react │ │ │ │ ├── BackgroundGeolocationModule.java │ │ │ │ ├── BackgroundGeolocationPackage.java │ │ │ │ ├── ConfigMapper.java │ │ │ │ ├── data │ │ │ │ └── LocationMapper.java │ │ │ │ └── headless │ │ │ │ ├── HeadlessService.java │ │ │ │ └── HeadlessTaskRunner.java │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values │ │ │ └── strings.xml │ │ │ └── xml │ │ │ ├── authenticator.xml │ │ │ └── syncadapter.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── marianhello │ │ └── bgloc │ │ └── react │ │ └── ConfigMapperTest.java └── settings.gradle ├── index.d.ts ├── index.js ├── ios ├── RCTBackgroundGeolocation.xcodeproj │ └── project.pbxproj ├── RCTBackgroundGeolocation │ ├── RCTBackgroundGeolocation.h │ └── RCTBackgroundGeolocation.m ├── README.md ├── npm_download.sh └── pkgversion.js ├── issuehunt-shield-v1.svg ├── package.json ├── react-native.config.js ├── res └── dummy.apk └── scripts ├── config.js ├── google_cloud.sh ├── isInstalled.js ├── npm_deprecate.sh ├── postlink.js └── postunlink.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Android Gradle CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-android/ for more details 4 | # https://github.com/flyve-mdm/android-mdm-agent/blob/develop/.circleci/config.yml 5 | 6 | version: 2 7 | reference: 8 | 9 | ## Workspaces 10 | project_dir: &project_dir 11 | ~/mauron85_bgloc 12 | 13 | android_config: &android_config 14 | working_directory: ~/mauron85_bgloc/android 15 | docker: 16 | # specify the version you desire here 17 | - image: circleci/android:api-28-alpha 18 | # Specify service dependencies here if necessary 19 | # CircleCI maintains a library of pre-built images 20 | # documented at https://circleci.com/docs/2.0/circleci-images/ 21 | # - image: circleci/postgres:9.4 22 | environment: 23 | # Customize the JVM maximum heap limit 24 | JVM_OPTS: -Xmx3200m 25 | TERM: dumb 26 | 27 | project_checkout: &project_checkout 28 | checkout: 29 | path: *project_dir 30 | 31 | submodule_checkout: &submodule_checkout 32 | run: 33 | name: Checkout git submodules 34 | command: git submodule init && git submodule update 35 | 36 | gradle_key: &gradle_key 37 | jars-{{ checksum "build.gradle" }}-{{ checksum "lib/build.gradle" }} 38 | 39 | restore_gradle_cache: &restore_gradle_cache 40 | restore_cache: 41 | key: *gradle_key 42 | 43 | save_gradle_cache: &save_gradle_cache 44 | save_cache: 45 | key: *gradle_key 46 | paths: 47 | - ~/.gradle 48 | - ~/.m2 49 | 50 | android_dependencies: &android_dependencies 51 | run: 52 | name: Download Android Dependencies 53 | command: ./gradlew androidDependencies 54 | 55 | jobs: 56 | # unit test 57 | build: 58 | <<: *android_config 59 | steps: 60 | - *project_checkout 61 | - *submodule_checkout 62 | - *restore_gradle_cache 63 | - *android_dependencies 64 | - *save_gradle_cache 65 | - run: 66 | name: Run Build 67 | command: ./gradlew build -x lint -x test 68 | # - store_artifacts: 69 | # path: build/reports 70 | # destination: reports 71 | # - store_test_results: 72 | # path: build/test-results 73 | # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples 74 | 75 | # Test Instrumentation with Android 4.1 JELLY_BEAN API 16 76 | test_instrumentation: 77 | <<: *android_config 78 | steps: 79 | - *project_checkout 80 | - *submodule_checkout 81 | - *restore_gradle_cache 82 | - *android_dependencies 83 | - *save_gradle_cache 84 | - run: 85 | name: Create debug apk 86 | command: ./gradlew assembleDebug assembleAndroidTest 87 | - run: 88 | name: Run Tests on Firebase test lab 89 | command: source ../scripts/google_cloud.sh 90 | 91 | workflows: 92 | version: 2 93 | build_and_test: 94 | jobs: 95 | - build 96 | - test_instrumentation: 97 | requires: 98 | - build 99 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 180 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: stale 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: > 18 | This issue has been automatically closed, because it has not had 19 | recent activity. If you believe this issue shouldn't be closed, please 20 | reopen or write down a comment requesting issue reopening with explanation 21 | why you think it's important. Thank you for your contributions. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | 5 | # Xcode 6 | xcuserdata 7 | project.xcworkspace 8 | ios/react-native 9 | 10 | # Android/IJ 11 | .idea 12 | .gradle 13 | local.properties 14 | *.iml 15 | build 16 | *.skip 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ios-common"] 2 | path = ios/common 3 | url = https://github.com/mauron85/background-geolocation-ios.git 4 | [submodule "android-common"] 5 | path = android/common 6 | url = https://github.com/mauron85/background-geolocation-android.git 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github 2 | .DS_Store 3 | node_modules 4 | npm-debug.log 5 | 6 | # Xcode 7 | xcuserdata 8 | project.xcworkspace 9 | ios/react-native 10 | 11 | # Android/IJ 12 | .idea 13 | .gradle 14 | local.properties 15 | *.iml 16 | build 17 | *.skip 18 | 19 | # Git modules 20 | .gitmodules 21 | .git 22 | 23 | # CircleCI 24 | .circleci 25 | res/ 26 | -------------------------------------------------------------------------------- /@mauron85_react-native-background-geolocation.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "@mauron85_react-native-background-geolocation" 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | 11 | s.authors = package['author'] 12 | s.homepage = package['homepage'] 13 | s.platform = :ios, "9.0" 14 | 15 | s.source = { :path => "ios" } 16 | s.source_files = "ios/**/*.{h,m}" 17 | s.exclude_files = "ios/common/BackgroundGeolocationTests/*.{h,m}" 18 | 19 | s.dependency 'React' 20 | end 21 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### [0.6.3] - 2019-09-16 4 | ### Fixed 5 | - Android fix RejectedExecutionException (fixes #319 #259 #243 #149 #68) 6 | - Android add stop guard 7 | 8 | ### [0.6.2] - 2019-09-10 9 | ### Changed 10 | - Android remove preoreo target 11 | - Android add android.permission.FOREGROUND_SERVICE permission 12 | 13 | ### [0.6.1] - 2019-08-29 14 | ### Changed 15 | - Remove deprecated isLocationEnabled method 16 | - Android use react-native headless JS instead of jsevaluator 17 | 18 | ### [0.6.0] - 2019-08-27 19 | ### Fixed 20 | - Android fix conflicting provider (fixes #344) 21 | 22 | ### Changed 23 | - Android autolinking for RN 0.60 24 | 25 | ### [0.5.6] - 2019-08-27 26 | ### Fixed 27 | - Android allow to start service from background on API >=26 (fixes #356) 28 | 29 | ### [0.5.5] - 2019-08-13 30 | ### Fixed 31 | - Android fix tone generator crash 32 | - Android Removed minsdk from manifest (fixes #357) - @maleriepace 33 | - Android add additional check for applicationId (PR #36 common repo) - @mysport12 34 | - Android minSdk version should not be declared on manifest - @wesleycoder and @maleriepace 35 | - Android Change the react-native link command repositories (PR #374) - @mantaroh 36 | - Update CHANGES_zh-Hans.md - @Kennytian 37 | - Fixed typo in README - @diegogurpegui 38 | 39 | Many thanks to all contributors 40 | 41 | ### [0.5.2] - 2019-03-28 42 | ### Fixed 43 | - Android fix don't start service on app visibility change events fixes 44 | - Android ignore failing instrumentation tests 45 | 46 | ### [0.5.1] - 2019-03-25 47 | ### Fixed 48 | - Android fix #360 - When app crashes for other reasons the service is started by the system 49 | 50 | ### [0.5.0] - 2019-01-31 51 | 52 | ### Added 53 | - ios implement config.stopOnTerminate using startMonitoringSignificantLocationChanges 54 | 55 | Commit: [5149178c65322d04f4e9e47bd278b17cf0e4bd9a](https://github.com/mauron85/background-geolocation-ios/commit/5149178c65322d04f4e9e47bd278b17cf0e4bd9a) 56 | Origin-PR: [#7](https://github.com/mauron85/background-geolocation-ios/pull/7) 57 | Contributed-By: [@StanislavMayorov](https://github.com/StanislavMayorov) 58 | 59 | ### Fixed 60 | - Android - cannot find symbol Assert.assertNotNull 61 | 62 | Commit: [ec334ba6a8612c399d608bbfc4aacfad68fc2105](https://github.com/mauron85/background-geolocation-android/commit/ec334ba6a8612c399d608bbfc4aacfad68fc2105) 63 | Origin-PR: [#25](https://github.com/mauron85/background-geolocation-android/pull/25) 64 | Origin-Issue: [#340](https://github.com/mauron85/react-native-background-geolocation/issues/340) 65 | Contributed-By: [@scurtoni](https://github.com/scurtoni) 66 | 67 | ### [0.5.0-alpha.XY] - unreleased 68 | 69 | This release brings abstractions, allowing code reuse 70 | between ReactNative Cordova plugin variants. 71 | As result enabling faster pace of development and 72 | bug fixing on shared codebase. 73 | 74 | ### Added 75 | - post/sync attributes customization via `postTemplate` config prop 76 | - iOS ACTIVITY_PROVIDER (experimental) 77 | - enable partial plugin reconfiguration 78 | - on "activity" changed event 79 | - Android Use gradle to choose authority (PR #136) by @jsdario 80 | - iOS configuration persistence 81 | 82 | Since alpha.8: 83 | - Android automatic linking with react-native link 84 | - iOS checkStatus returns status of location services (locationServicesEnabled) 85 | - iOS RAW_LOCATION_PROVIDER continue to run on app terminate 86 | 87 | Since alpha.10: 88 | - Android checkStatus returns status of location services (locationServicesEnabled) 89 | 90 | Since alpha.15: 91 | - Android location parameters isFromMockProvider, mockLocationsEnabled, radius, provider 92 | - Android Headless Task 93 | 94 | Since alpha.16: 95 | - iOS add background modes and permissions on postlink 96 | - add crossplatform prepublish script execution (PR #165) by @dobrynia 97 | 98 | Since alpha.17: 99 | - Android allow to override version of libraries with ext declaration 100 | 101 | Since alpha.19: 102 | - Android Oreo experimental support 103 | 104 | Since alpha.20: 105 | - option to get logs by offset and filter by log level 106 | - log uncaught exceptions 107 | 108 | Since alpha.22: 109 | - method forceSync 110 | 111 | Since alpha.26: 112 | - Android add httpHeaders validation 113 | 114 | Since alpha.28: 115 | - implement getCurrentLocation 116 | - iOS implement getStationaryLocation 117 | 118 | Since alpha.31: 119 | - Android Gradle3 support (experimental) 120 | 121 | Since alpha.37: 122 | - Transforming/filtering locations in native code (by [@danielgindi](https://github.com/danielgindi/)) 123 | More info: https://github.com/mauron85/background-geolocation-android/pull/8 124 | 125 | Since alpha.40: 126 | - notificationsEnabled config option (by [@danielgindi](https://github.com/danielgindi/)) 127 | More info: https://github.com/mauron85/react-native-background-geolocation/pull/269 128 | - Allow stopping location updates on status "285 Updates Not Required" (by [@danielgindi](https://github.com/danielgindi/)) 129 | More info: https://github.com/mauron85/react-native-background-geolocation/pull/271 130 | 131 | Since alpha.43: 132 | - Listen for 401 Unauthorized status codes received from http server (by [@FeNoMeNa](https://github.com/FeNoMeNa/)) 133 | More info: https://github.com/mauron85/react-native-background-geolocation/pull/308/files 134 | 135 | Since alpha.44: 136 | - typescript definitions (index.d.ts) 137 | - Android allow override minSdkVersion 138 | 139 | Since alpha.45: 140 | - allow nested location props in postTemplate 141 | 142 | Since alpha.47: 143 | - Android make sync account name configurable 144 | in "android/app/src/main/res/values/strings.xml" 145 | ``` 146 | Sync Locations 147 | ``` 148 | 149 | ### Changed 150 | 151 | Since alpha.6: 152 | - iOS saveBatteryOnBackground defaults to false 153 | 154 | Since alpha.8: 155 | - shared code base with Cordova 156 | 157 | Since alpha.11: 158 | - Android derive sync authority and provider from applicationId 159 | - Android remove android.permission.GET_ACCOUNTS 160 | 161 | Since alpha.19: 162 | - Android postlink register project in settings.gradle instead of file copying 163 | (BREAKING CHANGE - read android-setup section) 164 | 165 | Since alpha.20: 166 | - iOS use Android log format (BREAKING CHANGE) 167 | 168 | Since alpha.22: 169 | - Android remove sync delay when conditions are met 170 | - Android consider HTTP 201 response code as succesful post 171 | - Android obey system sync setting 172 | 173 | Since alpha.26: 174 | - Android show service notification only when in background 175 | - Android remove config option startForeground (related to above) 176 | - Android remove wake locks from both Android providers (by @grassick) 177 | - Android remove restriction on postTemplate string only values 178 | 179 | Since alpha.28: 180 | - Android bring back startForeground config option (BREAKING CHANGE!) 181 | 182 | startForeground has slightly different meaning. 183 | 184 | If false (default) then service will create notification and promotes 185 | itself to foreground service, when client unbinds from service. 186 | This typically happens when application is moving to background. 187 | If app is moving back to foreground (becoming visible to user) 188 | service destroys notification and also stop being foreground service. 189 | 190 | If true service will create notification and will stay in foreground at all times. 191 | 192 | Since alpha.30: 193 | - Android internal changes (permission handling) 194 | - Android gradle build changes 195 | 196 | Since alpha.38: 197 | - Android disable notification sound and vibration on oreo 198 | (PR: [#9](https://github.com/mauron85/background-geolocation-android/pull/9) 199 | by [@danielgindi](https://github.com/danielgindi/), 200 | Closes #260) 201 | 202 | Since alpha.48: 203 | - removeAllListeners - remove all event listeners when calling without parameter 204 | 205 | ### Fixed 206 | 207 | Since alpha.4: 208 | - iOS open location settings on iOS 10 and later (PR #158) by @asafron 209 | 210 | Since alpha.8: 211 | - checkStatus authorization 212 | - Android fix for Build Failed: cannot find symbol 213 | 214 | Since alpha.9: 215 | - Android fix #118 - NullPointerException LocationService.onTaskRemoved 216 | - Android permission - check and request permissions in runtime 217 | 218 | Since alpha.13: 219 | - Android fix allowBackup attribute conflict 220 | 221 | Since alpha.14: 222 | - Android fix #166 - Error: more than one library with package name 223 | 'com.google.android.gms.license' 224 | 225 | Since alpha.15: 226 | - Android only pass valid location parameters 227 | - iOS reset connectivity status on stop 228 | - iOS fix App Store Rejection - Prefs Non-Public URL Scheme 229 | 230 | Since alpha.17: 231 | - Android fix service accidently started with default or stored config 232 | 233 | Since alpha.21: 234 | - Android uninstall common module on postunlink 235 | - Android prevent multiple registration of common project 236 | - Android fix some nullpointer exceptions 92649c70e0ce0072464f47f1d096bef40047b8a6 237 | - iOS update plist on changes only 238 | 239 | Since alpha.22: 240 | - Android add guards to prevent some race conditions 241 | - Android config null handling 242 | 243 | Since alpha.25: 244 | - Android issue #185 - handle invalid configuration 245 | 246 | Since alpha.27: 247 | - iOS fix forceSync params 248 | - fix #183 - Error when adding 'activity' event listener 249 | 250 | Since alpha.28: 251 | - iOS display debug notifications in foreground on iOS >= 10 252 | - iOS fix error message format 253 | - iOS activity provider stationary event 254 | 255 | Since alpha.35: 256 | - Android getCurrentLocation runs on background thread (PR #219 by [@djereg](https://github.com/djereg/)) 257 | - iOS Fix crash on delete all location ([7392e39](https://github.com/mauron85/background-geolocation-ios/commit/7392e391c3de3ff0d6f5ef2ef19c34aba612bf9b) by [@acerbetti](https://github.com/acerbetti/)) 258 | 259 | Since alpha.36: 260 | - Android Defer start and configure until service is ready 261 | (PR: [#7](https://github.com/mauron85/background-geolocation-android/pull/7) 262 | Commit: [00e1314](https://github.com/mauron85/background-geolocation-android/commit/00e131478ad4e37576eb85581bb663b65302a4e0) by [@danielgindi](https://github.com/danielgindi/), 263 | fixes #201, #181, #172) 264 | 265 | Since alpha.38: 266 | - iOS Avoid taking control of UNUserNotificationCenter 267 | (PR: [#268](https://github.com/mauron85/react-native-background-geolocation/pull/268) 268 | by [@danielgindi](https://github.com/danielgindi/), 269 | fixes #206, #256) 270 | 271 | Since alpha.41: 272 | - Android fix locationService treating success as errors 273 | (PR: [#13](https://github.com/mauron85/background-geolocation-android/pull/13) 274 | by [@hoisel](https://github.com/hoisel/)) 275 | 276 | Since alpha.42: 277 | - Android make sure mService exists when we call start or stop 278 | (PR: [#17](https://github.com/mauron85/background-geolocation-android/pull/17) 279 | by [@ivosabev](https://github.com/ivosabev/), 280 | fixes #257, #280) 281 | 282 | Since alpha.44: 283 | - Android automatically use gradle4 template when gradle >= 4.4 is detected 284 | (no need for gradle3EXPERIMENTAL) 285 | - Android upgrade default libraries to version compatible with RN57 286 | - Android fix gradle4 compile, testCompile deprecated warnings 287 | - Android fix service crash on boot for Android 8 when startOnBoot option is used 288 | 289 | Since alpha.46: 290 | - Android use global location content provider (in attempt to fix db locking issue) 291 | - fix type definition 292 | 293 | Since alpha.48: 294 | - Android prefix content_authority to prevent collision with other plugins (fixes #320) 295 | 296 | Since alpha.49: 297 | - Android fix App Crashes when entering / leaving Background (fixes #319) 298 | 299 | Since alpha.50: 300 | - Android fix service checkStatus isStarted 301 | - Android fix crash on permission when started from background 302 | 303 | ### [0.4.1] - 2017-12-19 304 | #### Changed 305 | - react native peer dependency >0.49.0 306 | 307 | ### [0.4.0] - 2017-12-13 308 | release 309 | 310 | ### [0.4.0-rc.3] - 2017-11-23 311 | ### Added 312 | - iOS send http headers on background sync 313 | 314 | ### [0.4.0-rc.2] - 2017-11-13 315 | 316 | ### Fixed 317 | - Android ConfigMapper mapToConfig missing config props (fixes #122) 318 | 319 | ### Added 320 | - Android return location id for getLocations 321 | 322 | ### [0.4.0-rc.1] - 2017-11-10 323 | 324 | ### Fixed 325 | - iOS fix crash when calling getConfig before configure 326 | 327 | #### Added 328 | - checkStatus if service is running 329 | - events [start, stop, authorization, background, foreground] 330 | - implement all methods for both platforms 331 | - new RAW_LOCATION_PROVIDER 332 | 333 | #### Changed 334 | 335 | - start and stop methods doesn't accept callback (use event listeners instead) 336 | - for background syncing syncUrl option is required 337 | - on Android DISTANCE_FILTER_PROVIDER now accept arbitrary values (before only 10, 100, 1000) 338 | - all plugin constants are in directly BackgroundGeolocation namespace. (check index.js) 339 | - plugin can be started without executing configure (stored settings or defaults will be used) 340 | - location property locationId renamed to just id 341 | - iOS pauseLocationUpdates now default to false (becuase iOS docs now states that you need to restart manually if you set it to true) 342 | - iOS finish method replaced with startTask and endTask 343 | 344 | ### [0.3.3] - 2017-11-01 345 | #### Fixed 346 | - Android location sync should also be completed on 201 status code (PR #71) 347 | 348 | ### [0.3.2] - 2017-11-01 349 | #### Fixed 350 | - iOS implementation for isLocationEnabled (PR #92) 351 | 352 | ### [0.3.1] - 2017-10-04 353 | #### Fixed 354 | - (tpisto) iOS compile error in React Native 0.48.x (fixes #108) 355 | 356 | ### [0.3.0-alpha.1] - 2017-08-15 357 | #### Fixed 358 | - RN 0.47 compatibility (fixes #95) 359 | 360 | ### [0.2.0-alpha.7] - 2017-03-21 361 | #### Fixed 362 | - iOS fixing build issue #44 363 | 364 | ### [0.2.0-alpha.6] - 2017-02-18 365 | #### Fixed 366 | - iOS RN 0.40 compatibility 367 | 368 | ### [0.2.0-alpha.5] - 2016-09-15 369 | #### Fixed 370 | - Android fix issue #10 371 | 372 | ### [0.2.0-alpha.4] - 2016-09-10 373 | #### Fixed 374 | - Android fix crash on destroy when plugin was not configured 375 | 376 | ### [0.2.0-alpha.3] - 2016-09-07 377 | #### Fixed 378 | - fix Android issue #10 - crash on refresh 379 | 380 | #### Added 381 | - Android onStationary 382 | - Android getLogEntries 383 | 384 | #### Removed 385 | - Android location filtering 386 | 387 | #### Changed 388 | - Android project directory structure 389 | (please read updated install instructions) 390 | - Android db logging instead of file 391 | 392 | ### [0.2.0-alpha.2] - 2016-08-31 393 | #### Fixed 394 | - fix config not persisted 395 | - tmp fix Android time long to int conversion 396 | 397 | #### Added 398 | - Android isLocationEnabled 399 | - Android showAppSettings 400 | - Android showLocationSettings 401 | - Android getLocations 402 | - Android getConfig 403 | 404 | ### [0.2.0-alpha.1] - 2016-08-17 405 | #### Changed 406 | - upgrading plugin to match cordova 2.2.0-alpha.6 407 | 408 | ### [0.1.1] - 2016-06-08 409 | #### Fixed 410 | - fix iOS crash on stop 411 | 412 | ### [0.1.0] - 2016-06-07 413 | #### Added 414 | - initial iOS implementation 415 | 416 | ### [0.0.1] - 2016-06-04 417 | - initial Android implementation 418 | -------------------------------------------------------------------------------- /CHANGES_zh-Hans.md: -------------------------------------------------------------------------------- 1 | ## 变更日志 2 | 3 | ### [0.6.2] - 2019-09-10 4 | ### 变量 5 | - Android 端移除 preoreo 目标 6 | - Android 端添加 android.permission.FOREGROUND_SERVICE 权限 7 | 8 | ### [0.6.1] - 2019-08-29 9 | ### 变更 10 | - 移除过时的 isLocationEnabled 方法 11 | - Android 端用 react-native headless js 替换 jsevaluator 12 | 13 | ### [0.6.0] - 2019-08-27 14 | ### 修复 15 | - 修复 Android 端提供者(provider)冲突 (fixes #344) 16 | 17 | ### 变更 18 | - 支持 RN 0.60 的自动链接(autolinking) 19 | 20 | ### [0.5.6] - 2019-08-27 21 | ### 修复 22 | - Android API >= 26 允许从后台开启服务 (fixes #356) 23 | 24 | ### [0.5.5] - 2019-08-13 25 | ### 修复 26 | - Android 修复 ToneGenerator 闪退问题 27 | - Android 从 manifest 里删除 minSdk (fixes #357) - @maleriepace 28 | - Android 为 applicationId 添加选项检查 (PR #36 common repo) - @mysport12 29 | - Android 不会在 manifest 里声明 minSdk 版本了 @wesleycoder 和 @maleriepace 30 | - Android 改变 react-native link command 仓库地址 (PR #374) - @mantaroh 31 | - 更新 CHANGES_zh-Hans.md 翻译文档 - @Kennytian 32 | - 修正 README 里的错别字 - @diegogurpegui 33 | - 感谢所有的贡献者 34 | 35 | ### [0.5.2] - 2019-03-28 36 | ### 修复 37 | - Android 修复程序无法启动 APP VisibilityChange 事件缺陷 38 | - Android 忽略失败的 instrumentation 测试项 39 | 40 | ### [0.5.1] - 2019-03-25 41 | ### 修复 42 | - Android 修复 #360 - 当应用因其他原因崩溃时,系统会启动该服务 43 | 44 | ### [0.5.0] - 2019-01-31 45 | 46 | ### 新增 47 | - 用 startMonitoringSignificantLocationChanges 实现 iOS config.stopOnTerminate 功能 48 | 49 | Commit: [5149178c65322d04f4e9e47bd278b17cf0e4bd9a](https://github.com/mauron85/background-geolocation-ios/commit/5149178c65322d04f4e9e47bd278b17cf0e4bd9a) 50 | Origin-PR: [#7](https://github.com/mauron85/background-geolocation-ios/pull/7) 51 | Contributed-By: [@StanislavMayorov](https://github.com/StanislavMayorov) 52 | 53 | ### 修复 54 | - Android - 无法找到 Assert.assertNotNull 标识 55 | 56 | Commit: [ec334ba6a8612c399d608bbfc4aacfad68fc2105](https://github.com/mauron85/background-geolocation-android/commit/ec334ba6a8612c399d608bbfc4aacfad68fc2105) 57 | Origin-PR: [#25](https://github.com/mauron85/background-geolocation-android/pull/25) 58 | Origin-Issue: [#340](https://github.com/mauron85/react-native-background-geolocation/issues/340) 59 | Contributed-By: [@scurtoni](https://github.com/scurtoni) 60 | 61 | ### [0.5.0-alpha.XY] - 未发布 62 | 63 | 此版本提供了抽象代码以复用 React Native Cordova 插件之间变量,从而加快开发速度修复共享代码库的错误。 64 | 65 | ### 新增 66 | - 通过 `postTemplate` 配置属性来提交/同步 67 | - iOS ACTIVITY_PROVIDER(实验性) 68 | - 允许部分插件复用配置 69 | - 在 'activity' 上改变事件 70 | - Android 使用 gradle 选择权限 由 @jsdario(PR#136) 71 | - iOS 配置持久化 72 | 73 | 从 alpha.8 开始: 74 | - Android 自动 link react-native 配置 75 | - iOS checkStatus 返回位置服务的状态(locationServicesEnabled) 76 | - iOS RAW_LOCATION_PROVIDER 持续在应用终止时运行 77 | 78 | 从 alpha.10 开始: 79 | - Android checkStatus 返回位置服务的状态(locationServicesEnabled) 80 | 81 | 从 alpha.15 开始: 82 | - Android 位置参数 isFromMockProvider,mockLocationsEnabled,radius,provider 83 | - Android 无头任务 84 | 85 | 自 alpha.16 开始: 86 | - iOS 在 postlink 上添加后台模式和权限 87 | - 添加跨平台预发布脚本执行 由 @dobrynia 提供(PR#165) 88 | 89 | 从 alpha.17 开始: 90 | - Android 允许使用 ext 声明覆盖库的版本 91 | 92 | 自alpha.19 开始: 93 | - Android Oreo 实验支持 94 | 95 | 自 alpha.20 开始: 96 | - 按日期级别获取日志并按日志级别过滤的选项 97 | - 记录未捕获的异常 98 | 99 | 从 alpha.22 开始: 100 | - 方法 forceSync 101 | 102 | 从 alpha.26 开始: 103 | - Android 添加 httpHeaders 验证 104 | 105 | 从 alpha.28 开始: 106 | - 实现 getCurrentLocation 107 | - iOS 实现 getStationaryLocation 108 | 109 | 从 alpha.31 开始: 110 | - Android Gradle 3 支持(实验性) 111 | 112 | 从 alpha.37 开始: 113 | - 在原生代码中转换/过滤位置(由[@danielgindi](https://github.com/danielgindi/)) 114 | 更多信息:https://github.com/mauron85/background-geolocation-android/pull/8 115 | 116 | 从 alpha.40 开始: 117 | - notificationsEnabled 配置选项(由[@danielgindi](https://github.com/danielgindi/)) 118 | 更多信息:https://github.com/mauron85/react-native-background-geolocation/pull/269 119 | -允许在停止状态「285 Updates Not Required」更新位置(由[@danielgindi](https://github.com/danielgindi/)) 120 | 更多信息:https://github.com/mauron85/react-native-background-geolocation/pull/271 121 | 122 | ### 变更 123 | 124 | 从 alpha.6 开始: 125 | - iOS saveBatteryOnBackground 默认为 false 126 | 127 | 从 alpha.8 开始: 128 | - 与 Cordova 共享代码库 129 | 130 | 从 alpha.11 开始: 131 | - Android 从 applicationId 派生同步权限和提供者 132 | - Android 删除 android.permission.GET_ACCOUNTS 133 | 134 | 自 alpha.19 开始: 135 | - 在 settings.gradle 中的 Android postlink 注册项目,而不是文件复制(**重大变更** - 阅读 android-setup 部分) 136 | 137 | 自 alpha.20 开始: 138 | - iOS使用Android日志格式(**重大变更**) 139 | 140 | 自 alpha.22 开始: 141 | - 满足条件时 Android 删除同步延迟 142 | - Android 认为 HTTP 201 是成功的请求 143 | - Android 服从系统同步设置 144 | 145 | 从 alpha.26 开始: 146 | - Android 仅在后台显示服务通知 147 | - Android 删除配置选项 startForeground(与上面相关) 148 | - Android 从两个 Android 提供商处删除唤醒锁(由 @grassick 提供) 149 | - Android 仅对 postTemplate 字符串值删除限制 150 | 151 | 从 alpha.28 开始: 152 | - Android 带回了 startForeground 配置选项(**重大变更**!) 153 | 154 | startForeground 的含义略有不同。 155 | 156 | 如果为 false(默认),则服务将创建通知并进行提升 157 | 当客户端从服务解除绑定时,它本身就是前台服务。 158 | 这通常发生在应用程序移至后台时。 159 | 如果应用程序正在移回前台(对用户可见) 160 | 服务会销毁通知并停止前台服务。 161 | 162 | 如果真正的服务将创建通知并始终保持在前台。 163 | 164 | 从 alpha.30 开始: 165 | - Android 内部更改(权限处理) 166 | - Android gradle 构建更改 167 | 168 | 从 alpha.38 开始: 169 | - Android 禁用 Oreo 的通知声音和振动 170 | (PR:[#9](https://github.com/mauron85/background-geolocation-android/pull/9) 171 | 由 [@danielgindi](https://github.com/danielgindi/),关闭#260) 172 | 173 | ### 修复 174 | 175 | 从 alpha.4 开始: 176 | - @asafron 可在 iOS 10 及更高版本(PR#158)上的打开 iOS 位置设置 177 | 178 | 从 alpha.8 开始: 179 | - checkStatus 授权 180 | - 修复 Android 找不到符号的编译错误 181 | 182 | 自 alpha.9 开始: 183 | - Android 修复 #118 - NullPointerException LocationService.onTaskRemoved 184 | - Android 权限 - 在运行时检查和请求权限 185 | 186 | 从 alpha.13 开始: 187 | - Android 修复 allowBackup 属性冲突 188 | 189 | 从alpha.14开始: 190 | - Android 修复#166 - 错误:含有 'com.google.android.gms.license' 多个库 191 | 192 | 从 alpha.15 开始: 193 | - Android 仅传递有效的位置参数 194 | - 停止时 iOS 重置连接状态 195 | - iOS 修复 App Store 拒绝问题 - Prefs 非公共 URL Scheme 196 | 197 | 从 alpha.17 开始: 198 | - Android 修复服务意外用默认配置或存储配置启动 199 | 200 | 从 alpha.21 开始: 201 | - Android 在 postunlink 上卸载 Android 的常用模块 202 | - Android 防止注册多个 common 项目 203 | - Android 修复一些空指针异常 92649c70e0ce0072464f47f1d096bef40047b8a6 204 | - iOS 只更新了 info.plist 205 | 206 | 从 alpha.22 开始: 207 | - Android 为防止一些逆向工程添加混淆 208 | - Android 处理配置为 null 的情况 209 | 210 | 从 alpha.25 开始: 211 | - Android 问题 #185 - 处理无效配置 212 | 213 | 从 alpha.27 开始: 214 | - iOS 修复强制同步参数 215 | - 修复 #183 - 添加 'activity' 事件侦听器时出错 216 | 217 | 从 alpha.28 开始: 218 | - iOS 在 iOS >= 10 的前台显示调试通知 219 | - iOS 修复错误消息格式 220 | - iOS 行动提供者静止不动(原文iOS activity provider stationary event) 221 | 222 | 从 alpha.35 开始: 223 | - Android getCurrentLocation 在后台线程上运行 (PR #219 by [@djereg](https://github.com/djereg/)) 224 | - iOS 修复删除所有位置时崩溃的问题 ([7392e39](https://github.com/mauron85/background-geolocation-ios/commit/7392e391c3de3ff0d6f5ef2ef19c34aba612bf9b) by [@acerbetti](https://github.com/acerbetti/)) 225 | 226 | 从 alpha.36 开始: 227 | - Android Defer 启动并配置,直到服务准备就绪 228 | (PR: [#7](https://github.com/mauron85/background-geolocation-android/pull/7) 229 | Commit: [00e1314](https://github.com/mauron85/background-geolocation-android/commit/00e131478ad4e37576eb85581bb663b65302a4e0) by [@danielgindi](https://github.com/danielgindi/), 230 | 修复 #201, #181, #172) 231 | 232 | 从 alpha.38 开始: 233 | - iOS 避免控制 UNUserNotificationCenter 234 | (PR: [#268](https://github.com/mauron85/react-native-background-geolocation/pull/268) 235 | by [@danielgindi](https://github.com/danielgindi/), 236 | 修复 #206, #256) 237 | 238 | ### [0.4.1] - 2017-12-19 239 | #### 变更 240 | - react native 版本必须大于 0.49.0 241 | 242 | ### [0.4.0] - 2017-12-13 243 | 发布 0.4.0 244 | 245 | ### [0.4.0-rc.3] - 2017-11-23 246 | ### 新增 247 | - iOS 在后台同步时发送 http headers 248 | 249 | ### [0.4.0-rc.2] - 2017-11-13 250 | 251 | ### 修复 252 | - Android ConfigMapper mapToConfig 缺少配置属性(修复#122) 253 | 254 | ### 新增 255 | - Android 为 getLocations 方法返回 location id 256 | 257 | ### [0.4.0-rc.1] - 2017-11-10 258 | 259 | ### 修复 260 | - 修复 iOS 在配置之前调用 getConfig 方法 261 | 262 | #### 新增 263 | - checkStatus 判断服务是否正在运行 264 | - 事件 [start, stop, authorization, background, foreground] 265 | - 实现两个平台的所有方法 266 | - RAW_LOCATION_PROVIDER 模式 267 | 268 | #### 变更 269 | - start 和 stop 方法不接受回调(改为使用事件监听器) 270 | - syncUrl 为后台同步的必填项 271 | - Android 上的 DISTANCE_FILTER_PROVIDER 现在接受任意值(之前只能 10, 100, 1000 之前选择) 272 | - 所有插件常量都直接在 BackgroundGeolocation 命名空间中。(查看 index.js) 273 | - 可以在不执行 configure 的情况下启动插件(使用存储的设置或默认值) 274 | - location 属性 locationId 重命名为 id 275 | - iOS pauseLocationUpdates 现在默认为 false(因为 iOS 文档现在声明如果将其设置为true,则需要手动重启) 276 | - iOS finish 方法替换为 startTask 和 endTask 277 | 278 | ### [0.3.3] - 2017-11-01 279 | #### 修复 280 | - 状态码是 201 时 Android 也应该同步位置 (PR #71) 281 | 282 | ### [0.3.2] - 2017-11-01 283 | #### 修复 284 | - 为 iOS 实现 isLocationEnabled 属性(PR #92) 285 | 286 | ### [0.3.1] - 2017-10-04 287 | #### 修复 288 | - (tpisto) React Native 0.48.x 中的 iOS 编译错误(修复#108) 289 | 290 | ### [0.3.0-alpha.1] - 2017-08-15 291 | #### 修复 292 | - 兼容 iOS RN 0.47 版 (修复 #95) 293 | 294 | ### [0.2.0-alpha.7] - 2017-03-21 295 | #### 修复 296 | - 修复 iOS 问题 #44 297 | 298 | ### [0.2.0-alpha.6] - 2017-02-18 299 | #### 修复 300 | - 兼容 iOS RN 0.40 版 301 | 302 | ### [0.2.0-alpha.5] - 2016-09-15 303 | #### 修复 304 | - 修复 Android 问题 #10 305 | 306 | ### [0.2.0-alpha.4] - 2016-09-10 307 | #### 修复 308 | - 修复了 Android 在未配置插件时销毁的崩溃问题 309 | 310 | ### [0.2.0-alpha.3] - 2016-09-07 311 | #### 修复 312 | - 修复 Android 问题 #10 - 刷新时崩溃 313 | 314 | #### 新增 315 | - Android onStationary 方法 316 | - Android getLogEntries 方法 317 | 318 | #### 删除 319 | - Android 位置过滤 320 | 321 | #### 变更 322 | - Android 项目目录结构 323 | (请阅读更新安装说明) 324 | - 用 Android 数据库代替文件来记录日志 325 | 326 | ### [0.2.0-alpha.2] - 2016-08-31 327 | #### 修复 328 | - 修复 config 参数无法保持的问题 329 | - 临时修复 Android 时间长到 int 型转换 330 | 331 | #### 新增 332 | - Android isLocationEnabled 属性 333 | - Android showAppSettings 方法 334 | - Android showLocationSettings 方法 335 | - Android getLocations 方法 336 | - Android getConfig 方法 337 | 338 | ### [0.2.0-alpha.1] - 2016-08-17 339 | #### 变更 340 | - 为适配 cordova 2.2.0-alpha.6 升级插件 341 | 342 | ### [0.1.1] - 2016-06-08 343 | #### 修复 344 | - 修复 iOS 停止时崩溃问题 345 | 346 | ### [0.1.0] - 2016-06-07 347 | #### 新增 348 | - 初始 iOS 实现 349 | 350 | ### [0.0.1] - 2016-06-04 351 | - 初始 Android 实现 352 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Many thanks to all contributors 2 | 3 | * [christocracy](https://github.com/christocracy) 4 | * [huttj](https://github.com/huttj) 5 | * [erikkemperman](https://github.com/erikkemperman) 6 | * [codebling](https://github.com/codebling) 7 | * [pmwisdom](https://github.com/pmwisdom) 8 | * [dobrynia](https://github.com/dobrynia) 9 | -------------------------------------------------------------------------------- /DISTANCE_FILTER_PROVIDER.md: -------------------------------------------------------------------------------- 1 | ## Behaviour 2 | 3 | This provider has features allowing you to control the behaviour of background-tracking, striking a balance between accuracy and battery-usage. In stationary-mode, the plugin attempts to decrease its power usage and accuracy by setting up a circular stationary-region of configurable `stationaryRadius`. iOS has a nice system [Significant Changes API](https://developer.apple.com/library/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/CLLocationManager/CLLocationManager.html#//apple_ref/occ/instm/CLLocationManager/startMonitoringSignificantLocationChanges), which allows the os to suspend your app until a cell-tower change is detected (typically 2-3 city-block change) Android uses [LocationManager#addProximityAlert](http://developer.android.com/reference/android/location/LocationManager.html). 4 | 5 | When the plugin detects your user has moved beyond his stationary-region, it engages the native platform's geolocation system for aggressive monitoring according to the configured `desiredAccuracy`, `distanceFilter` and `interval`. The plugin attempts to intelligently scale `distanceFilter` based upon the current reported speed. Each time `distanceFilter` is determined to have changed by 5m/s, it recalculates it by squaring the speed rounded-to-nearest-five and adding `distanceFilter` (I arbitrarily came up with that formula. Better ideas?). 6 | 7 | `(round(speed, 5))^2 + distanceFilter` 8 | 9 | ### distanceFilter 10 | is calculated as the square of speed-rounded-to-nearest-5 and adding configured #distanceFilter. 11 | 12 | `(round(speed, 5))^2 + distanceFilter` 13 | 14 | For example, at biking speed of 7.7 m/s with a configured distanceFilter of 30m: 15 | 16 | `=> round(7.7, 5)^2 + 30` 17 | `=> (10)^2 + 30` 18 | `=> 100 + 30` 19 | `=> 130` 20 | 21 | A gps location will be recorded each time the device moves 130m. 22 | 23 | At highway speed of 30 m/s with distanceFilter: 30, 24 | 25 | `=> round(30, 5)^2 + 30` 26 | `=> (30)^2 + 30` 27 | `=> 900 + 30` 28 | `=> 930` 29 | 30 | A gps location will be recorded every 930m 31 | 32 | Note the following real example of background-geolocation on highway 101 towards San Francisco as the driver slows down as he runs into slower traffic (geolocations become compressed as distanceFilter decreases) 33 | 34 | ![distanceFilter at highway speed](/distance-filter-highway.png "distanceFilter at highway speed") 35 | 36 | Compare now background-geolocation in the scope of a city. In this image, the left-hand track is from a cab-ride, while the right-hand track is walking speed. 37 | 38 | ![distanceFilter at city scale](/distance-filter-city.png "distanceFilter at city scale") 39 | 40 | **NOTE:** `distanceFilter` is elastically auto-calculated by the plugin: When speed increases, distanceFilter increases; when speed decreases, so does distanceFilter. 41 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## Your Environment 5 | 6 | 7 | * Plugin version: 8 | * Platform: iOS or Android 9 | * OS version: 10 | * Device manufacturer and model: 11 | 12 | * Running in Simulator: 13 | * React Native version: 14 | * Plugin configuration options: 15 | * Link to your project: 16 | 17 | ## Context 18 | 19 | 20 | ## Expected Behavior 21 | 22 | 23 | ## Actual Behavior 24 | 25 | 26 | ## Possible Fix 27 | 28 | 29 | ## Steps to Reproduce 30 | 31 | 32 | 1. 33 | 2. 34 | 3. 35 | 4. 36 | 37 | ## Context 38 | 39 | 40 | ## Debug logs 41 | 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PROVIDERS.md: -------------------------------------------------------------------------------- 1 | # Location providers 2 | 3 | ## Which provider should I use? 4 | 5 | ### DISTANCE_FILTER_PROVIDER 6 | 7 | This is classic provider, originally from cristocracy. It's best to use this one as background location provider. It is using Stationary API and elastic distance filter to achieve optimal battery and data usage. You can read more about this provider in [DISTANCE_FILTER_PROVIDER.md](/DISTANCE_FILTER_PROVIDER.md). 8 | 9 | ### ACTIVITY_PROVIDER (Android only) 10 | 11 | This one is best to use as foreground location provider (but works in background as well). It uses Android FusedLocationProviderApi and ActivityRecognitionApi for maximum battery saving. This provider is alternative to w3c ```window.navigator.watchPosition```, but you're in control how often should updates be polled from GPS. Slower updates means lower battery consumption. You can adjust position update interval by settings options ```interval``` and ```fastestInterval```. Option ```fastestInterval``` is used, when there are other apps asking for positions. In that case your app can be updated more often and ```fastestInterval``` is the upper limit of how fast can your app process location updates. Option ```activitiesInterval``` specifies how often activity recognition occurs. Larger values will result in fewer activity detections while improving battery life. Smaller values will result in more frequent activity detections but will consume more power since the device must be woken up more frequently 12 | 13 | ### RAW_PROVIDER 14 | 15 | This provider doesn't do any location processing, but rather returns locations as recorded by device sensors. 16 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | # Run tests 2 | 3 | # Unit 4 | 5 | Notice: currently not possible - [read explanation](lib/src/test/java/com/marianhello/bgloc/react/ConfigMapperTest.java) 6 | 7 | ``` 8 | ndkDir=$(pwd)/react-ndk/all/x86_64 \ 9 | JAVA_OPTS="-Djava.library.path=\".:$ndkDir\"" \ 10 | LD_LIBRARY_PATH="$ndkDir:$LD_LIBRARY_PATH" ./gradlew lib:test 11 | ``` 12 | 13 | # Instrumented 14 | 15 | Test, that run on android device can be run from Android Studio. 16 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | maven { 5 | url 'https://maven.google.com/' 6 | name 'Google' 7 | } 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.4.1' 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | maven { 19 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 20 | url "$rootDir/../node_modules/react-native/android" 21 | } 22 | maven { 23 | url 'https://maven.google.com/' 24 | name 'Google' 25 | } 26 | jcenter() 27 | // repo for react-native 28 | // https://mvnrepository.com/artifact/com.facebook.react/react-native?repo=springio-plugins-release 29 | maven { 30 | url 'http://repo.spring.io/plugins-release/' 31 | name 'Spring Plugins' 32 | } 33 | } 34 | } 35 | 36 | ext { 37 | reactNativeLibVersion = "0.55.3" 38 | } 39 | 40 | task clean(type: Delete) { 41 | delete rootProject.buildDir 42 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauron85/react-native-background-geolocation/f0d434291cfae4a0e510e1e7a05d4e1e7508870e/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /android/lib/build.gradle: -------------------------------------------------------------------------------- 1 | // Notice: Oreo builds are disabled by default until all problems are resolved!!! 2 | // 3 | // For Oreo compatibility we are forced to use supportLibVersion >= 26.1.0 4 | // Unfortunately Android stopped supporting downloading libraries through the SDK Manager, 5 | // @see https://developer.android.com/topic/libraries/support-library/setup.html 6 | // 7 | // You can enable oreo by adding following into root build.gradle: 8 | // 9 | // allprojects { 10 | // repositories { 11 | // maven { url 'https://maven.google.com' } 12 | // } 13 | // } 14 | // 15 | // ext { 16 | // compileSdkVersion = 26 17 | // targetSdkVersion = 26 18 | // buildToolsVersion = "26.0.2" 19 | // supportLibVersion = "26.1.0" 20 | // googlePlayServicesVersion = "11.8.0" 21 | // } 22 | // 23 | // 24 | // If you don't like this please ask react guys nicely to add google maven repo into 25 | // their templates. 26 | 27 | apply plugin: 'com.android.library' 28 | 29 | apply from: '../common/VERSIONS.gradle' 30 | def hasApp = findProject(':app') != null 31 | 32 | // https://hackernoon.com/android-how-to-add-gradle-dependencies-using-foreach-c4cbcc070458 33 | def projDependencies = [ 34 | [configuration: "implementation", dependency: libs.slf4j], 35 | [configuration: "implementation", dependency: libs.reactNative], 36 | [configuration: "testImplementation", dependency: testLibs.testRunner], 37 | [configuration: "testImplementation", dependency: testLibs.testRules], 38 | [configuration: "testImplementation", dependency: testLibs.junit], 39 | [configuration: "testImplementation", dependency: testLibs.powermockMockito], 40 | [configuration: "testImplementation", dependency: testLibs.powermockjUnit], 41 | [configuration: "testImplementation", dependency: testLibs.powermockClassloading], 42 | [configuration: "testImplementation", dependency: testLibs.mockitoCore], 43 | [configuration: "testImplementation", dependency: testLibs.festAssertCore], 44 | [configuration: "testImplementation", dependency: testLibs.robolectric], 45 | [configuration: "androidTestImplementation", dependency: testLibs.testRunner], 46 | [configuration: "androidTestImplementation", dependency: testLibs.testRules], 47 | ] 48 | 49 | repositories { 50 | // Google dependencies are now hosted at Maven 51 | // unfortunately this is ignored when installing as react-native plugin 52 | maven { 53 | url 'https://maven.google.com' 54 | } 55 | jcenter() 56 | } 57 | 58 | android { 59 | compileSdkVersion project.ext.getCompileSdkVersion() 60 | buildToolsVersion project.ext.getBuildToolsVersion() 61 | if (hasApp) { 62 | evaluationDependsOn(':app') 63 | } 64 | 65 | // Tip: https://stackoverflow.com/questions/39987669/renamingdelegatingcontext-is-deprecated-how-do-we-test-sqlite-db-now/52170737#52170737 66 | // Gradle automatically adds 'android.test.runner' as a dependency. 67 | useLibrary 'android.test.runner' 68 | useLibrary 'android.test.base' 69 | useLibrary 'android.test.mock' 70 | 71 | defaultConfig { 72 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 73 | minSdkVersion project.ext.getMinSdkVersion() 74 | targetSdkVersion project.ext.getTargetSdkVersion() 75 | versionCode 1 // intentionally not updating version as we're not uploading to any java repository 76 | versionName "1.0" 77 | } 78 | 79 | lintOptions { 80 | abortOnError false 81 | } 82 | } 83 | 84 | dependencies { 85 | implementation project(path: ':@mauron85_react-native-background-geolocation-common') 86 | 87 | projDependencies.each { 88 | add(it.configuration, it.dependency) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /android/lib/src/androidTest/java/com/marianhello/bgloc/react/ConfigMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | import android.support.test.filters.SmallTest; 7 | 8 | import com.facebook.react.bridge.Arguments; 9 | import com.facebook.react.bridge.ReadableMap; 10 | import com.facebook.react.bridge.WritableMap; 11 | import com.facebook.soloader.SoLoader; 12 | import com.marianhello.bgloc.Config; 13 | import com.marianhello.bgloc.data.ArrayListLocationTemplate; 14 | import com.marianhello.bgloc.data.HashMapLocationTemplate; 15 | import com.marianhello.bgloc.data.LocationTemplate; 16 | import com.marianhello.bgloc.data.LocationTemplateFactory; 17 | 18 | import junit.framework.Assert; 19 | 20 | import org.json.JSONException; 21 | import org.junit.Before; 22 | import org.junit.Test; 23 | import org.junit.runner.RunWith; 24 | 25 | import java.io.IOException; 26 | import java.util.ArrayList; 27 | import java.util.HashMap; 28 | 29 | @RunWith(AndroidJUnit4.class) 30 | @SmallTest 31 | public class ConfigMapperTest { 32 | 33 | private Context mContext; 34 | 35 | @Before 36 | public void setUp() throws IOException { 37 | Context mContext = InstrumentationRegistry.getContext(); 38 | SoLoader.init(mContext, 0); 39 | } 40 | 41 | @Test 42 | public void testDefaultToJSONObject() { 43 | Config config = Config.getDefault(); 44 | ReadableMap map = ConfigMapper.toMap(config); 45 | 46 | Assert.assertEquals(config.getStationaryRadius(), map.getDouble("stationaryRadius"), 0f); 47 | Assert.assertEquals(config.getDistanceFilter().intValue(), map.getInt("distanceFilter")); 48 | Assert.assertEquals(config.getDesiredAccuracy().intValue(), map.getInt("desiredAccuracy")); 49 | Assert.assertEquals(config.isDebugging().booleanValue(), map.getBoolean("debug")); 50 | Assert.assertEquals(config.getNotificationTitle(), map.getString("notificationTitle")); 51 | Assert.assertEquals(config.getNotificationText(), map.getString("notificationText")); 52 | Assert.assertEquals(config.getStopOnTerminate().booleanValue(), map.getBoolean("stopOnTerminate")); 53 | Assert.assertEquals(config.getStartOnBoot().booleanValue(), map.getBoolean("startOnBoot")); 54 | Assert.assertEquals(config.getLocationProvider().intValue(), map.getInt("locationProvider")); 55 | Assert.assertEquals(config.getInterval().intValue(), map.getInt("interval")); 56 | Assert.assertEquals(config.getFastestInterval().intValue(), map.getInt("fastestInterval")); 57 | Assert.assertEquals(config.getActivitiesInterval().intValue(), map.getInt("activitiesInterval")); 58 | Assert.assertEquals(config.getNotificationIconColor(), map.getString("notificationIconColor")); 59 | Assert.assertEquals(config.getLargeNotificationIcon(), map.getString("notificationIconLarge")); 60 | Assert.assertEquals(config.getSmallNotificationIcon(), map.getString("notificationIconSmall")); 61 | Assert.assertEquals(config.getStartForeground().booleanValue(), map.getBoolean("startForeground")); 62 | Assert.assertEquals(config.getStopOnStillActivity().booleanValue(), map.getBoolean("stopOnStillActivity")); 63 | Assert.assertEquals(config.getUrl(), map.getString("url")); 64 | Assert.assertEquals(config.getSyncUrl(), map.getString("syncUrl")); 65 | Assert.assertEquals(config.getSyncThreshold().intValue(), map.getInt("syncThreshold")); 66 | Assert.assertEquals(config.getHttpHeaders(), map.getMap("httpHeaders").toHashMap()); 67 | Assert.assertEquals(config.getMaxLocations().intValue(), map.getInt("maxLocations")); 68 | Assert.assertEquals(LocationTemplateFactory.getDefault(), LocationTemplateFactory.fromHashMap(map.getMap("postTemplate").toHashMap())); 69 | } 70 | 71 | @Test 72 | public void testNullableProps() throws JSONException { 73 | WritableMap map = Arguments.createMap(); 74 | map.putNull("url"); 75 | map.putNull("syncUrl"); 76 | map.putNull("notificationIconColor"); 77 | map.putNull("notificationTitle"); 78 | map.putNull("notificationText"); 79 | map.putNull("notificationIconLarge"); 80 | map.putNull("notificationIconSmall"); 81 | 82 | Config config = ConfigMapper.fromMap((ReadableMap)map); 83 | 84 | Assert.assertEquals(Config.NullString, config.getUrl()); 85 | Assert.assertTrue(config.hasUrl()); 86 | Assert.assertFalse(config.hasValidUrl()); 87 | 88 | Assert.assertEquals(Config.NullString, config.getSyncUrl()); 89 | Assert.assertTrue(config.hasSyncUrl()); 90 | Assert.assertFalse(config.hasValidSyncUrl()); 91 | 92 | Assert.assertEquals(Config.NullString, config.getNotificationIconColor()); 93 | Assert.assertFalse(config.hasNotificationIconColor()); 94 | 95 | Assert.assertEquals(Config.NullString, config.getNotificationTitle()); 96 | Assert.assertTrue(config.hasNotificationTitle()); 97 | 98 | Assert.assertEquals(Config.NullString, config.getNotificationText()); 99 | Assert.assertTrue(config.hasNotificationText()); 100 | 101 | Assert.assertEquals(Config.NullString, config.getLargeNotificationIcon()); 102 | Assert.assertFalse(config.hasLargeNotificationIcon()); 103 | 104 | Assert.assertEquals(Config.NullString, config.getSmallNotificationIcon()); 105 | Assert.assertFalse(config.hasSmallNotificationIcon()); 106 | } 107 | 108 | @Test 109 | public void testNullablePropsToJSONObject() throws JSONException { 110 | Config config = new Config(); 111 | config.setUrl(Config.NullString); 112 | config.setSyncUrl(Config.NullString); 113 | config.setNotificationIconColor(Config.NullString); 114 | config.setNotificationTitle(Config.NullString); 115 | config.setNotificationText(Config.NullString); 116 | config.setLargeNotificationIcon(Config.NullString); 117 | config.setSmallNotificationIcon(Config.NullString); 118 | 119 | ReadableMap map = ConfigMapper.toMap(config); 120 | 121 | Assert.assertEquals(null, map.getString("url")); 122 | Assert.assertEquals(null, map.getString("syncUrl")); 123 | Assert.assertEquals(null, map.getString("notificationIconColor")); 124 | Assert.assertEquals(null, map.getString("notificationTitle")); 125 | Assert.assertEquals(null, map.getString("notificationText")); 126 | Assert.assertEquals(null, map.getString("notificationIconLarge")); 127 | Assert.assertEquals(null, map.getString("notificationIconSmall")); 128 | } 129 | 130 | @Test 131 | public void testNullHashMapTemplateToJSONObject() { 132 | Config config = new Config(); 133 | LocationTemplate tpl = new HashMapLocationTemplate((HashMap)null); 134 | config.setTemplate(tpl); 135 | 136 | ReadableMap jConfig = ConfigMapper.toMap(config); 137 | Assert.assertEquals(null, jConfig.getMap("postTemplate")); 138 | } 139 | 140 | @Test 141 | public void testEmptyHashMapTemplateToJSONObject() { 142 | Config config = new Config(); 143 | HashMap map = new HashMap(); 144 | LocationTemplate tpl = new HashMapLocationTemplate(map); 145 | config.setTemplate(tpl); 146 | 147 | ReadableMap jConfig = ConfigMapper.toMap(config); 148 | Assert.assertFalse(jConfig.getMap("postTemplate").keySetIterator().hasNextKey()); 149 | } 150 | 151 | @Test 152 | public void testHashMapTemplateToJSONObject() { 153 | Config config = new Config(); 154 | HashMap map = new HashMap(); 155 | map.put("foo", "bar"); 156 | map.put("pretzels", 123); 157 | LocationTemplate tpl = new HashMapLocationTemplate(map); 158 | config.setTemplate(tpl); 159 | 160 | ReadableMap jConfig = ConfigMapper.toMap(config); 161 | Assert.assertEquals("{ NativeMap: {\"foo\":\"bar\",\"pretzels\":123} }", jConfig.getMap("postTemplate").toString()); 162 | } 163 | 164 | @Test 165 | public void testNullArrayListLocationTemplateToJSONObject() { 166 | Config config = new Config(); 167 | LocationTemplate tpl = new ArrayListLocationTemplate((ArrayListLocationTemplate)null); 168 | config.setTemplate(tpl); 169 | 170 | ReadableMap jConfig = ConfigMapper.toMap(config); 171 | Assert.assertEquals(null, jConfig.getMap("postTemplate")); 172 | } 173 | 174 | @Test 175 | public void testEmptyArrayListLocationTemplateToJSONObject() { 176 | Config config = new Config(); 177 | ArrayList list = new ArrayList(); 178 | LocationTemplate tpl = new ArrayListLocationTemplate(list); 179 | config.setTemplate(tpl); 180 | 181 | ReadableMap jConfig = ConfigMapper.toMap(config); 182 | Assert.assertTrue(jConfig.getArray("postTemplate").size() == 0); 183 | } 184 | 185 | @Test 186 | public void testArrayListLocationTemplateToJSONObject() { 187 | Config config = new Config(); 188 | ArrayList list = new ArrayList(); 189 | list.add("foo"); 190 | list.add(123); 191 | list.add("foo"); 192 | 193 | LocationTemplate tpl = new ArrayListLocationTemplate(list); 194 | config.setTemplate(tpl); 195 | 196 | ReadableMap jConfig = ConfigMapper.toMap(config); 197 | Assert.assertEquals("[\"foo\",123,\"foo\"]", jConfig.getArray("postTemplate").toString()); 198 | } 199 | } -------------------------------------------------------------------------------- /android/lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 23 | 27 | 28 | 29 | 30 | 32 | 33 | 35 | 36 | 37 | 38 | 41 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /android/lib/src/main/java/com/iodine/start/ArrayUtil.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ArrayUtil exposes a set of helper methods for working with 4 | ReadableArray (by React Native), Object[], and JSONArray. 5 | */ 6 | 7 | package com.iodine.start; 8 | 9 | import com.facebook.react.bridge.Arguments; 10 | import com.facebook.react.bridge.ReadableArray; 11 | import com.facebook.react.bridge.ReadableType; 12 | import com.facebook.react.bridge.WritableArray; 13 | 14 | import java.util.Map; 15 | 16 | import org.json.JSONArray; 17 | import org.json.JSONObject; 18 | import org.json.JSONException; 19 | 20 | public class ArrayUtil { 21 | 22 | public static JSONArray toJSONArray(ReadableArray readableArray) throws JSONException { 23 | JSONArray jsonArray = new JSONArray(); 24 | 25 | for (int i = 0; i < readableArray.size(); i++) { 26 | ReadableType type = readableArray.getType(i); 27 | 28 | switch (type) { 29 | case Null: 30 | jsonArray.put(i, null); 31 | break; 32 | case Boolean: 33 | jsonArray.put(i, readableArray.getBoolean(i)); 34 | break; 35 | case Number: 36 | jsonArray.put(i, readableArray.getDouble(i)); 37 | break; 38 | case String: 39 | jsonArray.put(i, readableArray.getString(i)); 40 | break; 41 | case Map: 42 | jsonArray.put(i, MapUtil.toJSONObject(readableArray.getMap(i))); 43 | break; 44 | case Array: 45 | jsonArray.put(i, ArrayUtil.toJSONArray(readableArray.getArray(i))); 46 | break; 47 | } 48 | } 49 | 50 | return jsonArray; 51 | } 52 | 53 | public static Object[] toArray(JSONArray jsonArray) throws JSONException { 54 | Object[] array = new Object[jsonArray.length()]; 55 | 56 | for (int i = 0; i < jsonArray.length(); i++) { 57 | Object value = jsonArray.get(i); 58 | 59 | if (value instanceof JSONObject) { 60 | value = MapUtil.toMap((JSONObject) value); 61 | } 62 | if (value instanceof JSONArray) { 63 | value = ArrayUtil.toArray((JSONArray) value); 64 | } 65 | 66 | array[i] = value; 67 | } 68 | 69 | return array; 70 | } 71 | 72 | public static Object[] toArray(ReadableArray readableArray) { 73 | Object[] array = new Object[readableArray.size()]; 74 | 75 | for (int i = 0; i < readableArray.size(); i++) { 76 | ReadableType type = readableArray.getType(i); 77 | 78 | switch (type) { 79 | case Null: 80 | array[i] = null; 81 | break; 82 | case Boolean: 83 | array[i] = readableArray.getBoolean(i); 84 | break; 85 | case Number: 86 | array[i] = readableArray.getDouble(i); 87 | break; 88 | case String: 89 | array[i] = readableArray.getString(i); 90 | break; 91 | case Map: 92 | array[i] = MapUtil.toMap(readableArray.getMap(i)); 93 | break; 94 | case Array: 95 | array[i] = ArrayUtil.toArray(readableArray.getArray(i)); 96 | break; 97 | } 98 | } 99 | 100 | return array; 101 | } 102 | 103 | public static WritableArray toWritableArray(Object[] array) { 104 | WritableArray writableArray = Arguments.createArray(); 105 | 106 | for (int i = 0; i < array.length; i++) { 107 | Object value = array[i]; 108 | 109 | if (value == null) { 110 | writableArray.pushNull(); 111 | } 112 | if (value instanceof Boolean) { 113 | writableArray.pushBoolean((Boolean) value); 114 | } 115 | if (value instanceof Double) { 116 | writableArray.pushDouble((Double) value); 117 | } 118 | if (value instanceof Integer) { 119 | writableArray.pushInt((Integer) value); 120 | } 121 | if (value instanceof String) { 122 | writableArray.pushString((String) value); 123 | } 124 | if (value instanceof Map) { 125 | writableArray.pushMap(MapUtil.toWritableMap((Map) value)); 126 | } 127 | if (value.getClass().isArray()) { 128 | writableArray.pushArray(ArrayUtil.toWritableArray((Object[]) value)); 129 | } 130 | } 131 | 132 | return writableArray; 133 | } 134 | } -------------------------------------------------------------------------------- /android/lib/src/main/java/com/iodine/start/MapUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | MapUtil exposes a set of helper methods for working with 3 | ReadableMap (by React Native), Map, and JSONObject. 4 | */ 5 | 6 | package com.iodine.start; 7 | 8 | import com.facebook.react.bridge.Arguments; 9 | import com.facebook.react.bridge.ReadableMap; 10 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 11 | import com.facebook.react.bridge.ReadableType; 12 | import com.facebook.react.bridge.WritableMap; 13 | 14 | import java.util.Map; 15 | import java.util.HashMap; 16 | import java.util.Iterator; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONObject; 20 | import org.json.JSONException; 21 | 22 | public class MapUtil { 23 | 24 | public static JSONObject toJSONObject(ReadableMap readableMap) throws JSONException { 25 | JSONObject jsonObject = new JSONObject(); 26 | 27 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 28 | 29 | while (iterator.hasNextKey()) { 30 | String key = iterator.nextKey(); 31 | ReadableType type = readableMap.getType(key); 32 | 33 | switch (type) { 34 | case Null: 35 | jsonObject.put(key, null); 36 | break; 37 | case Boolean: 38 | jsonObject.put(key, readableMap.getBoolean(key)); 39 | break; 40 | case Number: 41 | jsonObject.put(key, readableMap.getDouble(key)); 42 | break; 43 | case String: 44 | jsonObject.put(key, readableMap.getString(key)); 45 | break; 46 | case Map: 47 | jsonObject.put(key, MapUtil.toJSONObject(readableMap.getMap(key))); 48 | break; 49 | case Array: 50 | jsonObject.put(key, ArrayUtil.toJSONArray(readableMap.getArray(key))); 51 | break; 52 | } 53 | } 54 | 55 | return jsonObject; 56 | } 57 | 58 | public static Map toMap(JSONObject jsonObject) throws JSONException { 59 | Map map = new HashMap<>(); 60 | Iterator iterator = jsonObject.keys(); 61 | 62 | while (iterator.hasNext()) { 63 | String key = iterator.next(); 64 | Object value = jsonObject.get(key); 65 | 66 | if (value instanceof JSONObject) { 67 | value = MapUtil.toMap((JSONObject) value); 68 | } 69 | if (value instanceof JSONArray) { 70 | value = ArrayUtil.toArray((JSONArray) value); 71 | } 72 | 73 | map.put(key, value); 74 | } 75 | 76 | return map; 77 | } 78 | 79 | public static Map toMap(ReadableMap readableMap) { 80 | Map map = new HashMap<>(); 81 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 82 | 83 | while (iterator.hasNextKey()) { 84 | String key = iterator.nextKey(); 85 | ReadableType type = readableMap.getType(key); 86 | 87 | switch (type) { 88 | case Null: 89 | map.put(key, null); 90 | break; 91 | case Boolean: 92 | map.put(key, readableMap.getBoolean(key)); 93 | break; 94 | case Number: 95 | map.put(key, readableMap.getDouble(key)); 96 | break; 97 | case String: 98 | map.put(key, readableMap.getString(key)); 99 | break; 100 | case Map: 101 | map.put(key, MapUtil.toMap(readableMap.getMap(key))); 102 | break; 103 | case Array: 104 | map.put(key, ArrayUtil.toArray(readableMap.getArray(key))); 105 | break; 106 | } 107 | } 108 | 109 | return map; 110 | } 111 | 112 | public static WritableMap toWritableMap(Map map) { 113 | WritableMap writableMap = Arguments.createMap(); 114 | Iterator iterator = map.entrySet().iterator(); 115 | 116 | while (iterator.hasNext()) { 117 | Map.Entry pair = (Map.Entry)iterator.next(); 118 | Object value = pair.getValue(); 119 | 120 | if (value == null) { 121 | writableMap.putNull((String) pair.getKey()); 122 | } else if (value instanceof Boolean) { 123 | writableMap.putBoolean((String) pair.getKey(), (Boolean) value); 124 | } else if (value instanceof Double) { 125 | writableMap.putDouble((String) pair.getKey(), (Double) value); 126 | } else if (value instanceof Integer) { 127 | writableMap.putInt((String) pair.getKey(), (Integer) value); 128 | } else if (value instanceof String) { 129 | writableMap.putString((String) pair.getKey(), (String) value); 130 | } else if (value instanceof Map) { 131 | writableMap.putMap((String) pair.getKey(), MapUtil.toWritableMap((Map) value)); 132 | } else if (value.getClass() != null && value.getClass().isArray()) { 133 | writableMap.putArray((String) pair.getKey(), ArrayUtil.toWritableArray((Object[]) value)); 134 | } 135 | } 136 | 137 | return writableMap; 138 | } 139 | } -------------------------------------------------------------------------------- /android/lib/src/main/java/com/marianhello/bgloc/react/BackgroundGeolocationModule.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react; 2 | 3 | import android.content.Context; 4 | 5 | import com.facebook.react.bridge.Arguments; 6 | import com.facebook.react.bridge.Callback; 7 | import com.facebook.react.bridge.LifecycleEventListener; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 10 | import com.facebook.react.bridge.ReactMethod; 11 | import com.facebook.react.bridge.ReadableMap; 12 | import com.facebook.react.bridge.WritableArray; 13 | import com.facebook.react.bridge.WritableMap; 14 | import com.facebook.react.modules.core.DeviceEventManagerModule; 15 | import com.marianhello.bgloc.BackgroundGeolocationFacade; 16 | import com.marianhello.bgloc.Config; 17 | import com.marianhello.bgloc.PluginDelegate; 18 | import com.marianhello.bgloc.PluginException; 19 | import com.marianhello.bgloc.data.BackgroundActivity; 20 | import com.marianhello.bgloc.data.BackgroundLocation; 21 | import com.marianhello.bgloc.react.data.LocationMapper; 22 | import com.marianhello.bgloc.react.headless.HeadlessTaskRunner; 23 | import com.marianhello.logging.LogEntry; 24 | import com.marianhello.logging.LoggerManager; 25 | 26 | import org.json.JSONException; 27 | 28 | import java.util.Collection; 29 | 30 | public class BackgroundGeolocationModule extends ReactContextBaseJavaModule implements LifecycleEventListener, PluginDelegate { 31 | 32 | public static final String LOCATION_EVENT = "location"; 33 | public static final String STATIONARY_EVENT = "stationary"; 34 | public static final String ACTIVITY_EVENT = "activity"; 35 | 36 | public static final String FOREGROUND_EVENT = "foreground"; 37 | public static final String BACKGROUND_EVENT = "background"; 38 | public static final String AUTHORIZATION_EVENT = "authorization"; 39 | 40 | public static final String START_EVENT = "start"; 41 | public static final String STOP_EVENT = "stop"; 42 | public static final String ABORT_REQUESTED_EVENT = "abort_requested"; 43 | public static final String HTTP_AUTHORIZATION_EVENT = "http_authorization"; 44 | public static final String ERROR_EVENT = "error"; 45 | 46 | private static final int PERMISSIONS_REQUEST_CODE = 1; 47 | 48 | private BackgroundGeolocationFacade facade; 49 | private org.slf4j.Logger logger; 50 | 51 | public static class ErrorMap { 52 | public static ReadableMap from(String message, int code) { 53 | WritableMap out = Arguments.createMap(); 54 | out.putInt("code", code); 55 | out.putString("message", message); 56 | return out; 57 | } 58 | 59 | public static ReadableMap from(String message, Throwable cause, int code) { 60 | WritableMap out = Arguments.createMap(); 61 | out.putInt("code", code); 62 | out.putString("message", message); 63 | out.putMap("cause", from(cause)); 64 | return out; 65 | } 66 | 67 | public static ReadableMap from(PluginException e) { 68 | WritableMap out = Arguments.createMap(); 69 | out.putInt("code", e.getCode()); 70 | out.putString("message", e.getMessage()); 71 | if (e.getCause() != null) { 72 | out.putMap("cause", from(e.getCause())); 73 | } 74 | 75 | return out; 76 | } 77 | 78 | private static WritableMap from(Throwable e) { 79 | WritableMap out = Arguments.createMap(); 80 | out.putString("message", e.getMessage()); 81 | return out; 82 | } 83 | } 84 | 85 | public BackgroundGeolocationModule(ReactApplicationContext reactContext) { 86 | super(reactContext); 87 | reactContext.addLifecycleEventListener(this); 88 | 89 | facade = new BackgroundGeolocationFacade(getContext(), this); 90 | logger = LoggerManager.getLogger(BackgroundGeolocationModule.class); 91 | } 92 | 93 | @Override 94 | public String getName() { 95 | return "BackgroundGeolocation"; 96 | } 97 | 98 | /** 99 | * Called when the activity will start interacting with the user. 100 | * 101 | */ 102 | @Override 103 | public void onHostResume() { 104 | logger.info("App will be resumed"); 105 | facade.resume(); 106 | sendEvent(FOREGROUND_EVENT, null); 107 | } 108 | 109 | /** 110 | * Called when the system is about to start resuming a previous activity. 111 | */ 112 | @Override 113 | public void onHostPause() { 114 | logger.info("App will be paused"); 115 | facade.pause(); 116 | sendEvent(BACKGROUND_EVENT, null); 117 | } 118 | 119 | /** 120 | * The final call you receive before your activity is destroyed. 121 | * Checks to see if it should turn off 122 | */ 123 | @Override 124 | public void onHostDestroy() { 125 | logger.info("Destroying plugin"); 126 | facade.destroy(); 127 | // facade = null; 128 | } 129 | 130 | private void runOnBackgroundThread(Runnable runnable) { 131 | // currently react-native has no other thread we can run on 132 | new Thread(runnable).start(); 133 | } 134 | 135 | 136 | @ReactMethod 137 | public void start() { 138 | facade.start(); 139 | } 140 | 141 | @ReactMethod 142 | public void stop() { 143 | facade.stop(); 144 | } 145 | 146 | @ReactMethod 147 | public void switchMode(Integer mode, Callback success, Callback error) { 148 | facade.switchMode(mode); 149 | } 150 | 151 | @ReactMethod 152 | public void configure(final ReadableMap options, final Callback success, final Callback error) { 153 | runOnBackgroundThread(new Runnable() { 154 | @Override 155 | public void run() { 156 | try { 157 | Config config = ConfigMapper.fromMap(options); 158 | facade.configure(config); 159 | success.invoke(true); 160 | } catch (JSONException e) { 161 | logger.error("Configuration error: {}", e.getMessage()); 162 | error.invoke(ErrorMap.from("Configuration error", e, PluginException.CONFIGURE_ERROR)); 163 | } catch (PluginException e) { 164 | logger.error("Configuration error: {}", e.getMessage()); 165 | error.invoke(ErrorMap.from(e)); 166 | } 167 | } 168 | }); 169 | } 170 | 171 | @ReactMethod 172 | public void showLocationSettings() { 173 | BackgroundGeolocationFacade.showLocationSettings(getContext()); 174 | } 175 | 176 | @ReactMethod 177 | public void showAppSettings() { 178 | BackgroundGeolocationFacade.showAppSettings(getContext()); 179 | } 180 | 181 | @ReactMethod 182 | public void getStationaryLocation(Callback success, Callback error) { 183 | BackgroundLocation stationaryLocation = facade.getStationaryLocation(); 184 | if (stationaryLocation != null) { 185 | success.invoke(LocationMapper.toWriteableMap(stationaryLocation)); 186 | } else { 187 | success.invoke(); 188 | } 189 | } 190 | 191 | @ReactMethod 192 | public void getLocations(final Callback success, final Callback error) { 193 | runOnBackgroundThread(new Runnable() { 194 | public void run() { 195 | WritableArray locationsArray = Arguments.createArray(); 196 | Collection locations = facade.getLocations(); 197 | for (BackgroundLocation location : locations) { 198 | locationsArray.pushMap(LocationMapper.toWriteableMapWithId(location)); 199 | } 200 | success.invoke(locationsArray); 201 | } 202 | }); 203 | } 204 | 205 | @ReactMethod 206 | public void getValidLocations(final Callback success, Callback error) { 207 | runOnBackgroundThread(new Runnable() { 208 | public void run() { 209 | WritableArray locationsArray = Arguments.createArray(); 210 | Collection locations = facade.getValidLocations(); 211 | for (BackgroundLocation location : locations) { 212 | locationsArray.pushMap(LocationMapper.toWriteableMapWithId(location)); 213 | } 214 | success.invoke(locationsArray); 215 | } 216 | }); 217 | } 218 | 219 | @ReactMethod 220 | public void deleteLocation(final Integer locationId, final Callback success, Callback error) { 221 | runOnBackgroundThread(new Runnable() { 222 | public void run() { 223 | facade.deleteLocation(locationId.longValue()); 224 | success.invoke(true); 225 | } 226 | }); 227 | } 228 | 229 | @ReactMethod 230 | public void deleteAllLocations(final Callback success, Callback error) { 231 | runOnBackgroundThread(new Runnable() { 232 | public void run() { 233 | facade.deleteAllLocations(); 234 | success.invoke(true); 235 | } 236 | }); 237 | } 238 | 239 | @ReactMethod 240 | public void getCurrentLocation(final ReadableMap options, final Callback success, final Callback error) { 241 | runOnBackgroundThread(new Runnable() { 242 | public void run() { 243 | try { 244 | int timeout = options.hasKey("timeout") ? options.getInt("timeout") : Integer.MAX_VALUE; 245 | long maximumAge = options.hasKey("maximumAge") ? options.getInt("maximumAge") : Long.MAX_VALUE; 246 | boolean enableHighAccuracy = options.hasKey("enableHighAccuracy") && options.getBoolean("enableHighAccuracy"); 247 | 248 | BackgroundLocation location = facade.getCurrentLocation(timeout, maximumAge, enableHighAccuracy); 249 | success.invoke(LocationMapper.toWriteableMap(location)); 250 | } catch (PluginException e) { 251 | error.invoke(ErrorMap.from(e)); 252 | } 253 | } 254 | }); 255 | } 256 | 257 | @ReactMethod 258 | public void getConfig(final Callback success, final Callback error) { 259 | runOnBackgroundThread(new Runnable() { 260 | public void run() { 261 | Config config = facade.getConfig(); 262 | ReadableMap out = ConfigMapper.toMap(config); 263 | success.invoke(out); 264 | } 265 | }); 266 | } 267 | 268 | @ReactMethod 269 | public void getLogEntries(final Integer limit, final Integer offset, final String minLevel, final Callback success, final Callback error) { 270 | runOnBackgroundThread(new Runnable() { 271 | public void run() { 272 | WritableArray logEntriesArray = Arguments.createArray(); 273 | Collection logEntries = facade.getLogEntries(limit, offset, minLevel); 274 | for (LogEntry logEntry : logEntries) { 275 | WritableMap out = Arguments.createMap(); 276 | out.putInt("id", logEntry.getId()); 277 | out.putInt("context", logEntry.getContext()); 278 | out.putString("level", logEntry.getLevel()); 279 | out.putString("message", logEntry.getMessage()); 280 | out.putString("timestamp", new Long(logEntry.getTimestamp()).toString()); 281 | out.putString("logger", logEntry.getLoggerName()); 282 | if (logEntry.hasStackTrace()) { 283 | out.putString("stackTrace", logEntry.getStackTrace()); 284 | } 285 | 286 | logEntriesArray.pushMap(out); 287 | } 288 | success.invoke(logEntriesArray); 289 | } 290 | }); 291 | } 292 | 293 | @ReactMethod 294 | public void checkStatus(final Callback success, final Callback error) { 295 | runOnBackgroundThread(new Runnable() { 296 | public void run() { 297 | try { 298 | WritableMap out = Arguments.createMap(); 299 | out.putBoolean("isRunning", facade.isRunning()); 300 | out.putBoolean("hasPermissions", facade.hasPermissions()); //@Deprecated 301 | out.putBoolean("locationServicesEnabled", facade.locationServicesEnabled()); 302 | out.putInt("authorization", getAuthorizationStatus()); 303 | success.invoke(out); 304 | } catch (PluginException e) { 305 | logger.error("Location service checked failed: {}", e.getMessage()); 306 | error.invoke(ErrorMap.from(e)); 307 | } 308 | } 309 | }); 310 | } 311 | 312 | @ReactMethod 313 | public void registerHeadlessTask(Callback success, Callback error) { 314 | logger.debug("Registering headless task"); 315 | facade.registerHeadlessTask(HeadlessTaskRunner.class.getName()); 316 | success.invoke(); 317 | } 318 | 319 | @ReactMethod 320 | public void forceSync(Callback success, Callback error) { 321 | facade.forceSync(); 322 | success.invoke(); 323 | } 324 | 325 | private void sendEvent(String eventName, Object params) { 326 | getReactApplicationContext() 327 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 328 | .emit(eventName, params); 329 | } 330 | 331 | private void sendError(PluginException error) { 332 | WritableMap out = Arguments.createMap(); 333 | out.putInt("code", error.getCode()); 334 | out.putString("message", error.getMessage()); 335 | 336 | sendEvent(ERROR_EVENT, out); 337 | } 338 | 339 | private void sendError(int code, String message) { 340 | WritableMap out = Arguments.createMap(); 341 | out.putInt("code", code); 342 | out.putString("message", message); 343 | 344 | sendEvent(ERROR_EVENT, out); 345 | } 346 | 347 | public int getAuthorizationStatus() { 348 | return facade.getAuthorizationStatus(); 349 | } 350 | 351 | public Context getContext() { 352 | return getReactApplicationContext().getBaseContext(); 353 | } 354 | 355 | @Override 356 | public void onAuthorizationChanged(int authStatus) { 357 | sendEvent(AUTHORIZATION_EVENT, authStatus); 358 | } 359 | 360 | @Override 361 | public void onLocationChanged(BackgroundLocation location) { 362 | sendEvent(LOCATION_EVENT, LocationMapper.toWriteableMapWithId(location)); 363 | } 364 | 365 | @Override 366 | public void onStationaryChanged(BackgroundLocation location) { 367 | sendEvent(STATIONARY_EVENT, LocationMapper.toWriteableMapWithId(location)); 368 | } 369 | 370 | @Override 371 | public void onActivityChanged(BackgroundActivity activity) { 372 | WritableMap out = Arguments.createMap(); 373 | out.putInt("confidence", activity.getConfidence()); 374 | out.putString("type", BackgroundActivity.getActivityString(activity.getType())); 375 | sendEvent(ACTIVITY_EVENT, out); 376 | } 377 | 378 | @Override 379 | public void onServiceStatusChanged(int status) { 380 | switch (status) { 381 | case BackgroundGeolocationFacade.SERVICE_STARTED: 382 | sendEvent(START_EVENT, null); 383 | return; 384 | case BackgroundGeolocationFacade.SERVICE_STOPPED: 385 | sendEvent(STOP_EVENT, null); 386 | return; 387 | } 388 | } 389 | 390 | @Override 391 | public void onError(PluginException error) { 392 | sendError(error); 393 | } 394 | 395 | @Override 396 | public void onAbortRequested() { 397 | sendEvent(ABORT_REQUESTED_EVENT, null); 398 | } 399 | 400 | @Override 401 | public void onHttpAuthorization() { 402 | sendEvent(HTTP_AUTHORIZATION_EVENT, null); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /android/lib/src/main/java/com/marianhello/bgloc/react/BackgroundGeolocationPackage.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.JavaScriptModule; 9 | import com.facebook.react.bridge.NativeModule; 10 | import com.facebook.react.bridge.ReactApplicationContext; 11 | import com.facebook.react.uimanager.ViewManager; 12 | 13 | public class BackgroundGeolocationPackage implements ReactPackage { 14 | 15 | @Override 16 | public List createNativeModules(ReactApplicationContext reactContext) { 17 | List modules = new ArrayList(); 18 | modules.add(new BackgroundGeolocationModule(reactContext)); 19 | return modules; 20 | } 21 | 22 | @Override 23 | public List createViewManagers(ReactApplicationContext reactContext) { 24 | return Collections.emptyList(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/lib/src/main/java/com/marianhello/bgloc/react/ConfigMapper.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.ReadableMap; 5 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 6 | import com.facebook.react.bridge.ReadableType; 7 | import com.facebook.react.bridge.WritableMap; 8 | import com.iodine.start.ArrayUtil; 9 | import com.iodine.start.MapUtil; 10 | import com.marianhello.bgloc.Config; 11 | import com.marianhello.bgloc.data.ArrayListLocationTemplate; 12 | import com.marianhello.bgloc.data.HashMapLocationTemplate; 13 | import com.marianhello.bgloc.data.LocationTemplate; 14 | import com.marianhello.bgloc.data.LocationTemplateFactory; 15 | 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | import java.util.HashMap; 20 | import java.util.Iterator; 21 | import java.util.Map; 22 | 23 | /** 24 | * Created by finch on 29.11.2016. 25 | */ 26 | 27 | public class ConfigMapper { 28 | public static Config fromMap(ReadableMap options) throws JSONException { 29 | Config config = new Config(); 30 | if (options.hasKey("stationaryRadius")) config.setStationaryRadius((float) options.getDouble("stationaryRadius")); 31 | if (options.hasKey("distanceFilter")) config.setDistanceFilter(options.getInt("distanceFilter")); 32 | if (options.hasKey("desiredAccuracy")) config.setDesiredAccuracy(options.getInt("desiredAccuracy")); 33 | if (options.hasKey("debug")) config.setDebugging(options.getBoolean("debug")); 34 | if (options.hasKey("notificationTitle")) config.setNotificationTitle( 35 | !options.isNull("notificationTitle") ? options.getString("notificationTitle") : Config.NullString 36 | ); 37 | if (options.hasKey("notificationText")) config.setNotificationText( 38 | !options.isNull("notificationText") ? options.getString("notificationText") : Config.NullString 39 | ); 40 | if (options.hasKey("notificationIconLarge")) config.setLargeNotificationIcon( 41 | !options.isNull("notificationIconLarge") ? options.getString("notificationIconLarge") : Config.NullString 42 | ); 43 | if (options.hasKey("notificationIconSmall")) config.setSmallNotificationIcon( 44 | !options.isNull("notificationIconSmall") ? options.getString("notificationIconSmall") : Config.NullString 45 | ); 46 | if (options.hasKey("notificationIconColor")) config.setNotificationIconColor( 47 | !options.isNull("notificationIconColor") ? options.getString("notificationIconColor") : Config.NullString 48 | ); 49 | if (options.hasKey("stopOnTerminate")) config.setStopOnTerminate(options.getBoolean("stopOnTerminate")); 50 | if (options.hasKey("startOnBoot")) config.setStartOnBoot(options.getBoolean("startOnBoot")); 51 | if (options.hasKey("startForeground")) config.setStartForeground(options.getBoolean("startForeground")); 52 | if (options.hasKey("notificationsEnabled")) config.setNotificationsEnabled(options.getBoolean("notificationsEnabled")); 53 | if (options.hasKey("locationProvider")) config.setLocationProvider(options.getInt("locationProvider")); 54 | if (options.hasKey("interval")) config.setInterval(options.getInt("interval")); 55 | if (options.hasKey("fastestInterval")) config.setFastestInterval(options.getInt("fastestInterval")); 56 | if (options.hasKey("activitiesInterval")) config.setActivitiesInterval(options.getInt("activitiesInterval")); 57 | if (options.hasKey("stopOnStillActivity")) config.setStopOnStillActivity(options.getBoolean("stopOnStillActivity")); 58 | if (options.hasKey("url")) config.setUrl( 59 | !options.isNull("url") ? options.getString("url") : Config.NullString 60 | ); 61 | if (options.hasKey("syncUrl")) config.setSyncUrl( 62 | !options.isNull("syncUrl") ? options.getString("syncUrl") : Config.NullString 63 | ); 64 | if (options.hasKey("syncThreshold")) config.setSyncThreshold(options.getInt("syncThreshold")); 65 | if (options.hasKey("httpHeaders")) { 66 | HashMap httpHeaders = new HashMap(); 67 | ReadableType type = options.getType("httpHeaders"); 68 | if (type != ReadableType.Map) { 69 | throw new JSONException("httpHeaders must be object"); 70 | } 71 | JSONObject httpHeadersJson = MapUtil.toJSONObject(options.getMap("httpHeaders")); 72 | config.setHttpHeaders(httpHeadersJson); 73 | } 74 | if (options.hasKey("maxLocations")) config.setMaxLocations(options.getInt("maxLocations")); 75 | 76 | if (options.hasKey("postTemplate")) { 77 | if (options.isNull("postTemplate")) { 78 | config.setTemplate(LocationTemplateFactory.getDefault()); 79 | } else { 80 | ReadableType type = options.getType("postTemplate"); 81 | Object postTemplate = null; 82 | if (type == ReadableType.Map) { 83 | postTemplate = MapUtil.toJSONObject(options.getMap("postTemplate")); 84 | } else if (type == ReadableType.Array) { 85 | postTemplate = ArrayUtil.toJSONArray(options.getArray("postTemplate")); 86 | } 87 | config.setTemplate(LocationTemplateFactory.fromJSON(postTemplate)); 88 | } 89 | } 90 | 91 | return config; 92 | } 93 | 94 | public static ReadableMap toMap(Config config) { 95 | WritableMap out = Arguments.createMap(); 96 | WritableMap httpHeaders = Arguments.createMap(); 97 | if (config.getStationaryRadius() != null) { 98 | out.putDouble("stationaryRadius", config.getStationaryRadius()); 99 | } 100 | if (config.getDistanceFilter() != null) { 101 | out.putInt("distanceFilter", config.getDistanceFilter()); 102 | } 103 | if (config.getDesiredAccuracy() != null) { 104 | out.putInt("desiredAccuracy", config.getDesiredAccuracy()); 105 | } 106 | if (config.isDebugging() != null) { 107 | out.putBoolean("debug", config.isDebugging()); 108 | } 109 | if (config.getNotificationTitle() != null) { 110 | if (config.getNotificationTitle() != Config.NullString) { 111 | out.putString("notificationTitle", config.getNotificationTitle()); 112 | } else { 113 | out.putNull("notificationTitle"); 114 | } 115 | } 116 | if (config.getNotificationText() != null) { 117 | if (config.getNotificationText() != Config.NullString) { 118 | out.putString("notificationText", config.getNotificationText()); 119 | } else { 120 | out.putNull("notificationText"); 121 | } 122 | } 123 | if (config.getLargeNotificationIcon() != null) { 124 | if (config.getLargeNotificationIcon() != Config.NullString) { 125 | out.putString("notificationIconLarge", config.getLargeNotificationIcon()); 126 | } else { 127 | out.putNull("notificationIconLarge"); 128 | } 129 | } 130 | if (config.getSmallNotificationIcon() != null) { 131 | if (config.getSmallNotificationIcon() != Config.NullString) { 132 | out.putString("notificationIconSmall", config.getSmallNotificationIcon()); 133 | } else { 134 | out.putNull("notificationIconSmall"); 135 | } 136 | } 137 | if (config.getNotificationIconColor() != null) { 138 | if (config.getNotificationIconColor() != Config.NullString) { 139 | out.putString("notificationIconColor", config.getNotificationIconColor()); 140 | } else { 141 | out.putNull("notificationIconColor"); 142 | } 143 | } 144 | if (config.getStopOnTerminate() != null) { 145 | out.putBoolean("stopOnTerminate", config.getStopOnTerminate()); 146 | } 147 | if (config.getStartOnBoot() != null) { 148 | out.putBoolean("startOnBoot", config.getStartOnBoot()); 149 | } 150 | if (config.getStartForeground() != null) { 151 | out.putBoolean("startForeground", config.getStartForeground()); 152 | } 153 | if (config.getNotificationsEnabled() != null) { 154 | out.putBoolean("notificationsEnabled", config.getNotificationsEnabled()); 155 | } 156 | if (config.getLocationProvider() != null) { 157 | out.putInt("locationProvider", config.getLocationProvider()); 158 | } 159 | if (config.getInterval() != null) { 160 | out.putInt("interval", config.getInterval()); 161 | } 162 | if (config.getFastestInterval() != null) { 163 | out.putInt("fastestInterval", config.getFastestInterval()); 164 | } 165 | if (config.getActivitiesInterval() != null) { 166 | out.putInt("activitiesInterval", config.getActivitiesInterval()); 167 | } 168 | if (config.getStopOnStillActivity() != null) { 169 | out.putBoolean("stopOnStillActivity", config.getStopOnStillActivity()); 170 | } 171 | if (config.getUrl() != null) { 172 | if (config.getUrl() != Config.NullString) { 173 | out.putString("url", config.getUrl()); 174 | } else { 175 | out.putNull("url"); 176 | } 177 | } 178 | if (config.getSyncUrl() != null) { 179 | if (config.getSyncUrl() != Config.NullString) { 180 | out.putString("syncUrl", config.getSyncUrl()); 181 | } else { 182 | out.putNull("syncUrl"); 183 | } 184 | } 185 | if (config.getSyncThreshold() != null) { 186 | out.putInt("syncThreshold", config.getSyncThreshold()); 187 | } 188 | // httpHeaders 189 | Iterator> it = config.getHttpHeaders().entrySet().iterator(); 190 | while (it.hasNext()) { 191 | Map.Entry pair = it.next(); 192 | httpHeaders.putString(pair.getKey(), pair.getValue()); 193 | } 194 | out.putMap("httpHeaders", httpHeaders); 195 | if (config.getMaxLocations() != null) { 196 | out.putInt("maxLocations", config.getMaxLocations()); 197 | } 198 | 199 | LocationTemplate tpl = config.getTemplate(); 200 | if (tpl instanceof HashMapLocationTemplate) { 201 | Map map = ((HashMapLocationTemplate)tpl).toMap(); 202 | if (map != null) { 203 | out.putMap("postTemplate", MapUtil.toWritableMap(map)); 204 | } else { 205 | out.putNull("postTemplate"); 206 | } 207 | } else if (tpl instanceof ArrayListLocationTemplate) { 208 | Object[] keys = ((ArrayListLocationTemplate)tpl).toArray(); 209 | if (keys != null) { 210 | out.putArray("postTemplate", ArrayUtil.toWritableArray(keys)); 211 | } else { 212 | out.putNull("postTemplate"); 213 | } 214 | } 215 | return out; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /android/lib/src/main/java/com/marianhello/bgloc/react/data/LocationMapper.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react.data; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.marianhello.bgloc.data.BackgroundLocation; 6 | import com.marianhello.utils.Convert; 7 | 8 | /** 9 | * Created by finch on 29.11.2016. 10 | */ 11 | 12 | public class LocationMapper { 13 | public static WritableMap toWriteableMap(BackgroundLocation location) { 14 | WritableMap out = Arguments.createMap(); 15 | out.putString("provider", location.getProvider()); 16 | Integer locationProvider = location.getLocationProvider(); 17 | if (locationProvider != null) out.putInt("locationProvider", locationProvider); 18 | out.putDouble("time", new Long(location.getTime()).doubleValue()); 19 | out.putDouble("latitude", location.getLatitude()); 20 | out.putDouble("longitude", location.getLongitude()); 21 | if (location.hasAccuracy()) out.putDouble("accuracy", location.getAccuracy()); 22 | if (location.hasSpeed()) out.putDouble("speed", location.getSpeed()); 23 | if (location.hasAltitude()) out.putDouble("altitude", location.getAltitude()); 24 | if (location.hasBearing()) out.putDouble("bearing", location.getBearing()); 25 | if (location.hasRadius()) out.putDouble("radius", location.getRadius()); 26 | if (location.hasIsFromMockProvider()) out.putBoolean("isFromMockProvider", location.isFromMockProvider()); 27 | if (location.hasMockLocationsEnabled()) out.putBoolean("mockLocationsEnabled", location.areMockLocationsEnabled()); 28 | 29 | return out; 30 | } 31 | 32 | public static WritableMap toWriteableMapWithId(BackgroundLocation location) { 33 | WritableMap out = toWriteableMap(location); 34 | Long locationId = location.getLocationId(); 35 | if (locationId != null) out.putInt("id", Convert.safeLongToInt(locationId)); 36 | 37 | return out; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /android/lib/src/main/java/com/marianhello/bgloc/react/headless/HeadlessService.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react.headless; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | 7 | import com.facebook.react.HeadlessJsTaskService; 8 | import com.facebook.react.bridge.Arguments; 9 | import com.facebook.react.jstasks.HeadlessJsTaskConfig; 10 | 11 | public class HeadlessService extends HeadlessJsTaskService { 12 | public final String TASK_KEY = "com.marianhello.bgloc.react.headless.Task"; 13 | 14 | @Override 15 | protected @Nullable 16 | HeadlessJsTaskConfig getTaskConfig(Intent intent) { 17 | Bundle extras = intent.getExtras(); 18 | if (extras != null) { 19 | return new HeadlessJsTaskConfig( 20 | TASK_KEY, 21 | Arguments.fromBundle(extras), 22 | 60000, // timeout for the task 23 | true // optional: defines whether or not the task is allowed in foreground. Default is false 24 | ); 25 | } 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/lib/src/main/java/com/marianhello/bgloc/react/headless/HeadlessTaskRunner.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react.headless; 2 | 3 | import android.content.Intent; 4 | 5 | import com.marianhello.bgloc.headless.Task; 6 | import com.marianhello.bgloc.headless.AbstractTaskRunner; 7 | 8 | public class HeadlessTaskRunner extends AbstractTaskRunner { 9 | @Override 10 | public void runTask(Task task) { 11 | Intent service = new Intent(mContext, HeadlessService.class); 12 | service.putExtras(task.getBundle()); 13 | mContext.startService(service); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/lib/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauron85/react-native-background-geolocation/f0d434291cfae4a0e510e1e7a05d4e1e7508870e/android/lib/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/lib/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauron85/react-native-background-geolocation/f0d434291cfae4a0e510e1e7a05d4e1e7508870e/android/lib/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/lib/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauron85/react-native-background-geolocation/f0d434291cfae4a0e510e1e7a05d4e1e7508870e/android/lib/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/lib/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauron85/react-native-background-geolocation/f0d434291cfae4a0e510e1e7a05d4e1e7508870e/android/lib/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/lib/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauron85/react-native-background-geolocation/f0d434291cfae4a0e510e1e7a05d4e1e7508870e/android/lib/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/lib/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | app_name 4 | -------------------------------------------------------------------------------- /android/lib/src/main/res/xml/authenticator.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /android/lib/src/main/res/xml/syncadapter.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /android/lib/src/test/java/com/marianhello/bgloc/react/ConfigMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.marianhello.bgloc.react; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.JavaOnlyArray; 5 | import com.facebook.react.bridge.JavaOnlyMap; 6 | import com.facebook.react.bridge.ReadableMap; 7 | import com.marianhello.bgloc.Config; 8 | 9 | import junit.framework.Assert; 10 | 11 | import org.junit.Before; 12 | import org.junit.Ignore; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.mockito.invocation.InvocationOnMock; 16 | import org.mockito.stubbing.Answer; 17 | import org.powermock.api.mockito.PowerMockito; 18 | import org.powermock.core.classloader.annotations.PowerMockIgnore; 19 | import org.powermock.core.classloader.annotations.PrepareForTest; 20 | import org.robolectric.RobolectricTestRunner; 21 | 22 | /** 23 | * Running following test suite is currently NOT possible! 24 | * Run instrumented test (androidTest) instead. 25 | * 26 | * TL;DR 27 | * It requires jni native lib "reactnativejni" (libreactnativejni.so), 28 | * which with lot of effort can be built for Linux. 29 | * However "libreactnativejni.so" has dependency on "libandroid.so" which 30 | * doesn't exist for Linux. 31 | 32 | * Related links:: 33 | * [1] https://facebook.github.io/react-native/docs/building-from-source.html 34 | * [2] https://stackoverflow.com/questions/35275772/unsatisfiedlinkerror-when-unit-testing-writablenativemap/36504987#comment87006105_36504987 35 | * [3] https://stackoverflow.com/questions/50005396/libreactnativejni-so-is-built-as-elf32-i386-on-64bit-android-ndk/50007861#50007861 36 | * [5] https://github.com/SoftwareMansion/jsc-android-buildscripts 37 | */ 38 | 39 | @PrepareForTest({Arguments.class}) 40 | @RunWith(RobolectricTestRunner.class) 41 | @PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"}) 42 | public class ConfigMapperTest { 43 | @Before 44 | public void setUp() { 45 | PowerMockito.mockStatic(Arguments.class); 46 | PowerMockito.when(Arguments.createArray()).thenAnswer(new Answer() { 47 | @Override 48 | public Object answer(InvocationOnMock invocation) throws Throwable { 49 | return new JavaOnlyArray(); 50 | } 51 | }); 52 | PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer() { 53 | @Override 54 | public Object answer(InvocationOnMock invocation) throws Throwable { 55 | return new JavaOnlyMap(); 56 | } 57 | }); 58 | } 59 | 60 | @Test 61 | @Ignore 62 | public void testDefaultToJSONObject() { 63 | Config config = Config.getDefault(); 64 | ReadableMap map = ConfigMapper.toMap(config); 65 | 66 | Assert.assertEquals(config.getStationaryRadius(), map.getDouble("stationaryRadius"), 0f); 67 | Assert.assertEquals(config.getDistanceFilter().intValue(), map.getInt("distanceFilter")); 68 | Assert.assertEquals(config.getDesiredAccuracy().intValue(), map.getInt("desiredAccuracy")); 69 | Assert.assertEquals(config.isDebugging().booleanValue(), map.getBoolean("debug")); 70 | Assert.assertEquals(config.getNotificationTitle(), map.getString("notificationTitle")); 71 | Assert.assertEquals(config.getNotificationText(), map.getString("notificationText")); 72 | Assert.assertEquals(config.getStopOnTerminate().booleanValue(), map.getBoolean("stopOnTerminate")); 73 | Assert.assertEquals(config.getStartOnBoot().booleanValue(), map.getBoolean("startOnBoot")); 74 | Assert.assertEquals(config.getLocationProvider().intValue(), map.getInt("locationProvider")); 75 | Assert.assertEquals(config.getInterval().intValue(), map.getInt("interval")); 76 | Assert.assertEquals(config.getFastestInterval().intValue(), map.getInt("fastestInterval")); 77 | Assert.assertEquals(config.getActivitiesInterval().intValue(), map.getInt("activitiesInterval")); 78 | Assert.assertEquals(config.getNotificationIconColor(), map.getString("notificationIconColor")); 79 | Assert.assertEquals(config.getLargeNotificationIcon(), map.getString("notificationIconLarge")); 80 | Assert.assertEquals(config.getSmallNotificationIcon(), map.getString("notificationIconSmall")); 81 | Assert.assertEquals(config.getStartForeground().booleanValue(), map.getBoolean("startForeground")); 82 | Assert.assertEquals(config.getStopOnStillActivity().booleanValue(), map.getBoolean("stopOnStillActivity")); 83 | Assert.assertEquals(config.getUrl(), map.getString("url")); 84 | Assert.assertEquals(config.getSyncUrl(), map.getString("syncUrl")); 85 | Assert.assertEquals(config.getSyncThreshold().intValue(), map.getInt("syncThreshold")); 86 | // Assert.assertEquals(new JSONObject(config.getHttpHeaders()).toString(), map.getJSONObject("httpHeaders").toString()); 87 | Assert.assertEquals(config.getMaxLocations().intValue(), map.getInt("maxLocations")); 88 | // Assert.assertEquals(LocationTemplateFactory.getDefault().toString(), map.get("postTemplate").toString()); 89 | 90 | } 91 | } -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':lib' 2 | include ':@mauron85_react-native-background-geolocation-common' 3 | project(':@mauron85_react-native-background-geolocation-common').projectDir = new File(rootProject.projectDir, './common') -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for react-native-mauron85-background-geolocation 2 | // Project: https://github.com/mauron85/react-native-background-geolocation 3 | // Definitions by: Mauron85 (@mauron85), Norbert Györög (@djereg) 4 | // Definitions: https://github.com/mauron85/react-native-background-geolocation/blob/master/index.d.ts 5 | 6 | type Event = 'location' | 'stationary' | 'activity' | 'start' | 'stop' | 'error' | 'authorization' | 'foreground' | 'background' | 'abort_requested' | 'http_authorization'; 7 | type HeadlessTaskEventName = 'location' | 'stationary' | 'activity'; 8 | type iOSActivityType = 'AutomotiveNavigation' | 'OtherNavigation' | 'Fitness' | 'Other'; 9 | type NativeProvider = 'gps' | 'network' | 'passive' | 'fused'; 10 | type ActivityType = 'IN_VEHICLE' | 'ON_BICYCLE' | 'ON_FOOT' | 'RUNNING' | 'STILL' | 'TILTING' | 'UNKNOWN' | 'WALKING'; 11 | type LogLevel = 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR'; 12 | type LocationProvider = 0 | 1 | 2; 13 | type AuthorizationStatus = 0 | 1 | 2; 14 | type AccuracyLevel = 0 | 100 | 1000 | 10000 | number; 15 | type LocationErrorCode = 1 | 2 | 3; 16 | type ServiceMode = 0 | 1; 17 | 18 | export interface ConfigureOptions { 19 | /** 20 | * Set location provider 21 | * 22 | * Platform: all 23 | * Available providers: 24 | * DISTANCE_FILTER_PROVIDER, 25 | * ACTIVITY_PROVIDER 26 | * RAW_PROVIDER 27 | * 28 | * @default DISTANCE_FILTER_PROVIDER 29 | * @example 30 | * { locationProvider: BackgroundGeolocation.RAW_PROVIDER } 31 | */ 32 | locationProvider?: LocationProvider; 33 | 34 | /** 35 | * Desired accuracy in meters. 36 | * 37 | * Platform: all 38 | * Provider: all 39 | * Possible values: 40 | * HIGH_ACCURACY, 41 | * MEDIUM_ACCURACY, 42 | * LOW_ACCURACY, 43 | * PASSIVE_ACCURACY 44 | * Note: Accuracy has direct effect on power drain. Lower accuracy = lower power drain. 45 | * 46 | * @default MEDIUM_ACCURACY 47 | * @example 48 | * { desiredAccuracy: BackgroundGeolocation.LOW_ACCURACY } 49 | */ 50 | desiredAccuracy?: AccuracyLevel; 51 | 52 | /** 53 | * Stationary radius in meters. 54 | * 55 | * When stopped, the minimum distance the device must move beyond the stationary location for aggressive background-tracking to engage. 56 | * Platform: all 57 | * Provider: DISTANCE_FILTER 58 | * 59 | * @default 50 60 | */ 61 | stationaryRadius?: number; 62 | 63 | /** 64 | * When enabled, the plugin will emit sounds for life-cycle events of background-geolocation! See debugging sounds table. 65 | * 66 | * Platform: all 67 | * Provider: all 68 | * 69 | * @default false 70 | */ 71 | debug?: boolean; 72 | 73 | /** 74 | * The minimum distance (measured in meters) a device must move horizontally before an update event is generated. 75 | * 76 | * Platform: all 77 | * Provider: DISTANCE_FILTER, RAW 78 | * 79 | * @default 500 80 | * @see {@link https://apple.co/2oHo2CV|Apple docs} 81 | */ 82 | distanceFilter?: number; 83 | 84 | /** 85 | * Enable this in order to force a stop() when the application terminated. 86 | * E.g. on iOS, double-tap home button, swipe away the app. 87 | * 88 | * Platform: all 89 | * Provider: all 90 | * 91 | * @default true 92 | */ 93 | stopOnTerminate?: boolean; 94 | 95 | /** 96 | * Start background service on device boot. 97 | * 98 | * Platform: Android 99 | * Provider: all 100 | * 101 | * @default false 102 | */ 103 | startOnBoot?: boolean; 104 | 105 | /** 106 | * The minimum time interval between location updates in milliseconds. 107 | * 108 | * Platform: Android 109 | * Provider: all 110 | * 111 | * @default 60000 112 | * @see {@link https://bit.ly/1x00RUu|Android docs} 113 | */ 114 | interval?: number; 115 | 116 | /** 117 | * Fastest rate in milliseconds at which your app can handle location updates. 118 | * 119 | * Platform: Android 120 | * Provider: ACTIVITY 121 | * 122 | * @default 120000 123 | * @see {@link https://bit.ly/1x00RUu|Android docs} 124 | */ 125 | fastestInterval?: number; 126 | 127 | /** 128 | * Rate in milliseconds at which activity recognition occurs. 129 | * Larger values will result in fewer activity detections while improving battery life. 130 | * 131 | * Platform: Android 132 | * Provider: ACTIVITY 133 | * 134 | * @default 10000 135 | */ 136 | activitiesInterval?: number; 137 | 138 | /** 139 | * @deprecated Stop location updates, when the STILL activity is detected. 140 | */ 141 | stopOnStillActivity?: boolean; 142 | 143 | /** 144 | * Enable/disable local notifications when tracking and syncing locations. 145 | * 146 | * Platform: Android 147 | * Provider: all 148 | * 149 | * @default true 150 | */ 151 | notificationsEnabled?: boolean; 152 | 153 | /** 154 | * Allow location sync service to run in foreground state. 155 | * Foreground state also requires a notification to be presented to the user. 156 | * 157 | * Platform: Android 158 | * Provider: all 159 | * 160 | * @default false 161 | */ 162 | startForeground?: boolean; 163 | 164 | /** 165 | * Custom notification title in the drawer. 166 | * 167 | * Platform: Android 168 | * Provider: all 169 | 170 | * @default "Background tracking" 171 | */ 172 | notificationTitle?: string; 173 | 174 | /** 175 | * Custom notification text in the drawer. 176 | * 177 | * Platform: Android 178 | * Provider: all 179 | * 180 | * @default "ENABLED" 181 | */ 182 | notificationText?: string; 183 | 184 | /** 185 | * The accent color (hex triplet) to use for notification. 186 | * Eg. #4CAF50. 187 | * 188 | * Platform: Android 189 | * Provider: all 190 | */ 191 | notificationIconColor?: string; 192 | 193 | /** 194 | * The filename of a custom notification icon. 195 | * 196 | * Platform: Android 197 | * Provider: all 198 | */ 199 | notificationIconLarge?: string; 200 | 201 | /** 202 | * The filename of a custom notification icon. 203 | * 204 | * Platform: Android 205 | * Provider: all 206 | */ 207 | notificationIconSmall?: string; 208 | 209 | /** 210 | * Activity type. 211 | * Presumably, this affects iOS GPS algorithm. 212 | * 213 | * Possible values: 214 | * "AutomotiveNavigation", "OtherNavigation", "Fitness", "Other" 215 | * 216 | * Platform: iOS 217 | * Provider: all 218 | * 219 | * @default "OtherNavigation" 220 | * @see {@link https://apple.co/2oHofpH|Apple docs} 221 | */ 222 | activityType?: iOSActivityType; 223 | 224 | /** 225 | * Pauses location updates when app is paused. 226 | * 227 | * Platform: iOS 228 | * Provider: all 229 | * 230 | * @default false 231 | * @see {@link https://apple.co/2CbjEW2|Apple docs} 232 | */ 233 | pauseLocationUpdates?: boolean; 234 | 235 | /** 236 | * Switch to less accurate significant changes and region monitory when in background. 237 | * 238 | * Platform: iOS 239 | * Provider: all 240 | * 241 | * @default false 242 | */ 243 | saveBatteryOnBackground?: boolean; 244 | 245 | /** 246 | * Server url where to send HTTP POST with recorded locations 247 | * 248 | * Platform: all 249 | * Provider: all 250 | */ 251 | url?: string; 252 | 253 | /** 254 | * Server url where to send fail to post locations 255 | * 256 | * Platform: all 257 | * Provider: all 258 | */ 259 | syncUrl?: string; 260 | 261 | /** 262 | * Specifies how many previously failed locations will be sent to server at once. 263 | * 264 | * Platform: all 265 | * Provider: all 266 | * 267 | * @default 100 268 | */ 269 | syncThreshold?: string; 270 | 271 | /** 272 | * Optional HTTP headers sent along in HTTP request. 273 | * 274 | * Platform: all 275 | * Provider: all 276 | */ 277 | httpHeaders?: any; 278 | 279 | /** 280 | * Limit maximum number of locations stored into db. 281 | * 282 | * Platform: all 283 | * Provider: all 284 | * 285 | * @default 10000 286 | */ 287 | maxLocations?: number; 288 | 289 | /** 290 | * Customization post template. 291 | * 292 | * Platform: all 293 | * Provider: all 294 | */ 295 | postTemplate?: any; 296 | } 297 | 298 | export interface LocationOptions { 299 | /** 300 | * Maximum time in milliseconds device will wait for location. 301 | */ 302 | timeout?: number; 303 | 304 | /** 305 | * Maximum age in milliseconds of a possible cached location that is acceptable to return. 306 | */ 307 | maximumAge?: number; 308 | 309 | /** 310 | * If true and if the device is able to provide a more accurate position, it will do so. 311 | */ 312 | enableHighAccuracy?: boolean; 313 | } 314 | 315 | export interface Location { 316 | /** ID of location as stored in DB (or null) */ 317 | id: number; 318 | 319 | /** 320 | * Native provider reponsible for location. 321 | * 322 | * Possible values: 323 | * "gps", "network", "passive" or "fused" 324 | */ 325 | provider: NativeProvider; 326 | 327 | /** Configured location provider. */ 328 | locationProvider: number; 329 | 330 | /** UTC time of this fix, in milliseconds since January 1, 1970. */ 331 | time: number; 332 | 333 | /** Latitude, in degrees. */ 334 | latitude: number; 335 | 336 | /** Longitude, in degrees. */ 337 | longitude: number; 338 | 339 | /** Estimated accuracy of this location, in meters. */ 340 | accuracy: number; 341 | 342 | /** 343 | * Speed if it is available, in meters/second over ground. 344 | * 345 | * Note: Not all providers are capable of providing speed. 346 | * Typically network providers are not able to do so. 347 | */ 348 | speed: number; 349 | 350 | /** Altitude if available, in meters above the WGS 84 reference ellipsoid. */ 351 | altitude: number; 352 | 353 | /** Bearing, in degrees. */ 354 | bearing: number; 355 | 356 | /** 357 | * True if location was recorded by mock provider. (ANDROID ONLY) 358 | * 359 | * Note: this property is not enabled by default! 360 | * You can enable it "postTemplate" configure option. 361 | */ 362 | isFromMockProvider?: boolean; 363 | 364 | /** 365 | * True if device has mock locations enabled. (ANDROID ONLY) 366 | * 367 | * Note: this property is not enabled by default! 368 | * You can enable it "postTemplate" configure option. 369 | */ 370 | mockLocationsEnabled?: boolean; 371 | } 372 | 373 | export interface StationaryLocation extends Location { 374 | radius: number 375 | } 376 | 377 | export interface LocationError { 378 | /** 379 | * Reason of an error occurring when using the geolocating device. 380 | * 381 | * Possible error codes: 382 | * 1. PERMISSION_DENIED 383 | * 2. LOCATION_UNAVAILABLE 384 | * 3. TIMEOUT 385 | */ 386 | code: LocationErrorCode; 387 | 388 | /** Message describing the details of the error */ 389 | message: string; 390 | } 391 | 392 | export interface BackgroundGeolocationError { 393 | code: number; 394 | message: string; 395 | } 396 | 397 | export interface Activity { 398 | /** Percentage indicating the likelihood user is performing this activity. */ 399 | confidence: number; 400 | 401 | /** 402 | * Type of the activity. 403 | * 404 | * Possible values: 405 | * IN_VEHICLE, ON_BICYCLE, ON_FOOT, RUNNING, STILL, TILTING, UNKNOWN, WALKING 406 | */ 407 | type: ActivityType; 408 | } 409 | 410 | export interface ServiceStatus { 411 | /** TRUE if service is running. */ 412 | isRunning: boolean; 413 | 414 | /** TRUE if location services are enabled */ 415 | locationServicesEnabled: boolean; 416 | 417 | /** 418 | * Authorization status. 419 | * 420 | * Posible values: 421 | * NOT_AUTHORIZED, AUTHORIZED, AUTHORIZED_FOREGROUND 422 | * 423 | * @example 424 | * if (authorization == BackgroundGeolocation.NOT_AUTHORIZED) {...} 425 | */ 426 | authorization: AuthorizationStatus; 427 | } 428 | 429 | export interface LogEntry { 430 | /** ID of log entry as stored in db. */ 431 | id: number; 432 | 433 | /** Timestamp in milliseconds since beginning of UNIX epoch. */ 434 | timestamp: number; 435 | 436 | /** Log level */ 437 | level: LogLevel; 438 | 439 | /** Log message */ 440 | message: string; 441 | 442 | /** Recorded stacktrace. (Android only, on iOS part of message) */ 443 | stackTrace: string; 444 | } 445 | 446 | export interface EventSubscription { 447 | remove(): void; 448 | } 449 | 450 | export interface HeadlessTaskEvent { 451 | /** Name of the event [ "location", "stationary", "activity" ] */ 452 | name: HeadlessTaskEventName; 453 | 454 | /** Event parameters. */ 455 | params: any; 456 | } 457 | 458 | export interface BackgroundGeolocationPlugin { 459 | 460 | DISTANCE_FILTER_PROVIDER: LocationProvider; 461 | ACTIVITY_PROVIDER: LocationProvider; 462 | RAW_PROVIDER: LocationProvider; 463 | 464 | BACKGROUND_MODE: ServiceMode; 465 | FOREGROUND_MODE: ServiceMode; 466 | 467 | NOT_AUTHORIZED: AuthorizationStatus; 468 | AUTHORIZED: AuthorizationStatus; 469 | AUTHORIZED_FOREGROUND: AuthorizationStatus; 470 | 471 | HIGH_ACCURACY: AccuracyLevel; 472 | MEDIUM_ACCURACY: AccuracyLevel; 473 | LOW_ACCURACY: AccuracyLevel; 474 | PASSIVE_ACCURACY: AccuracyLevel; 475 | 476 | LOG_ERROR: LogLevel; 477 | LOG_WARN: LogLevel; 478 | LOG_INFO: LogLevel; 479 | LOG_DEBUG: LogLevel; 480 | LOG_TRACE: LogLevel; 481 | 482 | PERMISSION_DENIED: LocationErrorCode; 483 | LOCATION_UNAVAILABLE: LocationErrorCode; 484 | TIMEOUT: LocationErrorCode; 485 | 486 | events: Event[]; 487 | 488 | /** 489 | * Configure plugin. 490 | * Platform: iOS, Android 491 | * 492 | * @param options 493 | * @param success 494 | * @param fail 495 | */ 496 | configure( 497 | options: ConfigureOptions, 498 | success?: () => void, 499 | fail?: () => void 500 | ): void; 501 | 502 | /** 503 | * Start background geolocation. 504 | * Platform: iOS, Android 505 | */ 506 | start(): void; 507 | 508 | /** 509 | * Stop background geolocation. 510 | * Platform: iOS, Android 511 | */ 512 | stop(): void; 513 | 514 | /** 515 | * One time location check to get current location of the device. 516 | * 517 | * Platform: all 518 | * 519 | * @param success 520 | * @param fail 521 | * @param options 522 | */ 523 | getCurrentLocation( 524 | success: (location: Location) => void, 525 | fail?: (error: LocationError) => void | null, 526 | options?: LocationOptions 527 | ): void; 528 | 529 | /** 530 | * Returns current stationaryLocation if available. Null if not 531 | * 532 | * Platform: all 533 | * 534 | * @param success 535 | * @param fail 536 | */ 537 | getStationaryLocation( 538 | success: (location: StationaryLocation | null) => void, 539 | fail?: (error: BackgroundGeolocationError) => void, 540 | ): void; 541 | 542 | /** 543 | * Check status of the service 544 | * 545 | * @param success 546 | * @param fail 547 | */ 548 | checkStatus( 549 | success: (status: ServiceStatus) => void, 550 | fail?: (error: BackgroundGeolocationError) => void 551 | ): void; 552 | 553 | /** 554 | * Show app settings to allow change of app location permissions. 555 | * 556 | * Platform: Android >= 6, iOS >= 8.0 557 | */ 558 | showAppSettings(): void; 559 | 560 | /** 561 | * Show system settings to allow configuration of current location sources. 562 | * 563 | * Platform: Android 564 | */ 565 | showLocationSettings(): void; 566 | 567 | /** 568 | * Return all stored locations. 569 | * Useful for initial rendering of user location on a map just after application launch. 570 | * 571 | * Platform: iOS, Android 572 | * 573 | * @param success 574 | * @param fail 575 | * @see {@link https://github.com/mauron85/react-native-background-geolocation#getlocationssuccess-fail|Docs} 576 | */ 577 | getLocations( 578 | success: (locations: Location[]) => void, 579 | fail?: (error: BackgroundGeolocationError) => void 580 | ): void; 581 | 582 | /** 583 | * Method will return locations which have not yet been posted to server. 584 | * Platform: iOS, Android 585 | * @param success 586 | * @param fail 587 | * @see {@link https://github.com/mauron85/react-native-background-geolocation#getvalidlocationssuccess-fail|Docs} 588 | */ 589 | getValidLocations( 590 | success: (location: Location[]) => void, 591 | fail?: (error: BackgroundGeolocationError) => void 592 | ): void; 593 | 594 | /** 595 | * Delete location by locationId. 596 | * 597 | * Platform: iOS, Android 598 | * 599 | * @param locationId 600 | * @param success 601 | * @param fail 602 | */ 603 | deleteLocation( 604 | locationId: number, 605 | success?: () => void, 606 | fail?: (error: BackgroundGeolocationError) => void 607 | ): void; 608 | 609 | /** 610 | * Delete all stored locations. 611 | * 612 | * Platform: iOS, Android 613 | * 614 | * Note: You don't need to delete all locations. 615 | * The plugin manages the number of stored locations automatically and the total count never exceeds the number as defined by option.maxLocations. 616 | * 617 | * @param success 618 | * @param fail 619 | */ 620 | deleteAllLocations( 621 | success?: () => void, 622 | fail?: (error: BackgroundGeolocationError) => void 623 | ): void; 624 | 625 | /** 626 | * Switch plugin operation mode, 627 | * 628 | * Platform: iOS 629 | * 630 | * Normally the plugin will handle switching between BACKGROUND and FOREGROUND mode itself. 631 | * Calling switchMode you can override plugin behavior and force it to switch into other mode. 632 | * 633 | * @example 634 | * // switch to FOREGROUND mode 635 | * BackgroundGeolocation.switchMode(BackgroundGeolocation.FOREGROUND_MODE); 636 | * 637 | * // switch to BACKGROUND mode 638 | * BackgroundGeolocation.switchMode(BackgroundGeolocation.BACKGROUND_MODE); 639 | */ 640 | switchMode( 641 | modeId: ServiceMode, 642 | success?: () => void, 643 | fail?: (error: BackgroundGeolocationError) => void 644 | ): void; 645 | 646 | /** 647 | * Force sync of pending locations. 648 | * Option syncThreshold will be ignored and all pending locations will be immediately posted to syncUrl in single batch. 649 | * 650 | * Platform: Android, iOS 651 | * 652 | * @param success 653 | * @param fail 654 | */ 655 | forceSync( 656 | success?: () => void, 657 | fail?: (error: BackgroundGeolocationError) => void 658 | ): void; 659 | 660 | /** 661 | * Get stored configuration options. 662 | * 663 | * @param success 664 | * @param fail 665 | */ 666 | getConfig( 667 | success: (options: ConfigureOptions) => void, 668 | fail?: (error: BackgroundGeolocationError) => void 669 | ): void; 670 | 671 | /** 672 | * Return all logged events. Useful for plugin debugging. 673 | * 674 | * Platform: Android, iOS 675 | * 676 | * @param limit Limits number of returned entries. 677 | * @param fromId Return entries after fromId. Useful if you plan to implement infinite log scrolling 678 | * @param minLevel Available levels: ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"] 679 | * @param success 680 | * @param fail 681 | */ 682 | getLogEntries( 683 | limit: number, 684 | fromId: number, 685 | minLevel: LogLevel, 686 | success: (entries: LogEntry[]) => void, 687 | fail?: (error: BackgroundGeolocationError) => void 688 | ): void; 689 | 690 | /** 691 | * Unregister all event listeners for given event. 692 | * 693 | * If parameter event is not provided then all event listeners will be removed. 694 | * 695 | * @param event 696 | */ 697 | removeAllListeners(event?: Event): void; 698 | 699 | 700 | /** 701 | * Start background task (iOS only) 702 | * 703 | * To perform any long running operation on iOS 704 | * you need to create background task 705 | * IMPORTANT: task has to be ended by endTask 706 | * 707 | * @param success 708 | * @param fail 709 | */ 710 | startTask( 711 | success: (taskKey: number) => void, 712 | fail?: (error: BackgroundGeolocationError) => void 713 | ): void; 714 | 715 | /** 716 | * End background task indentified by taskKey (iOS only) 717 | * 718 | * @param taskKey 719 | * @param success 720 | * @param fail 721 | */ 722 | endTask( 723 | taskKey: number, 724 | success?: () => void, 725 | fail?: (error: BackgroundGeolocationError) => void 726 | ): void; 727 | 728 | /** 729 | * A special task that gets executed when the app is terminated, but 730 | * the plugin was configured to continue running in the background 731 | * (option stopOnTerminate: false). 732 | * 733 | * In this scenario the Activity was killed by the system and all registered 734 | * event listeners will not be triggered until the app is relaunched. 735 | * 736 | * @example 737 | * BackgroundGeolocation.headlessTask(function(event) { 738 | * 739 | * if (event.name === 'location' || event.name === 'stationary') { 740 | * var xhr = new XMLHttpRequest(); 741 | * xhr.open('POST', 'http://192.168.81.14:3000/headless'); 742 | * xhr.setRequestHeader('Content-Type', 'application/json'); 743 | * xhr.send(JSON.stringify(event.params)); 744 | * } 745 | * 746 | * return 'Processing event: ' + event.name; // will be logged 747 | * }); 748 | */ 749 | headlessTask( 750 | task: (event: HeadlessTaskEvent) => void 751 | ): void; 752 | 753 | /** 754 | * Register location event listener. 755 | * 756 | * @param eventName 757 | * @param callback 758 | */ 759 | on( 760 | eventName: 'location', 761 | callback: (location: Location) => void 762 | ): void; 763 | 764 | /** 765 | * Register stationary location event listener. 766 | * 767 | * @param eventName 768 | * @param callback 769 | */ 770 | on( 771 | eventName: 'stationary', 772 | callback: (location: StationaryLocation) => void 773 | ): void; 774 | 775 | /** 776 | * Register activity monitoring listener. 777 | * 778 | * @param eventName 779 | * @param callback 780 | */ 781 | on( 782 | eventName: 'activity', 783 | callback: (activity: Activity) => void 784 | ): void; 785 | 786 | /** 787 | * Register start event listener. 788 | * 789 | * Event is triggered when background service has been started succesfully. 790 | * 791 | * @param eventName 792 | * @param callback 793 | */ 794 | on( 795 | eventName: 'start', 796 | callback: () => void 797 | ): void; 798 | 799 | /** 800 | * Register stop event listener. 801 | * 802 | * Triggered when background service has been stopped succesfully. 803 | * 804 | * @param eventName 805 | * @param callback 806 | */ 807 | on( 808 | eventName: 'stop', 809 | callback: () => void 810 | ): void; 811 | 812 | /** 813 | * Register error listener. 814 | * 815 | * @param eventName 816 | * @param callback 817 | */ 818 | on( 819 | eventName: 'error', 820 | callback: (error: BackgroundGeolocationError) => void 821 | ): void; 822 | 823 | /** 824 | * Register authorization listener. 825 | * 826 | * Triggered when user changes authorization/permissions for 827 | * the app or toggles location services. 828 | * 829 | * @param eventName 830 | * @param callback 831 | */ 832 | on( 833 | eventName: 'authorization', 834 | callback: (status: AuthorizationStatus) => void 835 | ): void; 836 | 837 | /** 838 | * Register foreground event listener. 839 | * 840 | * Triggered when app entered foreground state and (visible to the user). 841 | * 842 | * @param eventName 843 | * @param callback 844 | */ 845 | on( 846 | eventName: 'foreground', 847 | callback: () => void 848 | ): void; 849 | 850 | /** 851 | * Register background event listener. 852 | * 853 | * Triggered when app entered background state and (not visible to the user). 854 | * 855 | * @param eventName 856 | * @param callback 857 | */ 858 | on( 859 | eventName: 'background', 860 | callback: () => void 861 | ): void; 862 | 863 | /** 864 | * Register abort_requested event listener. 865 | * 866 | * Triggered when server responded with "285 Updates Not Required" to post/sync request. 867 | * 868 | * @param eventName 869 | * @param callback 870 | */ 871 | on( 872 | eventName: 'abort_requested', 873 | callback: () => void 874 | ): void; 875 | 876 | /** 877 | * Register http_authorization event listener. 878 | * 879 | * Triggered when server responded with "401 Unauthorized" to post/sync request. 880 | * 881 | * @param eventName 882 | * @param callback 883 | */ 884 | on( 885 | eventName: 'http_authorization', 886 | callback: () => void 887 | ): void; 888 | 889 | } 890 | 891 | declare const BackgroundGeolocation: BackgroundGeolocationPlugin; 892 | 893 | export default BackgroundGeolocation; 894 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var { DeviceEventEmitter, NativeModules, AppRegistry } = require('react-native'); 4 | var RNBackgroundGeolocation = NativeModules.BackgroundGeolocation; 5 | var TAG = 'RNBackgroundGeolocation'; 6 | var TASK_KEY = 'com.marianhello.bgloc.react.headless.Task'; 7 | 8 | function emptyFn() {} 9 | function defaultErrorHandler(error) { 10 | var cause = error.cause || {}; 11 | var causeMessage = cause.message; 12 | throw TAG + ': ' + error.message + (causeMessage ? ': ' + cause.message : ''); 13 | } 14 | 15 | var BackgroundGeolocation = { 16 | events: [ 17 | 'location', 18 | 'stationary', 19 | 'activity', 20 | 'start', 21 | 'stop', 22 | 'error', 23 | 'authorization', 24 | 'foreground', 25 | 'background', 26 | 'abort_requested', 27 | 'http_authorization' 28 | ], 29 | 30 | DISTANCE_FILTER_PROVIDER: 0, 31 | ACTIVITY_PROVIDER: 1, 32 | RAW_PROVIDER: 2, 33 | 34 | BACKGROUND_MODE: 0, 35 | FOREGROUND_MODE: 1, 36 | 37 | NOT_AUTHORIZED: 0, 38 | AUTHORIZED: 1, 39 | AUTHORIZED_FOREGROUND: 2, 40 | 41 | HIGH_ACCURACY: 0, 42 | MEDIUM_ACCURACY: 100, 43 | LOW_ACCURACY: 1000, 44 | PASSIVE_ACCURACY: 10000, 45 | 46 | LOG_ERROR: 'ERROR', 47 | LOG_WARN: 'WARN', 48 | LOG_INFO: 'INFO', 49 | LOG_DEBUG: 'DEBUG', 50 | LOG_TRACE: 'TRACE', 51 | 52 | PERMISSION_DENIED: 1, 53 | LOCATION_UNAVAILABLE: 2, 54 | TIMEOUT: 3, 55 | 56 | configure: function(config, successFn, errorFn) { 57 | successFn = successFn || emptyFn; 58 | errorFn = errorFn || defaultErrorHandler; 59 | RNBackgroundGeolocation.configure(config, successFn, errorFn); 60 | }, 61 | 62 | start: function() { 63 | RNBackgroundGeolocation.start(); 64 | }, 65 | 66 | stop: function() { 67 | RNBackgroundGeolocation.stop(); 68 | }, 69 | 70 | checkStatus: function(successFn, errorFn) { 71 | successFn = successFn || emptyFn; 72 | errorFn = errorFn || emptyFn; 73 | RNBackgroundGeolocation.checkStatus(successFn, errorFn); 74 | }, 75 | 76 | showAppSettings: function() { 77 | RNBackgroundGeolocation.showAppSettings(); 78 | }, 79 | 80 | showLocationSettings: function() { 81 | RNBackgroundGeolocation.showLocationSettings(); 82 | }, 83 | 84 | /** 85 | * Returns current stationaryLocation if available. null if not 86 | */ 87 | getStationaryLocation: function (successFn, errorFn) { 88 | successFn = successFn || emptyFn; 89 | errorFn = errorFn || emptyFn; 90 | RNBackgroundGeolocation.getStationaryLocation(successFn, errorFn); 91 | }, 92 | 93 | getCurrentLocation: function(successFn, errorFn, options) { 94 | options = options || {}; 95 | successFn = successFn || emptyFn; 96 | errorFn = errorFn || emptyFn; 97 | RNBackgroundGeolocation.getCurrentLocation(options, successFn, errorFn); 98 | }, 99 | 100 | getLocations: function(successFn, errorFn) { 101 | successFn = successFn || emptyFn; 102 | errorFn = errorFn || emptyFn; 103 | RNBackgroundGeolocation.getLocations(successFn, errorFn); 104 | }, 105 | 106 | getValidLocations: function(successFn, errorFn) { 107 | successFn = successFn || emptyFn; 108 | errorFn = errorFn || emptyFn; 109 | RNBackgroundGeolocation.getValidLocations(successFn, errorFn); 110 | }, 111 | 112 | deleteLocation: function(locationId, successFn, errorFn) { 113 | successFn = successFn || emptyFn; 114 | errorFn = errorFn || emptyFn; 115 | RNBackgroundGeolocation.deleteLocation(locationId, successFn, errorFn); 116 | }, 117 | 118 | deleteAllLocations: function(successFn, errorFn) { 119 | successFn = successFn || emptyFn; 120 | errorFn = errorFn || emptyFn; 121 | RNBackgroundGeolocation.deleteAllLocations(successFn, errorFn); 122 | }, 123 | 124 | switchMode: function(modeId, successFn, errorFn) { 125 | successFn = successFn || emptyFn; 126 | errorFn = errorFn || emptyFn; 127 | RNBackgroundGeolocation.switchMode(modeId, successFn, errorFn); 128 | }, 129 | 130 | getConfig: function(successFn, errorFn) { 131 | successFn = successFn || emptyFn; 132 | errorFn = errorFn || emptyFn; 133 | RNBackgroundGeolocation.getConfig(successFn, errorFn); 134 | }, 135 | 136 | getLogEntries: function(limit, /* offset = 0, minLevel = "DEBUG", successFn = emptyFn, errorFn = emptyFn */) { 137 | var acnt = arguments.length; 138 | var offset, minLevel, successFn, errorFn; 139 | 140 | if (acnt > 1 && typeof arguments[1] == 'function') { 141 | // backward compatibility 142 | console.log('[WARN]: Calling deprecated variant of getLogEntries method.'); 143 | offset = 0; 144 | minLevel = BackgroundGeolocation.LOG_DEBUG; 145 | successFn = arguments[1] || emptyFn; 146 | errorFn = arguments[2] || emptyFn; 147 | } else { 148 | offset = acnt > 1 && arguments[1] !== undefined ? arguments[1] : 0; 149 | minLevel = acnt > 2 && arguments[2] !== undefined ? arguments[2] : BackgroundGeolocation.LOG_DEBUG; 150 | successFn = acnt > 3 && arguments[3] !== undefined ? arguments[3] : emptyFn; 151 | errorFn = acnt > 4 && arguments[4] !== undefined ? arguments[4] : emptyFn; 152 | } 153 | 154 | RNBackgroundGeolocation.getLogEntries(limit, offset, minLevel, successFn, errorFn); 155 | }, 156 | 157 | startTask: function(callbackFn) { 158 | if (typeof callbackFn !== 'function') { 159 | throw 'RNBackgroundGeolocation: startTask requires callback function'; 160 | } 161 | 162 | if (typeof RNBackgroundGeolocation.startTask === 'function') { 163 | RNBackgroundGeolocation.startTask(callbackFn); 164 | } else { 165 | // android does not need background tasks so we invoke callbackFn directly 166 | callbackFn(-1); 167 | } 168 | }, 169 | 170 | endTask: function(taskKey) { 171 | if (typeof RNBackgroundGeolocation.endTask === 'function') { 172 | RNBackgroundGeolocation.endTask(taskKey); 173 | } else { 174 | // noop 175 | } 176 | }, 177 | 178 | headlessTask: function(task, successFn, errorFn) { 179 | successFn = successFn || emptyFn; 180 | errorFn = errorFn || emptyFn; 181 | AppRegistry.registerHeadlessTask(TASK_KEY, () => task); 182 | RNBackgroundGeolocation.registerHeadlessTask(successFn, errorFn); 183 | }, 184 | 185 | forceSync: function(successFn, errorFn) { 186 | successFn = successFn || emptyFn; 187 | errorFn = errorFn || emptyFn; 188 | RNBackgroundGeolocation.forceSync(successFn, errorFn); 189 | }, 190 | 191 | on: function(event, callbackFn) { 192 | if (typeof callbackFn !== 'function') { 193 | throw TAG + ': callback function must be provided'; 194 | } 195 | if (this.events.indexOf(event) < 0) { 196 | throw TAG + ': Unknown event "' + event + '"'; 197 | } 198 | 199 | return DeviceEventEmitter.addListener(event, callbackFn); 200 | }, 201 | 202 | removeAllListeners: function(event) { 203 | if (!event) { 204 | this.events.forEach(function(event) { 205 | DeviceEventEmitter.removeAllListeners(event); 206 | }); 207 | return void 0; 208 | } 209 | if (this.events.indexOf(event) < 0) { 210 | console.log('[WARN] ' + TAG + ': removeAllListeners for unknown event "' + event + '"'); 211 | return void 0; 212 | } 213 | 214 | DeviceEventEmitter.removeAllListeners(event); 215 | return void 0; 216 | } 217 | }; 218 | 219 | module.exports = BackgroundGeolocation; -------------------------------------------------------------------------------- /ios/RCTBackgroundGeolocation.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4164B01620419D4000D7AC50 /* libBackgroundGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4164B00B20419C5800D7AC50 /* libBackgroundGeolocation.a */; }; 11 | 418F754A1D02DC3D0045FEA0 /* RCTBackgroundGeolocation.h in Copy Files */ = {isa = PBXBuildFile; fileRef = 418F75491D02DC3D0045FEA0 /* RCTBackgroundGeolocation.h */; }; 12 | 418F754C1D02DC3D0045FEA0 /* RCTBackgroundGeolocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 418F754B1D02DC3D0045FEA0 /* RCTBackgroundGeolocation.m */; }; 13 | 41E82EA11D5E4F9E00F65575 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 41E82EA01D5E4F9E00F65575 /* CoreLocation.framework */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXContainerItemProxy section */ 17 | 4164B00A20419C5800D7AC50 /* PBXContainerItemProxy */ = { 18 | isa = PBXContainerItemProxy; 19 | containerPortal = 4164B00520419C5800D7AC50 /* BackgroundGeolocation.xcodeproj */; 20 | proxyType = 2; 21 | remoteGlobalIDString = 418F75461D02DC3D0045FEA0; 22 | remoteInfo = BackgroundGeolocation; 23 | }; 24 | 4164B00C20419C5800D7AC50 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 4164B00520419C5800D7AC50 /* BackgroundGeolocation.xcodeproj */; 27 | proxyType = 2; 28 | remoteGlobalIDString = 414898261FB593B900323454; 29 | remoteInfo = BackgroundGeolocationTests; 30 | }; 31 | 4164B00E20419C6100D7AC50 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 4164B00520419C5800D7AC50 /* BackgroundGeolocation.xcodeproj */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 418F75451D02DC3D0045FEA0; 36 | remoteInfo = BackgroundGeolocation; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXCopyFilesBuildPhase section */ 41 | 418F75441D02DC3D0045FEA0 /* Copy Files */ = { 42 | isa = PBXCopyFilesBuildPhase; 43 | buildActionMask = 2147483647; 44 | dstPath = "include/$(PRODUCT_NAME)"; 45 | dstSubfolderSpec = 16; 46 | files = ( 47 | 418F754A1D02DC3D0045FEA0 /* RCTBackgroundGeolocation.h in Copy Files */, 48 | ); 49 | name = "Copy Files"; 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXCopyFilesBuildPhase section */ 53 | 54 | /* Begin PBXFileReference section */ 55 | 4164B00520419C5800D7AC50 /* BackgroundGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BackgroundGeolocation.xcodeproj; path = common/BackgroundGeolocation.xcodeproj; sourceTree = ""; }; 56 | 418F75461D02DC3D0045FEA0 /* libRCTBackgroundGeolocation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTBackgroundGeolocation.a; sourceTree = BUILT_PRODUCTS_DIR; }; 57 | 418F75491D02DC3D0045FEA0 /* RCTBackgroundGeolocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBackgroundGeolocation.h; sourceTree = ""; }; 58 | 418F754B1D02DC3D0045FEA0 /* RCTBackgroundGeolocation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RCTBackgroundGeolocation.m; sourceTree = ""; }; 59 | 41E82EA01D5E4F9E00F65575 /* CoreLocation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreLocation.framework; path = System/Library/Frameworks/CoreLocation.framework; sourceTree = SDKROOT; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 418F75431D02DC3D0045FEA0 /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 4164B01620419D4000D7AC50 /* libBackgroundGeolocation.a in Frameworks */, 68 | 41E82EA11D5E4F9E00F65575 /* CoreLocation.framework in Frameworks */, 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | 4161C06C201B8FB8007761EF /* Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | 4164B00620419C5800D7AC50 /* Products */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 4164B00B20419C5800D7AC50 /* libBackgroundGeolocation.a */, 86 | 4164B00D20419C5800D7AC50 /* BackgroundGeolocationTests.xctest */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 418F753D1D02DC3D0045FEA0 = { 92 | isa = PBXGroup; 93 | children = ( 94 | 4164B00520419C5800D7AC50 /* BackgroundGeolocation.xcodeproj */, 95 | 41E82EA01D5E4F9E00F65575 /* CoreLocation.framework */, 96 | 418F75481D02DC3D0045FEA0 /* RCTBackgroundGeolocation */, 97 | 418F75471D02DC3D0045FEA0 /* Products */, 98 | 4161C06C201B8FB8007761EF /* Frameworks */, 99 | ); 100 | sourceTree = ""; 101 | }; 102 | 418F75471D02DC3D0045FEA0 /* Products */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 418F75461D02DC3D0045FEA0 /* libRCTBackgroundGeolocation.a */, 106 | ); 107 | name = Products; 108 | sourceTree = ""; 109 | }; 110 | 418F75481D02DC3D0045FEA0 /* RCTBackgroundGeolocation */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 418F75491D02DC3D0045FEA0 /* RCTBackgroundGeolocation.h */, 114 | 418F754B1D02DC3D0045FEA0 /* RCTBackgroundGeolocation.m */, 115 | ); 116 | path = RCTBackgroundGeolocation; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 418F75451D02DC3D0045FEA0 /* RCTBackgroundGeolocation */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 418F754F1D02DC3D0045FEA0 /* Build configuration list for PBXNativeTarget "RCTBackgroundGeolocation" */; 125 | buildPhases = ( 126 | 418F75421D02DC3D0045FEA0 /* Sources */, 127 | 418F75431D02DC3D0045FEA0 /* Frameworks */, 128 | 418F75441D02DC3D0045FEA0 /* Copy Files */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | 4164B00F20419C6100D7AC50 /* PBXTargetDependency */, 134 | ); 135 | name = RCTBackgroundGeolocation; 136 | productName = RCTBackgroundGeolocation; 137 | productReference = 418F75461D02DC3D0045FEA0 /* libRCTBackgroundGeolocation.a */; 138 | productType = "com.apple.product-type.library.static"; 139 | }; 140 | /* End PBXNativeTarget section */ 141 | 142 | /* Begin PBXProject section */ 143 | 418F753E1D02DC3D0045FEA0 /* Project object */ = { 144 | isa = PBXProject; 145 | attributes = { 146 | LastUpgradeCheck = 0730; 147 | ORGANIZATIONNAME = mauron85; 148 | TargetAttributes = { 149 | 418F75451D02DC3D0045FEA0 = { 150 | CreatedOnToolsVersion = 7.3.1; 151 | }; 152 | }; 153 | }; 154 | buildConfigurationList = 418F75411D02DC3D0045FEA0 /* Build configuration list for PBXProject "RCTBackgroundGeolocation" */; 155 | compatibilityVersion = "Xcode 3.2"; 156 | developmentRegion = English; 157 | hasScannedForEncodings = 0; 158 | knownRegions = ( 159 | en, 160 | ); 161 | mainGroup = 418F753D1D02DC3D0045FEA0; 162 | productRefGroup = 418F75471D02DC3D0045FEA0 /* Products */; 163 | projectDirPath = ""; 164 | projectReferences = ( 165 | { 166 | ProductGroup = 4164B00620419C5800D7AC50 /* Products */; 167 | ProjectRef = 4164B00520419C5800D7AC50 /* BackgroundGeolocation.xcodeproj */; 168 | }, 169 | ); 170 | projectRoot = ""; 171 | targets = ( 172 | 418F75451D02DC3D0045FEA0 /* RCTBackgroundGeolocation */, 173 | ); 174 | }; 175 | /* End PBXProject section */ 176 | 177 | /* Begin PBXReferenceProxy section */ 178 | 4164B00B20419C5800D7AC50 /* libBackgroundGeolocation.a */ = { 179 | isa = PBXReferenceProxy; 180 | fileType = archive.ar; 181 | path = libBackgroundGeolocation.a; 182 | remoteRef = 4164B00A20419C5800D7AC50 /* PBXContainerItemProxy */; 183 | sourceTree = BUILT_PRODUCTS_DIR; 184 | }; 185 | 4164B00D20419C5800D7AC50 /* BackgroundGeolocationTests.xctest */ = { 186 | isa = PBXReferenceProxy; 187 | fileType = wrapper.cfbundle; 188 | path = BackgroundGeolocationTests.xctest; 189 | remoteRef = 4164B00C20419C5800D7AC50 /* PBXContainerItemProxy */; 190 | sourceTree = BUILT_PRODUCTS_DIR; 191 | }; 192 | /* End PBXReferenceProxy section */ 193 | 194 | /* Begin PBXSourcesBuildPhase section */ 195 | 418F75421D02DC3D0045FEA0 /* Sources */ = { 196 | isa = PBXSourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 418F754C1D02DC3D0045FEA0 /* RCTBackgroundGeolocation.m in Sources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXSourcesBuildPhase section */ 204 | 205 | /* Begin PBXTargetDependency section */ 206 | 4164B00F20419C6100D7AC50 /* PBXTargetDependency */ = { 207 | isa = PBXTargetDependency; 208 | name = BackgroundGeolocation; 209 | targetProxy = 4164B00E20419C6100D7AC50 /* PBXContainerItemProxy */; 210 | }; 211 | /* End PBXTargetDependency section */ 212 | 213 | /* Begin XCBuildConfiguration section */ 214 | 418F754D1D02DC3D0045FEA0 /* Debug */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | ALWAYS_SEARCH_USER_PATHS = NO; 218 | CLANG_ANALYZER_NONNULL = YES; 219 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 220 | CLANG_CXX_LIBRARY = "libc++"; 221 | CLANG_ENABLE_MODULES = YES; 222 | CLANG_ENABLE_OBJC_ARC = YES; 223 | CLANG_WARN_BOOL_CONVERSION = YES; 224 | CLANG_WARN_CONSTANT_CONVERSION = YES; 225 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 226 | CLANG_WARN_EMPTY_BODY = YES; 227 | CLANG_WARN_ENUM_CONVERSION = YES; 228 | CLANG_WARN_INT_CONVERSION = YES; 229 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 230 | CLANG_WARN_UNREACHABLE_CODE = YES; 231 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 232 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 233 | COPY_PHASE_STRIP = NO; 234 | DEBUG_INFORMATION_FORMAT = dwarf; 235 | ENABLE_STRICT_OBJC_MSGSEND = YES; 236 | ENABLE_TESTABILITY = YES; 237 | GCC_C_LANGUAGE_STANDARD = gnu99; 238 | GCC_DYNAMIC_NO_PIC = NO; 239 | GCC_NO_COMMON_BLOCKS = YES; 240 | GCC_OPTIMIZATION_LEVEL = 0; 241 | GCC_PREPROCESSOR_DEFINITIONS = ( 242 | "DEBUG=1", 243 | "$(inherited)", 244 | ); 245 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 246 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 247 | GCC_WARN_UNDECLARED_SELECTOR = YES; 248 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 249 | GCC_WARN_UNUSED_FUNCTION = YES; 250 | GCC_WARN_UNUSED_VARIABLE = YES; 251 | HEADER_SEARCH_PATHS = "${PROJECT_DIR}/**"; 252 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 253 | MTL_ENABLE_DEBUG_INFO = YES; 254 | ONLY_ACTIVE_ARCH = YES; 255 | SDKROOT = iphoneos; 256 | }; 257 | name = Debug; 258 | }; 259 | 418F754E1D02DC3D0045FEA0 /* Release */ = { 260 | isa = XCBuildConfiguration; 261 | buildSettings = { 262 | ALWAYS_SEARCH_USER_PATHS = NO; 263 | CLANG_ANALYZER_NONNULL = YES; 264 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 265 | CLANG_CXX_LIBRARY = "libc++"; 266 | CLANG_ENABLE_MODULES = YES; 267 | CLANG_ENABLE_OBJC_ARC = YES; 268 | CLANG_WARN_BOOL_CONVERSION = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INT_CONVERSION = YES; 274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 275 | CLANG_WARN_UNREACHABLE_CODE = YES; 276 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 277 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 278 | COPY_PHASE_STRIP = NO; 279 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 280 | ENABLE_NS_ASSERTIONS = NO; 281 | ENABLE_STRICT_OBJC_MSGSEND = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_NO_COMMON_BLOCKS = YES; 284 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 285 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 286 | GCC_WARN_UNDECLARED_SELECTOR = YES; 287 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 288 | GCC_WARN_UNUSED_FUNCTION = YES; 289 | GCC_WARN_UNUSED_VARIABLE = YES; 290 | HEADER_SEARCH_PATHS = "${PROJECT_DIR}/**"; 291 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 292 | MTL_ENABLE_DEBUG_INFO = NO; 293 | SDKROOT = iphoneos; 294 | VALIDATE_PRODUCT = YES; 295 | }; 296 | name = Release; 297 | }; 298 | 418F75501D02DC3D0045FEA0 /* Debug */ = { 299 | isa = XCBuildConfiguration; 300 | buildSettings = { 301 | HEADER_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "$(SRCROOT)/../../../React/**", 304 | "$(SRCROOT)/../../react-native/React/**", 305 | "$(SRCROOT)/../../../../ios/Pods/Headers/Public/**", 306 | ); 307 | OTHER_LDFLAGS = "-ObjC"; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SKIP_INSTALL = YES; 310 | }; 311 | name = Debug; 312 | }; 313 | 418F75511D02DC3D0045FEA0 /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | HEADER_SEARCH_PATHS = ( 317 | "$(inherited)", 318 | "$(SRCROOT)/../../../React/**", 319 | "$(SRCROOT)/../../react-native/React/**", 320 | "$(SRCROOT)/../../../../ios/Pods/Headers/Public/**", 321 | ); 322 | OTHER_LDFLAGS = "-ObjC"; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | SKIP_INSTALL = YES; 325 | }; 326 | name = Release; 327 | }; 328 | /* End XCBuildConfiguration section */ 329 | 330 | /* Begin XCConfigurationList section */ 331 | 418F75411D02DC3D0045FEA0 /* Build configuration list for PBXProject "RCTBackgroundGeolocation" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 418F754D1D02DC3D0045FEA0 /* Debug */, 335 | 418F754E1D02DC3D0045FEA0 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | 418F754F1D02DC3D0045FEA0 /* Build configuration list for PBXNativeTarget "RCTBackgroundGeolocation" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 418F75501D02DC3D0045FEA0 /* Debug */, 344 | 418F75511D02DC3D0045FEA0 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | /* End XCConfigurationList section */ 350 | }; 351 | rootObject = 418F753E1D02DC3D0045FEA0 /* Project object */; 352 | } 353 | -------------------------------------------------------------------------------- /ios/RCTBackgroundGeolocation/RCTBackgroundGeolocation.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTBackgroundGeolocation.h 3 | // RCTBackgroundGeolocation 4 | // 5 | // Created by Marian Hello on 04/06/16. 6 | // Copyright © 2016 mauron85. All rights reserved. 7 | // 8 | 9 | #import 10 | #if __has_include() 11 | #import 12 | #else 13 | #import "RCTBridgeModule.h" 14 | #endif 15 | #import "MAURProviderDelegate.h" 16 | 17 | @interface RCTBackgroundGeolocation : NSObject 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ios/RCTBackgroundGeolocation/RCTBackgroundGeolocation.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTBackgroundGeolocation.m 3 | // RCTBackgroundGeolocation 4 | // 5 | // Created by Marian Hello on 04/06/16. 6 | // Copyright © 2016 mauron85. All rights reserved. 7 | // 8 | 9 | #import "RCTBackgroundGeolocation.h" 10 | #if __has_include("RCTLog.h") 11 | #import "RCTLog.h" 12 | #else 13 | #import 14 | #endif 15 | #if __has_include("RCTEventDispatcher.h") 16 | #import "RCTEventDispatcher.h" 17 | #else 18 | #import 19 | #endif 20 | #import "MAURConfig.h" 21 | #import "MAURBackgroundGeolocationFacade.h" 22 | #import "MAURBackgroundTaskManager.h" 23 | 24 | #define isNull(value) value == nil || [value isKindOfClass:[NSNull class]] 25 | 26 | @implementation RCTBackgroundGeolocation { 27 | MAURBackgroundGeolocationFacade* facade; 28 | 29 | API_AVAILABLE(ios(10.0)) 30 | __weak id prevNotificationDelegate; 31 | } 32 | 33 | @synthesize bridge = _bridge; 34 | 35 | RCT_EXPORT_MODULE(); 36 | 37 | 38 | -(instancetype)init 39 | { 40 | self = [super init]; 41 | if (self) { 42 | facade = [[MAURBackgroundGeolocationFacade alloc] init]; 43 | facade.delegate = self; 44 | 45 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppPause:) name:UIApplicationDidEnterBackgroundNotification object:nil]; 46 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppResume:) name:UIApplicationWillEnterForegroundNotification object:nil]; 47 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onFinishLaunching:) name:UIApplicationDidFinishLaunchingNotification object:nil]; 48 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onAppTerminate:) name:UIApplicationWillTerminateNotification object:nil]; 49 | 50 | // HACK: it seems to be too late to register on launch observer so trigger it manually 51 | [self onFinishLaunching:nil]; 52 | } 53 | 54 | return self; 55 | } 56 | 57 | /** 58 | * configure plugin 59 | */ 60 | RCT_EXPORT_METHOD(configure:(NSDictionary*)configDictionary success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 61 | { 62 | RCTLogInfo(@"RCTBackgroundGeolocation #configure"); 63 | MAURConfig* config = [MAURConfig fromDictionary:configDictionary]; 64 | NSError *error = nil; 65 | 66 | if ([facade configure:config error:&error]) { 67 | success(@[[NSNull null]]); 68 | } else { 69 | NSDictionary *dict = [self errorToDictionary:MAURBGConfigureError message:@"Configuration error" cause:error]; 70 | failure(@[dict]); 71 | } 72 | } 73 | 74 | RCT_EXPORT_METHOD(start) 75 | { 76 | RCTLogInfo(@"RCTBackgroundGeolocation #start"); 77 | NSError *error = nil; 78 | [facade start:&error]; 79 | 80 | if (error == nil) { 81 | [self sendEvent:@"start"]; 82 | } else { 83 | [self sendError:error]; 84 | } 85 | } 86 | 87 | RCT_EXPORT_METHOD(stop) 88 | { 89 | RCTLogInfo(@"RCTBackgroundGeolocation #stop"); 90 | NSError *error = nil; 91 | [facade stop:&error]; 92 | 93 | if (error == nil) { 94 | [self sendEvent:@"stop"]; 95 | } else { 96 | [self sendError:error]; 97 | } 98 | } 99 | 100 | RCT_EXPORT_METHOD(switchMode:(NSNumber*)mode success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 101 | { 102 | RCTLogInfo(@"RCTBackgroundGeolocation #switchMode"); 103 | [facade switchMode:[mode integerValue]]; 104 | } 105 | 106 | RCT_EXPORT_METHOD(isLocationEnabled:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 107 | { 108 | RCTLogInfo(@"RCTBackgroundGeolocation #isLocationEnabled"); 109 | success(@[@([facade locationServicesEnabled])]); 110 | } 111 | 112 | RCT_EXPORT_METHOD(showAppSettings) 113 | { 114 | RCTLogInfo(@"RCTBackgroundGeolocation #showAppSettings"); 115 | [facade showAppSettings]; 116 | } 117 | 118 | RCT_EXPORT_METHOD(showLocationSettings) 119 | { 120 | RCTLogInfo(@"RCTBackgroundGeolocation #showLocationSettings"); 121 | [facade showLocationSettings]; 122 | } 123 | 124 | RCT_EXPORT_METHOD(getLocations:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 125 | { 126 | RCTLogInfo(@"RCTBackgroundGeolocation #getLocations"); 127 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 128 | NSArray *locations = [facade getLocations]; 129 | NSMutableArray* dictionaryLocations = [[NSMutableArray alloc] initWithCapacity:[locations count]]; 130 | for (MAURLocation* location in locations) { 131 | [dictionaryLocations addObject:[location toDictionaryWithId]]; 132 | } 133 | success(@[dictionaryLocations]); 134 | }); 135 | } 136 | 137 | RCT_EXPORT_METHOD(getValidLocations:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 138 | { 139 | RCTLogInfo(@"RCTBackgroundGeolocation #getValidLocations"); 140 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 141 | NSArray *locations = [facade getValidLocations]; 142 | NSMutableArray* dictionaryLocations = [[NSMutableArray alloc] initWithCapacity:[locations count]]; 143 | for (MAURLocation* location in locations) { 144 | [dictionaryLocations addObject:[location toDictionaryWithId]]; 145 | } 146 | success(@[dictionaryLocations]); 147 | }); 148 | } 149 | 150 | RCT_EXPORT_METHOD(deleteLocation:(int)locationId success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 151 | { 152 | RCTLogInfo(@"RCTBackgroundGeolocation #deleteLocation"); 153 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 154 | NSError *error = nil; 155 | BOOL result = [facade deleteLocation:[NSNumber numberWithInt:locationId] error:&error]; 156 | if (result) { 157 | success(@[[NSNull null]]); 158 | } else { 159 | NSDictionary *dict = [self errorToDictionary:MAURBGServiceError message:@"Failed to delete location" cause:error]; 160 | failure(@[dict]); 161 | } 162 | }); 163 | } 164 | 165 | RCT_EXPORT_METHOD(deleteAllLocations:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 166 | { 167 | RCTLogInfo(@"RCTBackgroundGeolocation #deleteAllLocations"); 168 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 169 | NSError *error = nil; 170 | BOOL result = [facade deleteAllLocations:&error]; 171 | if (result) { 172 | success(@[[NSNull null]]); 173 | } else { 174 | NSDictionary *dict = [self errorToDictionary:MAURBGServiceError message:@"Failed to delete locations" cause:error]; 175 | failure(@[dict]); 176 | } 177 | }); 178 | } 179 | 180 | RCT_EXPORT_METHOD(getCurrentLocation:(NSDictionary*)options success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 181 | { 182 | RCTLogInfo(@"RCTBackgroundGeolocation #getCurrentLocation"); 183 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 184 | NSError *error = nil; 185 | NSNumber *timeout = [options objectForKey:@"timeout"] ?: [NSNumber numberWithInt:INT_MAX]; 186 | NSNumber *maximumAge = [options objectForKey:@"maximumAge"] ?: [NSNumber numberWithLong:LONG_MAX]; 187 | NSNumber *enableHighAccuracy = [options objectForKey:@"enableHighAccuracy"] ?: [NSNumber numberWithBool:NO]; 188 | 189 | MAURLocation *location = [facade getCurrentLocation:timeout.intValue maximumAge:maximumAge.longValue enableHighAccuracy:enableHighAccuracy.boolValue error:&error]; 190 | if (location != nil) { 191 | success(@[[location toDictionary]]); 192 | } else { 193 | NSDictionary *dict = [self errorToDictionary:error]; 194 | failure(@[dict]); 195 | } 196 | }); 197 | } 198 | 199 | RCT_EXPORT_METHOD(getStationaryLocation:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 200 | { 201 | RCTLogInfo(@"RCTBackgroundGeolocation #getStationaryLocation"); 202 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 203 | MAURLocation *stationaryLocation = [facade getStationaryLocation]; 204 | if (stationaryLocation) { 205 | success(@[[stationaryLocation toDictionary]]); 206 | } else { 207 | success(@[@(NO)]); 208 | } 209 | }); 210 | } 211 | 212 | RCT_EXPORT_METHOD(getLogEntries:(int)limit fromLogEntryId:(int)logEntry minLogLevel:(NSString*)minLogLevel success:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 213 | { 214 | RCTLogInfo(@"RCTBackgroundGeolocation #getLogEntries"); 215 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 216 | NSArray *logs = [facade getLogEntries:limit fromLogEntryId:logEntry minLogLevelFromString:minLogLevel]; 217 | success(@[logs]); 218 | }); 219 | } 220 | 221 | RCT_EXPORT_METHOD(getConfig:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 222 | { 223 | RCTLogInfo(@"RCTBackgroundGeolocation #getConfig"); 224 | MAURConfig *config = [facade getConfig]; 225 | if (config == nil) { 226 | config = [[MAURConfig alloc] init]; // default config 227 | } 228 | success(@[[config toDictionary]]); 229 | } 230 | 231 | RCT_EXPORT_METHOD(checkStatus:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 232 | { 233 | RCTLogInfo(@"RCTBackgroundGeolocation #checkStatus"); 234 | BOOL isRunning = [facade isStarted]; 235 | BOOL locationServicesEnabled = [facade locationServicesEnabled]; 236 | NSInteger authorizationStatus = [facade authorizationStatus]; 237 | 238 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:3]; 239 | [dict setObject:[NSNumber numberWithBool:isRunning] forKey:@"isRunning"]; 240 | [dict setObject:[NSNumber numberWithBool:locationServicesEnabled] forKey:@"hasPermissions"]; // @deprecated 241 | [dict setObject:[NSNumber numberWithBool:locationServicesEnabled] forKey:@"locationServicesEnabled"]; 242 | [dict setObject:[NSNumber numberWithInteger:authorizationStatus] forKey:@"authorization"]; 243 | 244 | success(@[dict]); 245 | } 246 | 247 | RCT_EXPORT_METHOD(startTask:(RCTResponseSenderBlock)callback) 248 | { 249 | NSUInteger taskKey = [[MAURBackgroundTaskManager sharedTasks] beginTask]; 250 | callback(@[[NSNumber numberWithInteger:taskKey]]); 251 | } 252 | 253 | RCT_EXPORT_METHOD(endTask:(NSNumber* _Nonnull)taskKey) 254 | { 255 | [[MAURBackgroundTaskManager sharedTasks] endTaskWithKey:[taskKey integerValue]]; 256 | } 257 | 258 | RCT_EXPORT_METHOD(forceSync:(RCTResponseSenderBlock)success failure:(RCTResponseSenderBlock)failure) 259 | { 260 | [facade forceSync]; 261 | } 262 | 263 | -(void) sendEvent:(NSString*)name 264 | { 265 | NSString *event = [NSString stringWithFormat:@"%@", name]; 266 | [_bridge.eventDispatcher sendDeviceEventWithName:event body:[NSNull null]]; 267 | } 268 | 269 | -(void) sendEvent:(NSString*)name resultAsDictionary:(NSDictionary*)resultAsDictionary 270 | { 271 | NSString *event = [NSString stringWithFormat:@"%@", name]; 272 | [_bridge.eventDispatcher sendDeviceEventWithName:event body:resultAsDictionary]; 273 | } 274 | 275 | -(void) sendEvent:(NSString*)name resultAsArray:(NSArray*)resultAsArray 276 | { 277 | NSString *event = [NSString stringWithFormat:@"%@", name]; 278 | [_bridge.eventDispatcher sendDeviceEventWithName:event body:resultAsArray]; 279 | } 280 | 281 | -(void) sendEvent:(NSString*)name resultAsNumber:(NSNumber*)resultAsNumber 282 | { 283 | NSString *event = [NSString stringWithFormat:@"%@", name]; 284 | [_bridge.eventDispatcher sendDeviceEventWithName:event body:resultAsNumber]; 285 | } 286 | 287 | - (NSDictionary*) errorToDictionary:(NSInteger)code message:(NSString*)message cause:(NSError*)error 288 | { 289 | NSDictionary *userInfo = [error userInfo]; 290 | NSString *errorMessage = [error localizedDescription]; 291 | if (errorMessage == nil) { 292 | errorMessage = [[userInfo objectForKey:NSUnderlyingErrorKey] localizedDescription]; 293 | } 294 | return @{ @"code": [NSNumber numberWithInteger:code], @"message":message, @"cause":errorMessage}; 295 | } 296 | 297 | - (NSDictionary*) errorToDictionary:(NSError*)error 298 | { 299 | NSDictionary *userInfo = [error userInfo]; 300 | NSString *errorMessage = [error localizedDescription]; 301 | if (errorMessage == nil) { 302 | errorMessage = [[userInfo objectForKey:NSUnderlyingErrorKey] localizedDescription]; 303 | } 304 | return @{ @"code": [NSNumber numberWithInteger:error.code], @"message":errorMessage}; 305 | } 306 | 307 | -(void) sendError:(NSError*)error 308 | { 309 | [self sendEvent:@"error" resultAsDictionary:[self errorToDictionary:error]]; 310 | } 311 | 312 | - (NSString *)loggerDirectory 313 | { 314 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); 315 | NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory(); 316 | 317 | return [basePath stringByAppendingPathComponent:@"SQLiteLogger"]; 318 | } 319 | 320 | - (void) onAuthorizationChanged:(MAURLocationAuthorizationStatus)authStatus 321 | { 322 | RCTLogInfo(@"RCTBackgroundGeolocation onAuthorizationChanged"); 323 | [self sendEvent:@"authorization" resultAsNumber:[NSNumber numberWithInteger:authStatus]]; 324 | } 325 | 326 | - (void) onLocationChanged:(MAURLocation*)location 327 | { 328 | RCTLogInfo(@"RCTBackgroundGeolocation onLocationChanged"); 329 | [self sendEvent:@"location" resultAsDictionary:[location toDictionaryWithId]]; 330 | } 331 | 332 | - (void) onStationaryChanged:(MAURLocation*)location 333 | { 334 | RCTLogInfo(@"RCTBackgroundGeolocation onStationaryChanged"); 335 | [self sendEvent:@"stationary" resultAsDictionary:[location toDictionaryWithId]]; 336 | } 337 | 338 | - (void) onError:(NSError*)error 339 | { 340 | RCTLogInfo(@"RCTBackgroundGeolocation onError"); 341 | [self sendError:error]; 342 | } 343 | 344 | - (void) onLocationPause 345 | { 346 | RCTLogInfo(@"RCTBackgroundGeoLocation location updates paused"); 347 | [self sendEvent:@"stop"]; 348 | } 349 | 350 | - (void) onLocationResume 351 | { 352 | RCTLogInfo(@"RCTBackgroundGeoLocation location updates resumed"); 353 | [self sendEvent:@"start"]; 354 | } 355 | 356 | - (void) onActivityChanged:(MAURActivity *)activity 357 | { 358 | RCTLogInfo(@"RCTBackgroundGeoLocation activity changed"); 359 | [self sendEvent:@"activity" resultAsDictionary:[activity toDictionary]]; 360 | } 361 | 362 | - (void) onAppResume:(NSNotification *)notification 363 | { 364 | RCTLogInfo(@"RCTBackgroundGeoLocation resumed"); 365 | [facade switchMode:MAURForegroundMode]; 366 | [self sendEvent:@"foreground"]; 367 | } 368 | 369 | - (void) onAppPause:(NSNotification *)notification 370 | { 371 | RCTLogInfo(@"RCTBackgroundGeoLocation paused"); 372 | [facade switchMode:MAURBackgroundMode]; 373 | [self sendEvent:@"background"]; 374 | } 375 | 376 | - (void) onAbortRequested 377 | { 378 | RCTLogInfo(@"RCTBackgroundGeoLocation abort requested by the server"); 379 | 380 | if (_bridge) 381 | { 382 | [self sendEvent:@"abort_requested"]; 383 | } 384 | else 385 | { 386 | [facade stop:nil]; 387 | } 388 | } 389 | 390 | - (void) onHttpAuthorization 391 | { 392 | RCTLogInfo(@"RCTBackgroundGeoLocation http authorization"); 393 | 394 | if (_bridge) 395 | { 396 | [self sendEvent:@"http_authorization"]; 397 | } 398 | } 399 | 400 | /**@ 401 | * on UIApplicationDidFinishLaunchingNotification 402 | */ 403 | -(void) onFinishLaunching:(NSNotification *)notification 404 | { 405 | NSDictionary *dict = [notification userInfo]; 406 | 407 | MAURConfig *config = [facade getConfig]; 408 | if (config.isDebugging) 409 | { 410 | if (@available(iOS 10, *)) 411 | { 412 | UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; 413 | prevNotificationDelegate = center.delegate; 414 | center.delegate = self; 415 | } 416 | } 417 | 418 | if ([dict objectForKey:UIApplicationLaunchOptionsLocationKey]) { 419 | RCTLogInfo(@"RCTBackgroundGeolocation started by system on location event."); 420 | if (![config stopOnTerminate]) { 421 | [facade start:nil]; 422 | [facade switchMode:MAURBackgroundMode]; 423 | } 424 | } 425 | } 426 | 427 | -(void) userNotificationCenter:(UNUserNotificationCenter *)center 428 | willPresentNotification:(UNNotification *)notification 429 | withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler 430 | { 431 | if (prevNotificationDelegate && [prevNotificationDelegate respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) 432 | { 433 | // Give other delegates (like FCM) the chance to process this notification 434 | 435 | [prevNotificationDelegate userNotificationCenter:center willPresentNotification:notification withCompletionHandler:^(UNNotificationPresentationOptions options) { 436 | completionHandler(UNNotificationPresentationOptionAlert); 437 | }]; 438 | } 439 | else 440 | { 441 | completionHandler(UNNotificationPresentationOptionAlert); 442 | } 443 | } 444 | 445 | -(void) onAppTerminate:(NSNotification *)notification 446 | { 447 | RCTLogInfo(@"RCTBackgroundGeoLocation appTerminate"); 448 | [facade onAppTerminate]; 449 | } 450 | 451 | +(BOOL)requiresMainQueueSetup { 452 | return NO; 453 | } 454 | 455 | @end 456 | -------------------------------------------------------------------------------- /ios/README.md: -------------------------------------------------------------------------------- 1 | For development, react-native libs are required provided by react-native package. 2 | 3 | 1. Install semver package 4 | `npm install semver` 5 | 6 | 2. Install react-native package 7 | 8 | Following command will download and extract react-native package into current directory. 9 | Must be executed from within this (ios) directory: 10 | 11 | `./npm_download.sh react-native` 12 | 13 | 3. Rename Base folder 14 | 15 | Done in react-native project `Copy Headers` phase, but since we are not including 16 | react-native project, we have to do this manualy. 17 | 18 | `mv react-native/React/Base react-native/React/React` -------------------------------------------------------------------------------- /ios/npm_download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | package="$1" 3 | baseUrl="https://registry.npmjs.org" 4 | version=$(node pkgversion.js ${package}) 5 | archive="${package}-${version}.tgz" 6 | mkdir "${package}" 7 | echo "Downloading ${archive}" 8 | curl --progress-bar \ 9 | "${baseUrl}/${package}/-/${archive}" | \ 10 | tar xz --strip-components 1 -C "${package}" 11 | -------------------------------------------------------------------------------- /ios/pkgversion.js: -------------------------------------------------------------------------------- 1 | // Print latest available package version that matches criteria 2 | // as specified in package.json. 3 | // Inputs are: 4 | // 1. package name as command line parameter 5 | 6 | var semver = require('semver'); 7 | var exec = require('child_process').exec; 8 | var packages = require('../package.json'); 9 | 10 | var searchIn = ['dependencies', 'peerDependencies', 'devDependencies']; 11 | var pkgName = process.argv[2]; 12 | 13 | if (!pkgName) { 14 | process.stderr.write('Please provide module name\n'); 15 | process.exit(1); 16 | } 17 | 18 | var command = 'npm show ' + pkgName + ' versions --json'; 19 | var child = exec(command, function (error, stdout, stderr) { 20 | var version; 21 | 22 | if (error !== null) { 23 | process.stderr.write('Exec error: ' + error); 24 | process.exit(1); 25 | } 26 | 27 | var versions = JSON.parse(stdout); 28 | for (var i = 0; i < searchIn.length; i++) { 29 | var pkgSection = packages[searchIn[i]]; 30 | version = pkgSection && pkgSection[pkgName]; 31 | if (version) { 32 | process.stdout.write(semver.maxSatisfying(versions, version)); 33 | break; 34 | } 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mauron85/react-native-background-geolocation", 3 | "version": "0.6.3", 4 | "description": "optimized background location tracking", 5 | "main": "./index.js", 6 | "types": "./index.d.ts", 7 | "scripts": { 8 | "link": "@mauron85/react-native-background-geolocation/scripts/postlink.js", 9 | "unlink": "@mauron85/react-native-background-geolocation/scripts/postunlink.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mauron85/react-native-background-geolocation.git" 15 | }, 16 | "keywords": [ 17 | "gps", 18 | "geolocation", 19 | "position", 20 | "location", 21 | "tracking", 22 | "background", 23 | "react", 24 | "react-native", 25 | "react-component", 26 | "android", 27 | "ios" 28 | ], 29 | "author": "Marian Hello", 30 | "license": "Apache-2.0", 31 | "bugs": { 32 | "url": "https://github.com/mauron85/react-native-background-geolocation/issues" 33 | }, 34 | "homepage": "https://github.com/mauron85/react-native-background-geolocation#readme", 35 | "dependencies": { 36 | "plist": "^3.0.1" 37 | }, 38 | "peerDependencies": { 39 | "react-native": ">=0.60.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dependency: { 3 | platforms: { 4 | android: { 5 | sourceDir: "./android/lib" 6 | } 7 | }, 8 | hooks: { 9 | postlink: "node ./node_modules/@mauron85/react-native-background-geolocation/scripts/postlink.js", 10 | postunlink: "node ./node_modules/@mauron85/react-native-background-geolocation/scripts/postunlink.js" 11 | } 12 | } 13 | }; -------------------------------------------------------------------------------- /res/dummy.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mauron85/react-native-background-geolocation/f0d434291cfae4a0e510e1e7a05d4e1e7508870e/res/dummy.apk -------------------------------------------------------------------------------- /scripts/config.js: -------------------------------------------------------------------------------- 1 | const COMMON_MODULE_NAME_SUFFIX = 'common'; 2 | 3 | const path = require('path'); 4 | 5 | const moduleDir = path.resolve(__dirname, '..'); 6 | const modulePkg = require(path.join(moduleDir, 'package.json')); 7 | const moduleName = modulePkg.name.replace('/', '_'); 8 | const appDir = path.resolve(moduleDir, '..', '..', '..'); 9 | const commonModuleDir = 'common'; 10 | const commonModuleName = `${moduleName}-${COMMON_MODULE_NAME_SUFFIX}`; 11 | const settingsGradlePath = path.join(appDir, 'android', 'settings.gradle'); 12 | 13 | module.exports = { 14 | appDir, 15 | moduleDir, 16 | moduleName, 17 | commonModuleName, 18 | commonModuleDir, 19 | settingsGradlePath 20 | }; 21 | module.exports.REQUIRED_BACKGROUND_MODES = ['location']; 22 | module.exports.LINK_DEPENDENCIES = []; 23 | -------------------------------------------------------------------------------- /scripts/google_cloud.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # LICENSE 4 | # 5 | # This file is part of Flyve MDM Agent for Android. 6 | # 7 | # Flyve MDM Agent for Android is a subproject of Flyve MDM. Flyve MDM is a mobile 8 | # device management software. 9 | # 10 | # Flyve MDM is free software: you can redistribute it and/or 11 | # modify it under the terms of the GNU General Public License 12 | # as published by the Free Software Foundation; either version 3 13 | # of the License, or (at your option) any later version. 14 | # 15 | # Flyve MDM Agent for Android is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # -------------------------------------------------------------------------------- 20 | # @author Rafael Hernandez - 21 | # @author Naylin Medina - 22 | # @copyright Copyright (c) Teclib' 23 | # @license GPLv3 https://www.gnu.org/licenses/gpl-3.0.html 24 | # @link https://github.com/flyve-mdm/android-mdm-agent/ 25 | # @link http://flyve.org/android-mdm-agent/ 26 | # @link https://flyve-mdm.com/ 27 | # -------------------------------------------------------------------------------- 28 | # 29 | 30 | # Since we will download a video, we require integrity checking with CRC32c 31 | # But the crcmod installation in the docker image isn't using the module's C extension 32 | # So, uninstall it and install again with the C extension 33 | echo "y" | sudo pip uninstall crcmod 34 | 35 | sudo pip install -U crcmod 36 | 37 | project_dir="${HOME}/mauron85_bgloc" 38 | 39 | # create json key file 40 | echo $GCLOUD_SERVICE_KEY | base64 --decode --ignore-garbage > ${HOME}/gcloud-service-key.json 41 | 42 | # activate the account 43 | gcloud auth activate-service-account --key-file ${HOME}/gcloud-service-key.json 44 | 45 | # config the project 46 | gcloud config set project ${GCLOUD_PROJECT} 47 | 48 | # Run Instrumented test 49 | gcloud firebase test android run \ 50 | --type instrumentation \ 51 | --app ${project_dir}/res/dummy.apk \ 52 | --test $(ls -dt ${project_dir}/android/lib/build/outputs/apk/androidTest/debug/*.apk | head -1) \ 53 | --device model=Nexus6,version=25,locale=en,orientation=portrait \ 54 | --timeout 90s 55 | -------------------------------------------------------------------------------- /scripts/isInstalled.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const config = require('./config'); 3 | 4 | function forAndroid() { 5 | const installPattern = new RegExp(`(include)\\s\\\':${config.commonModuleName}\\\'`); 6 | const settingsGradle = fs.readFileSync(config.settingsGradlePath); 7 | return installPattern.test(settingsGradle); 8 | } 9 | 10 | function forIos() { 11 | // currently we only change Info.plist and changes are only applied when necessary 12 | // no need to do check, so returning false here 13 | return false; 14 | } 15 | 16 | module.exports = { forAndroid, forIos }; 17 | -------------------------------------------------------------------------------- /scripts/npm_deprecate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run from package root dir! 4 | 5 | >&2 echo "For safety reasons command is only echoed and not executed" 6 | >&2 echo "To execute command:" 7 | >&2 echo "./scripts/npm_deprecate.sh | bash" 8 | >&2 echo "" 9 | 10 | pkg_min_version="0.5.0-alpha.1" 11 | 12 | pkg_name=$(cat package.json \ 13 | | grep name \ 14 | | head -1 \ 15 | | awk -F: '{ print $2 }' \ 16 | | sed 's/[",]//g' \ 17 | | tr -d '[[:space:]]') 18 | 19 | pkg_version=$(npm view $pkg_name version) 20 | 21 | echo npm deprecate $pkg_name@"\">=${pkg_min_version} <${pkg_version}\"" "\"Using deprecated version! Please upgrade package.\"" -------------------------------------------------------------------------------- /scripts/postlink.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const config = require('./config'); 4 | const isInstalled = require('./isInstalled'); 5 | 6 | const appDir = config.appDir; 7 | const manifest = require(path.join(appDir, 'package.json')); 8 | 9 | if (!isInstalled.forAndroid()) { 10 | // Android register common project 11 | // TODO: it would be nicer if react-native link has support for multi projects itself 12 | // Please vote: 13 | // https://react-native.canny.io/feature-requests/p/enable-subprojects-in-native-android-components-to-enable-code-reuse-with-cordov 14 | const applyPatch = require('@react-native-community/cli-platform-android/build/link/patches/applyPatch').default; 15 | const makeSettingsPatch = require('@react-native-community/cli-platform-android/build/link/patches/makeSettingsPatch').default; 16 | applyPatch( 17 | config.settingsGradlePath, 18 | makeSettingsPatch( 19 | config.commonModuleName, 20 | { sourceDir: path.join(config.moduleDir, 'android', config.commonModuleDir) }, 21 | config 22 | ) 23 | ); 24 | } 25 | 26 | if (!isInstalled.forIos()) { 27 | const plist = require('plist'); 28 | const infoPlistPath = path.join(appDir, 'ios', manifest.name, 'Info.plist'); 29 | const infoPlistFile = fs.readFileSync(infoPlistPath, 'utf8'); 30 | const infoPlist = plist.parse(infoPlistFile); 31 | const pListChanges = {}; 32 | 33 | const existingBgModes = infoPlist.UIBackgroundModes || []; 34 | const missingBgModes = config.REQUIRED_BACKGROUND_MODES.filter(function(mode) { 35 | return existingBgModes.indexOf(mode) === -1; 36 | }); 37 | 38 | if (missingBgModes.length > 0) { 39 | pListChanges.UIBackgroundModes = missingBgModes; 40 | } 41 | 42 | if (!infoPlist.NSLocationAlwaysUsageDescription) { 43 | pListChanges.NSLocationAlwaysUsageDescription = 'App requires background tracking'; 44 | } 45 | if (!infoPlist.NSLocationAlwaysAndWhenInUseUsageDescription) { 46 | pListChanges.NSLocationAlwaysAndWhenInUseUsageDescription = 'App requires background tracking'; 47 | } 48 | if (!infoPlist.NSMotionUsageDescription) { 49 | pListChanges.NSMotionUsageDescription = 'App requires motion tracking'; 50 | } 51 | if (!infoPlist.NSLocationWhenInUseUsageDescription) { 52 | pListChanges.NSLocationWhenInUseUsageDescription = 'App requires background tracking'; 53 | } 54 | 55 | if (Object.keys(pListChanges).length > 0) { 56 | // only write to plist if there were changes 57 | Object.assign(infoPlist, pListChanges); 58 | fs.writeFileSync(infoPlistPath, plist.build(infoPlist)); 59 | } 60 | } 61 | 62 | const exec = require('child_process').execSync; 63 | 64 | const cliPath = path.join(appDir, 'node_modules', 'react-native', 'local-cli', 'cli.js'); 65 | const command = `node ${cliPath} link`; 66 | 67 | config.LINK_DEPENDENCIES.forEach((dependency) => { 68 | console.log('Exec command', command); 69 | exec(`${command} ${dependency}`); 70 | }); 71 | -------------------------------------------------------------------------------- /scripts/postunlink.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const config = require('./config'); 3 | const isInstalled = require('./isInstalled'); 4 | 5 | if (isInstalled.forAndroid()) { 6 | const revokePatch = require('@react-native-community/cli/build/commands/link/android/patches/revokePatch').default; 7 | const makeSettingsPatch = require('@react-native-community/cli/build/commands/link/android/patches/makeSettingsPatch').default; 8 | revokePatch( 9 | config.settingsGradlePath, 10 | makeSettingsPatch( 11 | config.commonModuleName, 12 | { sourceDir: path.join(config.moduleDir, 'android', config.commonModuleDir) }, 13 | config 14 | ) 15 | ); 16 | } 17 | 18 | 19 | if (isInstalled.forIos()) { 20 | // we should remove background modes and usage descriptions here 21 | // It will destroy all project customizations, so we rather leave it as is. 22 | } 23 | --------------------------------------------------------------------------------