├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── gradle.xml
├── compiler.xml
└── misc.xml
├── discovery
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── joshblour
│ │ │ └── discovery
│ │ │ ├── EasedValue.java
│ │ │ ├── BLEUser.java
│ │ │ ├── MultiScanner.java
│ │ │ ├── GattManager.java
│ │ │ ├── AdvertiserService.java
│ │ │ └── Discovery.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── joshblour
│ │ └── discovery
│ │ └── ApplicationTest.java
├── proguard-rules.pro
├── build.gradle
└── discovery.iml
├── settings.gradle
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── Discovery.iml
├── gradlew.bat
├── README.md
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | Discovery
--------------------------------------------------------------------------------
/discovery/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':discovery'
2 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/discovery/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Discovery
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yonahforst/discovery-android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Mar 18 12:52:29 CET 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/discovery/src/androidTest/java/com/joshblour/discovery/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.joshblour.discovery;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/discovery/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/Yonah/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/discovery/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
8 |
10 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/Discovery.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/discovery/src/main/java/com/joshblour/discovery/EasedValue.java:
--------------------------------------------------------------------------------
1 | package com.joshblour.discovery;
2 |
3 | /**
4 | * Created by Yonah on 16/10/15.
5 | */
6 | public class EasedValue {
7 | private Float mValue;
8 | private Float mVelocity;
9 | private Float mTargetValue;
10 | private Float mCurrentValue;
11 |
12 | public EasedValue() {
13 | this.mVelocity = 0.0f;
14 | this.mTargetValue = 0.0f;
15 | this.mCurrentValue = 0.0f;
16 | }
17 |
18 | public void setValue(Float value) {
19 | this.mTargetValue = value;
20 | }
21 |
22 | public Float getValue() {
23 | return mCurrentValue;
24 | }
25 |
26 | public void update() {
27 | // determine speed at which the ease will happen
28 | // this is based on difference between target and current value
29 | mVelocity += (mTargetValue - mCurrentValue) * 0.01f;
30 | mVelocity *= 0.7f;
31 |
32 | // ease the current value
33 | mCurrentValue += mVelocity;
34 |
35 | // limit how small the ease can get
36 | if(Math.abs(mTargetValue - mCurrentValue) < 0.001f){
37 | mCurrentValue = mTargetValue;
38 | mVelocity = 0.0f;
39 | }
40 |
41 | // keep above zero
42 | mCurrentValue = Math.max(0.0f, mCurrentValue);
43 |
44 | }
45 |
46 | public void reset() {
47 | mCurrentValue = mTargetValue;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/discovery/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | ext {
4 | bintrayRepo = 'maven'
5 | bintrayName = 'discovery'
6 |
7 | publishedGroupId = 'com.joshblour.discovery'
8 | libraryName = 'discovery'
9 | artifact = 'discovery'
10 |
11 | libraryDescription = 'Android port of https://github.com/omergul123/Discovery'
12 |
13 | siteUrl = 'https://github.com/joshblour/discovery-android'
14 | gitUrl = 'https://github.com/joshblour/discovery-android.git'
15 |
16 | libraryVersion = '0.0.6'
17 |
18 | developerId = 'joshblour'
19 | developerName = 'Yonah Forst'
20 | developerEmail = 'yonaforst@hotmail.com'
21 |
22 | licenseName = 'The Apache Software License, Version 2.0'
23 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
24 | allLicenses = ["Apache-2.0"]
25 | }
26 |
27 | android {
28 | compileSdkVersion 23
29 | buildToolsVersion "23.0.1"
30 |
31 | defaultConfig {
32 | minSdkVersion 18
33 | targetSdkVersion 23
34 | versionCode 1
35 | versionName "1.0"
36 | }
37 | buildTypes {
38 | release {
39 | minifyEnabled false
40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
41 | }
42 | }
43 | }
44 |
45 | dependencies {
46 | compile fileTree(dir: 'libs', include: ['*.jar'])
47 | }
48 |
49 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
50 | apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
51 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/discovery/src/main/java/com/joshblour/discovery/BLEUser.java:
--------------------------------------------------------------------------------
1 | package com.joshblour.discovery;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 |
5 | /**
6 | * Created by Yonah on 15/10/15.
7 | */
8 | public class BLEUser {
9 | private BluetoothDevice mDevice;
10 | private String mDeviceAddress;
11 | private String mUsername;
12 | private Boolean mIdentified;
13 | private Boolean mIsMyService;
14 | private Integer mRssi;
15 | private Integer mProximity;
16 | private long mUpdateTime;
17 | private EasedValue mEasedProximity;
18 |
19 | public BLEUser(final BluetoothDevice device) {
20 | this.mDevice = device;
21 | this.mDeviceAddress = device.getAddress();
22 | this.mRssi = 0;
23 | this.mEasedProximity = new EasedValue();
24 | }
25 |
26 | public Integer convertRSSItoProximity(Integer rssi) {
27 | // eased value doesn't support negative values
28 | this.mEasedProximity.setValue(Math.abs(rssi) * 1.0f);//convert to float
29 | this.mEasedProximity.update();
30 | Integer proximity = Math.round(this.mEasedProximity.getValue() * -1.0f);
31 | return proximity;
32 | }
33 | public String getDeviceAddress() {
34 | return mDeviceAddress;
35 | }
36 |
37 | public void setDeviceAddress(String deviceAddress) {
38 | this.mDeviceAddress = deviceAddress;
39 | }
40 |
41 | public String getUsername() {
42 | return mUsername;
43 | }
44 |
45 | public void setUsername(String mUsername) {
46 | this.mUsername = mUsername;
47 | }
48 |
49 | public Boolean isIdentified() {
50 | return mIdentified;
51 | }
52 |
53 | public void setIdentified(Boolean mIdentified) {
54 | this.mIdentified = mIdentified;
55 | }
56 |
57 | public Integer getRssi() {
58 | return mRssi;
59 | }
60 |
61 | public void setRssi(int mRssi) {
62 | this.mRssi = mRssi;
63 | this.setProximity(convertRSSItoProximity(mRssi));
64 | }
65 |
66 | public Integer getProximity() {
67 | return mProximity;
68 | }
69 |
70 | public void setProximity(Integer mProximity) {
71 | this.mProximity = mProximity;
72 | }
73 |
74 | public long getUpdateTime() {
75 | return mUpdateTime;
76 | }
77 |
78 | public void setUpdateTime(long mUpdateTime) {
79 | this.mUpdateTime = mUpdateTime;
80 | }
81 |
82 | // we need this because we are not filtering by serviceUUID.
83 | // with this flag, we can store them as identifed but not our service, so that we don't need to always reconnect.
84 | public void setIsMyService(Boolean isMyService) {
85 | this.mIsMyService = isMyService;
86 | }
87 |
88 | public Boolean isMyService() {
89 | return mIsMyService;
90 | }
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Discovery
2 | Android port of https://github.com/omergul123/Discovery
3 | Discover nearby devices using BLE.
4 |
5 | ##What
6 | Discovery is a very simple but useful library for discovering nearby devices with BLE(Bluetooth Low Energy) and for exchanging a value (kind of ID or username determined by you on the running app on peer device) regardless of whether the app on peer device works at foreground or background state.
7 |
8 | ##Why
9 | I ported [Discovery](https://github.com/omergul123/Discovery) from iOS to Android because I needed a beacon technology that worked cross-platform. I chose Discovery for its straightforward implementation and reliability in the background. You can read more about why Discovery was necessary for iOS here: https://github.com/omergul123/Discovery#the-concept-the-problem-and-why-we-need-discovery
10 |
11 | ##Install
12 |
13 | Add to your bundle.gradle (module) dependencies
14 |
15 | ````java
16 | dependencies {
17 | //...
18 | compile "com.joshblour.discovery:discovery:0.0.3"
19 | //...
20 | }
21 | ````
22 | ##Example usage
23 |
24 | ````java
25 | public class MainActivity extends Activity implements Discovery.DiscoveryCallback {
26 | public static final ParcelUuid uuidStr = ParcelUuid.fromString("B9407F30-F5F8-466E-AFF9-25556B57FE99");
27 | public static final String username = "myUsername";
28 |
29 | private Discovery mDiscovery;
30 |
31 | //...boilerplate onCreate code...
32 |
33 | public void startDiscovery() {
34 | mDiscovery = new Discovery(getApplicationContext(), uuidStr, username, this);
35 | }
36 |
37 | @Override
38 | public void didUpdateUsers(ArrayList users, Boolean usersChanged) {
39 | //reload your table from users
40 | }
41 | }
42 | ````
43 |
44 | ##API
45 | `public Discovery(Context context, ParcelUuid uuid, String username, DIStartOptions startOptions, DiscoveryCallback discoveryCallback )`
46 | - `uuid`: A UUID that identifies your application. If you want to discover iOS devices, both libraries need to be using the same UUID
47 | - `username`: The username to include in the broadcast. Note: in this implementation, we can only broadcast a max of 7 characters. You can use longer usernames but nearby devices will need to connect in order to read it. (the username is then cached so this only happens once per discovery)
48 | - `startOptions`:
49 | - DIStartAdvertisingAndDetecting
50 | - DIStartAdvertisingOnly
51 | - DIStartDetectingOnly
52 | - DIStartNone
53 | - `discoveryCallback`: implements `didUpdateUsers(ArrayList users, Boolean usersChanged)`
54 |
55 | `public Discovery(Context context, ParcelUuid uuid, String username, DiscoveryCallback discoveryCallback)` - same as above but starts using `DIStartAdvertisingAndDetecting`
56 |
57 | `public void setPaused(Boolean paused)` - pauses advertising and detection
58 |
59 | `public void setShouldDiscover(Boolean shouldDiscover)` - starts and stops discovery only
60 |
61 | `public void setShouldAdvertise(Boolean shouldAdvertise)` - starts and stops advertising only
62 |
63 | `public void setUserTimeoutInterval(Integer mUserTimeoutInterval)` - in seconds, default is 5. After not seeing a user for x seconds, we remove him from the users list in our callback.
64 |
65 |
66 | *The following two methods are specific to the Android version, since the Android docs advise against continuous scanning. Instead, we cycle scanning on and off. This also allows us to modify the scan behaviour when the app moves to the background.*
67 |
68 | `public void setScanForSeconds(Integer scanForSeconds)` - in seconds, default is 5. This parameter specifies the duration of the ON part of the scan cycle.
69 |
70 | `public void setWaitForSeconds(Integer waitForSeconds)` - in seconds default is 5. This parameter specifies the duration of the OFF part of the scan cycle.
71 |
72 | ##Problems
73 |
74 | ~~Can't detect iOS devices while they are in the background. This is because we are using a ScanFilter for the ServiceUUID to save battery. When an iOS app goes into the background, Apple moved all serviceUUIDs into a special 'overflow area' and our filter no longer picks them up~~ (disabled scan filters because I needed to detect iOS devices when they are in the background)
75 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/discovery/discovery.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | generateDebugAndroidTestSources
19 | generateDebugSources
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/discovery/src/main/java/com/joshblour/discovery/MultiScanner.java:
--------------------------------------------------------------------------------
1 | package com.joshblour.discovery;
2 |
3 | import android.annotation.TargetApi;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.bluetooth.le.ScanCallback;
7 | import android.bluetooth.le.ScanFilter;
8 | import android.bluetooth.le.ScanResult;
9 | import android.bluetooth.le.ScanSettings;
10 | import android.os.Build;
11 | import android.os.ParcelUuid;
12 | import android.util.Log;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 | import java.util.UUID;
17 |
18 | /**
19 | * Created by Yonah on 18/03/16.
20 | *
21 | * The purpose of this class is to abstract the two types of scanning that we support
22 | * Pre-Lollipop and Lollipop+
23 | *
24 | * You specify which type of scanning you want when you initiate the class.
25 | * We provide a consistent callback interface for both types of scanning
26 | *
27 | * You can optionally filter by a serviceUUID.
28 | * Note: specifying the service uuid will prevent discovery of ios apps in the background
29 | * since all serviceUUIDs get moved to an overflow area when the app goes to background,
30 | * and to our scanner, they disappear. To discovery ios backgrounded apps, you need to
31 | * start an unfiltered scan and then filter the results yourself.
32 | *
33 | */
34 | public class MultiScanner {
35 | private final static String TAG = "discovery-MultiScanner";
36 |
37 | public interface MultiScannerCallback {
38 | void onScanResult(BluetoothDevice device, int rssi, byte[] scanRecord);
39 | void onScanFailed(int errorCode);
40 | }
41 |
42 | BluetoothAdapter mAdapter;
43 | ParcelUuid mServiceUUID;
44 | MultiScannerCallback mScanCallback;
45 | boolean mUsePreLScanner;
46 | PostLScanCallback mPostLScanCallback;
47 | BluetoothAdapter.LeScanCallback mPreLScanCallback;
48 |
49 | public MultiScanner(BluetoothAdapter adapter, ParcelUuid uuid, MultiScannerCallback callback) {
50 | this(adapter, uuid, callback, false);
51 | }
52 |
53 | /*
54 | @param adapter - the system bluetooth adapter
55 | @param uuid - the uuid of the service we are searching for - leave this blank if you dont want to filter
56 | @param callback - a callback when the users are updated.
57 | @param usePreLScanner - an option to use the deprecated LEScanner, since it sometimes functions better
58 | */
59 |
60 | public MultiScanner(BluetoothAdapter adapter, ParcelUuid uuid, MultiScannerCallback callback, boolean usePreLScanner) {
61 | mAdapter = adapter;
62 | mServiceUUID = uuid;
63 | mScanCallback = callback;
64 | mUsePreLScanner = usePreLScanner;
65 |
66 | }
67 |
68 | public void start() {
69 | if (!mAdapter.isEnabled())
70 | return;
71 |
72 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !mUsePreLScanner) {
73 | // we only listen to the service that belongs to our uuid
74 | // this is important for performance and battery consumption
75 | ScanSettings settings = new ScanSettings.Builder()
76 | .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
77 | .build();
78 |
79 | List filters = new ArrayList<>();
80 |
81 | if (mServiceUUID != null) {
82 | // filtering by the ServiceUUID prevents us from discovering iOS devices broadcasting in the background
83 | // since their serviceUUID gets moved into the 'overflow area'.
84 | // we need to find a way to filter also by the manufacturerData to find only devices with our UUID.
85 | // more here: https://forums.developer.apple.com/thread/11705
86 | ScanFilter serviceUUIDFilter = new ScanFilter.Builder().setServiceUuid(mServiceUUID).build();
87 | filters.add(serviceUUIDFilter);
88 |
89 |
90 | // // An alternative is to filter by manufacturer data,
91 | // // but I still haven't figured out how to show only apple devices
92 | //
93 | // // Empty data
94 | // byte[] manData = new byte[]{1,0,0,0,0,0,0,0,0,0,0,0,0,8,64,0,0};
95 | // // Data Mask
96 | // byte[] mask = new byte[]{1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1};
97 | // // Add data array to filters
98 | // ScanFilter manDataFilter = new ScanFilter.Builder().setManufacturerData(76, manData, mask).build();
99 | // filters.add(manDataFilter);
100 | }
101 |
102 |
103 | if (mPostLScanCallback == null)
104 | mPostLScanCallback = new PostLScanCallback();
105 | mAdapter.getBluetoothLeScanner().startScan(filters, settings, mPostLScanCallback );
106 | } else {
107 | if (mPreLScanCallback == null)
108 | mPreLScanCallback = new PreLScanCallback();
109 |
110 | if (mServiceUUID != null) {
111 | UUID[] serviceUUIDs = {mServiceUUID.getUuid()};
112 | mAdapter.startLeScan(serviceUUIDs, mPreLScanCallback);
113 | } else {
114 | mAdapter.startLeScan(mPreLScanCallback);
115 | }
116 | }
117 | }
118 |
119 | public void stop() {
120 | if (!mAdapter.isEnabled())
121 | return;
122 |
123 | if (mPostLScanCallback != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
124 | mAdapter.getBluetoothLeScanner().stopScan(mPostLScanCallback);
125 | mAdapter.getBluetoothLeScanner().flushPendingScanResults(mPostLScanCallback);
126 | }
127 |
128 | if (mPreLScanCallback != null) {
129 | mAdapter.stopLeScan(mPreLScanCallback);
130 | }
131 | }
132 |
133 |
134 | private class PreLScanCallback implements BluetoothAdapter.LeScanCallback {
135 | @Override
136 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
137 | mScanCallback.onScanResult(device, rssi, scanRecord);
138 | }
139 | }
140 |
141 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
142 | private class PostLScanCallback extends ScanCallback {
143 | @Override
144 | public void onScanResult(int callbackType, ScanResult result) {
145 | mScanCallback.onScanResult(result.getDevice(), result.getRssi(), result.getScanRecord().getBytes());
146 | }
147 |
148 | @Override
149 | public void onBatchScanResults(List results) {
150 | Log.v(TAG, "ScanCallback batch results: " + results);
151 | for (ScanResult r : results) {
152 | onScanResult(-1, r);
153 | }
154 | }
155 |
156 | @Override
157 | public void onScanFailed(int errorCode) {
158 | mScanCallback.onScanFailed(errorCode);
159 | switch (errorCode) {
160 | case ScanCallback.SCAN_FAILED_ALREADY_STARTED:
161 | Log.e(TAG, "Scan failed: already started");
162 | break;
163 | case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
164 | Log.e(TAG, "Scan failed: app registration failed");
165 | break;
166 | case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED:
167 | Log.e(TAG, "Scan failed: feature unsupported");
168 | break;
169 | case ScanCallback.SCAN_FAILED_INTERNAL_ERROR:
170 | Log.e(TAG, "Scan failed: internal error");
171 | break;
172 | }
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/discovery/src/main/java/com/joshblour/discovery/GattManager.java:
--------------------------------------------------------------------------------
1 | package com.joshblour.discovery;
2 |
3 | import android.bluetooth.BluetoothDevice;
4 | import android.bluetooth.BluetoothGatt;
5 | import android.bluetooth.BluetoothGattCallback;
6 | import android.bluetooth.BluetoothGattCharacteristic;
7 | import android.bluetooth.BluetoothGattService;
8 | import android.bluetooth.BluetoothProfile;
9 | import android.content.Context;
10 | import android.os.Handler;
11 | import android.os.Looper;
12 | import android.os.ParcelUuid;
13 | import android.util.Log;
14 |
15 | import java.util.Date;
16 | import java.util.HashMap;
17 | import java.util.List;
18 | import java.util.Map;
19 |
20 | /**
21 | * Created by Yonah on 18/03/16.
22 | */
23 | public class GattManager {
24 | private final static String TAG = "discovery-GattManager";
25 |
26 | interface GattManagerCallback {
27 | void didIdentify(BluetoothDevice device, String username, ParcelUuid serviceUUID);
28 | void failedToMatchService(BluetoothDevice device);
29 | }
30 |
31 | private Integer mGattTimeoutInterval;
32 | private Map mGattConnectionStartTimes;
33 | private Map mGattConnections;
34 |
35 | private ParcelUuid mServiceUUID;
36 | private GattManagerCallback mCallback;
37 | private Context mContext;
38 | private final MyBluetoothGattCallback mMyBluetoothGattCallback = new MyBluetoothGattCallback();
39 |
40 | public GattManager(Context context, ParcelUuid serviceUUID, GattManagerCallback callback) {
41 | mContext = context;
42 | mServiceUUID = serviceUUID;
43 | mCallback = callback;
44 |
45 | mGattConnections = new HashMap<>();
46 | mGattConnectionStartTimes = new HashMap<>();
47 | mGattTimeoutInterval = 30;
48 |
49 | }
50 |
51 | // call this method to try to identify a device.
52 | // this will attempt to connect to the device and read its services
53 | // if a service matching ours is found. the callback didMatchService is called and we try to read the characteristics
54 | // if we can read the characteristic matching our service, the callback didIdentify is called with the username
55 | // if no service matching ours is found, the callback failedToMatchService is called.
56 | public void identify(final BluetoothDevice device) {
57 | boolean shouldConnect = false;
58 |
59 | // first check if there are any existing connection attempts in progress.
60 | // If there are, check to see if they have timed out.
61 | // If they have, cancel them and try again. If not, wait..
62 | // if no existing attempts, start a new one and store it (if successful).
63 | long currentTime = new Date().getTime();
64 | BluetoothGatt existingGatt = mGattConnections.get(device.getAddress());
65 |
66 | if (existingGatt == null) {
67 | Log.v(TAG, device.getAddress() + " - device not identified. will connect");
68 | shouldConnect = true;
69 | } else {
70 | long startedAt = mGattConnectionStartTimes.get(device.getAddress());
71 | if (currentTime - startedAt < mGattTimeoutInterval * 1000) {
72 | Log.v(TAG, device.getAddress() + " - device not identified. connection already in progress");
73 | shouldConnect = false;
74 | } else {
75 | Log.w(TAG, device.getAddress() + " - connection did timeout. will retry");
76 | existingGatt.disconnect();
77 | existingGatt.close();
78 | mGattConnections.remove(device.getAddress());
79 | mGattConnectionStartTimes.remove(device.getAddress());
80 | shouldConnect = true;
81 | }
82 | }
83 |
84 | if (shouldConnect) {
85 | Handler handler = new Handler(Looper.getMainLooper());
86 | handler.post(new Runnable() {
87 | @Override
88 | public void run() {
89 | BluetoothGatt gatt = device.connectGatt(mContext, false, mMyBluetoothGattCallback);
90 | if (gatt != null) {
91 | Log.v(TAG, device.getAddress() + " - attempted connection");
92 | mGattConnections.put(device.getAddress(), gatt);
93 | mGattConnectionStartTimes.put(device.getAddress(), new Date().getTime());
94 | }
95 | }
96 | });
97 | }
98 |
99 | }
100 |
101 | private class MyBluetoothGattCallback extends BluetoothGattCallback {
102 |
103 | @Override
104 | public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
105 | // this will get called when a device connects or disconnects
106 | if (newState == BluetoothProfile.STATE_CONNECTED) {
107 | Log.v(TAG, gatt.getDevice().getAddress() + " - connected!");
108 |
109 | boolean started = gatt.discoverServices();
110 |
111 | if (!started) {
112 | gatt.disconnect();
113 | }
114 | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
115 | Log.v(TAG, gatt.getDevice().getAddress() + " - disconnected...");
116 | gatt.close();
117 | mGattConnections.remove(gatt.getDevice().getAddress());
118 | mGattConnectionStartTimes.remove(gatt.getDevice().getAddress());
119 |
120 | } else {
121 | Log.v(TAG, gatt.getDevice().getAddress() + " status: " + status);
122 | }
123 | }
124 |
125 | @Override
126 | public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
127 | // this will get called after the client initiates a BluetoothGatt.discoverServices() call
128 | BluetoothGattService service = gatt.getService(mServiceUUID.getUuid());
129 | Log.v(TAG, gatt.getDevice().getAddress() + " - services discovered");
130 | Boolean isMyService = false;
131 |
132 | if (service != null) {
133 | List characteristics = service.getCharacteristics();
134 |
135 | for (BluetoothGattCharacteristic characteristic : characteristics) {
136 | if (characteristic.getUuid().equals(mServiceUUID.getUuid())) {
137 | isMyService = true;
138 | Log.v(TAG, gatt.getDevice().getAddress() + " - found MY service!");
139 |
140 | gatt.setCharacteristicNotification(characteristic, true);
141 | gatt.readCharacteristic(characteristic);
142 | }
143 | }
144 | }
145 |
146 |
147 | if (!isMyService) {
148 | mCallback.failedToMatchService(gatt.getDevice());
149 | gatt.disconnect();
150 | }
151 |
152 | }
153 |
154 | @Override
155 | public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
156 | onCharacteristicChanged(gatt, characteristic);
157 | }
158 |
159 | @Override
160 | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
161 | if (characteristic.getUuid().equals(mServiceUUID.getUuid())) {
162 | if (characteristic.getValue() != null) {
163 | String value = characteristic.getStringValue(0);
164 | ParcelUuid uuid = new ParcelUuid(characteristic.getUuid());
165 |
166 | // if the value is not nil, we found our username!
167 | if (value != null && value.length() > 0) {
168 | Log.v(TAG, gatt.getDevice().getAddress() + " - got username!!");
169 |
170 | mCallback.didIdentify(gatt.getDevice(), value, uuid);
171 |
172 | // cancel the subscription to our characteristic
173 | gatt.setCharacteristicNotification(characteristic, false);
174 | // and disconnect from the peripehral
175 | gatt.disconnect();
176 |
177 | }
178 | }
179 | }
180 | }
181 | }
182 |
183 | }
184 |
--------------------------------------------------------------------------------
/discovery/src/main/java/com/joshblour/discovery/AdvertiserService.java:
--------------------------------------------------------------------------------
1 | package com.joshblour.discovery;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Service;
5 | import android.bluetooth.BluetoothAdapter;
6 | import android.bluetooth.BluetoothDevice;
7 | import android.bluetooth.BluetoothGatt;
8 | import android.bluetooth.BluetoothGattCharacteristic;
9 | import android.bluetooth.BluetoothGattServer;
10 | import android.bluetooth.BluetoothGattServerCallback;
11 | import android.bluetooth.BluetoothGattService;
12 | import android.bluetooth.BluetoothManager;
13 | import android.bluetooth.le.AdvertiseCallback;
14 | import android.bluetooth.le.AdvertiseData;
15 | import android.bluetooth.le.AdvertiseSettings;
16 | import android.bluetooth.le.BluetoothLeAdvertiser;
17 | import android.content.BroadcastReceiver;
18 | import android.content.Context;
19 | import android.content.Intent;
20 | import android.content.IntentFilter;
21 | import android.os.Build;
22 | import android.os.Bundle;
23 | import android.os.IBinder;
24 | import android.os.ParcelUuid;
25 | import android.util.Log;
26 |
27 | /**
28 | * Created by Yonah on 21/01/16.
29 | * base on: http://developer.android.com/samples/BluetoothAdvertisements/project.html
30 | */
31 |
32 |
33 | /**
34 | * Manages BLE Advertising independent of the main app.
35 | * If the app goes off screen (or gets killed completely) advertising can continue because this
36 | * Service is maintaining the necessary Callback in memory.
37 | */
38 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
39 | public class AdvertiserService extends Service {
40 | private final static String TAG = "discovery-AdvertiserSvc";
41 |
42 | /**
43 | * A global variable to let AdvertiserFragment check if the Service is running without needing
44 | * to start or bind to it.
45 | * This is the best practice method as defined here:
46 | * https://groups.google.com/forum/#!topic/android-developers/jEvXMWgbgzE
47 | */
48 | public static boolean running = false;
49 |
50 | /**
51 | * Setting autorestart to true will cause the service to automatically relaunch if it's
52 | * killed by the system. Note: this will not auto relaunch the service if it's killed by
53 | * the user.
54 | *
55 | * This will also restart advertising if the bluetooth state is toggled off then on.
56 | */
57 | public static boolean shouldAutoRestart = false;
58 |
59 | /**
60 | * The number of times the service will try to restart itself after a failure.
61 | * Failure, not being killed.
62 | */
63 | public static int maxRetriesAfterFailure = 3;
64 |
65 |
66 | public static final String ADVERTISING_FAILED =
67 | "com.example.android.bluetoothadvertisements.advertising_failed";
68 |
69 | public static final String ADVERTISING_FAILED_EXTRA_CODE = "failureCode";
70 |
71 | private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
72 | private BluetoothAdapter mBluetoothAdapter;
73 | private BluetoothManager mBluetoothManager;
74 | private BluetoothGattServer mGattServer;
75 |
76 | private AdvertiseCallback mAdvertiseCallback;
77 | private BluetoothGattServerCallback mGattServerCallback;
78 | private ParcelUuid mUUID;
79 | private String mUsername;
80 |
81 | /**
82 | * how many times in a row we failed to start advertising
83 | */
84 | private int mRetriesAfterFailure = 0;
85 |
86 | /**
87 | * Monitor the bluetooth state. If autoRestart is true, start advertising whenever bluetooth
88 | * is turned back on.
89 | */
90 | private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
91 | @Override
92 | public void onReceive(Context context, Intent intent) {
93 | final String action = intent.getAction();
94 |
95 | if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
96 | final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
97 | BluetoothAdapter.ERROR);
98 |
99 | switch (state) {
100 | case BluetoothAdapter.STATE_ON:
101 | if (shouldAutoRestart)
102 | startAdvertising();
103 | break;
104 | }
105 | }
106 | }
107 | };
108 |
109 | @Override
110 | public void onCreate() {
111 | super.onCreate();
112 |
113 | // Register for broadcasts on BluetoothAdapter state change
114 | IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
115 | registerReceiver(mReceiver, filter);
116 | }
117 |
118 | @Override
119 | public void onDestroy() {
120 | // Log.v(TAG, "onDestroy");
121 |
122 | /**
123 | * Note that onDestroy is not guaranteed to be called quickly or at all. Services exist at
124 | * the whim of the system, and onDestroy can be delayed or skipped entirely if memory need
125 | * is critical.
126 | */
127 | running = false;
128 | stopAdvertising();
129 |
130 | // Unregister broadcast listeners
131 | unregisterReceiver(mReceiver);
132 |
133 |
134 | /**
135 | * If autorestart is true, launch a new service right before this one is killed. this
136 | * ensures that the system does turn off advertising by killing the service.
137 | */
138 | if (shouldAutoRestart) {
139 | Intent intent = new Intent(this, AdvertiserService.class);
140 | intent.putExtra("uuid", mUUID.toString());
141 | intent.putExtra("username", mUsername);
142 | startService(intent);
143 | }
144 |
145 | super.onDestroy();
146 | }
147 |
148 | /**
149 | * Required for extending service, but this will be a Started Service only, so no need for
150 | * binding.
151 | */
152 | @Override
153 | public IBinder onBind(Intent intent) {
154 | return null;
155 | }
156 |
157 | /**
158 | * Get references to system Bluetooth objects if we don't have them already.
159 | */
160 |
161 | @Override
162 | public int onStartCommand(Intent intent, int flags, int startId) {
163 | super.onStartCommand(intent, flags, startId);
164 | Bundle extras = intent.getExtras();
165 |
166 | this.mUUID = ParcelUuid.fromString(extras.getString("uuid"));
167 | this.mUsername = extras.getString("username");
168 |
169 | if (mBluetoothLeAdvertiser == null) {
170 | mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
171 | }
172 |
173 | if (mBluetoothManager != null) {
174 | mBluetoothAdapter = mBluetoothManager.getAdapter();
175 | }
176 |
177 | if (mBluetoothAdapter != null) {
178 | mBluetoothAdapter.setName(mUsername);
179 | mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
180 | }
181 |
182 |
183 | running = true;
184 | startAdvertising();
185 |
186 | return START_REDELIVER_INTENT;
187 | }
188 |
189 | /**
190 | * Starts BLE Advertising.
191 | */
192 | private void startAdvertising() {
193 | // Log.d(TAG, "Service: Starting Advertising");
194 |
195 | if (mAdvertiseCallback == null) {
196 | mAdvertiseCallback = new MyAdvertiseCallback();
197 | }
198 |
199 | if (mGattServerCallback == null) {
200 | mGattServerCallback = new MyGattServerCallback();
201 | }
202 |
203 | if (mGattServer == null) {
204 | mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
205 | mGattServer.addService(buildGattService());
206 | }
207 |
208 | if (mBluetoothLeAdvertiser != null) {
209 | AdvertiseSettings settings = buildAdvertiseSettings();
210 | AdvertiseData data = buildAdvertiseData();
211 | mBluetoothLeAdvertiser.startAdvertising(settings, data,
212 | mAdvertiseCallback);
213 | }
214 | }
215 |
216 | /**
217 | * Stops BLE Advertising.
218 | */
219 | private void stopAdvertising() {
220 | // Log.d(TAG, "Service: Stopping Advertising");
221 | if (mBluetoothLeAdvertiser != null) {
222 | mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
223 | mAdvertiseCallback = null;
224 | }
225 |
226 | if (mGattServer != null) {
227 | mGattServer.clearServices();
228 | mGattServer.close();
229 | mGattServer = null;
230 | }
231 | }
232 |
233 | /**
234 | * Returns an AdvertiseData object which includes the Service UUID and Device Name.
235 | */
236 | private AdvertiseData buildAdvertiseData() {
237 |
238 | /**
239 | * Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements.
240 | * This includes everything put into AdvertiseData including UUIDs, device info, &
241 | * arbitrary service or manufacturer data.
242 | * Attempting to send packets over this limit will result in a failure with error code
243 | * AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the
244 | * onStartFailure() method of an AdvertiseCallback implementation.
245 | */
246 |
247 | AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
248 | dataBuilder.addServiceUuid(mUUID);
249 | dataBuilder.setIncludeDeviceName(mUsername.length() < 8);
250 | dataBuilder.setIncludeTxPowerLevel(false);
251 |
252 | /* For example - this will cause advertising to fail (exceeds size limit) */
253 | //String failureData = "asdghkajsghalkxcjhfa;sghtalksjcfhalskfjhasldkjfhdskf";
254 | //dataBuilder.addServiceData(Constants.Service_UUID, failureData.getBytes());
255 |
256 | return dataBuilder.build();
257 | }
258 |
259 | /**
260 | * Returns an AdvertiseSettings object set to use low power (to help preserve battery life)
261 | * and disable the built-in timeout since this code uses its own timeout runnable.
262 | */
263 | private AdvertiseSettings buildAdvertiseSettings() {
264 | AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
265 | settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);
266 | settingsBuilder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
267 | settingsBuilder.setConnectable(true);
268 | settingsBuilder.setTimeout(0);
269 | return settingsBuilder.build();
270 | }
271 |
272 | /**
273 | * Returns a gatt service for the service uuid containing
274 | * a characteristic with the device name
275 | */
276 | private BluetoothGattService buildGattService() {
277 | BluetoothGattService gattService = new BluetoothGattService(
278 | mUUID.getUuid(),
279 | BluetoothGattService.SERVICE_TYPE_PRIMARY
280 | );
281 | BluetoothGattCharacteristic gattCharacteristic = new BluetoothGattCharacteristic(
282 | mUUID.getUuid(),
283 | BluetoothGattCharacteristic.PROPERTY_READ,
284 | BluetoothGattCharacteristic.PERMISSION_READ
285 | );
286 |
287 | gattCharacteristic.setValue(mUsername);
288 | gattService.addCharacteristic(gattCharacteristic);
289 | return gattService;
290 | }
291 |
292 | /*
293 | * Callback handles all incoming requests from GATT clients.
294 | * From connections to read/write requests.
295 | */
296 | private class MyGattServerCallback extends BluetoothGattServerCallback {
297 | @Override
298 | public void onCharacteristicReadRequest(BluetoothDevice device,
299 | int requestId,
300 | int offset,
301 | BluetoothGattCharacteristic characteristic) {
302 | super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
303 | // Log.i(TAG, "onCharacteristicReadRequest " + characteristic.getUuid().toString());
304 |
305 | if (characteristic.getUuid().equals(mUUID.getUuid())) {
306 | mGattServer.sendResponse(device,
307 | requestId,
308 | BluetoothGatt.GATT_SUCCESS,
309 | 0,
310 | mUsername.getBytes());
311 |
312 | }
313 | }
314 | }
315 |
316 | /**
317 | * Custom callback after Advertising succeeds or fails to start. Broadcasts the error code
318 | * in an Intent. Will rety advertising x times before finally failing and stopping
319 | *
320 | *
321 | */
322 | private class MyAdvertiseCallback extends AdvertiseCallback {
323 |
324 | @Override
325 | public void onStartFailure(int errorCode) {
326 | super.onStartFailure(errorCode);
327 |
328 | switch (errorCode) {
329 | case AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED:
330 | Log.e(TAG, "Advertise failed: already started");
331 | break;
332 | case AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE:
333 | Log.e(TAG, "Advertise failed: data too large");
334 | break;
335 | case AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED:
336 | Log.e(TAG, "Advertise failed: feature unsupported");
337 | break;
338 | case AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR:
339 | Log.e(TAG, "Advertise failed: internal error");
340 | break;
341 | case AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS:
342 | Log.e(TAG, "Advertise failed: too many advertisers");
343 | break;
344 | }
345 | mRetriesAfterFailure++;
346 |
347 | if (mRetriesAfterFailure < maxRetriesAfterFailure) {
348 | //RETRY CODE HERE!!
349 |
350 | new android.os.Handler().postDelayed(
351 | new Runnable() {
352 | public void run() {
353 | stopAdvertising();
354 | startAdvertising();
355 | }
356 | }, (int)(Math.pow(2, mRetriesAfterFailure) * 1000));
357 |
358 | } else {
359 | sendFailureIntent(errorCode);
360 | shouldAutoRestart = false;
361 | stopSelf();
362 | }
363 | }
364 |
365 | @Override
366 | public void onStartSuccess(AdvertiseSettings settingsInEffect) {
367 | super.onStartSuccess(settingsInEffect);
368 | mRetriesAfterFailure = 0;
369 | // Log.d(TAG, "Advertising successfully started");
370 | }
371 | }
372 |
373 | /**
374 | * Builds and sends a broadcast intent indicating Advertising has failed. Includes the error
375 | * code as an extra. This is intended to be picked up by the {@code AdvertiserFragment}.
376 | */
377 | private void sendFailureIntent(int errorCode){
378 |
379 | Intent failureIntent = new Intent();
380 | failureIntent.setAction(ADVERTISING_FAILED);
381 | failureIntent.putExtra(ADVERTISING_FAILED_EXTRA_CODE, errorCode);
382 | sendBroadcast(failureIntent);
383 | }
384 |
385 | }
386 |
--------------------------------------------------------------------------------
/discovery/src/main/java/com/joshblour/discovery/Discovery.java:
--------------------------------------------------------------------------------
1 | package com.joshblour.discovery;
2 |
3 | import android.annotation.TargetApi;
4 | import android.bluetooth.BluetoothAdapter;
5 | import android.bluetooth.BluetoothDevice;
6 | import android.bluetooth.BluetoothGatt;
7 | import android.bluetooth.BluetoothGattCallback;
8 | import android.bluetooth.BluetoothGattCharacteristic;
9 | import android.bluetooth.BluetoothGattService;
10 | import android.bluetooth.BluetoothManager;
11 | import android.bluetooth.BluetoothProfile;
12 | import android.bluetooth.le.AdvertiseCallback;
13 | import android.bluetooth.le.BluetoothLeScanner;
14 | import android.bluetooth.le.ScanCallback;
15 | import android.bluetooth.le.ScanFilter;
16 | import android.bluetooth.le.ScanResult;
17 | import android.bluetooth.le.ScanSettings;
18 | import android.content.BroadcastReceiver;
19 | import android.content.Context;
20 | import android.content.Intent;
21 | import android.os.Build;
22 | import android.os.Handler;
23 | import android.os.ParcelUuid;
24 | import android.util.Log;
25 |
26 |
27 | import java.util.ArrayList;
28 | import java.util.Collections;
29 | import java.util.Comparator;
30 | import java.util.Date;
31 | import java.util.HashMap;
32 | import java.util.List;
33 | import java.util.Map;
34 | import java.util.UUID;
35 |
36 | /**
37 | * Created by Yonah on 15/10/15.
38 | */
39 | @TargetApi(Build.VERSION_CODES.KITKAT)
40 | public class Discovery implements MultiScanner.MultiScannerCallback, GattManager.GattManagerCallback{
41 | private final static String TAG = "discovery-Discovery";
42 |
43 | public interface DiscoveryCallback {
44 | void didUpdateUsers(ArrayList users, Boolean usersChanged);
45 | }
46 |
47 |
48 |
49 | public enum DIStartOptions{
50 | DIStartAdvertisingAndDetecting,
51 | DIStartAdvertisingOnly,
52 | DIStartDetectingOnly,
53 | DIStartNone
54 | }
55 |
56 | private Context mContext;
57 | private String mUsername;
58 | private ParcelUuid mUUID;
59 | private Boolean mPaused;
60 | private Integer mUserTimeoutInterval;
61 | private Integer mScanForSeconds;
62 | private Integer mWaitForSeconds;
63 | private Boolean mShouldAdvertise;
64 | private Boolean mShouldDiscover;
65 | private Boolean mDisableAndroidLScanner;
66 | private Map mUsersMap;
67 |
68 |
69 | private Handler mHandler;
70 | private Runnable mRunnable;
71 | private DiscoveryCallback mDiscoveryCallback;
72 | private BluetoothAdapter mBluetoothAdapter;
73 |
74 | private GattManager mGattManager;
75 | private GattManager.GattManagerCallback mGattManagerCallback;
76 | private MultiScanner mScanner;
77 |
78 | public Discovery(Context context, ParcelUuid uuid, String username, DiscoveryCallback discoveryCallback) {
79 | this(context, uuid, username, DIStartOptions.DIStartAdvertisingAndDetecting, discoveryCallback);
80 | }
81 |
82 | public Discovery(Context context, ParcelUuid uuid, String username, DIStartOptions startOptions, DiscoveryCallback discoveryCallback ) {
83 | // initialize defaults
84 | mShouldAdvertise = false;
85 | mShouldDiscover = false;
86 | mDisableAndroidLScanner = false;
87 | mPaused = false;
88 | mUserTimeoutInterval = 5;
89 | mScanForSeconds = 5;
90 | mWaitForSeconds = 5;
91 | mContext = context;
92 | mUUID = uuid;
93 | mUsername = username;
94 | mDiscoveryCallback = discoveryCallback;
95 | mUsersMap = new HashMap<>();
96 | mHandler = new Handler();
97 |
98 | switch (startOptions) {
99 | case DIStartAdvertisingAndDetecting:
100 | this.setShouldAdvertise(true);
101 | this.setShouldDiscover(true);
102 | break;
103 | case DIStartAdvertisingOnly:
104 | this.setShouldAdvertise(true);
105 | break;
106 | case DIStartDetectingOnly:
107 | this.setShouldDiscover(true);
108 | break;
109 | case DIStartNone:
110 | default:
111 | break;
112 | }
113 | }
114 |
115 | public void setPaused(Boolean paused) {
116 | if (getBluetoothAdapter() == null)
117 | return;
118 |
119 | if (this.mPaused == paused)
120 | return;
121 | this.mPaused = paused;
122 |
123 | if (paused) {
124 | stopDetecting();
125 | stopAdvertising();
126 | } else {
127 | startDetectionCycling();
128 | startAdvertising();
129 | }
130 | }
131 |
132 | //***BEGIN DETECTION METHODS***
133 | public void setShouldDiscover(Boolean shouldDiscover) {
134 | if (getBluetoothAdapter() == null)
135 | return;
136 |
137 | if (this.mShouldDiscover == shouldDiscover)
138 | return;
139 |
140 | this.mShouldDiscover = shouldDiscover;
141 |
142 | if (shouldDiscover) {
143 | startDetectionCycling();
144 | } else {
145 | stopDetecting();
146 | checkList();
147 | }
148 | }
149 |
150 | // A more energy efficient way to detect.
151 | // It detects for mScanForSeconds(default: 5) then stops for mWaitForSeconds(default: 5) then starts again.
152 | // mShouldDiscover starts THIS method when set to true and stops it when set to false.
153 | private void startDetectionCycling() {
154 | if (!mShouldDiscover || mPaused)
155 | return;
156 |
157 | if (getBluetoothAdapter() == null)
158 | return;
159 |
160 | startDetecting();
161 | Log.v(TAG, "detection cycle started");
162 |
163 | if (mRunnable != null)
164 | mHandler.removeCallbacks(mRunnable);
165 |
166 | mRunnable = new Runnable() {
167 | @Override
168 | public void run() {
169 | stopDetecting();
170 | Log.v(TAG, "detection cycle stopped");
171 |
172 | Runnable runable = new Runnable() {
173 | @Override
174 | public void run() {
175 | startDetectionCycling();
176 | }
177 | };
178 | mHandler.postDelayed(runable, mWaitForSeconds * 1000);
179 | checkList();
180 | }
181 | };
182 | mHandler.postDelayed(mRunnable, mScanForSeconds * 1000);
183 | }
184 |
185 | public void startDetecting() {
186 | if (mScanner == null)
187 | mScanner = new MultiScanner(getBluetoothAdapter(), null, this, true);
188 |
189 | mScanner.start();
190 | }
191 |
192 | public void stopDetecting(){
193 | if (mScanner != null)
194 | mScanner.stop();
195 | }//***END DETECTION METHODS***
196 |
197 |
198 |
199 |
200 |
201 | //***BEGIN ADVERTISING METHODS***
202 | public void setShouldAdvertise(Boolean shouldAdvertise) {
203 | if (getBluetoothAdapter() == null)
204 | return;
205 |
206 | if (this.mShouldAdvertise == shouldAdvertise)
207 | return;
208 |
209 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
210 | this.mShouldAdvertise = false;
211 | return;
212 | }
213 |
214 | this.mShouldAdvertise = shouldAdvertise;
215 |
216 | if (shouldAdvertise) {
217 | startAdvertising();
218 | } else {
219 | stopAdvertising();
220 | }
221 | }
222 |
223 | private void startAdvertising() {
224 | if (getBluetoothAdapter().isEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
225 | AdvertiserService.shouldAutoRestart = true;
226 | if (!AdvertiserService.running) {
227 | mContext.startService(getAdvertiserServiceIntent(mContext));
228 | Log.v(TAG, "started advertising");
229 | }
230 | }
231 | }
232 |
233 | private void stopAdvertising() {
234 | if (getBluetoothAdapter().isEnabled() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
235 | AdvertiserService.shouldAutoRestart = false;
236 | mContext.stopService(getAdvertiserServiceIntent(mContext));
237 | Log.v(TAG, "stopped advertising");
238 | }
239 | }
240 |
241 | /**
242 | * Returns Intent addressed to the {@code AdvertiserService} class.
243 | */
244 | private Intent getAdvertiserServiceIntent(Context c) {
245 | Intent intent = new Intent(c, AdvertiserService.class);
246 | intent.putExtra("uuid", getUUID().toString());
247 | intent.putExtra("username", getUsername());
248 | return intent;
249 | } // ***END ADVERTISING METHODS***
250 |
251 |
252 | //***BEGIN METHODS TO PROCESS SCAN RESULTS***
253 |
254 | public void updateList() {
255 | updateList(true);
256 | }
257 |
258 | // sends an update to the delegate with an array of identified users
259 | public void updateList(Boolean usersChanged) {
260 | ArrayList users = new ArrayList<>(getUsersMap().values());
261 |
262 | // remove unidentified users and users who dont belong to our service
263 | ArrayList discardedItems = new ArrayList<>();
264 | for (BLEUser user : users) {
265 | if (!user.isIdentified()) {
266 | discardedItems.add(user);
267 | }
268 | }
269 | users.removeAll(discardedItems);
270 |
271 | // we sort the list according to "proximity".
272 | // so the client will receive ordered users according to the proximity.
273 | Collections.sort(users, new Comparator() {
274 | public int compare(BLEUser s1, BLEUser s2) {
275 | if (s1.getProximity() == null || s2.getProximity() == null)
276 | return 0;
277 | return s1.getProximity().compareTo(s2.getProximity());
278 | }
279 | });
280 |
281 |
282 | if (mDiscoveryCallback != null) {
283 | mDiscoveryCallback.didUpdateUsers(users, usersChanged);
284 | }
285 | }
286 |
287 | // removes users who haven't been seen in mUserTimeoutInterval seconds and triggers
288 | // an update to the delegate
289 | private void checkList() {
290 |
291 | if (getUsersMap() == null)
292 | return;
293 |
294 | long currentTime = new Date().getTime();
295 | ArrayList discardedKeys = new ArrayList<>();
296 |
297 | for (String key : getUsersMap().keySet()) {
298 | BLEUser bleUser = getUsersMap().get(key);
299 | long diff = currentTime - bleUser.getUpdateTime();
300 |
301 | // We remove the user if we haven't seen him for the userTimeInterval amount of seconds.
302 | // You can simply set the userTimeInterval variable anything you want.
303 | if (diff > getUserTimeoutInterval() * 1000) {
304 | discardedKeys.add(key);
305 | }
306 | }
307 |
308 |
309 | // update the list if we removed a user.
310 | if (discardedKeys.size() > 0) {
311 | for (String key : discardedKeys) {
312 | getUsersMap().remove(key);
313 | }
314 | updateList();
315 | } else {
316 | // simply update the list, because the order of the users may have changed.
317 | updateList(false);
318 | }
319 |
320 | }
321 |
322 | private BLEUser userForDevice(BluetoothDevice device) {
323 | BLEUser bleUser = getUsersMap().get(device.getAddress());
324 |
325 | if (bleUser == null) {
326 | bleUser = new BLEUser(device);
327 | bleUser.setUsername(null);
328 | bleUser.setIdentified(false);
329 | getUsersMap().put(bleUser.getDeviceAddress(), bleUser);
330 | }
331 |
332 | return bleUser;
333 | }
334 |
335 | @Override
336 | public void onScanResult(BluetoothDevice device, int rssi, byte[] scanRecord) {
337 |
338 | BLEUser bleUser = userForDevice(device);
339 |
340 | // before we report this device to our delegate as a success, two things:
341 | // 1) Make sure it contains our service (it's another device advertising with our unique uuid)
342 | // 2) Make sure we can read its username
343 |
344 | // We check if we can get a cached copy of the devices service uuids
345 | if (bleUser.isMyService() == null) {
346 | ParcelUuid[] uuids = device.getUuids();
347 | if (uuids != null && uuids.length > 0) {
348 | for (ParcelUuid uuid : uuids) {
349 | if (uuid.getUuid().equals(mUUID.getUuid())) {
350 | bleUser.setIsMyService(true);
351 | updateList(true);
352 | }
353 | }
354 | }
355 | }
356 |
357 | // We check if we can get the username from the advertisement data,
358 | // in case the advertising peer application is working at foreground
359 | if (bleUser.getUsername() == null) {
360 | String username = device.getName();
361 |
362 | if (username != null && username.length() > 0) {
363 | bleUser.setUsername(username);
364 | updateList(true);
365 | }
366 | }
367 |
368 | //if you have the username and a boolean value for isMyService, you have enough to identify the user
369 | if (bleUser.isMyService() != null && bleUser.getUsername() != null) {
370 | if (bleUser.isMyService()) {
371 | bleUser.setIdentified(true);
372 | }
373 | }
374 |
375 |
376 | if (bleUser.isIdentified()) {
377 | /// great! we know everything we need to about this service. just update the rssi and time and we're done
378 | Log.v(TAG, device.getAddress() + " - device is identified");
379 | } else if (bleUser.isMyService() == null) {
380 | // ok, maybe we know the username but we dont know if it's our service, so connect to gatt and check.
381 | if (mGattManager == null)
382 | mGattManager = new GattManager(mContext, mUUID, this);
383 |
384 | mGattManager.identify(device);
385 | } else if (!bleUser.isMyService()) {
386 | /// Ok, this isn't our service, we don't care about it.
387 | Log.v(TAG, device.getAddress() + " - device not our service");
388 | }
389 |
390 | bleUser.setRssi(rssi);
391 | bleUser.setUpdateTime(new Date().getTime());
392 | }
393 |
394 | @Override
395 | public void onScanFailed(int errorCode) {
396 |
397 | }
398 |
399 | @Override
400 | public void didIdentify(BluetoothDevice device, String username, ParcelUuid uuid) {
401 | BLEUser bleUser = userForDevice(device);
402 | bleUser.setUsername(username);
403 | bleUser.setIdentified(true);
404 | bleUser.setIsMyService(true);
405 | updateList(true);
406 | }
407 |
408 |
409 | @Override
410 | public void failedToMatchService(BluetoothDevice device) {
411 | BLEUser bleUser = userForDevice(device);
412 | bleUser.setIsMyService(false);
413 | }
414 |
415 |
416 | //***BEGIN GETTERS AND SETTERS**
417 | public String getUsername() {
418 | return mUsername;
419 | }
420 | public ParcelUuid getUUID() {
421 | return mUUID;
422 | }
423 | public Boolean getPaused() {
424 | return mPaused;
425 | }
426 | public Boolean getShouldDiscover() {
427 | return mShouldDiscover;
428 | }
429 | public Boolean getShouldAdvertise() {
430 | return mShouldAdvertise;
431 | }
432 | public Boolean getShouldDisableAndroidLScanner() {
433 | return mDisableAndroidLScanner;
434 | }
435 | public Integer getUserTimeoutInterval() {
436 | return mUserTimeoutInterval;
437 | }
438 | public void setUserTimeoutInterval(Integer mUserTimeoutInterval) {
439 | this.mUserTimeoutInterval = mUserTimeoutInterval;
440 | }
441 | public Map getUsersMap() {
442 | return mUsersMap;
443 | }
444 | public Integer getScanForSeconds() {
445 | return mScanForSeconds;
446 | }
447 | public Integer getWaitForSeconds() {
448 | return mWaitForSeconds;
449 | }
450 |
451 | public void setShouldDisableAndroidLScanner(Boolean disableAndroidLScanner) {
452 | this.mDisableAndroidLScanner = disableAndroidLScanner;
453 | }
454 | public void setScanForSeconds(Integer scanForSeconds) {
455 | this.mScanForSeconds = scanForSeconds;
456 | startDetectionCycling();
457 | }
458 |
459 | public void setWaitForSeconds(Integer waitForSeconds) {
460 | this.mWaitForSeconds = waitForSeconds;
461 | startDetectionCycling();
462 | }
463 |
464 | private BluetoothAdapter getBluetoothAdapter() {
465 | if (mBluetoothAdapter == null) {
466 | BluetoothManager manager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
467 | mBluetoothAdapter = manager.getAdapter();
468 | }
469 |
470 | return mBluetoothAdapter;
471 | }
472 |
473 | }
474 |
--------------------------------------------------------------------------------