├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pixplicity │ │ └── bluetoothdemo │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── pixplicity │ │ └── bluetoothdemo │ │ └── MainActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_clear.png │ ├── drawable-mdpi │ └── ic_clear.png │ ├── drawable-xhdpi │ └── ic_clear.png │ ├── drawable-xxhdpi │ └── ic_clear.png │ ├── drawable-xxxhdpi │ └── ic_clear.png │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | /*/build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio 30 | .idea/ 31 | /.idea/workspace.xml 32 | /.idea/libraries 33 | /captures 34 | *.iml 35 | 36 | # Mac 37 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pixplicity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android Bluetooth LE Demo 2 | 3 | Bluetooth Low Energy is a special version of Bluetooth that allows wireless communication with devices using very little power, which makes it suitable for mobile phones as well as battery powered devices such as beacons, heart rate monitors and other sensors. It has been available in Android since 4.3 (Jelly Bean). 4 | 5 | This project demonstrates how to communicate with a device using Bluetooth LE. It is set up as a working app project that you can import into Android Studio or compile using Gradle from the command line. It covers searching for nearby devices, sending data and receiving updates from the remote device. 6 | 7 | ### Running the project 8 | 9 | Some UUIDs of services and characteristics are hard-coded in the project. If you know the UUIDs of the device you're testing with you should [alter those values](https://github.com/Pixplicity/android-bluetooth-demo/blob/master/app/src/main/java/com/pixplicity/bluetoothdemo/MainActivity.java#L45) before running the project. 10 | 11 | ### In this project 12 | 13 | This app covers the following topics: 14 | 15 | - Checking if Bluetooth is enabled and requesting the user to enabled it if needed 16 | - Starting a search for Bluetooth devices 17 | - Connecting to a device of interest 18 | - Sending a byte to the remote device 19 | - Receiving updates whenever a value on the remote device changes. 20 | 21 | Note that we use the Jelly Bean (4.3) APIs for communication, even though those are deprecated in Lollipop (5.0), because the new APIs are only available from 5.0 and up. 22 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.pixplicity.bluetoothdemo" 9 | minSdkVersion 18 10 | targetSdkVersion 22 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | compile 'com.android.support:appcompat-v7:22.2.1' 25 | } 26 | -------------------------------------------------------------------------------- /app/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/mathijs/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/pixplicity/bluetoothdemo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.pixplicity.bluetoothdemo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | 11 | public ApplicationTest() { 12 | super(Application.class); 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/pixplicity/bluetoothdemo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.pixplicity.bluetoothdemo; 2 | 3 | import android.app.Service; 4 | import android.bluetooth.BluetoothAdapter; 5 | import android.bluetooth.BluetoothAdapter.LeScanCallback; 6 | import android.bluetooth.BluetoothDevice; 7 | import android.bluetooth.BluetoothGatt; 8 | import android.bluetooth.BluetoothGattCallback; 9 | import android.bluetooth.BluetoothGattCharacteristic; 10 | import android.bluetooth.BluetoothGattDescriptor; 11 | import android.bluetooth.BluetoothGattService; 12 | import android.bluetooth.BluetoothManager; 13 | import android.bluetooth.BluetoothProfile; 14 | import android.content.Context; 15 | import android.content.Intent; 16 | import android.os.Bundle; 17 | import android.support.annotation.NonNull; 18 | import android.support.v7.app.AppCompatActivity; 19 | import android.text.TextUtils; 20 | import android.util.Log; 21 | import android.view.View; 22 | import android.view.View.OnClickListener; 23 | import android.widget.Button; 24 | import android.widget.EditText; 25 | import android.widget.ProgressBar; 26 | import android.widget.TextView; 27 | import android.widget.Toast; 28 | 29 | import java.util.UUID; 30 | 31 | /** 32 | * Main screen of the application. This is where all the magic happens! 33 | * Usually, to keep your code clean you'd want to move all the logic concerning bluetooth to a 34 | * separate controller, a {@link Service service}, a utility class or a combination of those. 35 | */ 36 | public class MainActivity extends AppCompatActivity implements LeScanCallback { 37 | 38 | /** 39 | * The GATT standard defines this UUID as the identifier of the update notification descriptor, 40 | * i.e. the descriptor of a characteristic that defines if you will receive updates on the 41 | * value of the characteristic. 42 | */ 43 | private static final UUID CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 44 | // TODO Change this value to the UUID of the service you want to communicate with 45 | private static final UUID SERVICE_UUID = UUID.fromString("e0a9b597-68f7-4f45-91b1-8de008987048"); 46 | // TODO change this value to the UUID of the characteristic you want to write to 47 | private static final UUID CHARACTERISTIC_A_WRITE = UUID.fromString("186616c7-2993-4ef5-b2a9-04fae246cbb6"); 48 | // TODO change this value to the UUID of the characteristic you want to receive updates from 49 | private static final UUID CHARACTERISTIC_B_READ = UUID.fromString("f14b30fd-d8ad-4ed4-aac0-6d27465b9601"); 50 | 51 | /** 52 | * Request code used when starting the bluetooth settings Activity 53 | */ 54 | private static final int REQUEST_ENABLE_BT = RESULT_FIRST_USER; 55 | 56 | private static final String TAG = MainActivity.class.getSimpleName(); 57 | 58 | // UI elements 59 | private Button mBtScan, mBtWrite; 60 | private EditText mEtUUID; 61 | private View mScanResults; 62 | private TextView mTvScanResults, mTvStatus, mTvCharacteristic; 63 | private ProgressBar mProgress; 64 | 65 | // Bluetooth objects 66 | private BluetoothAdapter mAdapter; 67 | private BluetoothGatt mGatt; 68 | private BluetoothGattCharacteristic mWriteCharacteristic; 69 | 70 | private boolean mIsScanning = false; 71 | private boolean mSearchIsUUID; 72 | private byte mWriteValue = 0; 73 | 74 | @Override 75 | protected void onCreate(Bundle savedInstanceState) { 76 | super.onCreate(savedInstanceState); 77 | setContentView(R.layout.activity_main); 78 | 79 | // Initialize views 80 | mBtScan = (Button) findViewById(R.id.bt_scan); 81 | mBtWrite = (Button) findViewById(R.id.bt_write); 82 | mEtUUID = (EditText) findViewById(R.id.et_uuid); 83 | mProgress = (ProgressBar) findViewById(R.id.busy); 84 | mScanResults = findViewById(R.id.scan_results); 85 | mTvScanResults = (TextView) findViewById(R.id.tv_scan_results); 86 | mTvStatus = (TextView) findViewById(R.id.tv_status); 87 | mTvCharacteristic = (TextView) findViewById(R.id.tv_characteristic); 88 | 89 | // Button listeners 90 | mBtScan.setOnClickListener(new OnClickListener() { 91 | @Override 92 | public void onClick(@NonNull View view) { 93 | startScan(); 94 | } 95 | }); 96 | findViewById(R.id.bt_clear).setOnClickListener(new OnClickListener() { 97 | @Override 98 | public void onClick(@NonNull View view) { 99 | mEtUUID.setText(""); 100 | } 101 | }); 102 | mBtWrite.setText(getString(R.string.bt_write, mWriteValue)); 103 | mBtWrite.setOnClickListener(new OnClickListener() { 104 | @Override 105 | public void onClick(@NonNull View v) { 106 | write(); 107 | } 108 | }); 109 | 110 | // Initialize the adapter 111 | BluetoothManager btManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 112 | mAdapter = btManager.getAdapter(); 113 | if (!isBluetoothEnabled()) { 114 | // If bluetooth is not enabled, we ask the user to do so 115 | Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 116 | startActivityForResult(enableIntent, REQUEST_ENABLE_BT); 117 | // If the permission android.permission.BLUETOOTH_ADMIN is included in the manifest, 118 | // then we could also enable bluetooth without requiring a user action by doing: 119 | // mAdapter.enable(); 120 | // ...But that is not nice towards the user. 121 | } 122 | } 123 | 124 | /** 125 | * Updates the status TextView with the given string resource. 126 | * Can be called from a background thread. 127 | * 128 | * @param strId The resource id of the String to display. 129 | */ 130 | private void showStatus(final int strId) { 131 | runOnUiThread(new Runnable() { 132 | @Override 133 | public void run() { 134 | mTvStatus.setText(strId); 135 | } 136 | }); 137 | } 138 | 139 | @Override 140 | protected void onResume() { 141 | super.onResume(); 142 | // Enable the scan button if bluetooth is enabled and if 143 | // it is not already scanning 144 | mBtScan.setEnabled(isBluetoothEnabled() && !mIsScanning); 145 | } 146 | 147 | @Override 148 | protected void onPause() { 149 | super.onPause(); 150 | if (mIsScanning && mAdapter != null) { 151 | // Stop scanning! We're done! 152 | mAdapter.stopLeScan(this); 153 | scanStopped(); 154 | } 155 | if (mGatt != null) { 156 | // Clean up! This is important, as not closing the GATT connection can 157 | // cause problems when we want to reconnect later on. 158 | // Depending on the Android version there's also a very limited number of 159 | // simultaneous connections available (system wide), so we should free up connections 160 | // as soon as we can. 161 | mGatt.disconnect(); 162 | mGatt.close(); 163 | mGatt = null; 164 | } 165 | } 166 | 167 | /** 168 | * Checks if bluetooth is enabled in the system settings 169 | * 170 | * @return {@code true} if bluetooth is available and enabled, {@code false} otherwise 171 | */ 172 | public boolean isBluetoothEnabled() { 173 | return mAdapter != null && mAdapter.isEnabled(); 174 | } 175 | 176 | /** 177 | * Start scanning for nearby devices. If a UUID is entered in the EditText, it will scan 178 | * for that specific service. If a MAC address or nothing is entered it will scan for all 179 | * devices. 180 | * 181 | * @see #onLeScan(BluetoothDevice, int, byte[]) 182 | */ 183 | private void startScan() { 184 | mIsScanning = true; 185 | mProgress.setVisibility(View.VISIBLE); 186 | mBtScan.setEnabled(false); 187 | // If the input contains a colon, let's assume it is a MAC-address 188 | if (TextUtils.isEmpty(mEtUUID.getText()) || mEtUUID.getText().toString().contains(":")) { 189 | // Start a regular scan for all devices 190 | mSearchIsUUID = false; 191 | mAdapter.startLeScan(this); 192 | } else { 193 | String uuidStr = mEtUUID.getText().toString(); 194 | try { 195 | mSearchIsUUID = true; 196 | UUID uuid = UUID.fromString(uuidStr); 197 | // Start a scan for a service with a specific UUID 198 | mAdapter.startLeScan(new UUID[]{uuid}, this); 199 | } catch (IllegalArgumentException e) { 200 | // Abort scanning 201 | e.printStackTrace(); 202 | Toast.makeText(this, R.string.toast_invalid_uuid, Toast.LENGTH_LONG).show(); 203 | scanStopped(); 204 | } 205 | } 206 | } 207 | 208 | /** 209 | * Closes the GATT connection (if any) and updates the UI to show 210 | * the 'disconnected' state. Can be called from background threads. 211 | */ 212 | private void cleanUp() { 213 | showStatus(R.string.status_disconnected); 214 | runOnUiThread(new Runnable() { 215 | @Override 216 | public void run() { 217 | scanStopped(); 218 | mTvCharacteristic.setVisibility(View.GONE); 219 | } 220 | }); 221 | 222 | if (mGatt != null) { 223 | mGatt.disconnect(); 224 | mGatt.close(); 225 | mGatt = null; 226 | } 227 | } 228 | 229 | /** 230 | * Enables the UI elements to start a new scan. 231 | */ 232 | private void scanStopped() { 233 | mIsScanning = false; 234 | mProgress.setVisibility(View.INVISIBLE); 235 | mBtScan.setEnabled(true); 236 | } 237 | 238 | /** 239 | * Writes a value (alternating 0 and 1) to the characteristics 240 | */ 241 | private void write() { 242 | if (mWriteCharacteristic == null) { 243 | Log.e(TAG, "There's no characteristic to write to"); 244 | } 245 | 246 | // Disable until write has finished to prevent sending faster than the connection can handle 247 | mBtWrite.setEnabled(false); 248 | 249 | if ((mWriteCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) { 250 | Log.i(TAG, "Writing data to bluetooth device..."); 251 | mWriteCharacteristic.setValue(new byte[]{mWriteValue}); 252 | mGatt.writeCharacteristic(mWriteCharacteristic); 253 | } else { 254 | Log.w(TAG, "Characteristic " + CHARACTERISTIC_A_WRITE + " not writable"); 255 | } 256 | } 257 | 258 | /** 259 | * Called for every device that is found during the scan 260 | * 261 | * @param device The device that is found 262 | * @param rssi The received signal strength indication 263 | * @param scanRecord Extra data concerning the scanned device 264 | */ 265 | @Override 266 | public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { 267 | mScanResults.setVisibility(View.VISIBLE); 268 | mTvScanResults.append(getString(R.string.found_device, device.getAddress())); 269 | String search = mEtUUID.getText().toString(); 270 | 271 | if (mSearchIsUUID) { 272 | // When scanning for a service uuid, we can safely assume that the found devices 273 | // have the service we're 274 | connectDevice(device); 275 | } else if (search.contains(":") && device.getAddress().equals(search)) { 276 | // Does the device address match our search term? 277 | connectDevice(device); 278 | } 279 | } 280 | 281 | /** 282 | * Starts connecting to a device. The device is obtained a scan. 283 | * 284 | * @param device The device to connect with 285 | */ 286 | private void connectDevice(final BluetoothDevice device) { 287 | mTvStatus.setVisibility(View.VISIBLE); 288 | mTvStatus.setText(R.string.status_connecting); 289 | 290 | // We've found the device we want, so stop scanning. 291 | // This is important, because scanning is the most battery intensive part of the process. 292 | mAdapter.stopLeScan(this); 293 | scanStopped(); 294 | 295 | // Connect to the device 296 | mGatt = device.connectGatt(this, false, new BluetoothGattCallback() { 297 | @Override 298 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 299 | if (status == BluetoothGatt.GATT_SUCCESS) { 300 | switch (newState) { 301 | case BluetoothProfile.STATE_CONNECTED: 302 | showStatus(R.string.status_discovering); 303 | // Start discovering services. 304 | // Once done, the onServicesDiscovered callback will be called. 305 | if (!gatt.discoverServices()) { 306 | // If it fails, clean up 307 | cleanUp(); 308 | } 309 | break; 310 | case BluetoothProfile.STATE_DISCONNECTED: 311 | // The connection was closed, update the interface: 312 | showStatus(R.string.status_disconnected); 313 | break; 314 | } 315 | } else { 316 | // Connection failed, clean up 317 | cleanUp(); 318 | } 319 | } 320 | 321 | @Override 322 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { 323 | // Find the service we want to use 324 | BluetoothGattService service = gatt.getService(SERVICE_UUID); 325 | 326 | // Find the characteristic that we want to write to 327 | mWriteCharacteristic = service.getCharacteristic(CHARACTERISTIC_A_WRITE); 328 | 329 | // Find the characteristic that we want to read from 330 | BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_B_READ); 331 | 332 | // Enable notifications of that characteristic 333 | if (!gatt.setCharacteristicNotification(characteristic, true)) { 334 | Log.w(TAG, "Unable to get notifications for characteristic " + CHARACTERISTIC_B_READ); 335 | return; 336 | } 337 | // Enable notifications even further by enabling it in the characteristic's descriptors 338 | BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CHARACTERISTIC_UPDATE_NOTIFICATION_DESCRIPTOR_UUID); 339 | descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 340 | if (!gatt.writeDescriptor(descriptor)) { 341 | Log.w(TAG, "Unable to write to descriptor of characteristic " + characteristic.getUuid()); 342 | cleanUp(); 343 | } else { 344 | showStatus(R.string.status_connected); 345 | runOnUiThread(new Runnable() { 346 | @Override 347 | public void run() { 348 | findViewById(R.id.read_write).setVisibility(View.VISIBLE); 349 | } 350 | }); 351 | } 352 | } 353 | 354 | /** 355 | * Once notifications are enabled, this method is called every time the value of the characteristic changes. 356 | * 357 | * @param gatt The GATT connection 358 | * @param characteristic The characteristic that has changed. 359 | */ 360 | @Override 361 | public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { 362 | Log.i(TAG, "Characteristic changed: " + characteristic.getUuid() + " = " + characteristic.getValue()[0]); 363 | // Updating the interface needs to be executed on the UI thread 364 | runOnUiThread(new Runnable() { 365 | @Override 366 | public void run() { 367 | // The value can be of a variety of types. In this example we check for a 368 | // single byte; your actual device might give a String or integer instead 369 | byte value = characteristic.getValue()[0]; 370 | mTvCharacteristic.setVisibility(View.VISIBLE); 371 | mTvCharacteristic.setText(String.valueOf(value)); 372 | } 373 | }); 374 | } 375 | 376 | @Override 377 | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 378 | if (status != BluetoothGatt.GATT_SUCCESS) { 379 | Log.w(TAG, "Unable to write to characteristic " + characteristic.getUuid()); 380 | cleanUp(); 381 | } else { 382 | // Update interface 383 | runOnUiThread(new Runnable() { 384 | @Override 385 | public void run() { 386 | mBtWrite.setEnabled(true); 387 | mWriteValue = (byte) ((mWriteValue + 1) % 2); 388 | mBtWrite.setText(getString(R.string.bt_write, mWriteValue)); 389 | } 390 | }); 391 | } 392 | } 393 | }); 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pixplicity/android-bluetooth-demo/2a72d980c142bb9327e8761fac5ed61fa1837d84/app/src/main/res/drawable-hdpi/ic_clear.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pixplicity/android-bluetooth-demo/2a72d980c142bb9327e8761fac5ed61fa1837d84/app/src/main/res/drawable-mdpi/ic_clear.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pixplicity/android-bluetooth-demo/2a72d980c142bb9327e8761fac5ed61fa1837d84/app/src/main/res/drawable-xhdpi/ic_clear.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pixplicity/android-bluetooth-demo/2a72d980c142bb9327e8761fac5ed61fa1837d84/app/src/main/res/drawable-xxhdpi/ic_clear.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pixplicity/android-bluetooth-demo/2a72d980c142bb9327e8761fac5ed61fa1837d84/app/src/main/res/drawable-xxxhdpi/ic_clear.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 22 | 23 | 28 | 29 | 36 | 37 | 45 | 46 | 47 | 51 | 52 |