UUID 정리 Service UUID, Characteristic UUID 등등
public static final ParcelUuid Service_UUID = ParcelUuid
.fromString("9edc2f4b-4b4b-4912-9284-62be01477c0c");
블루투스 호환성 확인
private BluetoothAdapter mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
.getAdapter();
// Is Bluetooth supported on this device?
if (mBluetoothAdapter != null) {
// Is Bluetooth turned on?
if (mBluetoothAdapter.isEnabled()) {
// Are Bluetooth Advertisements supported on this device?
if (mBluetoothAdapter.isMultipleAdvertisementSupported()) {
// Everything is supported and enabled, load the fragments.
setupFragments();
} else {
// Bluetooth Advertisements are not supported.
showErrorText("Bluetooth Advertisements are not supported on this device.");
}
} else {
// Prompt user to turn on Bluetooth (logic continues in onActivityResult()).
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, Constants.REQUEST_ENABLE_BT);
}
} else {
// Bluetooth is not supported.
showErrorText("Bluetooth is not supported on this device.");
}
Bluetooth 5 LE 2M Phy가 호환되는지 확인
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!adapter.isLe2MPhySupported()) {
Log.e("TEST", "2M PHY not supported!");
return;
}
if (!adapter.isLeExtendedAdvertisingSupported()) {
Log.e("TEST", "LE Extended Advertising not supported!");
return;
}
}
Advertising(BroadCasting) Service 구현 oreo (android 8) 버전 이상 사용법
/**
* 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();
private static final int FOREGROUND_NOTIFICATION_ID = 1;
private static final String FOREGROUND_CHANNEL_ID = "com.example.sample1";
private static final String FOREGROUND_CHANNEL_NAME = "Bluetooth Service";
/**
* 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 AdvertisingSetCallback advertisingSetCallback;
private AdvertisingSet currentAdvertisingSet;
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);
stopForeground(true);
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) {
adapter = BluetoothAdapter.getDefaultAdapter();
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() {
goForeground();
Log.d(TAG, "Service: Starting Advertising");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (advertisingSetCallback == null) {
AdvertisingSetParameters settings = buildAdvertisingSetParameters();
AdvertiseData data = buildAdvertiseData();
AdvertiseData test = test();
advertisingSetCallback = new SampleAdvertisingSetCallback();
if (mBluetoothLeAdvertiser != null) {
mBluetoothLeAdvertiser.startAdvertisingSet(settings, data, test, null, null,
advertisingSetCallback);
}
}
}
}
/**
* Move service to the foreground, to avoid execution limits on background processes.
* <p>
* Callers should call stopForeground(true) when background work is complete.
*/
private void goForeground() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
if (Build.VERSION.SDK_INT >= 26) {
NotificationChannel channel = new NotificationChannel(
FOREGROUND_CHANNEL_ID,
FOREGROUND_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
Notification n = new NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID)
.setContentTitle("Advertising device via Bluetooth")
.setContentText("This device is discoverable to others nearby.")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(FOREGROUND_NOTIFICATION_ID, n);
}
/**
* Stops BLE Advertising.
*/
private void stopAdvertising() {
Log.d(TAG, "Service: Stopping Advertising");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (mBluetoothLeAdvertiser != null) {
mBluetoothLeAdvertiser.stopAdvertisingSet(advertisingSetCallback);
advertisingSetCallback = 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 data = new AdvertiseData.Builder()
.addServiceUuid(Constants.Service_UUID)
.build();
return data;
}
private AdvertiseData test() {
/**
* 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 data = new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.build();
return data;
}
private AdvertisingSetParameters buildAdvertisingSetParameters() {
AdvertisingSetParameters paramBuilder = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
paramBuilder = (new AdvertisingSetParameters.Builder())
.setLegacyMode(true) // True by default, but set here as a reminder.
.setConnectable(true)
.setScannable(true)
.setInterval(AdvertisingSetParameters.INTERVAL_HIGH)
.setTxPowerLevel(AdvertisingSetParameters.TX_POWER_MEDIUM)
.build();
}
return paramBuilder;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private class SampleAdvertisingSetCallback extends AdvertisingSetCallback {
@Override
public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {
Log.i("TEST", "onAdvertisingSetStarted(): txPower:" + txPower + " , status: "
+ status);
if (advertisingSet == null) {
Log.d("TEST", "advertisingSet null");
}
currentAdvertisingSet = advertisingSet;
currentAdvertisingSet.setScanResponseData(new AdvertiseData.Builder().addServiceData(Constants.CHARACTERISTIC_UUID, "Hi".getBytes()).build());
}
@Override
public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
Log.i("TEST", "onAdvertisingDataSet() :status:" + status);
}
@Override
public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {
Log.i("TEST", "onScanResponseDataSet(): status:" + status);
}
@Override
public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
Log.i("TEST", "onAdvertisingSetStopped():");
}
}
/**
* 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);
}
}
8버전 아래 사용법
/**
* 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();
private static final int FOREGROUND_NOTIFICATION_ID = 1;
private static final String FOREGROUND_CHANNEL_ID = "com.example.sample1";
private static final String FOREGROUND_CHANNEL_NAME = "Bluetooth Service";
/**
* 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);
stopForeground(true);
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) {
adapter = BluetoothAdapter.getDefaultAdapter();
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() {
goForeground();
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);
}
}
}
/**
* Move service to the foreground, to avoid execution limits on background processes.
* <p>
* Callers should call stopForeground(true) when background work is complete.
*/
private void goForeground() {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
notificationIntent, 0);
if (Build.VERSION.SDK_INT >= 26) {
NotificationChannel channel = new NotificationChannel(
FOREGROUND_CHANNEL_ID,
FOREGROUND_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
}
Notification n = new NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID)
.setContentTitle("Advertising device via Bluetooth")
.setContentText("This device is discoverable to others nearby.")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(FOREGROUND_NOTIFICATION_ID, n);
}
/**
* 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 data = new AdvertiseData.Builder()
.addServiceUuid(Constants.Service_UUID)
.build();
return data;
}
/**
* 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 settingsBuilder = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)
.setConnectable(true)
.setTimeout(0)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
.build();
return settingsBuilder;
}
/**
* 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("TEST", "Advertising failed");
sendFailureIntent(errorCode);
stopSelf();
}
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
Log.d("TEST", "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);
}
}