| 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); |
| } |
| |
| } |