Syncing to //developers/samples/android commmit 2be5f5ca32. Change-Id: Ia08c63655bd122c7fbb0cc3a50eec95d8edcb3f1
223 lines
7.7 KiB
Java
223 lines
7.7 KiB
Java
package com.example.android.bluetoothadvertisements;
|
|
|
|
import android.app.Service;
|
|
import android.bluetooth.BluetoothAdapter;
|
|
import android.bluetooth.BluetoothManager;
|
|
import android.bluetooth.le.AdvertiseCallback;
|
|
import android.bluetooth.le.AdvertiseData;
|
|
import android.bluetooth.le.AdvertiseSettings;
|
|
import android.bluetooth.le.BluetoothLeAdvertiser;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.util.Log;
|
|
import android.widget.Toast;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* Manages BLE Advertising independent of the main app.
|
|
* If the app goes off screen (or gets killed completely) advertising can continue because this
|
|
* Service is maintaining the necessary Callback in memory.
|
|
*/
|
|
public class AdvertiserService extends Service {
|
|
|
|
private static final String TAG = AdvertiserService.class.getSimpleName();
|
|
|
|
/**
|
|
* A global variable to let AdvertiserFragment check if the Service is running without needing
|
|
* to start or bind to it.
|
|
* This is the best practice method as defined here:
|
|
* https://groups.google.com/forum/#!topic/android-developers/jEvXMWgbgzE
|
|
*/
|
|
public static boolean running = false;
|
|
|
|
public static final String ADVERTISING_FAILED =
|
|
"com.example.android.bluetoothadvertisements.advertising_failed";
|
|
|
|
public static final String ADVERTISING_FAILED_EXTRA_CODE = "failureCode";
|
|
|
|
public static final int ADVERTISING_TIMED_OUT = 6;
|
|
|
|
private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
|
|
|
|
private AdvertiseCallback mAdvertiseCallback;
|
|
|
|
private Handler mHandler;
|
|
|
|
private Runnable timeoutRunnable;
|
|
|
|
/**
|
|
* Length of time to allow advertising before automatically shutting off. (10 minutes)
|
|
*/
|
|
private long TIMEOUT = TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES);
|
|
|
|
@Override
|
|
public void onCreate() {
|
|
running = true;
|
|
initialize();
|
|
startAdvertising();
|
|
setTimeout();
|
|
super.onCreate();
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
/**
|
|
* Note that onDestroy is not guaranteed to be called quickly or at all. Services exist at
|
|
* the whim of the system, and onDestroy can be delayed or skipped entirely if memory need
|
|
* is critical.
|
|
*/
|
|
running = false;
|
|
stopAdvertising();
|
|
mHandler.removeCallbacks(timeoutRunnable);
|
|
super.onDestroy();
|
|
}
|
|
|
|
/**
|
|
* Required for extending service, but this will be a Started Service only, so no need for
|
|
* binding.
|
|
*/
|
|
@Override
|
|
public IBinder onBind(Intent intent) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get references to system Bluetooth objects if we don't have them already.
|
|
*/
|
|
private void initialize() {
|
|
if (mBluetoothLeAdvertiser == null) {
|
|
BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
|
|
if (mBluetoothManager != null) {
|
|
BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();
|
|
if (mBluetoothAdapter != null) {
|
|
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
|
|
} else {
|
|
Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
|
}
|
|
} else {
|
|
Toast.makeText(this, getString(R.string.bt_null), Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Starts a delayed Runnable that will cause the BLE Advertising to timeout and stop after a
|
|
* set amount of time.
|
|
*/
|
|
private void setTimeout(){
|
|
mHandler = new Handler();
|
|
timeoutRunnable = new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
Log.d(TAG, "AdvertiserService has reached timeout of "+TIMEOUT+" milliseconds, stopping advertising.");
|
|
sendFailureIntent(ADVERTISING_TIMED_OUT);
|
|
stopSelf();
|
|
}
|
|
};
|
|
mHandler.postDelayed(timeoutRunnable, TIMEOUT);
|
|
}
|
|
|
|
/**
|
|
* Starts BLE Advertising.
|
|
*/
|
|
private void startAdvertising() {
|
|
Log.d(TAG, "Service: Starting Advertising");
|
|
|
|
if (mAdvertiseCallback == null) {
|
|
AdvertiseSettings settings = buildAdvertiseSettings();
|
|
AdvertiseData data = buildAdvertiseData();
|
|
mAdvertiseCallback = new SampleAdvertiseCallback();
|
|
|
|
if (mBluetoothLeAdvertiser != null) {
|
|
mBluetoothLeAdvertiser.startAdvertising(settings, data,
|
|
mAdvertiseCallback);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops BLE Advertising.
|
|
*/
|
|
private void stopAdvertising() {
|
|
Log.d(TAG, "Service: Stopping Advertising");
|
|
if (mBluetoothLeAdvertiser != null) {
|
|
mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
|
|
mAdvertiseCallback = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an AdvertiseData object which includes the Service UUID and Device Name.
|
|
*/
|
|
private AdvertiseData buildAdvertiseData() {
|
|
|
|
/**
|
|
* Note: There is a strict limit of 31 Bytes on packets sent over BLE Advertisements.
|
|
* This includes everything put into AdvertiseData including UUIDs, device info, &
|
|
* arbitrary service or manufacturer data.
|
|
* Attempting to send packets over this limit will result in a failure with error code
|
|
* AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE. Catch this error in the
|
|
* onStartFailure() method of an AdvertiseCallback implementation.
|
|
*/
|
|
|
|
AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
|
|
dataBuilder.addServiceUuid(Constants.Service_UUID);
|
|
dataBuilder.setIncludeDeviceName(true);
|
|
|
|
/* For example - this will cause advertising to fail (exceeds size limit) */
|
|
//String failureData = "asdghkajsghalkxcjhfa;sghtalksjcfhalskfjhasldkjfhdskf";
|
|
//dataBuilder.addServiceData(Constants.Service_UUID, failureData.getBytes());
|
|
|
|
return dataBuilder.build();
|
|
}
|
|
|
|
/**
|
|
* Returns an AdvertiseSettings object set to use low power (to help preserve battery life)
|
|
* and disable the built-in timeout since this code uses its own timeout runnable.
|
|
*/
|
|
private AdvertiseSettings buildAdvertiseSettings() {
|
|
AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
|
|
settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);
|
|
settingsBuilder.setTimeout(0);
|
|
return settingsBuilder.build();
|
|
}
|
|
|
|
/**
|
|
* Custom callback after Advertising succeeds or fails to start. Broadcasts the error code
|
|
* in an Intent to be picked up by AdvertiserFragment and stops this Service.
|
|
*/
|
|
private class SampleAdvertiseCallback extends AdvertiseCallback {
|
|
|
|
@Override
|
|
public void onStartFailure(int errorCode) {
|
|
super.onStartFailure(errorCode);
|
|
|
|
Log.d(TAG, "Advertising failed");
|
|
sendFailureIntent(errorCode);
|
|
stopSelf();
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
|
|
super.onStartSuccess(settingsInEffect);
|
|
Log.d(TAG, "Advertising successfully started");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds and sends a broadcast intent indicating Advertising has failed. Includes the error
|
|
* code as an extra. This is intended to be picked up by the {@code AdvertiserFragment}.
|
|
*/
|
|
private void sendFailureIntent(int errorCode){
|
|
Intent failureIntent = new Intent();
|
|
failureIntent.setAction(ADVERTISING_FAILED);
|
|
failureIntent.putExtra(ADVERTISING_FAILED_EXTRA_CODE, errorCode);
|
|
sendBroadcast(failureIntent);
|
|
}
|
|
|
|
} |