diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..79bc1d9c3 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,33 @@ +version: 2 +jobs: + build: + working_directory: ~/code + docker: + - image: circleci/android:api-25-alpha + environment: + JVM_OPTS: -Xmx3200m + steps: + - run: yes | sdkmanager --licenses || true + - run: yes | sdkmanager --update || exit 0 + - checkout + - restore_cache: + key: jars-{{ checksum "build.gradle" }}-{{ checksum "./build.gradle" }} +# - run: +# name: Chmod permissions #if permission for Gradlew Dependencies fail, use this. +# command: sudo chmod +x ./gradlew + - run: + name: Download Dependencies + command: ./gradlew androidDependencies + - save_cache: + paths: + - ~/.gradle + key: jars-{{ checksum "build.gradle" }}-{{ checksum "./build.gradle" }} + - run: + name: Run Tests + command: ./gradlew test + - store_artifacts: + path: ./build/reports + destination: reports + - store_test_results: + path: ./build/test-results + # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples diff --git a/.travis.yml b/.travis.yml index f86727187..c1b16f92b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: java -jdk: oraclejdk7 +jdk: oraclejdk8 before_install: - export TERM=dumb - cp gradle.properties.dist gradle.properties - sudo apt-get install -qq libstdc++6:i386 lib32z1 - - export COMPONENTS=build-tools-19.0.3,android-19,extra-android-m2repository + - export COMPONENTS=build-tools-28.0.3,android-28,extra-android-m2repository - curl -L https://raw.github.com/embarkmobile/android-sdk-installer/version-1/android-sdk-installer | bash /dev/stdin --install=$COMPONENTS - source ~/.android-sdk-installer/env install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3347bcafa..abf5443b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,86 @@ +### Development + +- Fix thread leak with 0 regions and settings applied, (#888, David G. Young) +- Prevent NPE when ScanState restore hangs or ScanJob thread is slow to start (#890, David G. Young) +- Prevent crash from IAE when ending scan cycle (#891, David G. Young) +- Prevent NPE in BluetoothMedic#runScanTest. (#893 Van Hoang Tran) + +### 2.16.2 / 2019-05-29 + +- Prevent crash on alarms going off with a different user active (#886, David G. Young) +- Fix thread leak caused by scheduling ScanJob when no consumers bound. (#885, David G. Young) +- Protect against a NPE when changing ranged regions. (#770, David G. Young) +- Fix intermittent failed ranging/monitoring callbacks in race conditions. (#842, David G. Young) +- Prevent duplicate callbacks on Android 8+ with a foreground service by stopping ScanJob. (#847, Stephen Ruda) +- Only apply Samsung-specific non-empty scan filters when the screen is off (#855, Marcel Schnelle) + +### 2.16.1 / 2019-02-10 + +- Fix crash on starting scanning with a forground service configured when multiple BeaconConsumer + instances bound. (#828, David G. Young) +- Fix broken RegionBoostrap callbacks caused by change in previous release (#829, David G. Young) + +### 2.16 / 2019-02-10 + +- New Region Bootstrap constructors allowing separate context and notifir (#821, Alper Tekin) +- Fix intermittent crash caused by internal Android NPE (#824, kababu007) +- Update gradle and robolectric (#805, Tony Tang) +- Fix problem on service shutdown that leaked threads and left scanning on (#804, David G. Young) + +### 2.15.4 / 2018-12-11 + +- Fix crash on BluetoothCrashResolver (#790, Michael Porter) + +### 2.15.3 / 2018-12-11 + +- Samsung screen-off scans on Android 8.1+ fixed. (#798, David G. Young) +- Fix bug preventing callbacks after unbind/bind when using ScanJobs. (#765, David G. Young) +- Prevent NPE on access CycledLEScanner after OOM on Android 8+. (#766, David G. Young) +- Make switching back and forth between a foreground service and scan jobs more reliable +(#767, David G. Young) +- Disable BluetoothCrashResolver on Android 5+ as a it is not helpful can can create log noise. + (#768, David G. Young) +- Prevent NPE on start scan. (#780, Adrián Nieto Rodríguez) +- Fix thread leak leading to OOM Exceptions when using ScanJobs (#785, David G. Young) + +### 2.15.2 / 2018-10-17 + +- Prevent infrequent out of memory crashes on Android 8+ (#750 Pappas Christodoulos, David G. Young) +- Prevent duplicate ranging/monitoring callbacks casued by bind/unbind with a service + (#748, Adrián Nieto Rodríguez, #745, David G. Young) +- Allow starting foreground service at boot (#746, David G. Young) +- Re-enable broken BeaconSimulator (#751, David G. Young) + +### 2.15.1 / 2018-09-01 + + - Prevent crash caused by internal Android exception when stopping scanning (#724, David G. Young) + - Fix Android 8 crashing apps on background monitoring/ranging data delivery (#720, David G. Young) + - Fix intermittent NPE on ranging beacons (#716, Federico Bertoli, David G. Young) + - Stop running scheduled jobs to do scans after last consumer unbound. (#702, David G. Young) + +### 2.15 / 2018-07-04 + +Enhancements: + - Optional foreground beacon scanning service for faster background detections on Android 8+ + (#663, David G. Young) + +Bug Fixes: + - Fixes inability to detect on some 5.x Samsung Devices without scan filters. (#693, David G. Young) + - Fix inverted logic for "disable ScanJob" warning (#700, Marcel Schnelle) + - Fix crash on scanning an Eddystone-URL packet with a negative-length URL. (#703, David G. Young) + +### 2.14 / 2018-05-17 + +Enhancements: + + - Add warnings about disabling scheduled scan jobs on Android 8+ (#674, David G. Young) + - Add warning about setting short scan periods on Android 8 (#677, David G. Young) + - BeaconTransmitter advertisements may be configured as connectable (#683, Michael Harper) + +Bug Fixes: + - Fix crashes of BluetoothMedic caused by Bluetooth being turned off (#675, David G. Young) + - Fix flawed in BeaconManager.isBound method on Android 8 (#655, David G. Young) + ### 2.13.1 / 2018-03-05 [Full Changelog](https://github.com/AltBeacon/android-beacon-library/compare/2.13.1...2.13) diff --git a/README.md b/README.md index 29bc3bf96..b59ac0483 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ to use the JCenter Repository ```groovy dependencies { ... - compile 'org.altbeacon:android-beacon-library:${altbeacon.version}' + implementation 'org.altbeacon:android-beacon-library:${altbeacon.version}' ... } ``` @@ -58,14 +58,14 @@ replacing `${altbeacon.version}` with the version you wish to use. ## How to build this Library This project uses an AndroidStudio/gradle build system and is known working with Android Studio -2.1 and Gradle 2.2.1 +3.4.1 and Gradle 5.1.1 Key Gradle build targets: ./gradlew test # run unit tests ./gradlew build # development build ./gradlew release -Prelease # release build - ./gradlew generatereleaseJavadoc -Prelease + ./gradlew generateReleaseJavadoc ## License diff --git a/build.gradle b/build.gradle index 8b3dc319c..084448ec3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,176 +1,30 @@ -ext { - isSnapshot = !project.hasProperty('release') - isSnapCi = System.getenv('SNAP_CI') != null - isSnapPullRequest = System.getenv('SNAP_PULL_REQUEST_NUMBER') != null -} - -/* - * Gets the version name from the latest Git tag - */ -def getVersionName = { - def stdout = new ByteArrayOutputStream() - try { - exec { - commandLine 'git', 'describe', '--tags' - standardOutput = stdout - } - return stdout.toString().trim() - } - catch (e) { - println("Can't get version from git: " + e); - return "adhoc" - } -} - buildscript { repositories { + google() jcenter() - maven { - url 'https://maven.google.com' - } - maven { - url 'https://dl.google.com/dl/android/maven2/' + mavenCentral() + repositories { + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } } } dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath 'com.android.tools.build:gradle:3.4.1' + //noinspection GradleDependency + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.1' + //noinspection GradleDependency classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:3.0.3' + // The 3.0 version of this plugin does not support Gradle 5, The latest source does support + // Grale 5. So we are using a snapshot until the 4.0 version is released. + //classpath "com.vanniktech:gradle-android-javadoc-plugin:0.3.0" + dependencies { + classpath "com.vanniktech:gradle-android-javadoc-plugin:0.4.0-SNAPSHOT" + } } } - -apply plugin: 'com.android.library' - -apply from: 'gradle/eclipse.gradle' - allprojects { - version = "${getVersionName()}${isSnapshot == true ? "-SNAPSHOT" : ""}" - group = "org.altbeacon" - repositories { + google() jcenter() - maven { - url 'https://maven.google.com' - } - maven { - url 'https://dl.google.com/dl/android/maven2/' - } - } -} - -android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' - - defaultConfig { - // Unfortunately 'com.android.support:appcompat-v7:26.0.0' - // requires minSdkVersion 14, forcing a bump verson minSdkVersion 7 - // But since only 0.8% of Android devices have < SDK 14 as of Une 2017, this will become - // the new min version for this library in order to target Android O - minSdkVersion 14 - targetSdkVersion 26 - versionCode 1 - versionName version - consumerProguardFiles 'proguard-rules.pro' - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - } - - compileOptions { - encoding "UTF-8" - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 } - - lintOptions { - abortOnError false - } - - packagingOptions { - exclude 'LICENSE.txt' - exclude 'META-INF/LICENSE' - exclude 'META-INF/LICENSE.txt' - exclude 'META-INF/NOTICE' - } -} - -dependencies { - compile fileTree ( dir: 'libs', include: ['*.jar'] ) - compile 'com.android.support:appcompat-v7:26.0.0' - compile 'com.android.support:support-annotations:26.0.0' - - testCompile('junit:junit:4.12') { - exclude group: 'org.hamcrest' - } - testCompile('org.hamcrest:hamcrest-junit:2.0.0.0') { - exclude group: 'junit' - } - testCompile('com.squareup:fest-android:1.0.+@aar') - testCompile('org.robolectric:robolectric:3.0') { - exclude group: 'junit' - } - testCompile('org.mockito:mockito-core:1.10.19') { - exclude group: 'org.hamcrest' - } - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - exclude group: 'org.hamcrest' - }) - androidTestCompile 'org.apache.commons:commons-math3:3.6.1' -} - -apply plugin: 'idea' - -idea { - module { - testOutputDir = file('build/test-classes/debug') - } -} - -task renameAarForRelease(type: Copy, dependsOn: build) { - description = "Rename the aar for easy release publishing" - - from "$buildDir/outputs/aar/" //${project.name}-release.aar - into "$buildDir/outputs/aar/" //${project.name}-${project.version}.aar" - include "${project.name}-release.aar" - rename { String fileName -> - fileName = "${project.name}-${project.version}.aar" - } -} - -task distribution(dependsOn: [bundleEclipse, build, clean, renameAarForRelease]) << { - println "Building with version=$version" -} - -task release(dependsOn: 'distribution') << { - println('Doing release build') -} - -android.libraryVariants.all { variant -> - - task("generate${variant.name}Javadoc", type: Javadoc) { - title = "Android Beacon Library $version API" - description "Generates Javadoc for $variant.name." - source = variant.javaCompile.source - ext.androidJar = - "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar" - classpath = files(variant.javaCompile.classpath.files, ext.androidJar) - options.linksOffline "http://d.android.com/reference/", "${android.sdkDirectory}/docs/reference" - exclude '**/BuildConfig.java' - exclude '**/R.java' - } - -} - -build.mustRunAfter clean - -apply from: 'gradle/credentials.gradle' -apply from: 'gradle/compile.gradle' -apply from: 'gradle/publishing.gradle' -apply from: 'gradle/bintray.gradle' -apply from: 'gradle/artifactory.gradle' - -artifactoryPublish { - // Skip deploying to artifactory if building a pull request - onlyIf { !isSnapPullRequest } } diff --git a/circle.yml b/circle.yml index 74b551dac..9d81d64db 100644 --- a/circle.yml +++ b/circle.yml @@ -3,7 +3,7 @@ machine: version: openjdk8 dependencies: pre: - - echo y | android update sdk --no-ui --all --filter "tools,android-26,build-tools-26.0.2,platform-tools,extra-android-m2repository,extra-google-m2repository" + - echo y | android update sdk --no-ui --all --filter "tools,android-28,build-tools-28.0.3,platform-tools,extra-android-m2repository,extra-google-m2repository" general: branches: ignore: diff --git a/eclipse-support/project.properties b/eclipse-support/project.properties index 1b8c5a340..9ca1deb71 100644 --- a/eclipse-support/project.properties +++ b/eclipse-support/project.properties @@ -11,5 +11,5 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-18 +target=android-28 android.library=true diff --git a/gradle.properties b/gradle.properties index 3737a926e..129c5292d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,3 +8,4 @@ project_connection = scm:git:https://github.com/AltBeacon/android-beacon-library project_dev_connection = scm:git:git@github.com:AltBeacon/android-beacon-library.git project_bintray_repo = android project_bintray_org = altbeacon +android.enableUnitTestBinaryResources=true \ No newline at end of file diff --git a/gradle/eclipse.gradle b/gradle/eclipse.gradle deleted file mode 100644 index 7fbfd935b..000000000 --- a/gradle/eclipse.gradle +++ /dev/null @@ -1,31 +0,0 @@ -// tasks for creating an eclipse bundle -task unzipAar(type: Copy, dependsOn: build) { - description = 'Unzip the aar in order to create an eclipse project' - - from zipTree(file("$buildDir/outputs/aar/${project.name}-release.aar")) - into file("$buildDir/outputs/aar/android-beacon-library") -} - -task bundleEclipse(type: Tar, dependsOn: unzipAar) { - description = 'Creates a tar file for eclipse distributions' - - destinationDir = file("$buildDir/outputs/aar/") - extension = 'tar.gz' - compression = Compression.GZIP - includeEmptyDirs = true - - from("$buildDir/outputs/aar/android-beacon-library") { - into 'android-beacon-library' - exclude '*.jar' - } - - from("$buildDir/outputs/aar/android-beacon-library") { - into 'android-beacon-library/libs' - include '*.jar' - } - - from("$rootDir/eclipse-support/") { - into 'android-beacon-library/' - exclude '**/.retain' - } -} diff --git a/gradle/package.gradle b/gradle/package.gradle new file mode 100644 index 000000000..8d9d2f472 --- /dev/null +++ b/gradle/package.gradle @@ -0,0 +1,43 @@ +apply plugin: 'idea' + +idea { + module { + testOutputDir = file('build/test-classes/debug') + } +} + +task renameAarForRelease(type: Copy, dependsOn: build) { + description = "Rename the aar for easy release publishing" + + from "$buildDir/outputs/aar/" //${project.name}-release.aar + into "$buildDir/outputs/aar/" //${project.name}-${project.version}.aar" + include "${project.name}-release.aar" + rename { String fileName -> + fileName = "${project.name}-${project.version}.aar" + } +} + +task distribution(dependsOn: [build, clean, renameAarForRelease]) { + doLast { + println "Building with version=$version" + } +} + +task release(dependsOn: 'distribution') { + doLast { + println('Doing release build') + } +} + +build.mustRunAfter clean + +apply from: '../gradle/credentials.gradle' +apply from: '../gradle/compile.gradle' +apply from: '../gradle/publishing.gradle' +apply from: '../gradle/bintray.gradle' +apply from: '../gradle/artifactory.gradle' + +artifactoryPublish { + // Skip deploying to artifactory if building a pull request + onlyIf { !isSnapPullRequest } +} diff --git a/gradle/version.gradle b/gradle/version.gradle new file mode 100644 index 000000000..7b4d57d57 --- /dev/null +++ b/gradle/version.gradle @@ -0,0 +1,28 @@ +ext { + isSnapshot = !project.hasProperty('release') + isSnapCi = System.getenv('SNAP_CI') != null + isSnapPullRequest = System.getenv('SNAP_PULL_REQUEST_NUMBER') != null +} + +/* + * Gets the version name from the latest Git tag + */ +def getVersionName = { + def stdout = new ByteArrayOutputStream() + try { + exec { + commandLine 'git', 'describe', '--tags' + standardOutput = stdout + } + return stdout.toString().trim() + } + catch (e) { + println("Can't get version from git: " + e); + return "adhoc" + } +} + +allprojects { + version = "${getVersionName()}${isSnapshot ? "-SNAPSHOT" : ""}" + group = "org.altbeacon" +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9a16f53a2..51d7dccfe 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,9 +1,6 @@ -#Sat Apr 01 10:19:45 EDT 2017 +#Thu Jan 10 18:19:47 EST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip -#distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-milestone-1-all.zip -#distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-milestone-1-all.zip -#distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/lib/build.gradle b/lib/build.gradle new file mode 100644 index 000000000..bea730cbc --- /dev/null +++ b/lib/build.gradle @@ -0,0 +1,71 @@ +apply plugin: 'com.android.library' +apply from: '../gradle/version.gradle' +apply plugin: "com.vanniktech.android.javadoc" + +android { + compileSdkVersion 28 + buildToolsVersion '28.0.3' + + defaultConfig { + // Unfortunately 'com.android.support:appcompat-v7:26.0.0' + // requires minSdkVersion 14, forcing a bump verson minSdkVersion 7 + // But since only 0.8% of Android devices have < SDK 14 as of Une 2017, this will become + // the new min version for this library in order to target Android O + minSdkVersion 14 + targetSdkVersion 28 + versionCode 1 + versionName version + consumerProguardFiles 'proguard-rules.pro' + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + compileOptions { + encoding "UTF-8" + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + lintOptions { + abortOnError false + } + + packagingOptions { + exclude 'LICENSE.txt' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE' + } + + testOptions { + execution 'ANDROID_TEST_ORCHESTRATOR' + animationsDisabled true + unitTests { + includeAndroidResources = true + } + } +} + +dependencies { + + implementation fileTree ( dir: 'libs', include: ['*.jar'] ) + // The support library is needed for + // LocalBroadcastManager, + implementation 'com.android.support:support-v4:28.0.0' + implementation 'com.android.support:support-annotations:28.0.0' + + testImplementation 'junit:junit:4.12' + testImplementation 'org.robolectric:robolectric:4.1' + testImplementation 'com.google.android:android-test:4.1.1.4' + testImplementation 'com.squareup:fest-android:1.0.8@aar' + testImplementation 'org.mockito:mockito-core:2.23.4' + + androidTestUtil 'com.android.support.test:orchestrator:1.0.2' + + androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation 'org.apache.commons:commons-math3:3.6.1' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' +} + +apply from: '../gradle/package.gradle' + + diff --git a/src/androidTest/java/org/altbeacon/beacon/NotifierSetCopyBenchmarksTest.java b/lib/src/androidTest/java/org/altbeacon/beacon/NotifierSetCopyBenchmarksTest.java similarity index 100% rename from src/androidTest/java/org/altbeacon/beacon/NotifierSetCopyBenchmarksTest.java rename to lib/src/androidTest/java/org/altbeacon/beacon/NotifierSetCopyBenchmarksTest.java diff --git a/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml similarity index 100% rename from src/main/AndroidManifest.xml rename to lib/src/main/AndroidManifest.xml diff --git a/src/main/java/org/altbeacon/beacon/AltBeacon.java b/lib/src/main/java/org/altbeacon/beacon/AltBeacon.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/AltBeacon.java rename to lib/src/main/java/org/altbeacon/beacon/AltBeacon.java diff --git a/src/main/java/org/altbeacon/beacon/AltBeaconParser.java b/lib/src/main/java/org/altbeacon/beacon/AltBeaconParser.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/AltBeaconParser.java rename to lib/src/main/java/org/altbeacon/beacon/AltBeaconParser.java diff --git a/src/main/java/org/altbeacon/beacon/Beacon.java b/lib/src/main/java/org/altbeacon/beacon/Beacon.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/Beacon.java rename to lib/src/main/java/org/altbeacon/beacon/Beacon.java diff --git a/src/main/java/org/altbeacon/beacon/BeaconConsumer.java b/lib/src/main/java/org/altbeacon/beacon/BeaconConsumer.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/BeaconConsumer.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconConsumer.java diff --git a/src/main/java/org/altbeacon/beacon/BeaconData.java b/lib/src/main/java/org/altbeacon/beacon/BeaconData.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/BeaconData.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconData.java diff --git a/src/main/java/org/altbeacon/beacon/BeaconDataNotifier.java b/lib/src/main/java/org/altbeacon/beacon/BeaconDataNotifier.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/BeaconDataNotifier.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconDataNotifier.java diff --git a/src/main/java/org/altbeacon/beacon/BeaconIntentProcessor.java b/lib/src/main/java/org/altbeacon/beacon/BeaconIntentProcessor.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/BeaconIntentProcessor.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconIntentProcessor.java diff --git a/src/main/java/org/altbeacon/beacon/BeaconLocalBroadcastProcessor.java b/lib/src/main/java/org/altbeacon/beacon/BeaconLocalBroadcastProcessor.java similarity index 93% rename from src/main/java/org/altbeacon/beacon/BeaconLocalBroadcastProcessor.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconLocalBroadcastProcessor.java index 8794252cf..b2fe90391 100644 --- a/src/main/java/org/altbeacon/beacon/BeaconLocalBroadcastProcessor.java +++ b/lib/src/main/java/org/altbeacon/beacon/BeaconLocalBroadcastProcessor.java @@ -40,10 +40,10 @@ * This is used with ScanJob and supports delivering intents even under Android O background * restrictions preventing starting a new IntentService. * - * It is not used with the BeaconService, as local broadcast intents cannot be deliverd across - * different processes which the BeaconService supports. + * It is not used with the BeaconService, if running in a separate process, as local broadcast + * intents cannot be deliverd across different processes which the BeaconService supports. * - * @see BeaconIntentProcessor for the equivalent use with BeaconService. + * @see BeaconIntentProcessor for the equivalent use with BeaconService in a separate process. ** * Internal library class. Do not use directly from outside the library * diff --git a/src/main/java/org/altbeacon/beacon/BeaconManager.java b/lib/src/main/java/org/altbeacon/beacon/BeaconManager.java similarity index 88% rename from src/main/java/org/altbeacon/beacon/BeaconManager.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconManager.java index 12e497a9f..780ce3eb3 100644 --- a/src/main/java/org/altbeacon/beacon/BeaconManager.java +++ b/lib/src/main/java/org/altbeacon/beacon/BeaconManager.java @@ -24,6 +24,7 @@ package org.altbeacon.beacon; import android.annotation.TargetApi; +import android.app.Notification; import android.bluetooth.BluetoothManager; import android.content.ComponentName; import android.content.Context; @@ -163,6 +164,10 @@ public class BeaconManager { private static boolean sAndroidLScanningDisabled = false; private static boolean sManifestCheckingDisabled = false; + @Nullable + private Notification mForegroundServiceNotification = null; + private int mForegroundServiceNotificationId = -1; + /** * Private lock object for singleton initialization protecting against denial-of-service attack. */ @@ -172,12 +177,13 @@ public class BeaconManager { * Set to true if you want to show library debugging. * * @param debug True turn on all logs for this library to be printed out to logcat. False turns - * off all logging. - * @deprecated To be removed in a future release. Use + * off detailed logging.. + * + * This is a convenience method that calls setLogger to a verbose logger and enables verbose + * logging. For more fine grained control, use: * {@link org.altbeacon.beacon.logging.LogManager#setLogger(org.altbeacon.beacon.logging.Logger)} * instead. */ - @Deprecated public static void setDebug(boolean debug) { if (debug) { LogManager.setLogger(Loggers.verboseLogger()); @@ -276,6 +282,11 @@ public void setBackgroundScanPeriod(long p) { */ public void setBackgroundBetweenScanPeriod(long p) { backgroundBetweenScanPeriod = p; + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + backgroundBetweenScanPeriod < 15*60*1000 /* 15 min */) { + LogManager.w(TAG, "Setting a short backgroundBetweenScanPeriod has no effect on "+ + "Android 8+, which is limited to scanning every ~15 minutes"); + } } public void setBackgroundRangeUpdatePeriod(long p) { @@ -359,7 +370,7 @@ protected BeaconManager(@NonNull Context context) { verifyServiceDeclaration(); } this.beaconParsers.add(new AltBeaconParser()); - mScheduledScanJobsEnabled = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + setScheduledScanJobsEnabledDefault(); } /*** @@ -421,7 +432,7 @@ public List getBeaconParsers() { */ @TargetApi(18) public boolean checkAvailability() throws BleNotAvailableException { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { throw new BleNotAvailableException("Bluetooth LE not supported by this device"); } return ((BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter().isEnabled(); @@ -435,14 +446,10 @@ public boolean checkAvailability() throws BleNotAvailableException { * @param consumer the Activity or Service that will receive the callback when the service is ready. */ public void bind(@NonNull BeaconConsumer consumer) { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } - if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { - LogManager.w(TAG, "This device does not support bluetooth LE. Will not start beacon scanning."); - return; - } synchronized (consumers) { ConsumerInfo newConsumerInfo = new ConsumerInfo(); ConsumerInfo alreadyBoundConsumerInfo = consumers.putIfAbsent(consumer, newConsumerInfo); @@ -458,6 +465,19 @@ public void bind(@NonNull BeaconConsumer consumer) { else { LogManager.d(TAG, "Binding to service"); Intent intent = new Intent(consumer.getApplicationContext(), BeaconService.class); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + this.getForegroundServiceNotification() != null) { + if (isAnyConsumerBound()) { + LogManager.i(TAG, "Not starting foreground beacon scanning" + + " service. A consumer is already bound, so it should be started"); + } + else { + LogManager.i(TAG, "Starting foreground beacon scanning service."); + mContext.startForegroundService(intent); + } + } + else { + } consumer.bindService(intent, newConsumerInfo.beaconServiceConnection, Context.BIND_AUTO_CREATE); } LogManager.d(TAG, "consumer count is now: %s", consumers.size()); @@ -472,7 +492,7 @@ public void bind(@NonNull BeaconConsumer consumer) { * @param consumer the Activity or Service that no longer needs to use the service. */ public void unbind(@NonNull BeaconConsumer consumer) { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } @@ -485,7 +505,9 @@ public void unbind(@NonNull BeaconConsumer consumer) { else { consumer.unbindService(consumers.get(consumer).beaconServiceConnection); } + LogManager.d(TAG, "Before unbind, consumer count is "+consumers.size()); consumers.remove(consumer); + LogManager.d(TAG, "After unbind, consumer count is "+consumers.size()); if (consumers.size() == 0) { // If this is the last consumer to disconnect, the service will exit // release the serviceMessenger. @@ -496,8 +518,10 @@ public void unbind(@NonNull BeaconConsumer consumer) { mBackgroundMode = false; // If we are using scan jobs, we cancel the active scan job if (mScheduledScanJobsEnabled) { - // TODO: Cancel the active scan job. Without this is keeps scanning as if - // a consumer is bound. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + LogManager.i(TAG, "Cancelling scheduled jobs after unbind of last consumer."); + ScanJobScheduler.getInstance().cancelSchedule(mContext); + } } } } @@ -559,7 +583,7 @@ public boolean isAnyConsumerBound() { * @see #setBackgroundBetweenScanPeriod(long p) */ public void setBackgroundMode(boolean backgroundMode) { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } @@ -586,11 +610,16 @@ public void setBackgroundMode(boolean backgroundMode) { * otherwise beacon scans may be run only once every 15 minutes in the background, and no low * power scans may be performed between scanning cycles. * + * Setting this value to false will disable ScanJobs when the app is run on Android 8+, which + * can prohibit delivery of callbacks when the app is in the background unless the scanning + * process is running in a foreground service. + * * This method may only be called if bind() has not yet been called, otherwise an * `IllegalStateException` is thown. * * @param enabled */ + public void setEnableScheduledScanJobs(boolean enabled) { if (isAnyConsumerBound()) { LogManager.e(TAG, "ScanJob may not be configured because a consumer is" + @@ -602,8 +631,16 @@ public void setEnableScheduledScanJobs(boolean enabled) { " availble prior to Android 5.0"); return; } + if (!enabled && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + LogManager.w(TAG, "Disabling ScanJobs on Android 8+ may disable delivery of "+ + "beacon callbacks in the background unless a foreground service is active."); + } + if(!enabled && android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ScanJobScheduler.getInstance().cancelSchedule(mContext); + } mScheduledScanJobsEnabled = enabled; } + public boolean getScheduledScanJobsEnabled() { return mScheduledScanJobsEnabled; } @@ -841,7 +878,7 @@ public void requestStateForRegion(@NonNull Region region) { */ @TargetApi(18) public void startRangingBeaconsInRegion(@NonNull Region region) throws RemoteException { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } @@ -879,7 +916,7 @@ public void startRangingBeaconsInRegion(@NonNull Region region) throws RemoteExc */ @TargetApi(18) public void stopRangingBeaconsInRegion(@NonNull Region region) throws RemoteException { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } @@ -920,7 +957,9 @@ public void applySettings() { protected void syncSettingsToService() { if (mScheduledScanJobsEnabled) { - ScanJobScheduler.getInstance().applySettingsToScheduledJob(mContext, this); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ScanJobScheduler.getInstance().applySettingsToScheduledJob(mContext, this); + } return; } try { @@ -943,7 +982,7 @@ protected void syncSettingsToService() { */ @TargetApi(18) public void startMonitoringBeaconsInRegion(@NonNull Region region) throws RemoteException { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } @@ -976,7 +1015,7 @@ public void startMonitoringBeaconsInRegion(@NonNull Region region) throws Remote */ @TargetApi(18) public void stopMonitoringBeaconsInRegion(@NonNull Region region) throws RemoteException { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } @@ -1000,7 +1039,7 @@ public void stopMonitoringBeaconsInRegion(@NonNull Region region) throws RemoteE */ @TargetApi(18) public void updateScanPeriods() throws RemoteException { - if (!isBleAvailable()) { + if (!isBleAvailableOrSimulated()) { LogManager.w(TAG, "Method invocation will be ignored."); return; } @@ -1015,12 +1054,15 @@ public void updateScanPeriods() throws RemoteException { @TargetApi(18) private void applyChangesToServices(int type, Region region) throws RemoteException { - if (mScheduledScanJobsEnabled) { - ScanJobScheduler.getInstance().applySettingsToScheduledJob(mContext, this); + if (!isAnyConsumerBound()) { + LogManager.w(TAG, "The BeaconManager is not bound to the service. Call beaconManager.bind(BeaconConsumer consumer) and wait for a callback to onBeaconServiceConnect()"); return; } - if (serviceMessenger == null) { - throw new RemoteException("The BeaconManager is not bound to the service. Call beaconManager.bind(BeaconConsumer consumer) and wait for a callback to onBeaconServiceConnect()"); + if (mScheduledScanJobsEnabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ScanJobScheduler.getInstance().applySettingsToScheduledJob(mContext, this); + } + return; } Message msg = Message.obtain(null, type, 0, 0); @@ -1169,7 +1211,7 @@ public static void logDebug(String tag, String message, Throwable t) { @Nullable protected static BeaconSimulator beaconSimulator; - protected static String distanceModelUpdateUrl = "http://data.altbeacon.org/android-distance.json"; + protected static String distanceModelUpdateUrl = "https://s3.amazonaws.com/android-beacon-library/android-distance.json"; public static String getDistanceModelUpdateUrl() { return distanceModelUpdateUrl; @@ -1243,6 +1285,12 @@ public void setNonBeaconLeScanCallback(@Nullable NonBeaconLeScanCallback callbac mNonBeaconLeScanCallback = callback; } + private boolean isBleAvailableOrSimulated() { + if (getBeaconSimulator() != null) { + return true; + } + return isBleAvailable(); + } private boolean isBleAvailable() { boolean available = false; if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { @@ -1412,6 +1460,81 @@ public static boolean getManifestCheckingDisabled() { return sManifestCheckingDisabled; } + + /** + * Configures the library to use a foreground service for bacon scanning. This allows nearly + * constant scanning on most Android versions to get around background limits, and displays an + * icon to the user to indicate that the app is doing something in the background, even on + * Android 8+. This will disable the user of the JobScheduler on Android 8 to do scans. Note + * that this method does not by itself enable constant scanning. The scan intervals will work + * as normal and must be configurd to specific values depending on how often you wish to scan. + * + * @see #setForegroundScanPeriod(long) + * @see #setForegroundBetweenScanPeriod(long) + * + * This method requires a notification to display a message to the user about why the app is + * scanning in the background. The notification must include an icon that will be displayed + * in the top bar whenever the scanning service is running. + * + * If the BeaconService is configured to run in a different process, this call will have no + * effect. + * + * @param notification - the notification that will be displayed when beacon scanning is active, + * along with the icon that shows up in the status bar. + * + * @throws IllegalStateException if called after consumers are already bound to the scanning + * service + */ + public void enableForegroundServiceScanning(Notification notification, int notificationId) + throws IllegalStateException { + if (isAnyConsumerBound()) { + throw new IllegalStateException("May not be called after consumers are already bound."); + } + if (notification == null) { + throw new NullPointerException("Notification cannot be null"); + } + setEnableScheduledScanJobs(false); + mForegroundServiceNotification = notification; + mForegroundServiceNotificationId = notificationId; + } + + /** + * Disables a foreground scanning service, if previously configured. + * + * @see #enableForegroundServiceScanning + * + * In order to call this method to disable a foreground service, you must unbind from the + * BeaconManager. You can then rebind after this call is made. + * + * @throws IllegalStateException if called after consumers are already bound to the scanning + * service + */ + public void disableForegroundServiceScanning() throws IllegalStateException { + if (isAnyConsumerBound()) { + throw new IllegalStateException("May not be called after consumers are already bound"); + } + mForegroundServiceNotification = null; + setScheduledScanJobsEnabledDefault(); + } + + /** + * @see #enableForegroundServiceScanning + * @return The notification shown for the beacon scanning service, if so configured + */ + public Notification getForegroundServiceNotification() { + return mForegroundServiceNotification; + } + + + /** + * @see #enableForegroundServiceScanning + * @return The notification shown for the beacon scanning service, if so configured + */ + public int getForegroundServiceNotificationId() { + return mForegroundServiceNotificationId; + } + + private boolean determineIfCalledFromSeparateScannerProcess() { if (isScannerInDifferentProcess() && !isMainProcess()) { LogManager.w(TAG, "Ranging/Monitoring may not be controlled from a separate "+ @@ -1430,4 +1553,7 @@ private static void warnIfScannerNotInSameProcess() { } } + private void setScheduledScanJobsEnabledDefault() { + mScheduledScanJobsEnabled = android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } } diff --git a/src/main/java/org/altbeacon/beacon/BeaconParser.java b/lib/src/main/java/org/altbeacon/beacon/BeaconParser.java similarity index 99% rename from src/main/java/org/altbeacon/beacon/BeaconParser.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconParser.java index 8591db4eb..ef55e7a86 100644 --- a/src/main/java/org/altbeacon/beacon/BeaconParser.java +++ b/lib/src/main/java/org/altbeacon/beacon/BeaconParser.java @@ -516,7 +516,13 @@ protected Beacon fromScanData(byte[] bytesToProcess, int rssi, BluetoothDevice d } // If this is a variable length identifier, we truncate it to the size that // is available in the packet - Identifier identifier = Identifier.fromBytes(bytesToProcess, mIdentifierStartOffsets.get(i) + startByte, pduToParse.getEndIndex()+1, mIdentifierLittleEndianFlags.get(i)); + int start = mIdentifierStartOffsets.get(i) + startByte; + int end = pduToParse.getEndIndex()+1; + if (end <= start) { + LogManager.d(TAG, "PDU is too short for identifer. Packet is malformed"); + return null; + } + Identifier identifier = Identifier.fromBytes(bytesToProcess, start, end, mIdentifierLittleEndianFlags.get(i)); identifiers.add(identifier); } else if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) { diff --git a/src/main/java/org/altbeacon/beacon/BeaconTransmitter.java b/lib/src/main/java/org/altbeacon/beacon/BeaconTransmitter.java similarity index 96% rename from src/main/java/org/altbeacon/beacon/BeaconTransmitter.java rename to lib/src/main/java/org/altbeacon/beacon/BeaconTransmitter.java index 136255936..4f8fb4f28 100644 --- a/src/main/java/org/altbeacon/beacon/BeaconTransmitter.java +++ b/lib/src/main/java/org/altbeacon/beacon/BeaconTransmitter.java @@ -1,22 +1,22 @@ package org.altbeacon.beacon; -import org.altbeacon.beacon.logging.LogManager; - import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; -import java.nio.ByteOrder; -import java.nio.ByteBuffer; -import java.util.UUID; import android.bluetooth.le.AdvertiseCallback; -import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.BluetoothLeAdvertiser; import android.content.Context; import android.content.pm.PackageManager; - import android.os.ParcelUuid; +import org.altbeacon.beacon.logging.LogManager; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.UUID; + /** * Provides a mechanism for transmitting as a beacon. Requires Android 5.0 */ @@ -41,6 +41,7 @@ public class BeaconTransmitter { private AdvertiseCallback mAdvertisingClientCallback; private boolean mStarted; private AdvertiseCallback mAdvertiseCallback; + private boolean mConnectable = false; /** * Creates a new beacon transmitter capable of transmitting beacons with the format @@ -124,6 +125,22 @@ public void setAdvertiseTxPowerLevel(int mAdvertiseTxPowerLevel) { this.mAdvertiseTxPowerLevel = mAdvertiseTxPowerLevel; } + /** + * Whether the advertisement should indicate the device is connectable. + * @param connectable + */ + public void setConnectable(boolean connectable) { + this.mConnectable = connectable; + } + + /** + * @see #setConnectable(boolean) + * @return connectable + */ + public boolean isConnectable() { + return mConnectable; + } + /** * Starts advertising with fields from the passed beacon * @param beacon @@ -191,7 +208,7 @@ public void startAdvertising() { settingsBuilder.setAdvertiseMode(mAdvertiseMode); settingsBuilder.setTxPowerLevel(mAdvertiseTxPowerLevel); - settingsBuilder.setConnectable(false); + settingsBuilder.setConnectable(mConnectable); mBluetoothLeAdvertiser.startAdvertising(settingsBuilder.build(), dataBuilder.build(), getAdvertiseCallback()); LogManager.d(TAG, "Started advertisement with callback: %s", getAdvertiseCallback()); diff --git a/src/main/java/org/altbeacon/beacon/BleNotAvailableException.java b/lib/src/main/java/org/altbeacon/beacon/BleNotAvailableException.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/BleNotAvailableException.java rename to lib/src/main/java/org/altbeacon/beacon/BleNotAvailableException.java diff --git a/src/main/java/org/altbeacon/beacon/Identifier.java b/lib/src/main/java/org/altbeacon/beacon/Identifier.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/Identifier.java rename to lib/src/main/java/org/altbeacon/beacon/Identifier.java diff --git a/src/main/java/org/altbeacon/beacon/IntentHandler.java b/lib/src/main/java/org/altbeacon/beacon/IntentHandler.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/IntentHandler.java rename to lib/src/main/java/org/altbeacon/beacon/IntentHandler.java diff --git a/src/main/java/org/altbeacon/beacon/MonitorNotifier.java b/lib/src/main/java/org/altbeacon/beacon/MonitorNotifier.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/MonitorNotifier.java rename to lib/src/main/java/org/altbeacon/beacon/MonitorNotifier.java diff --git a/src/main/java/org/altbeacon/beacon/RangeNotifier.java b/lib/src/main/java/org/altbeacon/beacon/RangeNotifier.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/RangeNotifier.java rename to lib/src/main/java/org/altbeacon/beacon/RangeNotifier.java diff --git a/src/main/java/org/altbeacon/beacon/Region.java b/lib/src/main/java/org/altbeacon/beacon/Region.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/Region.java rename to lib/src/main/java/org/altbeacon/beacon/Region.java diff --git a/src/main/java/org/altbeacon/beacon/client/BeaconDataFactory.java b/lib/src/main/java/org/altbeacon/beacon/client/BeaconDataFactory.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/client/BeaconDataFactory.java rename to lib/src/main/java/org/altbeacon/beacon/client/BeaconDataFactory.java diff --git a/src/main/java/org/altbeacon/beacon/client/DataProviderException.java b/lib/src/main/java/org/altbeacon/beacon/client/DataProviderException.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/client/DataProviderException.java rename to lib/src/main/java/org/altbeacon/beacon/client/DataProviderException.java diff --git a/src/main/java/org/altbeacon/beacon/client/NullBeaconDataFactory.java b/lib/src/main/java/org/altbeacon/beacon/client/NullBeaconDataFactory.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/client/NullBeaconDataFactory.java rename to lib/src/main/java/org/altbeacon/beacon/client/NullBeaconDataFactory.java diff --git a/src/main/java/org/altbeacon/beacon/distance/AndroidModel.java b/lib/src/main/java/org/altbeacon/beacon/distance/AndroidModel.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/distance/AndroidModel.java rename to lib/src/main/java/org/altbeacon/beacon/distance/AndroidModel.java diff --git a/src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java b/lib/src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java rename to lib/src/main/java/org/altbeacon/beacon/distance/CurveFittedDistanceCalculator.java diff --git a/src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java b/lib/src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java rename to lib/src/main/java/org/altbeacon/beacon/distance/DistanceCalculator.java diff --git a/src/main/java/org/altbeacon/beacon/distance/DistanceConfigFetcher.java b/lib/src/main/java/org/altbeacon/beacon/distance/DistanceConfigFetcher.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/distance/DistanceConfigFetcher.java rename to lib/src/main/java/org/altbeacon/beacon/distance/DistanceConfigFetcher.java diff --git a/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java b/lib/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java rename to lib/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java diff --git a/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceUpdater.java b/lib/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceUpdater.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceUpdater.java rename to lib/src/main/java/org/altbeacon/beacon/distance/ModelSpecificDistanceUpdater.java diff --git a/src/main/java/org/altbeacon/beacon/logging/AbstractAndroidLogger.java b/lib/src/main/java/org/altbeacon/beacon/logging/AbstractAndroidLogger.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/AbstractAndroidLogger.java rename to lib/src/main/java/org/altbeacon/beacon/logging/AbstractAndroidLogger.java diff --git a/src/main/java/org/altbeacon/beacon/logging/EmptyLogger.java b/lib/src/main/java/org/altbeacon/beacon/logging/EmptyLogger.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/EmptyLogger.java rename to lib/src/main/java/org/altbeacon/beacon/logging/EmptyLogger.java diff --git a/src/main/java/org/altbeacon/beacon/logging/InfoAndroidLogger.java b/lib/src/main/java/org/altbeacon/beacon/logging/InfoAndroidLogger.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/InfoAndroidLogger.java rename to lib/src/main/java/org/altbeacon/beacon/logging/InfoAndroidLogger.java diff --git a/src/main/java/org/altbeacon/beacon/logging/LogManager.java b/lib/src/main/java/org/altbeacon/beacon/logging/LogManager.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/LogManager.java rename to lib/src/main/java/org/altbeacon/beacon/logging/LogManager.java diff --git a/src/main/java/org/altbeacon/beacon/logging/Logger.java b/lib/src/main/java/org/altbeacon/beacon/logging/Logger.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/Logger.java rename to lib/src/main/java/org/altbeacon/beacon/logging/Logger.java diff --git a/src/main/java/org/altbeacon/beacon/logging/Loggers.java b/lib/src/main/java/org/altbeacon/beacon/logging/Loggers.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/Loggers.java rename to lib/src/main/java/org/altbeacon/beacon/logging/Loggers.java diff --git a/src/main/java/org/altbeacon/beacon/logging/VerboseAndroidLogger.java b/lib/src/main/java/org/altbeacon/beacon/logging/VerboseAndroidLogger.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/VerboseAndroidLogger.java rename to lib/src/main/java/org/altbeacon/beacon/logging/VerboseAndroidLogger.java diff --git a/src/main/java/org/altbeacon/beacon/logging/WarningAndroidLogger.java b/lib/src/main/java/org/altbeacon/beacon/logging/WarningAndroidLogger.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/logging/WarningAndroidLogger.java rename to lib/src/main/java/org/altbeacon/beacon/logging/WarningAndroidLogger.java diff --git a/src/main/java/org/altbeacon/beacon/powersave/BackgroundPowerSaver.java b/lib/src/main/java/org/altbeacon/beacon/powersave/BackgroundPowerSaver.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/powersave/BackgroundPowerSaver.java rename to lib/src/main/java/org/altbeacon/beacon/powersave/BackgroundPowerSaver.java diff --git a/src/main/java/org/altbeacon/beacon/service/ArmaRssiFilter.java b/lib/src/main/java/org/altbeacon/beacon/service/ArmaRssiFilter.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/ArmaRssiFilter.java rename to lib/src/main/java/org/altbeacon/beacon/service/ArmaRssiFilter.java diff --git a/src/main/java/org/altbeacon/beacon/service/BeaconService.java b/lib/src/main/java/org/altbeacon/beacon/service/BeaconService.java similarity index 86% rename from src/main/java/org/altbeacon/beacon/service/BeaconService.java rename to lib/src/main/java/org/altbeacon/beacon/service/BeaconService.java index e4d6d1af4..e58fcde5e 100644 --- a/src/main/java/org/altbeacon/beacon/service/BeaconService.java +++ b/lib/src/main/java/org/altbeacon/beacon/service/BeaconService.java @@ -25,6 +25,7 @@ import android.app.AlarmManager; +import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; @@ -44,6 +45,7 @@ import android.support.annotation.RestrictTo.Scope; import org.altbeacon.beacon.Beacon; +import org.altbeacon.beacon.BeaconLocalBroadcastProcessor; import org.altbeacon.beacon.BeaconManager; import org.altbeacon.beacon.BeaconParser; import org.altbeacon.beacon.BuildConfig; @@ -74,6 +76,8 @@ public class BeaconService extends Service { private final Handler handler = new Handler(); private BluetoothCrashResolver bluetoothCrashResolver; private ScanHelper mScanHelper; + private BeaconLocalBroadcastProcessor mBeaconNotificationProcessor; + /* * The scan period is how long we wait between restarting the BLE advertisement scans * Each time we restart we only see the unique advertisements once (e.g. unique beacons) @@ -208,8 +212,10 @@ else if (msg.what == MSG_SYNC_SETTINGS) { @MainThread @Override public void onCreate() { - bluetoothCrashResolver = new BluetoothCrashResolver(this); - bluetoothCrashResolver.start(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + bluetoothCrashResolver = new BluetoothCrashResolver(this); + bluetoothCrashResolver.start(); + } mScanHelper = new ScanHelper(this); if (mScanHelper.getCycledScanner() == null) { @@ -224,6 +230,8 @@ public void onCreate() { beaconManager.setScannerInSameProcess(true); if (beaconManager.isMainProcess()) { LogManager.i(TAG, "beaconService version %s is starting up on the main process", BuildConfig.VERSION_NAME); + // if we are on the main process, we use local broadcast notifications to deliver results. + ensureNotificationProcessorSetup(); } else { LogManager.i(TAG, "beaconService version %s is starting up on a separate process", BuildConfig.VERSION_NAME); @@ -231,14 +239,13 @@ public void onCreate() { LogManager.i(TAG, "beaconService PID is "+processUtils.getPid()+" with process name "+processUtils.getProcessName()); } - try { - PackageItemInfo info = this.getPackageManager().getServiceInfo(new ComponentName(this, BeaconService.class), PackageManager.GET_META_DATA); - if (info != null && info.metaData != null && info.metaData.get("longScanForcingEnabled") != null && - info.metaData.get("longScanForcingEnabled").toString().equals("true")) { - LogManager.i(TAG, "longScanForcingEnabled to keep scans going on Android N for > 30 minutes"); + String longScanForcingEnabled = getManifestMetadataValue("longScanForcingEnabled"); + if (longScanForcingEnabled != null && longScanForcingEnabled.equals("true")) { + LogManager.i(TAG, "longScanForcingEnabled to keep scans going on Android N for > 30 minutes"); + if (mScanHelper.getCycledScanner() != null) { mScanHelper.getCycledScanner().setLongScanForcingEnabled(true); } - } catch (PackageManager.NameNotFoundException e) {} + } mScanHelper.reloadParsers(); @@ -255,6 +262,46 @@ public void onCreate() { } catch (Exception e) { LogManager.e(e, TAG, "Cannot get simulated Scan data. Make sure your org.altbeacon.beacon.SimulatedScanData class defines a field with the signature 'public static List beacons'"); } + this.startForegroundIfConfigured(); + } + + + private void ensureNotificationProcessorSetup() { + if (mBeaconNotificationProcessor == null) { + mBeaconNotificationProcessor = new BeaconLocalBroadcastProcessor(this); + mBeaconNotificationProcessor.register(); + } + } + + + /* + * This starts the scanning service as a foreground service if it is so configured in the + * manifest + */ + private void startForegroundIfConfigured() { + BeaconManager beaconManager = BeaconManager.getInstanceForApplication( + this.getApplicationContext()); + Notification notification = beaconManager + .getForegroundServiceNotification(); + int notificationId = beaconManager + .getForegroundServiceNotificationId(); + if (notification != null && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + this.startForeground(notificationId, notification); + } + } + + private String getManifestMetadataValue(String key) { + String value = null; + try { + PackageItemInfo info = this.getPackageManager().getServiceInfo(new ComponentName(this, BeaconService.class), PackageManager.GET_META_DATA); + if (info != null && info.metaData != null) { + return info.metaData.get(key).toString(); + } + } + catch (PackageManager.NameNotFoundException e) { + } + return null; } @Override @@ -278,9 +325,12 @@ public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } + // called when the last bound client calls unbind @Override public boolean onUnbind(Intent intent) { - LogManager.i(TAG, "unbinding"); + LogManager.i(TAG, "unbinding so destroying self"); + this.stopForeground(true); + this.stopSelf(); return false; } @@ -292,12 +342,21 @@ public void onDestroy() { LogManager.w(TAG, "Not supported prior to API 18."); return; } - bluetoothCrashResolver.stop(); + if (mBeaconNotificationProcessor != null) { + mBeaconNotificationProcessor.unregister(); + } + if (bluetoothCrashResolver != null) { + bluetoothCrashResolver.stop(); + } LogManager.i(TAG, "onDestroy called. stopping scanning"); handler.removeCallbacksAndMessages(null); - mScanHelper.getCycledScanner().stop(); - mScanHelper.getCycledScanner().destroy(); + + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().stop(); + mScanHelper.getCycledScanner().destroy(); + } mScanHelper.getMonitoringStatus().stopStatusPreservation(); + mScanHelper.terminateThreads(); } @Override @@ -331,7 +390,9 @@ public void startRangingBeaconsInRegion(Region region, Callback callback) { mScanHelper.getRangedRegionState().put(region, new RangeState(callback)); LogManager.d(TAG, "Currently ranging %s regions.", mScanHelper.getRangedRegionState().size()); } - mScanHelper.getCycledScanner().start(); + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().start(); + } } @MainThread @@ -344,7 +405,9 @@ public void stopRangingBeaconsInRegion(Region region) { } if (rangedRegionCount == 0 && mScanHelper.getMonitoringStatus().regionsCount() == 0) { - mScanHelper.getCycledScanner().stop(); + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().stop(); + } } } @@ -353,7 +416,9 @@ public void startMonitoringBeaconsInRegion(Region region, Callback callback) { LogManager.d(TAG, "startMonitoring called"); mScanHelper.getMonitoringStatus().addRegion(region, callback); LogManager.d(TAG, "Currently monitoring %s regions.", mScanHelper.getMonitoringStatus().regionsCount()); - mScanHelper.getCycledScanner().start(); + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().start(); + } } @MainThread @@ -362,15 +427,17 @@ public void stopMonitoringBeaconsInRegion(Region region) { mScanHelper.getMonitoringStatus().removeRegion(region); LogManager.d(TAG, "Currently monitoring %s regions.", mScanHelper.getMonitoringStatus().regionsCount()); if (mScanHelper.getMonitoringStatus().regionsCount() == 0 && mScanHelper.getRangedRegionState().size() == 0) { - mScanHelper.getCycledScanner().stop(); + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().stop(); + } } } @MainThread public void setScanPeriods(long scanPeriod, long betweenScanPeriod, boolean backgroundFlag) { -//<<<<<<< HEAD - mScanHelper.getCycledScanner().setScanPeriods(scanPeriod, betweenScanPeriod, backgroundFlag); -// mCycledScanner.setScanPeriods(scanPeriod, betweenScanPeriod, backgroundFlag); + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().setScanPeriods(scanPeriod, betweenScanPeriod, backgroundFlag); + } } public void setRangeUpdatePeriods(long rangeUpdatePeriod, long betweenRangeUpdatePeriod) { diff --git a/src/main/java/org/altbeacon/beacon/service/Callback.java b/lib/src/main/java/org/altbeacon/beacon/service/Callback.java similarity index 98% rename from src/main/java/org/altbeacon/beacon/service/Callback.java rename to lib/src/main/java/org/altbeacon/beacon/service/Callback.java index a8c9ad926..512f031d8 100644 --- a/src/main/java/org/altbeacon/beacon/service/Callback.java +++ b/lib/src/main/java/org/altbeacon/beacon/service/Callback.java @@ -52,7 +52,7 @@ public Callback(String intentPackageName) { * @return false if it callback cannot be made */ public boolean call(Context context, String dataName, Bundle data) { - boolean useLocalBroadcast = BeaconManager.getInstanceForApplication(context).getScheduledScanJobsEnabled(); + boolean useLocalBroadcast = BeaconManager.getInstanceForApplication(context).isMainProcess(); boolean success = false; if(useLocalBroadcast) { diff --git a/src/main/java/org/altbeacon/beacon/service/DetectionTracker.java b/lib/src/main/java/org/altbeacon/beacon/service/DetectionTracker.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/DetectionTracker.java rename to lib/src/main/java/org/altbeacon/beacon/service/DetectionTracker.java diff --git a/src/main/java/org/altbeacon/beacon/service/ExtraDataBeaconTracker.java b/lib/src/main/java/org/altbeacon/beacon/service/ExtraDataBeaconTracker.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/ExtraDataBeaconTracker.java rename to lib/src/main/java/org/altbeacon/beacon/service/ExtraDataBeaconTracker.java diff --git a/src/main/java/org/altbeacon/beacon/service/MonitoringData.java b/lib/src/main/java/org/altbeacon/beacon/service/MonitoringData.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/MonitoringData.java rename to lib/src/main/java/org/altbeacon/beacon/service/MonitoringData.java diff --git a/src/main/java/org/altbeacon/beacon/service/MonitoringStatus.java b/lib/src/main/java/org/altbeacon/beacon/service/MonitoringStatus.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/MonitoringStatus.java rename to lib/src/main/java/org/altbeacon/beacon/service/MonitoringStatus.java diff --git a/src/main/java/org/altbeacon/beacon/service/RangeState.java b/lib/src/main/java/org/altbeacon/beacon/service/RangeState.java similarity index 70% rename from src/main/java/org/altbeacon/beacon/service/RangeState.java rename to lib/src/main/java/org/altbeacon/beacon/service/RangeState.java index bba100799..ed5aa7658 100644 --- a/src/main/java/org/altbeacon/beacon/service/RangeState.java +++ b/lib/src/main/java/org/altbeacon/beacon/service/RangeState.java @@ -47,8 +47,8 @@ public Callback getCallback() { } public void addBeacon(Beacon beacon) { - if (mRangedBeacons.containsKey(beacon)) { - RangedBeacon rangedBeacon = mRangedBeacons.get(beacon); + RangedBeacon rangedBeacon = mRangedBeacons.get(beacon); + if (rangedBeacon != null) { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d(TAG, "adding %s to existing range for: %s", beacon, rangedBeacon); } @@ -71,23 +71,25 @@ public synchronized Collection finalizeBeacons() { synchronized (mRangedBeacons) { for (Beacon beacon : mRangedBeacons.keySet()) { RangedBeacon rangedBeacon = mRangedBeacons.get(beacon); - if (rangedBeacon.isTracked()) { - rangedBeacon.commitMeasurements(); // calculates accuracy - if (!rangedBeacon.noMeasurementsAvailable()) { - finalizedBeacons.add(rangedBeacon.getBeacon()); + if (rangedBeacon != null) { + if (rangedBeacon.isTracked()) { + rangedBeacon.commitMeasurements(); // calculates accuracy + if (!rangedBeacon.noMeasurementsAvailable()) { + finalizedBeacons.add(rangedBeacon.getBeacon()); + } + } + // If we still have useful measurements, keep it around but mark it as not + // tracked anymore so we don't pass it on as visible unless it is seen again + if (!rangedBeacon.noMeasurementsAvailable() == true) { + //if TrackingCache is enabled, allow beacon to not receive + //measurements for a certain amount of time + if (!sUseTrackingCache || rangedBeacon.isExpired()) + rangedBeacon.setTracked(false); + newRangedBeacons.put(beacon, rangedBeacon); + } + else { + LogManager.d(TAG, "Dumping beacon from RangeState because it has no recent measurements."); } - } - // If we still have useful measurements, keep it around but mark it as not - // tracked anymore so we don't pass it on as visible unless it is seen again - if (!rangedBeacon.noMeasurementsAvailable() == true) { - //if TrackingCache is enabled, allow beacon to not receive - //measurements for a certain amount of time - if (!sUseTrackingCache || rangedBeacon.isExpired()) - rangedBeacon.setTracked(false); - newRangedBeacons.put(beacon, rangedBeacon); - } - else { - LogManager.d(TAG, "Dumping beacon from RangeState because it has no recent measurements."); } } mRangedBeacons = newRangedBeacons; diff --git a/src/main/java/org/altbeacon/beacon/service/RangedBeacon.java b/lib/src/main/java/org/altbeacon/beacon/service/RangedBeacon.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/RangedBeacon.java rename to lib/src/main/java/org/altbeacon/beacon/service/RangedBeacon.java diff --git a/src/main/java/org/altbeacon/beacon/service/RangingData.java b/lib/src/main/java/org/altbeacon/beacon/service/RangingData.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/RangingData.java rename to lib/src/main/java/org/altbeacon/beacon/service/RangingData.java diff --git a/src/main/java/org/altbeacon/beacon/service/RegionMonitoringState.java b/lib/src/main/java/org/altbeacon/beacon/service/RegionMonitoringState.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/RegionMonitoringState.java rename to lib/src/main/java/org/altbeacon/beacon/service/RegionMonitoringState.java diff --git a/src/main/java/org/altbeacon/beacon/service/RssiFilter.java b/lib/src/main/java/org/altbeacon/beacon/service/RssiFilter.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/RssiFilter.java rename to lib/src/main/java/org/altbeacon/beacon/service/RssiFilter.java diff --git a/src/main/java/org/altbeacon/beacon/service/RunningAverageRssiFilter.java b/lib/src/main/java/org/altbeacon/beacon/service/RunningAverageRssiFilter.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/RunningAverageRssiFilter.java rename to lib/src/main/java/org/altbeacon/beacon/service/RunningAverageRssiFilter.java diff --git a/src/main/java/org/altbeacon/beacon/service/ScanHelper.java b/lib/src/main/java/org/altbeacon/beacon/service/ScanHelper.java similarity index 82% rename from src/main/java/org/altbeacon/beacon/service/ScanHelper.java rename to lib/src/main/java/org/altbeacon/beacon/service/ScanHelper.java index a4fb597d1..571d3c182 100644 --- a/src/main/java/org/altbeacon/beacon/service/ScanHelper.java +++ b/lib/src/main/java/org/altbeacon/beacon/service/ScanHelper.java @@ -16,6 +16,7 @@ import android.os.Build; import android.support.annotation.MainThread; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.RequiresApi; import android.support.annotation.WorkerThread; import android.support.annotation.RestrictTo; @@ -45,6 +46,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; /** * Created by dyoung on 6/16/17. @@ -59,8 +61,10 @@ class ScanHelper { private static final String TAG = ScanHelper.class.getSimpleName(); + @Nullable private ExecutorService mExecutor; private BeaconManager mBeaconManager; + @Nullable private CycledLeScanner mCycledScanner; private MonitoringStatus mMonitoringStatus; private final Map mRangedRegionState = new HashMap<>(); @@ -76,10 +80,37 @@ class ScanHelper { ScanHelper(Context context) { mContext = context; mBeaconManager = BeaconManager.getInstanceForApplication(context); - mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1); } - CycledLeScanner getCycledScanner() { + private ExecutorService getExecutor() { + if (mExecutor == null) { + mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1); + } + return mExecutor; + } + + void terminateThreads() { + if (mExecutor != null) { + mExecutor.shutdown(); + try { + if (!mExecutor.awaitTermination(10, TimeUnit.MILLISECONDS)) { + LogManager.e(TAG, "Can't stop beacon parsing thread."); + } + } + catch (InterruptedException e) { + LogManager.e(TAG, "Interrupted waiting to stop beacon parsing thread."); + } + mExecutor = null; + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + terminateThreads(); + } + + @Nullable CycledLeScanner getCycledScanner() { return mCycledScanner; } @@ -126,11 +157,12 @@ void processScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) { NonBeaconLeScanCallback nonBeaconLeScanCallback = mBeaconManager.getNonBeaconLeScanCallback(); try { - new ScanHelper.ScanProcessor(nonBeaconLeScanCallback).executeOnExecutor(mExecutor, + new ScanHelper.ScanProcessor(nonBeaconLeScanCallback).executeOnExecutor(getExecutor(), new ScanHelper.ScanData(device, rssi, scanRecord)); } catch (RejectedExecutionException e) { - LogManager.w(TAG, "Ignoring scan result because we cannot keep up."); + } catch (OutOfMemoryError e) { + LogManager.w(TAG, "Ignoring scan result because we cannot start a thread to keep up."); } } @@ -164,17 +196,26 @@ void startAndroidOBackgroundScan(Set beaconParsers) { } else if (!bluetoothAdapter.isEnabled()) { LogManager.w(TAG, "Failed to start background scan on Android O: BluetoothAdapter is not enabled"); } else { - int result = bluetoothAdapter.getBluetoothLeScanner().startScan(filters, settings, getScanCallbackIntent()); - if (result != 0) { - LogManager.e(TAG, "Failed to start background scan on Android O. Code: "+result); - } - else { - LogManager.d(TAG, "Started passive beacon scan"); + BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner(); + if (scanner != null) { + int result = scanner.startScan(filters, settings, getScanCallbackIntent()); + if (result != 0) { + LogManager.e(TAG, "Failed to start background scan on Android O. Code: " + result); + } else { + LogManager.d(TAG, "Started passive beacon scan"); + } + } else { + LogManager.e(TAG, "Failed to start background scan on Android O: scanner is null"); } } - } - catch (SecurityException e) { + } catch (SecurityException e) { LogManager.e(TAG, "SecurityException making Android O background scanner"); + } catch (NullPointerException e) { + // Needed to stop a crash caused by internal NPE thrown by Android. See issue #636 + LogManager.e(TAG, "NullPointerException starting Android O background scanner", e); + } catch (RuntimeException e) { + // Needed to stop a crash caused by internal Android throw. See issue #701 + LogManager.e(TAG, "Unexpected runtime exception starting Android O background scanner", e); } } @@ -196,6 +237,12 @@ void stopAndroidOBackgroundScan() { } } catch (SecurityException e) { LogManager.e(TAG, "SecurityException stopping Android O background scanner"); + } catch (NullPointerException e) { + // Needed to stop a crash caused by internal NPE thrown by Android. See issue #636 + LogManager.e(TAG, "NullPointerException stopping Android O background scanner", e); + } catch (RuntimeException e) { + // Needed to stop a crash caused by internal Android throw. See issue #701 + LogManager.e(TAG, "Unexpected runtime exception stopping Android O background scanner", e); } } @@ -218,31 +265,13 @@ public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { @MainThread @SuppressLint("WrongThread") public void onCycleEnd() { - mDistinctPacketDetector.clearDetections(); - mMonitoringStatus.updateNewlyOutside(); - processRangeData(); - // If we want to use simulated scanning data, do it here. This is used for testing in an emulator - if (mSimulatedScanData != null) { - // if simulatedScanData is provided, it will be seen every scan cycle. *in addition* to anything actually seen in the air - // it will not be used if we are not in debug mode - LogManager.w(TAG, "Simulated scan data is deprecated and will be removed in a future release. Please use the new BeaconSimulator interface instead."); - - if (0 != (mContext.getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) { - for (Beacon beacon : mSimulatedScanData) { - // This is an expensive call and we do not want to block the main thread. - // But here we are in debug/test mode so we allow it on the main thread. - //noinspection WrongThread - processBeaconFromScan(beacon); - } - } else { - LogManager.w(TAG, "Simulated scan data provided, but ignored because we are not running in debug mode. Please remove simulated scan data for production."); - } - } if (BeaconManager.getBeaconSimulator() != null) { + LogManager.d(TAG, "Beacon simulator enabled"); // if simulatedScanData is provided, it will be seen every scan cycle. *in addition* to anything actually seen in the air // it will not be used if we are not in debug mode if (BeaconManager.getBeaconSimulator().getBeacons() != null) { if (0 != (mContext.getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) { + LogManager.d(TAG, "Beacon simulator returns "+BeaconManager.getBeaconSimulator().getBeacons().size()+" beacons."); for (Beacon beacon : BeaconManager.getBeaconSimulator().getBeacons()) { // This is an expensive call and we do not want to block the main thread. // But here we are in debug/test mode so we allow it on the main thread. @@ -256,6 +285,14 @@ public void onCycleEnd() { LogManager.w(TAG, "getBeacons is returning null. No simulated beacons to report."); } } + else { + if (LogManager.isVerboseLoggingEnabled()) { + LogManager.d(TAG, "Beacon simulator not enabled"); + } + } + mDistinctPacketDetector.clearDetections(); + mMonitoringStatus.updateNewlyOutside(); + processRangeData(); } @Override @@ -406,10 +443,14 @@ protected void onProgressUpdate(Void... values) { private List matchingRegions(Beacon beacon, Collection regions) { List matched = new ArrayList<>(); for (Region region : regions) { - if (region.matchesBeacon(beacon)) { - matched.add(region); - } else { - LogManager.d(TAG, "This region (%s) does not match beacon: %s", region, beacon); + // Need to check if region is null in case it was removed from the collection by + // another thread during iteration + if (region != null) { + if (region.matchesBeacon(beacon)) { + matched.add(region); + } else { + LogManager.d(TAG, "This region (%s) does not match beacon: %s", region, beacon); + } } } return matched; diff --git a/lib/src/main/java/org/altbeacon/beacon/service/ScanJob.java b/lib/src/main/java/org/altbeacon/beacon/service/ScanJob.java new file mode 100644 index 000000000..86fbddff4 --- /dev/null +++ b/lib/src/main/java/org/altbeacon/beacon/service/ScanJob.java @@ -0,0 +1,400 @@ +package org.altbeacon.beacon.service; + +import android.annotation.TargetApi; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Handler; +import android.support.annotation.Nullable; + +import org.altbeacon.beacon.Beacon; +import org.altbeacon.beacon.BeaconManager; +import org.altbeacon.beacon.BuildConfig; +import org.altbeacon.beacon.Region; +import org.altbeacon.beacon.distance.ModelSpecificDistanceCalculator; +import org.altbeacon.beacon.logging.LogManager; +import org.altbeacon.beacon.utils.ProcessUtils; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Used to perform scans periodically using the JobScheduler + * + * Only one instance of this will be active, even with multiple jobIds. If one job + * is already running when another is scheduled to start, onStartJob gets called again on the same + * instance. + * + * If the OS decides to create a new instance, it will call onStopJob() on the old instance + * + * Created by dyoung on 3/24/17. + * @hide + */ +@TargetApi(Build.VERSION_CODES.LOLLIPOP) +public class ScanJob extends JobService { + private static final String TAG = ScanJob.class.getSimpleName(); + /* + Periodic scan jobs are used in general, but they cannot be started immediately. So we have + a second immediate scan job to kick off when scanning gets started or settings changed. + Once the periodic one gets run, the immediate is cancelled. + */ + private static int sOverrideImmediateScanJobId = -1; + private static int sOverridePeriodicScanJobId = -1; + + @Nullable + private ScanState mScanState = null; + private Handler mStopHandler = new Handler(); + @Nullable + private ScanHelper mScanHelper; + private boolean mInitialized = false; + private boolean mStopCalled = false; + + @Override + public boolean onStartJob(final JobParameters jobParameters) { + // We start off on the main UI thread here. + // But the ScanState restore from storage sometimes hangs, so we start new thread here just + // to kick that off. This way if the restore hangs, we don't hang the UI thread. + LogManager.d(TAG, "ScanJob Lifecycle START: "+ScanJob.this); + new Thread(new Runnable() { + public void run() { + if (!initialzeScanHelper()) { + LogManager.e(TAG, "Cannot allocate a scanner to look for beacons. System resources are low."); + ScanJob.this.jobFinished(jobParameters , false); + } + ScanJobScheduler.getInstance().ensureNotificationProcessorSetup(getApplicationContext()); + if (jobParameters.getJobId() == getImmediateScanJobId(ScanJob.this)) { + LogManager.i(TAG, "Running immediate scan job: instance is "+ScanJob.this); + } + else { + LogManager.i(TAG, "Running periodic scan job: instance is "+ScanJob.this); + } + + List queuedScanResults = ScanJobScheduler.getInstance().dumpBackgroundScanResultQueue(); + LogManager.d(TAG, "Processing %d queued scan resuilts", queuedScanResults.size()); + for (ScanResult result : queuedScanResults) { + ScanRecord scanRecord = result.getScanRecord(); + if (scanRecord != null) { + if (mScanHelper != null) { + mScanHelper.processScanResult(result.getDevice(), result.getRssi(), scanRecord.getBytes()); + } + } + } + LogManager.d(TAG, "Done processing queued scan resuilts"); + + // This syncronized block is around the scan start. + // Without it, it is possilbe that onStopJob is called in another thread and + // closing out the CycledScanner + synchronized(ScanJob.this) { + if (mStopCalled) { + LogManager.d(TAG, "Quitting scan job before we even start. Somebody told us to stop."); + ScanJob.this.jobFinished(jobParameters , false); + return; + } + + boolean startedScan; + if (mInitialized) { + LogManager.d(TAG, "Scanning already started. Resetting for current parameters"); + startedScan = restartScanning(); + } + else { + startedScan = startScanning(); + } + mStopHandler.removeCallbacksAndMessages(null); + + if (startedScan) { + if (mScanState != null) { + LogManager.i(TAG, "Scan job running for "+mScanState.getScanJobRuntimeMillis()+" millis"); + mStopHandler.postDelayed(new Runnable() { + @Override + public void run() { + LogManager.i(TAG, "Scan job runtime expired: " + ScanJob.this); + stopScanning(); + mScanState.save(); + ScanJob.this.jobFinished(jobParameters , false); + + // need to execute this after the current block or Android stops this job prematurely + mStopHandler.post(new Runnable() { + @Override + public void run() { + scheduleNextScan(); + } + }); + + } + }, mScanState.getScanJobRuntimeMillis()); + } + } + else { + LogManager.i(TAG, "Scanning not started so Scan job is complete."); + stopScanning(); + mScanState.save(); + LogManager.d(TAG, "ScanJob Lifecycle STOP (start fail): "+ScanJob.this); + ScanJob.this.jobFinished(jobParameters , false); + } + } + + } + }).start(); + + return true; + } + + private void scheduleNextScan(){ + if (mScanState != null) { + if(!mScanState.getBackgroundMode()){ + // immediately reschedule scan if running in foreground + LogManager.d(TAG, "In foreground mode, schedule next scan"); + ScanJobScheduler.getInstance().forceScheduleNextScan(ScanJob.this); + } else { + startPassiveScanIfNeeded(); + } + } + } + + private void startPassiveScanIfNeeded() { + if (mScanState != null) { + LogManager.d(TAG, "Checking to see if we need to start a passive scan"); + boolean insideAnyRegion = false; + // Clone the collection before iterating to prevent ConcurrentModificationException per #577 + List regions = new ArrayList<>(mScanState.getMonitoringStatus().regions()); + for (Region region : regions) { + RegionMonitoringState state = mScanState.getMonitoringStatus().stateOf(region); + if (state != null && state.getInside()) { + insideAnyRegion = true; + } + } + if (insideAnyRegion) { + // TODO: Set up a scan filter for not detecting a beacon pattern + LogManager.i(TAG, "We are inside a beacon region. We will not scan between cycles."); + } + else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (mScanHelper != null) { + mScanHelper.startAndroidOBackgroundScan(mScanState.getBeaconParsers()); + } + } + else { + LogManager.d(TAG, "This is not Android O. No scanning between cycles when using ScanJob"); + } + } + } + } + + @Override + public boolean onStopJob(JobParameters params) { + // See corresponding synchronized block in onStartJob + synchronized(ScanJob.this) { + mStopCalled = true; + if (params.getJobId() == getPeriodicScanJobId(this)) { + LogManager.i(TAG, "onStopJob called for periodic scan " + this); + } + else { + LogManager.i(TAG, "onStopJob called for immediate scan " + this); + } + LogManager.d(TAG, "ScanJob Lifecycle STOP: "+ScanJob.this); + // Cancel the stop timer. The OS is stopping prematurely + mStopHandler.removeCallbacksAndMessages(null); + + stopScanning(); + startPassiveScanIfNeeded(); + if (mScanHelper != null) { + mScanHelper.terminateThreads(); + } + } + return false; + } + + private void stopScanning() { + mInitialized = false; + if (mScanHelper != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mScanHelper.stopAndroidOBackgroundScan(); + } + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().stop(); + mScanHelper.getCycledScanner().destroy(); + } + } + LogManager.d(TAG, "Scanning stopped"); + } + + // Returns false if ScanHelper cannot be initialized + private boolean initialzeScanHelper() { + mScanState = ScanState.restore(ScanJob.this); + if (mScanState != null) { + ScanHelper scanHelper = new ScanHelper(this); + mScanState.setLastScanStartTimeMillis(System.currentTimeMillis()); + scanHelper.setMonitoringStatus(mScanState.getMonitoringStatus()); + scanHelper.setRangedRegionState(mScanState.getRangedRegionState()); + scanHelper.setBeaconParsers(mScanState.getBeaconParsers()); + scanHelper.setExtraDataBeaconTracker(mScanState.getExtraBeaconDataTracker()); + if (scanHelper.getCycledScanner() == null) { + try { + scanHelper.createCycledLeScanner(mScanState.getBackgroundMode(), null); + } + catch (OutOfMemoryError e) { + LogManager.w(TAG, "Failed to create CycledLeScanner thread."); + return false; + } + } + mScanHelper = scanHelper; + } + else { + return false; + } + return true; + } + + // Returns true of scanning actually was started, false if it did not need to be + private boolean restartScanning() { + if (mScanState != null && mScanHelper != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + mScanHelper.stopAndroidOBackgroundScan(); + } + long scanPeriod = mScanState.getBackgroundMode() ? mScanState.getBackgroundScanPeriod() : mScanState.getForegroundScanPeriod(); + long betweenScanPeriod = mScanState.getBackgroundMode() ? mScanState.getBackgroundBetweenScanPeriod() : mScanState.getForegroundBetweenScanPeriod(); + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().setScanPeriods(scanPeriod, + betweenScanPeriod, + mScanState.getBackgroundMode()); + } + mInitialized = true; + if (scanPeriod <= 0) { + LogManager.w(TAG, "Starting scan with scan period of zero. Exiting ScanJob."); + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().stop(); + } + return false; + } + + if (mScanHelper.getRangedRegionState().size() > 0 || mScanHelper.getMonitoringStatus().regions().size() > 0) { + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().start(); + } + return true; + } + else { + if (mScanHelper.getCycledScanner() != null) { + mScanHelper.getCycledScanner().stop(); + } + return false; + } + } + else { + return false; + } + } + + // Returns true of scanning actually was started, false if it did not need to be + private boolean startScanning() { + BeaconManager beaconManager = BeaconManager.getInstanceForApplication(getApplicationContext()); + beaconManager.setScannerInSameProcess(true); + if (beaconManager.isMainProcess()) { + LogManager.i(TAG, "scanJob version %s is starting up on the main process", BuildConfig.VERSION_NAME); + } + else { + LogManager.i(TAG, "beaconScanJob library version %s is starting up on a separate process", BuildConfig.VERSION_NAME); + ProcessUtils processUtils = new ProcessUtils(ScanJob.this); + LogManager.i(TAG, "beaconScanJob PID is "+processUtils.getPid()+" with process name "+processUtils.getProcessName()); + } + ModelSpecificDistanceCalculator defaultDistanceCalculator = new ModelSpecificDistanceCalculator(ScanJob.this, BeaconManager.getDistanceModelUpdateUrl()); + Beacon.setDistanceCalculator(defaultDistanceCalculator); + return restartScanning(); + } + + /** + * Allows configuration of the job id for the Android Job Scheduler. If not configured, this + * will default to the value in the AndroidManifest.xml + * + * WARNING: If using this library in a multi-process application, this method may not work. + * This is considered a private API and may be removed at any time. + * + * the preferred way of setting this is in the AndroidManifest.xml as so: + * + * + * + * + * + * @param id identifier to give the job + */ + @SuppressWarnings("unused") + public static void setOverrideImmediateScanJobId(int id) { + sOverrideImmediateScanJobId = id; + } + + /** + * Allows configuration of the job id for the Android Job Scheduler. If not configured, this + * will default to the value in the AndroidManifest.xml + * + * WARNING: If using this library in a multi-process application, this method may not work. + * This is considered a private API and may be removed at any time. + * + * the preferred way of setting this is in the AndroidManifest.xml as so: + * + * + * + * + * + * + * + * @param id identifier to give the job + */ + @SuppressWarnings("unused") + public static void setOverridePeriodicScanJobId(int id) { + sOverridePeriodicScanJobId = id; + } + + /** + * Returns the job id to be used to schedule this job. This may be set in the + * AndroidManifest.xml or in single process applications by using #setOverrideJobId + * @param context the application context + * @return the job id + */ + public static int getImmediateScanJobId(Context context) { + if (sOverrideImmediateScanJobId >= 0) { + LogManager.i(TAG, "Using ImmediateScanJobId from static override: "+ + sOverrideImmediateScanJobId); + return sOverrideImmediateScanJobId; + } + return getJobIdFromManifest(context, "immediateScanJobId"); + } + + /** + * Returns the job id to be used to schedule this job. This may be set in the + * AndroidManifest.xml or in single process applications by using #setOverrideJobId + * @param context the application context + * @return the job id + */ + public static int getPeriodicScanJobId(Context context) { + if (sOverrideImmediateScanJobId >= 0) { + LogManager.i(TAG, "Using PeriodicScanJobId from static override: "+ + sOverridePeriodicScanJobId); + return sOverridePeriodicScanJobId; + } + return getJobIdFromManifest(context, "periodicScanJobId"); + } + + private static int getJobIdFromManifest(Context context, String name) { + PackageItemInfo info = null; + try { + info = context.getPackageManager().getServiceInfo(new ComponentName(context, + ScanJob.class), PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { /* do nothing here */ } + if (info != null && info.metaData != null && info.metaData.get(name) != null) { + int jobId = info.metaData.getInt(name); + LogManager.i(TAG, "Using "+name+" from manifest: "+jobId); + return jobId; + } + else { + throw new RuntimeException("Cannot get job id from manifest. " + + "Make sure that the "+name+" is configured in the manifest for the ScanJob."); + } + } +} diff --git a/lib/src/main/java/org/altbeacon/beacon/service/ScanJobScheduler.java b/lib/src/main/java/org/altbeacon/beacon/service/ScanJobScheduler.java new file mode 100644 index 000000000..1fd100ac2 --- /dev/null +++ b/lib/src/main/java/org/altbeacon/beacon/service/ScanJobScheduler.java @@ -0,0 +1,210 @@ +package org.altbeacon.beacon.service; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.bluetooth.le.ScanResult; +import android.content.ComponentName; +import android.content.Context; +import android.os.Build; +import android.os.PersistableBundle; +import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; + +import org.altbeacon.beacon.BeaconLocalBroadcastProcessor; +import org.altbeacon.beacon.BeaconManager; +import org.altbeacon.beacon.logging.LogManager; + +import java.util.ArrayList; +import java.util.List; + +/** + * Schedules two types of ScanJobs: + * 1. Periodic, which are set to go every scanPeriod+betweenScanPeriod + * 2. Immediate, which go right now. + * + * Immediate ScanJobs are used when the app is in the foreground and wants to get immediate results + * or when beacons have been detected with background scan filters and delivered via Intents and + * a scan needs to run in a timely manner to collect data about those beacons known to be newly + * in the vicinity despite the app being in the background. + * + * Created by dyoung on 6/7/17. + * @hide + */ +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class ScanJobScheduler { + private static final String TAG = ScanJobScheduler.class.getSimpleName(); + private static final Object SINGLETON_LOCK = new Object(); + private static final long MIN_MILLIS_BETWEEN_SCAN_JOB_SCHEDULING = 10000L; + @Nullable + private static volatile ScanJobScheduler sInstance = null; + @NonNull + private Long mScanJobScheduleTime = 0L; + @NonNull + private List mBackgroundScanResultQueue = new ArrayList<>(); + @Nullable + private BeaconLocalBroadcastProcessor mBeaconNotificationProcessor; + + @NonNull + public static ScanJobScheduler getInstance() { + ScanJobScheduler instance = sInstance; + if (instance == null) { + synchronized (SINGLETON_LOCK) { + instance = sInstance; + if (instance == null) { + sInstance = instance = new ScanJobScheduler(); + } + } + } + return instance; + } + + private ScanJobScheduler() { + } + + void ensureNotificationProcessorSetup(Context context) { + if (mBeaconNotificationProcessor == null) { + mBeaconNotificationProcessor = new BeaconLocalBroadcastProcessor(context); + } + mBeaconNotificationProcessor.register(); + } + + /** + * @return previoulsy queued scan results delivered in the background + */ + List dumpBackgroundScanResultQueue() { + List retval = mBackgroundScanResultQueue; + mBackgroundScanResultQueue = new ArrayList<>(); + return retval; + } + + private void applySettingsToScheduledJob(Context context, BeaconManager beaconManager, ScanState scanState) { + scanState.applyChanges(beaconManager); + LogManager.d(TAG, "Applying scan job settings with background mode "+scanState.getBackgroundMode()); + schedule(context, scanState, false); + } + + public void applySettingsToScheduledJob(Context context, BeaconManager beaconManager) { + LogManager.d(TAG, "Applying settings to ScanJob"); + JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + ScanState scanState = ScanState.restore(context); + applySettingsToScheduledJob(context, beaconManager, scanState); + } + + public void cancelSchedule(Context context) { + JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + jobScheduler.cancel(ScanJob.getImmediateScanJobId(context)); + jobScheduler.cancel(ScanJob.getPeriodicScanJobId(context)); + + if (mBeaconNotificationProcessor != null) { + mBeaconNotificationProcessor.unregister(); + } + } + + public void scheduleAfterBackgroundWakeup(Context context, List scanResults) { + if (scanResults != null) { + mBackgroundScanResultQueue.addAll(scanResults); + } + synchronized (this) { + // We typically get a bunch of calls in a row here, separated by a few millis. Only do this once. + if (System.currentTimeMillis() - mScanJobScheduleTime > MIN_MILLIS_BETWEEN_SCAN_JOB_SCHEDULING) { + LogManager.d(TAG, "scheduling an immediate scan job because last did "+(System.currentTimeMillis() - mScanJobScheduleTime)+"seconds ago."); + mScanJobScheduleTime = System.currentTimeMillis(); + } + else { + LogManager.d(TAG, "Not scheduling an immediate scan job because we just did recently."); + return; + } + } + ScanState scanState = ScanState.restore(context); + schedule(context, scanState, true); + } + + public void forceScheduleNextScan(Context context) { + ScanState scanState = ScanState.restore(context); + schedule(context, scanState, false); + } + + private void schedule(Context context, ScanState scanState, boolean backgroundWakeup) { + ensureNotificationProcessorSetup(context); + + long betweenScanPeriod = scanState.getScanJobIntervalMillis() - scanState.getScanJobRuntimeMillis(); + + long millisToNextJobStart; + if (backgroundWakeup) { + LogManager.d(TAG, "We just woke up in the background based on a new scan result. Start scan job immediately."); + millisToNextJobStart = 0; + } + else { + if (betweenScanPeriod > 0) { + // If we pause between scans, then we need to start scanning on a normalized time + millisToNextJobStart = (SystemClock.elapsedRealtime() % scanState.getScanJobIntervalMillis()); + } + else { + millisToNextJobStart = 0; + } + + if (millisToNextJobStart < 50) { + // always wait a little bit to start scanning in case settings keep changing. + // by user restarting settings and scanning. 50ms should be fine + millisToNextJobStart = 50; + } + } + + JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + + int monitoredAndRangedRegionCount = scanState.getMonitoringStatus().regions().size() + scanState.getRangedRegionState().size(); + if (monitoredAndRangedRegionCount > 0) { + if (backgroundWakeup || !scanState.getBackgroundMode()) { + // If we are in the foreground, and we want to start a scan soon, we will schedule an + // immediate job + if (millisToNextJobStart < scanState.getScanJobIntervalMillis() - 50) { + // If the next time we want to scan is less than 50ms from the periodic scan cycle, then] + // we schedule it for that specific time. + LogManager.d(TAG, "Scheduling immediate ScanJob to run in "+millisToNextJobStart+" millis"); + JobInfo immediateJob = new JobInfo.Builder(ScanJob.getImmediateScanJobId(context), new ComponentName(context, ScanJob.class)) + .setPersisted(true) // This makes it restart after reboot + .setExtras(new PersistableBundle()) + .setMinimumLatency(millisToNextJobStart) + .setOverrideDeadline(millisToNextJobStart).build(); + int error = jobScheduler.schedule(immediateJob); + if (error < 0) { + LogManager.e(TAG, "Failed to schedule scan job. Beacons will not be detected. Error: "+error); + } + } else { + LogManager.d(TAG, "Not scheduling immediate scan, assuming periodic is about to run"); + } + } + else { + LogManager.d(TAG, "Not scheduling an immediate scan because we are in background mode. Cancelling existing immediate ScanJob."); + jobScheduler.cancel(ScanJob.getImmediateScanJobId(context)); + } + + JobInfo.Builder periodicJobBuilder = new JobInfo.Builder(ScanJob.getPeriodicScanJobId(context), new ComponentName(context, ScanJob.class)) + .setPersisted(true) // This makes it restart after reboot + .setExtras(new PersistableBundle()); + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // ON Android N+ we specify a tolerance of 0ms (capped at 5% by the OS) to ensure + // our scans happen within 5% of the schduled time. + periodicJobBuilder.setPeriodic(scanState.getScanJobIntervalMillis(), 0L).build(); + } + else { + periodicJobBuilder.setPeriodic(scanState.getScanJobIntervalMillis()).build(); + } + final JobInfo jobInfo = periodicJobBuilder.build(); + LogManager.d(TAG, "Scheduling ScanJob " + jobInfo + " to run every "+scanState.getScanJobIntervalMillis()+" millis"); + int error = jobScheduler.schedule(jobInfo); + if (error < 0) { + LogManager.e(TAG, "Failed to schedule scan job. Beacons will not be detected. Error: "+error); + } + + } + else { + LogManager.d(TAG, "We are not monitoring or ranging any regions. We are going to cancel all scan jobs."); + jobScheduler.cancel(ScanJob.getImmediateScanJobId(context)); + jobScheduler.cancel(ScanJob.getPeriodicScanJobId(context)); + } + } +} diff --git a/src/main/java/org/altbeacon/beacon/service/ScanState.java b/lib/src/main/java/org/altbeacon/beacon/service/ScanState.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/ScanState.java rename to lib/src/main/java/org/altbeacon/beacon/service/ScanState.java diff --git a/src/main/java/org/altbeacon/beacon/service/SettingsData.java b/lib/src/main/java/org/altbeacon/beacon/service/SettingsData.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/SettingsData.java rename to lib/src/main/java/org/altbeacon/beacon/service/SettingsData.java diff --git a/src/main/java/org/altbeacon/beacon/service/StartRMData.java b/lib/src/main/java/org/altbeacon/beacon/service/StartRMData.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/StartRMData.java rename to lib/src/main/java/org/altbeacon/beacon/service/StartRMData.java diff --git a/src/main/java/org/altbeacon/beacon/service/Stats.java b/lib/src/main/java/org/altbeacon/beacon/service/Stats.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/Stats.java rename to lib/src/main/java/org/altbeacon/beacon/service/Stats.java diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanCallback.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanCallback.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanCallback.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanCallback.java diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java similarity index 95% rename from src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java index 94fe151ed..c9d6f06b2 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java +++ b/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java @@ -6,13 +6,16 @@ import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; +import android.os.PowerManager; import android.os.SystemClock; import android.support.annotation.AnyThread; import android.support.annotation.MainThread; @@ -58,6 +61,7 @@ public abstract class CycledLeScanner { // avoid doing too many scans in a limited time on Android 7.0 or because we are capable of // multiple detections. If true, it indicates scanning needs to be stopped when we finish. private boolean mScanningLeftOn = false; + private BroadcastReceiver mCancelAlarmOnUserSwitchBroadcastReceiver = null; protected long mBetweenScanPeriod; @@ -279,7 +283,6 @@ public void destroy() { // Remove any postDelayed Runnables queued for the next scan cycle mHandler.removeCallbacksAndMessages(null); - // We cannot quit the thread used by the handler until queued Runnables have been processed, // because the handler is what stops scanning, and we do not want scanning left on. // So we stop the thread using the handler, so we make sure it happens after all other @@ -292,6 +295,7 @@ public void run() { mScanThread.quit(); } }); + cleanupCancelAlarmOnUserSwitch(); } protected abstract void stopScan(); @@ -522,12 +526,40 @@ protected void setWakeUpAlarm() { if (milliseconds < mScanPeriod) { milliseconds = mScanPeriod; } - AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + milliseconds, getWakeUpOperation()); LogManager.d(TAG, "Set a wakeup alarm to go off in %s ms: %s", milliseconds, getWakeUpOperation()); + cancelAlarmOnUserSwitch(); + } + + // Added to prevent crash on switching users. See #876 + protected void cancelAlarmOnUserSwitch() { + if (mCancelAlarmOnUserSwitchBroadcastReceiver == null) { + IntentFilter filter = new IntentFilter(); + filter.addAction( Intent.ACTION_USER_BACKGROUND ); + filter.addAction( Intent.ACTION_USER_FOREGROUND ); + + mCancelAlarmOnUserSwitchBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + LogManager.w(TAG, "User switch detected. Cancelling alarm to prevent potential crash."); + cancelWakeUpAlarm(); + } + }; + mContext.registerReceiver(mCancelAlarmOnUserSwitchBroadcastReceiver, filter); + } + } + protected void cleanupCancelAlarmOnUserSwitch() { + if (mCancelAlarmOnUserSwitchBroadcastReceiver != null) { + try { + mContext.unregisterReceiver(mCancelAlarmOnUserSwitchBroadcastReceiver); + } + catch (IllegalArgumentException e) {} // thrown if OS does not think it was registered + mCancelAlarmOnUserSwitchBroadcastReceiver = null; + } } + protected PendingIntent getWakeUpOperation() { if (mWakeUpOperation == null) { Intent wakeupIntent = new Intent(mContext, StartupBroadcastReceiver.class); diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForAndroidO.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForAndroidO.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForAndroidO.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForAndroidO.java diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForJellyBeanMr2.java diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java similarity index 90% rename from src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java index 011c890d1..bbb531594 100644 --- a/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java +++ b/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScannerForLollipop.java @@ -9,7 +9,9 @@ import android.bluetooth.le.ScanSettings; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.ParcelUuid; +import android.os.PowerManager; import android.os.SystemClock; import android.support.annotation.MainThread; import android.support.annotation.WorkerThread; @@ -34,10 +36,12 @@ public class CycledLeScannerForLollipop extends CycledLeScanner { private long mBackgroundLScanFirstDetectionTime = 0; private boolean mMainScanCycleActive = false; private final BeaconManager mBeaconManager; + private final PowerManager mPowerManager; public CycledLeScannerForLollipop(Context context, long scanPeriod, long betweenScanPeriod, boolean backgroundFlag, CycledLeScanCallback cycledLeScanCallback, BluetoothCrashResolver crashResolver) { super(context, scanPeriod, betweenScanPeriod, backgroundFlag, cycledLeScanCallback, crashResolver); mBeaconManager = BeaconManager.getInstanceForApplication(mContext); + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); } @Override @@ -176,14 +180,34 @@ protected void startScan() { filters = new ScanFilterUtils().createScanFiltersForBeaconParsers( mBeaconManager.getBeaconParsers()); } else { - LogManager.d(TAG, "starting non-filtered scan in SCAN_MODE_LOW_LATENCY"); + LogManager.d(TAG, "starting a scan in SCAN_MODE_LOW_LATENCY"); settings = (new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)).build(); - // We create wildcard scan filters that match any advertisement so that we can detect + // We create scan filters that match so that we can detect // beacons in foreground mode even if the screen is off. This is a necessary workaround // for a change in Android 8.1 that blocks scan results when the screen is off unless - // there is a scan filter associatd with the scan. Prior to 8.1, filters could just be - // left null. The wildcard filter matches everything. - filters = new ScanFilterUtils().createWildcardScanFilters(); + // there is a scan filter associated with the scan. Prior to 8.1, filters could just be + // left null. + // We only add these filters on 8.1+ devices, because adding scan filters has been reported + // to cause scan failures on some Samsung devices with Android 5.x + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + if (Build.MANUFACTURER.equalsIgnoreCase("samsung") && !mPowerManager.isInteractive()) { + // On the Samsung Galaxy Note 8.1, scans are blocked with screen off when the + // scan filter is empty (wildcard). We do a more detailed filter on Samsung only + // because it might block detections of AltBeacon packets with non-standard + // manufacturer codes. See #769 for details. + LogManager.d(TAG, "Using a non-empty scan filter since this is Samsung 8.1+"); + filters = new ScanFilterUtils().createScanFiltersForBeaconParsers( + mBeaconManager.getBeaconParsers()); + } + else { + LogManager.d(TAG, "Using an empty scan filter since this is 8.1+ on Non-Samsung"); + // The wildcard filter matches everything. + filters = new ScanFilterUtils().createWildcardScanFilters(); + } + } + else { + LogManager.d(TAG, "Using no scan filter since this is pre-8.1"); + } } if (settings != null) { diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetector.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetector.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetector.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetector.java diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/NonBeaconLeScanCallback.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/NonBeaconLeScanCallback.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/scanner/NonBeaconLeScanCallback.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/NonBeaconLeScanCallback.java diff --git a/src/main/java/org/altbeacon/beacon/service/scanner/ScanFilterUtils.java b/lib/src/main/java/org/altbeacon/beacon/service/scanner/ScanFilterUtils.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/service/scanner/ScanFilterUtils.java rename to lib/src/main/java/org/altbeacon/beacon/service/scanner/ScanFilterUtils.java diff --git a/src/main/java/org/altbeacon/beacon/simulator/BeaconSimulator.java b/lib/src/main/java/org/altbeacon/beacon/simulator/BeaconSimulator.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/simulator/BeaconSimulator.java rename to lib/src/main/java/org/altbeacon/beacon/simulator/BeaconSimulator.java diff --git a/src/main/java/org/altbeacon/beacon/simulator/StaticBeaconSimulator.java b/lib/src/main/java/org/altbeacon/beacon/simulator/StaticBeaconSimulator.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/simulator/StaticBeaconSimulator.java rename to lib/src/main/java/org/altbeacon/beacon/simulator/StaticBeaconSimulator.java diff --git a/src/main/java/org/altbeacon/beacon/startup/BootstrapNotifier.java b/lib/src/main/java/org/altbeacon/beacon/startup/BootstrapNotifier.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/startup/BootstrapNotifier.java rename to lib/src/main/java/org/altbeacon/beacon/startup/BootstrapNotifier.java diff --git a/src/main/java/org/altbeacon/beacon/startup/RegionBootstrap.java b/lib/src/main/java/org/altbeacon/beacon/startup/RegionBootstrap.java similarity index 74% rename from src/main/java/org/altbeacon/beacon/startup/RegionBootstrap.java rename to lib/src/main/java/org/altbeacon/beacon/startup/RegionBootstrap.java index 6dc42d510..4931a08be 100644 --- a/src/main/java/org/altbeacon/beacon/startup/RegionBootstrap.java +++ b/lib/src/main/java/org/altbeacon/beacon/startup/RegionBootstrap.java @@ -7,6 +7,7 @@ import org.altbeacon.beacon.BeaconConsumer; import org.altbeacon.beacon.BeaconManager; +import org.altbeacon.beacon.MonitorNotifier; import org.altbeacon.beacon.Region; import org.altbeacon.beacon.logging.LogManager; @@ -34,12 +35,57 @@ public class RegionBootstrap { protected static final String TAG = "AppStarter"; private BeaconManager beaconManager; - private BootstrapNotifier application; + private MonitorNotifier monitorNotifier; + private Context context; private List regions; private boolean disabled = false; private BeaconConsumer beaconConsumer; private boolean serviceConnected = false; + /** + * Constructor to bootstrap your Application on an entry/exit from a single region. + * + * @param context + * @param monitorNotifier + * @param region + */ + public RegionBootstrap(final Context context, final MonitorNotifier monitorNotifier, Region region) { + if (context == null) { + throw new NullPointerException("Application Context should not be null"); + } + this.context = context.getApplicationContext(); + this.monitorNotifier = monitorNotifier; + regions = new ArrayList(); + regions.add(region); + + beaconManager = BeaconManager.getInstanceForApplication(context); + beaconConsumer = new InternalBeaconConsumer(); + beaconManager.bind(beaconConsumer); + LogManager.d(TAG, "Waiting for BeaconService connection"); + } + + /** + * Constructor to bootstrap your Application on an entry/exit from multiple regions + * + * @param context + * @param monitorNotifier + * @param regions + */ + public RegionBootstrap(final Context context, final MonitorNotifier monitorNotifier, List regions) { + if (context == null) { + throw new NullPointerException("Application Context should not be null"); + } + this.context = context.getApplicationContext(); + this.monitorNotifier = monitorNotifier; + + this.regions = regions; + + beaconManager = BeaconManager.getInstanceForApplication(context); + beaconConsumer = new InternalBeaconConsumer(); + beaconManager.bind(beaconConsumer); + LogManager.d(TAG, "Waiting for BeaconService connection"); + } + /** * Constructor to bootstrap your Application on an entry/exit from a single region. * @@ -50,10 +96,11 @@ public RegionBootstrap(BootstrapNotifier application, Region region) { if (application.getApplicationContext() == null) { throw new NullPointerException("The BootstrapNotifier instance is returning null from its getApplicationContext() method. Have you implemented this method?"); } - beaconManager = BeaconManager.getInstanceForApplication(application.getApplicationContext()); - this.application = application; + this.context = application.getApplicationContext(); regions = new ArrayList(); regions.add(region); + this.monitorNotifier = application; + beaconManager = BeaconManager.getInstanceForApplication(context); beaconConsumer = new InternalBeaconConsumer(); beaconManager.bind(beaconConsumer); LogManager.d(TAG, "Waiting for BeaconService connection"); @@ -61,6 +108,7 @@ public RegionBootstrap(BootstrapNotifier application, Region region) { /** * Constructor to bootstrap your Application on an entry/exit from multiple regions + * * @param application * @param regions */ @@ -68,11 +116,11 @@ public RegionBootstrap(BootstrapNotifier application, List regions) { if (application.getApplicationContext() == null) { throw new NullPointerException("The BootstrapNotifier instance is returning null from its getApplicationContext() method. Have you implemented this method?"); } - beaconManager = BeaconManager.getInstanceForApplication(application.getApplicationContext()); - this.application = application; + this.context = application.getApplicationContext(); this.regions = regions; - + this.monitorNotifier = application; + beaconManager = BeaconManager.getInstanceForApplication(context); beaconConsumer = new InternalBeaconConsumer(); beaconManager.bind(beaconConsumer); LogManager.d(TAG, "Waiting for BeaconService connection"); @@ -110,7 +158,7 @@ public void addRegion(Region region) { } catch (RemoteException e) { LogManager.e(e, TAG, "Can't add bootstrap region"); } - }else{ + } else { LogManager.w(TAG, "Adding a region: service not yet Connected"); } regions.add(region); @@ -130,7 +178,7 @@ public void removeRegion(Region region) { } catch (RemoteException e) { LogManager.e(e, TAG, "Can't stop bootstrap region"); } - }else{ + } else { LogManager.w(TAG, "Removing a region: service not yet Connected"); } regions.remove(region); @@ -147,7 +195,7 @@ private class InternalBeaconConsumer implements BeaconConsumer { @Override public void onBeaconServiceConnect() { LogManager.d(TAG, "Activating background region monitoring"); - beaconManager.addMonitorNotifier(application); + beaconManager.addMonitorNotifier(monitorNotifier); serviceConnected = true; try { for (Region region : regions) { @@ -168,8 +216,8 @@ public void onBeaconServiceConnect() { @Override public boolean bindService(Intent intent, ServiceConnection conn, int arg2) { this.serviceIntent = intent; - application.getApplicationContext().startService(intent); - return application.getApplicationContext().bindService(intent, conn, arg2); + context.startService(intent); + return context.bindService(intent, conn, arg2); } @@ -178,7 +226,7 @@ public boolean bindService(Intent intent, ServiceConnection conn, int arg2) { */ @Override public Context getApplicationContext() { - return application.getApplicationContext(); + return context; } /** @@ -186,8 +234,8 @@ public Context getApplicationContext() { */ @Override public void unbindService(ServiceConnection conn) { - application.getApplicationContext().unbindService(conn); - application.getApplicationContext().stopService(serviceIntent); + context.unbindService(conn); + context.stopService(serviceIntent); serviceConnected = false; } } diff --git a/src/main/java/org/altbeacon/beacon/startup/StartupBroadcastReceiver.java b/lib/src/main/java/org/altbeacon/beacon/startup/StartupBroadcastReceiver.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/startup/StartupBroadcastReceiver.java rename to lib/src/main/java/org/altbeacon/beacon/startup/StartupBroadcastReceiver.java diff --git a/src/main/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessor.java b/lib/src/main/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessor.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessor.java rename to lib/src/main/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessor.java diff --git a/src/main/java/org/altbeacon/beacon/utils/ProcessUtils.java b/lib/src/main/java/org/altbeacon/beacon/utils/ProcessUtils.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/utils/ProcessUtils.java rename to lib/src/main/java/org/altbeacon/beacon/utils/ProcessUtils.java diff --git a/src/main/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressor.java b/lib/src/main/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressor.java similarity index 100% rename from src/main/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressor.java rename to lib/src/main/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressor.java diff --git a/src/main/java/org/altbeacon/bluetooth/BleAdvertisement.java b/lib/src/main/java/org/altbeacon/bluetooth/BleAdvertisement.java similarity index 100% rename from src/main/java/org/altbeacon/bluetooth/BleAdvertisement.java rename to lib/src/main/java/org/altbeacon/bluetooth/BleAdvertisement.java diff --git a/src/main/java/org/altbeacon/bluetooth/BluetoothCrashResolver.java b/lib/src/main/java/org/altbeacon/bluetooth/BluetoothCrashResolver.java similarity index 100% rename from src/main/java/org/altbeacon/bluetooth/BluetoothCrashResolver.java rename to lib/src/main/java/org/altbeacon/bluetooth/BluetoothCrashResolver.java diff --git a/src/main/java/org/altbeacon/bluetooth/BluetoothMedic.java b/lib/src/main/java/org/altbeacon/bluetooth/BluetoothMedic.java similarity index 92% rename from src/main/java/org/altbeacon/bluetooth/BluetoothMedic.java rename to lib/src/main/java/org/altbeacon/bluetooth/BluetoothMedic.java index 96425e523..4eb07069e 100644 --- a/src/main/java/org/altbeacon/bluetooth/BluetoothMedic.java +++ b/lib/src/main/java/org/altbeacon/bluetooth/BluetoothMedic.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Build; import android.os.Handler; import android.os.PersistableBundle; import android.support.annotation.NonNull; @@ -258,30 +259,33 @@ public void onScanFailed(int errorCode) { } }; if(scanner != null) { - scanner.startScan(callback); - while (this.mScanTestResult == null) { - LogManager.d(TAG, "Waiting for scan test to complete..."); + try { + scanner.startScan(callback); + while (this.mScanTestResult == null) { + LogManager.d(TAG, "Waiting for scan test to complete..."); - try { - Thread.sleep(1000L); - } catch (InterruptedException e) { /* do nothing */ } + try { + Thread.sleep(1000L); + } catch (InterruptedException e) { /* do nothing */ } - if (System.currentTimeMillis() - testStartTime > 5000L) { - LogManager.d(TAG, "Timeout running scan test"); - break; + if (System.currentTimeMillis() - testStartTime > 5000L) { + LogManager.d(TAG, "Timeout running scan test"); + break; + } } - } - try { scanner.stopScan(callback); - } catch (IllegalStateException e) { /* do nothing */ } // caught if bluetooth is off here + } catch (IllegalStateException e) { + LogManager.d(TAG, "Bluetooth is off. Cannot run scan test."); + } catch (NullPointerException e) { + // Needed to stop a crash caused by internal NPE thrown by Android. See issue #636 + LogManager.e(TAG, "NullPointerException. Cannot run scan test.", e); + } } else { LogManager.d(TAG, "Cannot get scanner"); } } - - LogManager.d(TAG, "scan test complete"); return this.mScanTestResult == null || this.mScanTestResult; } @@ -302,8 +306,8 @@ public boolean runTransmitterTest(final Context context) { initializeWithContext(context); this.mTransmitterTestResult = null; long testStartTime = System.currentTimeMillis(); - if (this.mAdapter != null) { - final BluetoothLeAdvertiser advertiser = this.mAdapter.getBluetoothLeAdvertiser(); + if (mAdapter != null) { + final BluetoothLeAdvertiser advertiser = getAdvertiserSafely(mAdapter); if(advertiser != null) { AdvertiseSettings settings = (new Builder()).setAdvertiseMode(0).build(); AdvertiseData data = (new android.bluetooth.le.AdvertiseData.Builder()) @@ -454,4 +458,17 @@ private void scheduleRegularTests(Context context) { jobScheduler.schedule(builder.build()); } } -} \ No newline at end of file + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private BluetoothLeAdvertiser getAdvertiserSafely(BluetoothAdapter adapter) { + try { + // This can sometimes throw a NullPointerException as reported here: + // https://github.com/AltBeacon/android-beacon-library/issues/672 + return adapter.getBluetoothLeAdvertiser(); + } + catch (Exception e) { + LogManager.w(TAG, "Cannot get bluetoothLeAdvertiser", e); + } + return null; + } +} diff --git a/src/main/java/org/altbeacon/bluetooth/BluetoothTestJob.java b/lib/src/main/java/org/altbeacon/bluetooth/BluetoothTestJob.java similarity index 100% rename from src/main/java/org/altbeacon/bluetooth/BluetoothTestJob.java rename to lib/src/main/java/org/altbeacon/bluetooth/BluetoothTestJob.java diff --git a/src/main/java/org/altbeacon/bluetooth/Pdu.java b/lib/src/main/java/org/altbeacon/bluetooth/Pdu.java similarity index 100% rename from src/main/java/org/altbeacon/bluetooth/Pdu.java rename to lib/src/main/java/org/altbeacon/bluetooth/Pdu.java diff --git a/src/main/resources/model-distance-calculations.json b/lib/src/main/resources/model-distance-calculations.json similarity index 100% rename from src/main/resources/model-distance-calculations.json rename to lib/src/main/resources/model-distance-calculations.json diff --git a/src/test/AndroidManifest.xml b/lib/src/test/AndroidManifest.xml similarity index 100% rename from src/test/AndroidManifest.xml rename to lib/src/test/AndroidManifest.xml diff --git a/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java b/lib/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java rename to lib/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java index 0ee136130..0eba592e6 100644 --- a/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/AltBeaconParserTest.java @@ -12,7 +12,7 @@ import org.junit.Test; import org.robolectric.annotation.Config; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) /* HOW TO SEE DEBUG LINES FROM YOUR UNIT TESTS: diff --git a/src/test/java/org/altbeacon/beacon/AltBeaconTest.java b/lib/src/test/java/org/altbeacon/beacon/AltBeaconTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/AltBeaconTest.java rename to lib/src/test/java/org/altbeacon/beacon/AltBeaconTest.java index 2d726781a..c25c7d775 100644 --- a/src/test/java/org/altbeacon/beacon/AltBeaconTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/AltBeaconTest.java @@ -16,7 +16,7 @@ import static org.hamcrest.Matchers.hasProperty; import static org.junit.Assert.assertEquals; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) diff --git a/src/test/java/org/altbeacon/beacon/BeaconParserTest.java b/lib/src/test/java/org/altbeacon/beacon/BeaconParserTest.java similarity index 98% rename from src/test/java/org/altbeacon/beacon/BeaconParserTest.java rename to lib/src/test/java/org/altbeacon/beacon/BeaconParserTest.java index e93653094..9128fe375 100644 --- a/src/test/java/org/altbeacon/beacon/BeaconParserTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/BeaconParserTest.java @@ -1,27 +1,20 @@ package org.altbeacon.beacon; -import android.annotation.TargetApi; -import android.os.Build; -import android.os.Parcel; - -import static android.test.MoreAsserts.assertNotEqual; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - import org.altbeacon.beacon.logging.LogManager; import org.altbeacon.beacon.logging.Loggers; -import org.robolectric.RobolectricTestRunner; - -import org.junit.runner.RunWith; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import java.util.Arrays; -@Config(sdk = 18) +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) @@ -144,7 +137,6 @@ public void testRecognizeBeaconWithFormatSpecifyingManufacturer() { assertEquals("manufacturer should be parsed", 0x118 ,beacon.getManufacturer()); } - @TargetApi(Build.VERSION_CODES.GINGERBREAD) @Test public void testReEncodesBeacon() { org.robolectric.shadows.ShadowLog.stream = System.err; @@ -157,7 +149,6 @@ public void testReEncodesBeacon() { assertArrayEquals("beacon advertisement bytes should be the same after re-encoding", expectedMatch, regeneratedBytes); } - @TargetApi(Build.VERSION_CODES.GINGERBREAD) @Test public void testReEncodesBeaconForEddystoneTelemetry() { org.robolectric.shadows.ShadowLog.stream = System.err; @@ -185,7 +176,6 @@ public void testLittleEndianIdentifierParsing() { assertEquals("manufacturer should be parsed", 0x118, beacon.getManufacturer()); } - @TargetApi(Build.VERSION_CODES.GINGERBREAD) @Test public void testReEncodesLittleEndianBeacon() { org.robolectric.shadows.ShadowLog.stream = System.err; diff --git a/src/test/java/org/altbeacon/beacon/BeaconTest.java b/lib/src/test/java/org/altbeacon/beacon/BeaconTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/BeaconTest.java rename to lib/src/test/java/org/altbeacon/beacon/BeaconTest.java index 63d37ea8f..7a59b6034 100644 --- a/src/test/java/org/altbeacon/beacon/BeaconTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/BeaconTest.java @@ -27,7 +27,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) diff --git a/src/test/java/org/altbeacon/beacon/BeaconTransmitterTest.java b/lib/src/test/java/org/altbeacon/beacon/BeaconTransmitterTest.java similarity index 86% rename from src/test/java/org/altbeacon/beacon/BeaconTransmitterTest.java rename to lib/src/test/java/org/altbeacon/beacon/BeaconTransmitterTest.java index 3eeac944b..07bc92d9d 100644 --- a/src/test/java/org/altbeacon/beacon/BeaconTransmitterTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/BeaconTransmitterTest.java @@ -7,13 +7,13 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; import java.util.Arrays; import static junit.framework.Assert.assertEquals; -@Config(sdk = 18) +@Config(sdk = 28) /** * Created by dyoung on 7/22/14. @@ -25,7 +25,7 @@ public class BeaconTransmitterTest { @Test public void testBeaconAdvertisingBytes() { org.robolectric.shadows.ShadowLog.stream = System.err; - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; Beacon beacon = new Beacon.Builder() .setId1("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6") @@ -38,7 +38,7 @@ public void testBeaconAdvertisingBytes() { BeaconParser beaconParser = new BeaconParser() .setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"); byte[] data = beaconParser.getBeaconAdvertisementData(beacon); - BeaconTransmitter beaconTransmitter = new BeaconTransmitter(context, beaconParser); +// BeaconTransmitter beaconTransmitter = new BeaconTransmitter(context, beaconParser); // TODO: can't actually start transmitter here because Robolectric does not support API 21 assertEquals("Data should be 24 bytes long", 24, data.length); @@ -53,7 +53,7 @@ public void testBeaconAdvertisingBytes() { @Test public void testBeaconAdvertisingBytesForEddystone() { org.robolectric.shadows.ShadowLog.stream = System.err; - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; Beacon beacon = new Beacon.Builder() .setId1("0x2f234454f4911ba9ffa6") @@ -64,7 +64,7 @@ public void testBeaconAdvertisingBytesForEddystone() { BeaconParser beaconParser = new BeaconParser() .setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"); byte[] data = beaconParser.getBeaconAdvertisementData(beacon); - BeaconTransmitter beaconTransmitter = new BeaconTransmitter(context, beaconParser); +// BeaconTransmitter beaconTransmitter = new BeaconTransmitter(context, beaconParser); // TODO: can't actually start transmitter here because Robolectric does not support API 21 String byteString = ""; diff --git a/src/test/java/org/altbeacon/beacon/GattBeaconTest.java b/lib/src/test/java/org/altbeacon/beacon/GattBeaconTest.java similarity index 88% rename from src/test/java/org/altbeacon/beacon/GattBeaconTest.java rename to lib/src/test/java/org/altbeacon/beacon/GattBeaconTest.java index 57329ad7b..c37f76a15 100644 --- a/src/test/java/org/altbeacon/beacon/GattBeaconTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/GattBeaconTest.java @@ -9,14 +9,15 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; import java.util.Arrays; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; -@Config(sdk = 18) +@Config(sdk = 28) /** * Created by dyoung on 2/6/15. @@ -113,7 +114,7 @@ public void testDetectsGattBeaconWithCnn() { @Test public void testBeaconAdvertisingBytes() { org.robolectric.shadows.ShadowLog.stream = System.err; - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; Beacon beacon = new Beacon.Builder() @@ -126,7 +127,7 @@ public void testBeaconAdvertisingBytes() { BeaconParser beaconParser = new BeaconParser() .setBeaconLayout("s:0-1=0123,m:2-2=00,d:3-3,p:4-4,i:5-14,i:15-20"); byte[] data = beaconParser.getBeaconAdvertisementData(beacon); - BeaconTransmitter beaconTransmitter = new BeaconTransmitter(context, beaconParser); +// BeaconTransmitter beaconTransmitter = new BeaconTransmitter(context, beaconParser); // TODO: can't actually start transmitter here because Robolectric does not support API 21 assertEquals("Data should be 19 bytes long", 19, data.length); @@ -157,6 +158,19 @@ public void testDetectsUriBeacon() { } + @Test + public void doesNotCrashOnMalformedEddystoneBeacon() { + org.robolectric.shadows.ShadowLog.stream = System.err; + LogManager.setLogger(Loggers.verboseLogger()); + LogManager.setVerboseLoggingEnabled(true); + LogManager.d("GattBeaconTest", "Parsing malformed packet"); + byte[] bytes = hexStringToByteArray("0201060303aafe0416aafe100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + BeaconParser parser = new BeaconParser().setBeaconLayout("s:0-1=feaa,m:2-2=10,p:3-3:-41,i:4-20v"); + LogManager.d("xxx", "------"); + Beacon gattBeacon = parser.fromScanData(bytes, -55, null); + assertNull("GattBeacon should be null when not parsed successfully", gattBeacon); + } + diff --git a/src/test/java/org/altbeacon/beacon/IdentifierTest.java b/lib/src/test/java/org/altbeacon/beacon/IdentifierTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/IdentifierTest.java rename to lib/src/test/java/org/altbeacon/beacon/IdentifierTest.java index 990340d82..1bf3f7da0 100644 --- a/src/test/java/org/altbeacon/beacon/IdentifierTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/IdentifierTest.java @@ -16,7 +16,7 @@ import java.util.Arrays; import java.util.UUID; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) /* diff --git a/src/test/java/org/altbeacon/beacon/RegionTest.java b/lib/src/test/java/org/altbeacon/beacon/RegionTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/RegionTest.java rename to lib/src/test/java/org/altbeacon/beacon/RegionTest.java index 05ba5952d..e2edd854d 100644 --- a/src/test/java/org/altbeacon/beacon/RegionTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/RegionTest.java @@ -21,7 +21,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) diff --git a/src/test/java/org/altbeacon/beacon/SBeaconTest.java b/lib/src/test/java/org/altbeacon/beacon/SBeaconTest.java similarity index 95% rename from src/test/java/org/altbeacon/beacon/SBeaconTest.java rename to lib/src/test/java/org/altbeacon/beacon/SBeaconTest.java index 6918cda04..9268aa979 100644 --- a/src/test/java/org/altbeacon/beacon/SBeaconTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/SBeaconTest.java @@ -1,22 +1,17 @@ package org.altbeacon.beacon; -import android.annotation.TargetApi; import android.bluetooth.BluetoothDevice; -import android.os.Build; import android.os.Parcel; - -import org.altbeacon.beacon.logging.LogManager; -import org.altbeacon.beacon.logging.Loggers; import org.junit.Test; +import org.robolectric.annotation.Config; import java.util.ArrayList; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; -import org.robolectric.annotation.Config; -@Config(sdk = 18) +@Config(sdk = 28) /** * Created by dyoung on 7/22/14. @@ -90,7 +85,7 @@ protected SBeacon(Parcel in) { class SBeaconParser extends BeaconParser { private static final String TAG = "SBeaconParser"; - @TargetApi(Build.VERSION_CODES.ECLAIR) + @Override public Beacon fromScanData(byte[] scanData, int rssi, BluetoothDevice device) { int startByte = 2; diff --git a/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java b/lib/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java similarity index 93% rename from src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java rename to lib/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java index 7b8071f35..c2910f630 100644 --- a/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculatorTest.java @@ -6,12 +6,12 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; import static org.junit.Assert.assertEquals; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) /* HOW TO SEE DEBUG LINES FROM YOUR UNIT TESTS: @@ -54,7 +54,7 @@ public void testSelectsNexus4OnExactMatch() { @Test public void testCalculatesDistanceForMotoXPro() { - final Context applicationContext = ShadowApplication.getInstance().getApplicationContext(); + final Context applicationContext = RuntimeEnvironment.application; org.robolectric.shadows.ShadowLog.stream = System.err; final AndroidModel model = new AndroidModel("5.0.2", "LXG22.67-7.1", "Moto X Pro", "XT1115"); @@ -68,7 +68,7 @@ public void testCalculatesDistanceForMotoXPro() { public void testConcurrentModificationException() { org.robolectric.shadows.ShadowLog.stream = System.err; - final Context applicationContext = ShadowApplication.getInstance().getApplicationContext(); + final Context applicationContext = RuntimeEnvironment.application; final AndroidModel model = new AndroidModel("4.4.2", "KOT49H", "Nexus 4", "LGE"); final String modelMapJson = diff --git a/src/test/java/org/altbeacon/beacon/logging/LogManagerTest.java b/lib/src/test/java/org/altbeacon/beacon/logging/LogManagerTest.java similarity index 100% rename from src/test/java/org/altbeacon/beacon/logging/LogManagerTest.java rename to lib/src/test/java/org/altbeacon/beacon/logging/LogManagerTest.java diff --git a/src/test/java/org/altbeacon/beacon/logging/LoggersTest.java b/lib/src/test/java/org/altbeacon/beacon/logging/LoggersTest.java similarity index 100% rename from src/test/java/org/altbeacon/beacon/logging/LoggersTest.java rename to lib/src/test/java/org/altbeacon/beacon/logging/LoggersTest.java diff --git a/src/test/java/org/altbeacon/beacon/logging/VerboseAndroidLoggerTest.java b/lib/src/test/java/org/altbeacon/beacon/logging/VerboseAndroidLoggerTest.java similarity index 96% rename from src/test/java/org/altbeacon/beacon/logging/VerboseAndroidLoggerTest.java rename to lib/src/test/java/org/altbeacon/beacon/logging/VerboseAndroidLoggerTest.java index 218c4f718..5a211efa9 100644 --- a/src/test/java/org/altbeacon/beacon/logging/VerboseAndroidLoggerTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/logging/VerboseAndroidLoggerTest.java @@ -22,6 +22,8 @@ import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; +import java.util.List; + import static android.util.Log.DEBUG; import static android.util.Log.ERROR; import static android.util.Log.INFO; @@ -34,7 +36,7 @@ * * @author Andrew Reitz */ -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) public class VerboseAndroidLoggerTest { private String tag = getClass().getName(); @@ -136,7 +138,8 @@ public void errorWithThrowableLoggedCorrectly() { } private void assertLogged(int type, String tag, String msg, Throwable throwable) { - ShadowLog.LogItem lastLog = ShadowLog.getLogs().get(0); + List logs = ShadowLog.getLogs(); + ShadowLog.LogItem lastLog = logs.get(logs.size() - 1); assertEquals(type, lastLog.type); assertEquals(msg, lastLog.msg); assertEquals(tag, lastLog.tag); diff --git a/src/test/java/org/altbeacon/beacon/logging/WarningAndroidLoggerTest.java b/lib/src/test/java/org/altbeacon/beacon/logging/WarningAndroidLoggerTest.java similarity index 87% rename from src/test/java/org/altbeacon/beacon/logging/WarningAndroidLoggerTest.java rename to lib/src/test/java/org/altbeacon/beacon/logging/WarningAndroidLoggerTest.java index 1f8387f3a..838943751 100644 --- a/src/test/java/org/altbeacon/beacon/logging/WarningAndroidLoggerTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/logging/WarningAndroidLoggerTest.java @@ -16,7 +16,8 @@ */ package org.altbeacon.beacon.logging; -import org.hamcrest.Matchers; +import android.util.Log; + import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -25,14 +26,10 @@ import java.util.List; -import static android.util.Log.DEBUG; import static android.util.Log.ERROR; -import static android.util.Log.INFO; -import static android.util.Log.VERBOSE; import static android.util.Log.WARN; import static junit.framework.Assert.assertEquals; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertNotEquals; +import static org.hamcrest.Matchers.isOneOf; import static org.junit.Assert.assertThat; /** @@ -40,7 +37,7 @@ * * @author Andrew Reitz */ -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) public class WarningAndroidLoggerTest { private String tag = getClass().getName(); @@ -137,7 +134,8 @@ public void errorWithThrowableLoggedCorrectly() { } private void assertLogged(int type, String tag, String msg, Throwable throwable) { - ShadowLog.LogItem lastLog = ShadowLog.getLogs().get(0); + List logs = ShadowLog.getLogs(); + ShadowLog.LogItem lastLog = logs.get(logs.size() - 1); assertEquals(type, lastLog.type); assertEquals(msg, lastLog.msg); assertEquals(tag, lastLog.tag); @@ -146,6 +144,10 @@ private void assertLogged(int type, String tag, String msg, Throwable throwable) private void assertNotLogged() { final List logs = ShadowLog.getLogs(); - assertThat(logs, empty()); + for (ShadowLog.LogItem log : logs) { + //INFO level log was introduced by ASOP + //https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/content/res/AssetManager.java + assertThat(log.type, isOneOf(Log.INFO, Log.WARN, Log.ERROR)); + } } } diff --git a/src/test/java/org/altbeacon/beacon/org/altbeacon/beacon/simulator/BeaconSimulatorTest.java b/lib/src/test/java/org/altbeacon/beacon/org/altbeacon/beacon/simulator/BeaconSimulatorTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/org/altbeacon/beacon/simulator/BeaconSimulatorTest.java rename to lib/src/test/java/org/altbeacon/beacon/org/altbeacon/beacon/simulator/BeaconSimulatorTest.java index 49850a955..6f8ca3fad 100644 --- a/src/test/java/org/altbeacon/beacon/org/altbeacon/beacon/simulator/BeaconSimulatorTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/org/altbeacon/beacon/simulator/BeaconSimulatorTest.java @@ -22,7 +22,7 @@ import dalvik.annotation.TestTarget; import org.robolectric.annotation.Config; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) public class BeaconSimulatorTest { diff --git a/src/test/java/org/altbeacon/beacon/service/ArmaRssiFilterTest.java b/lib/src/test/java/org/altbeacon/beacon/service/ArmaRssiFilterTest.java similarity index 96% rename from src/test/java/org/altbeacon/beacon/service/ArmaRssiFilterTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/ArmaRssiFilterTest.java index 80fd0c4f3..25f0fb30a 100644 --- a/src/test/java/org/altbeacon/beacon/service/ArmaRssiFilterTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/ArmaRssiFilterTest.java @@ -7,7 +7,7 @@ import static org.junit.Assert.assertEquals; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 18) +@Config(sdk = 28) public class ArmaRssiFilterTest { diff --git a/src/test/java/org/altbeacon/beacon/service/BeaconServiceTest.java b/lib/src/test/java/org/altbeacon/beacon/service/BeaconServiceTest.java similarity index 91% rename from src/test/java/org/altbeacon/beacon/service/BeaconServiceTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/BeaconServiceTest.java index d77e7eaba..ff8398f85 100644 --- a/src/test/java/org/altbeacon/beacon/service/BeaconServiceTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/BeaconServiceTest.java @@ -1,8 +1,6 @@ package org.altbeacon.beacon.service; -import android.annotation.TargetApi; import android.os.AsyncTask; -import android.os.Build; import org.altbeacon.beacon.BeaconManager; import org.altbeacon.beacon.logging.LogManager; @@ -13,8 +11,8 @@ import org.junit.runner.RunWith; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.android.controller.ServiceController; import org.robolectric.annotation.Config; -import org.robolectric.util.ServiceController; import java.util.concurrent.ThreadPoolExecutor; @@ -24,7 +22,7 @@ * Created by dyoung on 7/1/15. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = 18) +@Config(sdk = 28) public class BeaconServiceTest { @Before @@ -40,12 +38,11 @@ public void before() { * affect the size of the available threads in the main Android AsyncTask.THREAD_POOL_EXECUTOR * @throws Exception */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Test public void beaconScanCallbackTest() throws Exception { final ServiceController beaconServiceServiceController = Robolectric.buildService(BeaconService.class); - beaconServiceServiceController.attach(); +// beaconServiceServiceController.attach(); BeaconService beaconService = beaconServiceServiceController.get(); beaconService.onCreate(); CycledLeScanCallback callback = beaconService.getCycledLeScanCallback(); diff --git a/src/test/java/org/altbeacon/beacon/service/ExtraDataBeaconTrackerTest.java b/lib/src/test/java/org/altbeacon/beacon/service/ExtraDataBeaconTrackerTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/service/ExtraDataBeaconTrackerTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/ExtraDataBeaconTrackerTest.java index 24927ac5c..182a2b67d 100644 --- a/src/test/java/org/altbeacon/beacon/service/ExtraDataBeaconTrackerTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/ExtraDataBeaconTrackerTest.java @@ -15,7 +15,7 @@ import static org.junit.Assert.assertNull; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 18) +@Config(sdk = 28) public class ExtraDataBeaconTrackerTest { Beacon getManufacturerBeacon() { return new Beacon.Builder().setId1("1") diff --git a/src/test/java/org/altbeacon/beacon/service/MonitoringStatusTest.java b/lib/src/test/java/org/altbeacon/beacon/service/MonitoringStatusTest.java similarity index 76% rename from src/test/java/org/altbeacon/beacon/service/MonitoringStatusTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/MonitoringStatusTest.java index 0ec704706..af6894085 100644 --- a/src/test/java/org/altbeacon/beacon/service/MonitoringStatusTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/MonitoringStatusTest.java @@ -1,27 +1,19 @@ package org.altbeacon.beacon.service; -import android.annotation.TargetApi; import android.content.Context; -import android.os.AsyncTask; -import android.os.Build; -import android.util.Log; import org.altbeacon.beacon.BeaconManager; import org.altbeacon.beacon.Region; import org.altbeacon.beacon.logging.LogManager; import org.altbeacon.beacon.logging.Loggers; -import org.altbeacon.beacon.service.scanner.CycledLeScanCallback; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; -import org.robolectric.util.ServiceController; import java.util.Collection; -import java.util.concurrent.ThreadPoolExecutor; import static org.junit.Assert.assertEquals; @@ -29,7 +21,7 @@ * Created by dyoung on 7/1/16. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = 18) +@Config(sdk = 28) public class MonitoringStatusTest { private static final String TAG = MonitoringStatusTest.class.getSimpleName(); @Before @@ -37,13 +29,14 @@ public void before() { org.robolectric.shadows.ShadowLog.stream = System.err; LogManager.setLogger(Loggers.verboseLogger()); LogManager.setVerboseLoggingEnabled(true); - BeaconManager.setsManifestCheckingDisabled(true); + BeaconManager.setManifestCheckingDisabled(true); + Context context = RuntimeEnvironment.application; + new MonitoringStatus(context).clear(); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Test public void savesStatusOfUpTo50RegionsTest() throws Exception { - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; MonitoringStatus monitoringStatus = new MonitoringStatus(context); for (int i = 0; i < 50; i++) { Region region = new Region(""+i, null, null, null); @@ -54,10 +47,9 @@ public void savesStatusOfUpTo50RegionsTest() throws Exception { assertEquals("restored regions should be same number as saved", 50, monitoringStatus2.regions().size()); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Test public void clearsStatusOfOver50RegionsTest() throws Exception { - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; MonitoringStatus monitoringStatus = new MonitoringStatus(context); for (int i = 0; i < 51; i++) { Region region = new Region(""+i, null, null, null); @@ -68,10 +60,9 @@ public void clearsStatusOfOver50RegionsTest() throws Exception { assertEquals("restored regions should be none", 0, monitoringStatus2.regions().size()); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Test public void refusesToRestoreRegionsIfTooMuchTimeHasPassedSinceSavingTest() throws Exception { - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; MonitoringStatus monitoringStatus = new MonitoringStatus(context); for (int i = 0; i < 50; i++) { Region region = new Region(""+i, null, null, null); @@ -84,17 +75,17 @@ public void refusesToRestoreRegionsIfTooMuchTimeHasPassedSinceSavingTest() throw assertEquals("restored regions should be none", 0, monitoringStatus2.regions().size()); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Test public void allowsAccessToRegionsAfterRestore() throws Exception { - Context context = ShadowApplication.getInstance().getApplicationContext(); - MonitoringStatus monitoringStatus = new MonitoringStatus(context); + Context context = RuntimeEnvironment.application; + BeaconManager beaconManager = BeaconManager.getInstanceForApplication(context); + MonitoringStatus monitoringStatus = MonitoringStatus.getInstanceForApplication(context); for (int i = 0; i < 50; i++) { Region region = new Region(""+i, null, null, null); monitoringStatus.addRegion(region, null); } monitoringStatus.saveMonitoringStatusIfOn(); - BeaconManager beaconManager = BeaconManager.getInstanceForApplication(context); + monitoringStatus.restoreMonitoringStatus(); Collection regions = beaconManager.getMonitoredRegions(); assertEquals("beaconManager should return restored regions", 50, regions.size()); } diff --git a/src/test/java/org/altbeacon/beacon/service/RangingDataTest.java b/lib/src/test/java/org/altbeacon/beacon/service/RangingDataTest.java similarity index 94% rename from src/test/java/org/altbeacon/beacon/service/RangingDataTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/RangingDataTest.java index 90f92dbf1..7f71f07ed 100644 --- a/src/test/java/org/altbeacon/beacon/service/RangingDataTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/RangingDataTest.java @@ -13,7 +13,7 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; +import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; @@ -22,7 +22,7 @@ import static org.junit.Assert.assertEquals; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 18) +@Config(sdk = 28) public class RangingDataTest { @Before public void before() { @@ -34,7 +34,7 @@ public void before() { @Test public void testSerialization() throws Exception { - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; ArrayList identifiers = new ArrayList(); identifiers.add(Identifier.parse("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6")); identifiers.add(Identifier.parse("1")); @@ -69,7 +69,7 @@ public void testSerialization() throws Exception { @Test // On MacBookPro 2.5 GHz Core I7, 10000 serialization/deserialiation cycles of RangingData took 22ms public void testSerializationBenchmark() throws Exception { - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; ArrayList identifiers = new ArrayList(); identifiers.add(Identifier.parse("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6")); identifiers.add(Identifier.parse("1")); diff --git a/src/test/java/org/altbeacon/beacon/service/RunningAverageRssiFilterTest.java b/lib/src/test/java/org/altbeacon/beacon/service/RunningAverageRssiFilterTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/service/RunningAverageRssiFilterTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/RunningAverageRssiFilterTest.java index 8eb7cc084..e124ad7ad 100644 --- a/src/test/java/org/altbeacon/beacon/service/RunningAverageRssiFilterTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/RunningAverageRssiFilterTest.java @@ -8,7 +8,7 @@ import static org.junit.Assert.assertEquals; @RunWith(RobolectricTestRunner.class) -@Config(sdk = 18) +@Config(sdk = 28) public class RunningAverageRssiFilterTest { diff --git a/src/test/java/org/altbeacon/beacon/service/ScanStateTest.java b/lib/src/test/java/org/altbeacon/beacon/service/ScanStateTest.java similarity index 77% rename from src/test/java/org/altbeacon/beacon/service/ScanStateTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/ScanStateTest.java index ba4b95c06..463fdad36 100644 --- a/src/test/java/org/altbeacon/beacon/service/ScanStateTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/ScanStateTest.java @@ -6,23 +6,17 @@ import android.annotation.TargetApi; import android.content.Context; -import android.os.AsyncTask; import android.os.Build; import org.altbeacon.beacon.BeaconManager; import org.altbeacon.beacon.logging.LogManager; import org.altbeacon.beacon.logging.Loggers; -import org.altbeacon.beacon.service.scanner.CycledLeScanCallback; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowApplication; -import org.robolectric.util.ServiceController; - -import java.util.concurrent.ThreadPoolExecutor; import static org.junit.Assert.assertEquals; @@ -30,7 +24,7 @@ * Created by dyoung on 7/1/15. */ @RunWith(RobolectricTestRunner.class) -@Config(sdk = 18) +@Config(sdk = 28) public class ScanStateTest { @Before @@ -41,10 +35,9 @@ public void before() { BeaconManager.setsManifestCheckingDisabled(true); } - @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Test public void serializationTest() throws Exception { - Context context = ShadowApplication.getInstance().getApplicationContext(); + Context context = RuntimeEnvironment.application; ScanState scanState = new ScanState(context); MonitoringStatus monitoringStatus = new MonitoringStatus(context); scanState.setMonitoringStatus(monitoringStatus); diff --git a/src/test/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetectorTest.java b/lib/src/test/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetectorTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetectorTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetectorTest.java index f82e152c8..cb642a3ab 100644 --- a/src/test/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetectorTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/scanner/DistinctPacketDetectorTest.java @@ -8,7 +8,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) public class DistinctPacketDetectorTest { diff --git a/src/test/java/org/altbeacon/beacon/service/scanner/ScanFilterUtilsTest.java b/lib/src/test/java/org/altbeacon/beacon/service/scanner/ScanFilterUtilsTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/service/scanner/ScanFilterUtilsTest.java rename to lib/src/test/java/org/altbeacon/beacon/service/scanner/ScanFilterUtilsTest.java index deaa6b048..c61b98823 100644 --- a/src/test/java/org/altbeacon/beacon/service/scanner/ScanFilterUtilsTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/service/scanner/ScanFilterUtilsTest.java @@ -26,7 +26,7 @@ import static org.junit.Assert.assertTrue; import org.mockito.Mockito; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) public class ScanFilterUtilsTest { diff --git a/src/test/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessorTest.java b/lib/src/test/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessorTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessorTest.java rename to lib/src/test/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessorTest.java index 3ab4773fc..2634868d1 100644 --- a/src/test/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessorTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/utils/EddystoneTelemetryAccessorTest.java @@ -16,7 +16,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) public class EddystoneTelemetryAccessorTest { diff --git a/src/test/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressorTest.java b/lib/src/test/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressorTest.java similarity index 99% rename from src/test/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressorTest.java rename to lib/src/test/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressorTest.java index d9d038df5..c5984a472 100644 --- a/src/test/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressorTest.java +++ b/lib/src/test/java/org/altbeacon/beacon/utils/UrlBeaconUrlCompressorTest.java @@ -12,7 +12,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) public class UrlBeaconUrlCompressorTest { diff --git a/src/test/java/org/altbeacon/bluetooth/BleAdvertisementTest.java b/lib/src/test/java/org/altbeacon/bluetooth/BleAdvertisementTest.java similarity index 99% rename from src/test/java/org/altbeacon/bluetooth/BleAdvertisementTest.java rename to lib/src/test/java/org/altbeacon/bluetooth/BleAdvertisementTest.java index db34412b7..0566b8245 100644 --- a/src/test/java/org/altbeacon/bluetooth/BleAdvertisementTest.java +++ b/lib/src/test/java/org/altbeacon/bluetooth/BleAdvertisementTest.java @@ -8,7 +8,7 @@ import org.robolectric.annotation.Config; -@Config(sdk = 18) +@Config(sdk = 28) @RunWith(RobolectricTestRunner.class) diff --git a/src/test/resources/model-distance-calculations.json b/lib/src/test/resources/model-distance-calculations.json similarity index 100% rename from src/test/resources/model-distance-calculations.json rename to lib/src/test/resources/model-distance-calculations.json diff --git a/settings.gradle b/settings.gradle index 138498524..ca94a4b5a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ -rootProject.name="android-beacon-library" +include ':lib' +//include ':demo' +project(":lib").name = "android-beacon-library" +//include ':app' diff --git a/src/main/java/org/altbeacon/beacon/service/ScanDataProcessor.java b/src/main/java/org/altbeacon/beacon/service/ScanDataProcessor.java deleted file mode 100644 index 758f09952..000000000 --- a/src/main/java/org/altbeacon/beacon/service/ScanDataProcessor.java +++ /dev/null @@ -1,173 +0,0 @@ -package org.altbeacon.beacon.service; - -import android.annotation.TargetApi; -import android.app.Service; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.ScanResult; -import android.content.pm.ApplicationInfo; -import android.os.Build; - -import org.altbeacon.beacon.Beacon; -import org.altbeacon.beacon.BeaconManager; -import org.altbeacon.beacon.BeaconParser; -import org.altbeacon.beacon.Region; -import org.altbeacon.beacon.logging.LogManager; -import org.altbeacon.beacon.service.scanner.NonBeaconLeScanCallback; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Created by dyoung on 3/24/17. - * @hice - */ - -public class ScanDataProcessor { - private static final String TAG = ScanDataProcessor.class.getSimpleName(); - private Service mService; - private Map mRangedRegionState = new HashMap(); - private MonitoringStatus mMonitoringStatus; - private Set mBeaconParsers = new HashSet(); - private ExtraDataBeaconTracker mExtraDataBeaconTracker; - // TODO: implement this - private NonBeaconLeScanCallback mNonBeaconLeScanCallback; - private DetectionTracker mDetectionTracker = DetectionTracker.getInstance(); - - int trackedBeaconsPacketCount; - - - public ScanDataProcessor(Service scanService, ScanState scanState) { - mService = scanService; - mMonitoringStatus = scanState.getMonitoringStatus(); - mRangedRegionState = scanState.getRangedRegionState(); - mMonitoringStatus = scanState.getMonitoringStatus(); - mExtraDataBeaconTracker = scanState.getExtraBeaconDataTracker(); - mBeaconParsers = scanState.getBeaconParsers(); - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public void process(ScanResult scanResult) { - ScanData scanData= new ScanData(scanResult.getDevice(), scanResult.getRssi(), scanResult.getScanRecord().getBytes()); - process(scanData); - } - - public void process(ScanData scanData) { - Beacon beacon = null; - - for (BeaconParser parser : mBeaconParsers) { - beacon = parser.fromScanData(scanData.scanRecord, - scanData.rssi, scanData.device); - - if (beacon != null) { - break; - } - } - if (beacon != null) { - mDetectionTracker.recordDetection(); - trackedBeaconsPacketCount++; - processBeaconFromScan(beacon); - } else { - if (mNonBeaconLeScanCallback != null) { - mNonBeaconLeScanCallback.onNonBeaconLeScan(scanData.device, scanData.rssi, scanData.scanRecord); - } - } - - } - private class ScanData { - public ScanData(BluetoothDevice device, int rssi, byte[] scanRecord) { - this.device = device; - this.rssi = rssi; - this.scanRecord = scanRecord; - } - - int rssi; - BluetoothDevice device; - byte[] scanRecord; - } - - private void processBeaconFromScan(Beacon beacon) { - if (Stats.getInstance().isEnabled()) { - Stats.getInstance().log(beacon); - } - if (LogManager.isVerboseLoggingEnabled()) { - LogManager.d(TAG, - "beacon detected : %s", beacon.toString()); - } - - beacon = mExtraDataBeaconTracker.track(beacon); - // If this is a Gatt beacon that should be ignored, it will be set to null as a result of - // the above - if (beacon == null) { - if (LogManager.isVerboseLoggingEnabled()) { - LogManager.d(TAG, - "not processing detections for GATT extra data beacon"); - } - } else { - mMonitoringStatus.updateNewlyInsideInRegionsContaining(beacon); - - List matchedRegions = null; - Iterator matchedRegionIterator; - LogManager.d(TAG, "looking for ranging region matches for this beacon out of "+mRangedRegionState.keySet().size()+" regions."); - synchronized (mRangedRegionState) { - matchedRegions = matchingRegions(beacon, mRangedRegionState.keySet()); - matchedRegionIterator = matchedRegions.iterator(); - while (matchedRegionIterator.hasNext()) { - Region region = matchedRegionIterator.next(); - LogManager.d(TAG, "matches ranging region: %s", region); - RangeState rangeState = mRangedRegionState.get(region); - if (rangeState != null) { - rangeState.addBeacon(beacon); - } - } - } - } - } - private List matchingRegions(Beacon beacon, Collection regions) { - List matched = new ArrayList(); - for (Region region : regions) { - if (region.matchesBeacon(beacon)) { - matched.add(region); - } else { - LogManager.d(TAG, "This region (%s) does not match beacon: %s", region, beacon); - } - } - return matched; - } - - public void onCycleEnd() { - mMonitoringStatus.updateNewlyOutside(); - processRangeData(); - if (BeaconManager.getBeaconSimulator() != null) { - // if simulatedScanData is provided, it will be seen every scan cycle. *in addition* to anything actually seen in the air - // it will not be used if we are not in debug mode - if (BeaconManager.getBeaconSimulator().getBeacons() != null) { - if (0 != (mService.getApplicationContext().getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) { - for (Beacon beacon : BeaconManager.getBeaconSimulator().getBeacons()) { - processBeaconFromScan(beacon); - } - } else { - LogManager.w(TAG, "Beacon simulations provided, but ignored because we are not running in debug mode. Please remove beacon simulations for production."); - } - } else { - LogManager.w(TAG, "getBeacons is returning null. No simulated beacons to report."); - } - } - } - private void processRangeData() { - synchronized (mRangedRegionState) { - for (Region region : mRangedRegionState.keySet()) { - RangeState rangeState = mRangedRegionState.get(region); - LogManager.d(TAG, "Calling ranging callback"); - Callback callback = new Callback(mService.getPackageName()); - callback.call(mService, "rangingData", new RangingData(rangeState.finalizeBeacons(), region).toBundle()); - } - } - } - -}