├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── id │ │ └── aashari │ │ └── code │ │ └── miband2 │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── id │ │ │ └── aashari │ │ │ └── code │ │ │ └── miband2 │ │ │ ├── Activities │ │ │ ├── DeviceScanActivity.java │ │ │ └── MainActivity.java │ │ │ └── Helpers │ │ │ └── CustomBluetoothProfile.java │ └── res │ │ ├── layout │ │ ├── actionbar_indeterminate_progress.xml │ │ ├── activity_device_scan.xml │ │ ├── activity_main.xml │ │ ├── activity_two_drecyclerview.xml │ │ └── listitem_device.xml │ │ ├── menu │ │ └── main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── id │ └── aashari │ └── code │ └── miband2 │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andi Muqsith Ashari 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mi-band-2 2 | This is a very raw communication between Xiaomi Band 2 and Android application using Bluetooth, every communication is not encrypted by Xiaomi so we can sniff the data requested/received from Xiaomi Band device, you can find the UUID of Service, Characteristic and Descriptor on the Internet, here is a very simple UUID to read Heart Rate, Alarm, and Battery Info that I used in this application: 3 | 4 | **Basic Service:** 5 | 6 | UUID of Service: 0000fee0-0000-1000-8000-00805f9b34fb 7 | 8 | Battery Info Characteristic: 00000006-0000-3512-2118-0009af100700 9 | 10 | **Alert Service** 11 | 12 | UUID of Service: 00001802-0000-1000-8000-00805f9b34fb 13 | 14 | New Alert Characteristic: 00002a06-0000-1000-8000-00805f9b34fb 15 | 16 | **Heart Rate Service** 17 | 18 | UUID of Service: 0000180d-0000-1000-8000-00805f9b34fb 19 | 20 | Measurement Characteristic: 00002a37-0000-1000-8000-00805f9b34fb 21 | 22 | Control Characteristic: 00002a39-0000-1000-8000-00805f9b34fb 23 | 24 | Descriptor: 00002902-0000-1000-8000-00805f9b34fb 25 | 26 | There's so many things you can explore about how MI Band device connect and communicate with your Android device using Bluetooth connection, you can also connect and pairing to device programmatically, but in this simple project, I didn't include pairing code so you must pair your Mi band device first using My Fit application then connect it programmatically (just clone this project to see the example). 27 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "id.aashari.code.miband2" 8 | minSdkVersion 21 9 | targetSdkVersion 19 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 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 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.3.1' 28 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 29 | testCompile 'junit:junit:4.12' 30 | } 31 | -------------------------------------------------------------------------------- /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 /home/aashari/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/id/aashari/code/miband2/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package id.aashari.code.miband2; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("id.aashari.code.miband2", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/java/id/aashari/code/miband2/Activities/DeviceScanActivity.java: -------------------------------------------------------------------------------- 1 | package id.aashari.code.miband2.Activities; 2 | 3 | import android.app.Activity; 4 | import android.app.ListActivity; 5 | import android.bluetooth.BluetoothAdapter; 6 | import android.bluetooth.BluetoothDevice; 7 | import android.bluetooth.BluetoothManager; 8 | import android.content.Context; 9 | import android.content.Intent; 10 | import android.content.pm.PackageManager; 11 | import android.os.Bundle; 12 | import android.os.Handler; 13 | import android.support.v7.app.AppCompatActivity; 14 | import android.view.LayoutInflater; 15 | import android.view.Menu; 16 | import android.view.MenuInflater; 17 | import android.view.MenuItem; 18 | import android.view.View; 19 | import android.view.ViewGroup; 20 | import android.widget.AdapterView; 21 | import android.widget.BaseAdapter; 22 | import android.widget.ListView; 23 | import android.widget.TextView; 24 | import android.widget.Toast; 25 | 26 | 27 | import java.util.ArrayList; 28 | 29 | import id.aashari.code.miband2.R; 30 | 31 | /** 32 | * Activity for scanning and displaying available Bluetooth LE devices. 33 | */ 34 | public class DeviceScanActivity extends AppCompatActivity { 35 | private LeDeviceListAdapter mLeDeviceListAdapter; 36 | private BluetoothAdapter mBluetoothAdapter; 37 | private boolean mScanning; 38 | private Handler mHandler; 39 | 40 | private static final int REQUEST_ENABLE_BT = 1; 41 | // Stops scanning after 10 seconds. 42 | private static final long SCAN_PERIOD = 10000; 43 | ListView listView; 44 | 45 | @Override 46 | public void onCreate(Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | setContentView(R.layout.activity_device_scan); 49 | 50 | listView = (ListView) findViewById(R.id.listView); 51 | 52 | 53 | mHandler = new Handler(); 54 | 55 | // Use this check to determine whether BLE is supported on the device. Then you can 56 | // selectively disable BLE-related features. 57 | if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { 58 | Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); 59 | finish(); 60 | } 61 | 62 | // Initializes a Bluetooth adapter. For API level 18 and above, get a reference to 63 | // BluetoothAdapter through BluetoothManager. 64 | final BluetoothManager bluetoothManager = 65 | (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 66 | mBluetoothAdapter = bluetoothManager.getAdapter(); 67 | 68 | // Checks if Bluetooth is supported on the device. 69 | if (mBluetoothAdapter == null) { 70 | Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show(); 71 | finish(); 72 | return; 73 | } 74 | } 75 | 76 | @Override 77 | public boolean onCreateOptionsMenu(Menu menu) { 78 | MenuInflater inflater = getMenuInflater(); 79 | inflater.inflate(R.menu.main, menu); 80 | if (!mScanning) { 81 | menu.findItem(R.id.menu_stop).setVisible(false); 82 | menu.findItem(R.id.menu_scan).setVisible(true); 83 | menu.findItem(R.id.menu_refresh).setActionView(null); 84 | } else { 85 | menu.findItem(R.id.menu_stop).setVisible(true); 86 | menu.findItem(R.id.menu_scan).setVisible(false); 87 | menu.findItem(R.id.menu_refresh).setActionView( 88 | R.layout.actionbar_indeterminate_progress); 89 | } 90 | return true; 91 | } 92 | 93 | @Override 94 | public boolean onOptionsItemSelected(MenuItem item) { 95 | switch (item.getItemId()) { 96 | case R.id.menu_scan: 97 | mLeDeviceListAdapter.clear(); 98 | scanLeDevice(true); 99 | break; 100 | case R.id.menu_stop: 101 | scanLeDevice(false); 102 | break; 103 | } 104 | return true; 105 | } 106 | 107 | @Override 108 | protected void onResume() { 109 | super.onResume(); 110 | 111 | // Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled, 112 | // fire an intent to display a dialog asking the user to grant permission to enable it. 113 | if (!mBluetoothAdapter.isEnabled()) { 114 | if (!mBluetoothAdapter.isEnabled()) { 115 | Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); 116 | startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); 117 | } 118 | } 119 | 120 | // Initializes list view adapter. 121 | mLeDeviceListAdapter = new LeDeviceListAdapter(); 122 | listView.setAdapter(mLeDeviceListAdapter); 123 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 124 | @Override 125 | public void onItemClick(AdapterView adapterView, View view, int i, long l) { 126 | final BluetoothDevice device = mLeDeviceListAdapter.getDevice(i); 127 | if (device == null) return; 128 | final Intent intent = new Intent(DeviceScanActivity.this, MainActivity.class); 129 | intent.putExtra(MainActivity.EXTRAS_DEVICE_NAME, device.getName()); 130 | intent.putExtra(MainActivity.EXTRAS_DEVICE_ADDRESS, device.getAddress()); 131 | intent.putExtra("device", device); 132 | if (mScanning) { 133 | mBluetoothAdapter.stopLeScan(mLeScanCallback); 134 | mScanning = false; 135 | } 136 | startActivity(intent); 137 | } 138 | }); 139 | scanLeDevice(true); 140 | } 141 | 142 | @Override 143 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 144 | // User chose not to enable Bluetooth. 145 | if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) { 146 | finish(); 147 | return; 148 | } 149 | super.onActivityResult(requestCode, resultCode, data); 150 | } 151 | 152 | @Override 153 | protected void onPause() { 154 | super.onPause(); 155 | scanLeDevice(false); 156 | mLeDeviceListAdapter.clear(); 157 | } 158 | 159 | 160 | 161 | private void scanLeDevice(final boolean enable) { 162 | if (enable) { 163 | // Stops scanning after a pre-defined scan period. 164 | mHandler.postDelayed(new Runnable() { 165 | @Override 166 | public void run() { 167 | mScanning = false; 168 | mBluetoothAdapter.stopLeScan(mLeScanCallback); 169 | invalidateOptionsMenu(); 170 | } 171 | }, SCAN_PERIOD); 172 | 173 | mScanning = true; 174 | mBluetoothAdapter.startLeScan(mLeScanCallback); 175 | } else { 176 | mScanning = false; 177 | mBluetoothAdapter.stopLeScan(mLeScanCallback); 178 | } 179 | invalidateOptionsMenu(); 180 | } 181 | 182 | // Adapter for holding devices found through scanning. 183 | private class LeDeviceListAdapter extends BaseAdapter { 184 | private ArrayList mLeDevices; 185 | private LayoutInflater mInflator; 186 | 187 | public LeDeviceListAdapter() { 188 | super(); 189 | mLeDevices = new ArrayList(); 190 | mInflator = DeviceScanActivity.this.getLayoutInflater(); 191 | } 192 | 193 | public void addDevice(BluetoothDevice device) { 194 | if (!mLeDevices.contains(device)) { 195 | mLeDevices.add(device); 196 | } 197 | } 198 | 199 | public BluetoothDevice getDevice(int position) { 200 | return mLeDevices.get(position); 201 | } 202 | 203 | public void clear() { 204 | mLeDevices.clear(); 205 | } 206 | 207 | @Override 208 | public int getCount() { 209 | return mLeDevices.size(); 210 | } 211 | 212 | @Override 213 | public Object getItem(int i) { 214 | return mLeDevices.get(i); 215 | } 216 | 217 | @Override 218 | public long getItemId(int i) { 219 | return i; 220 | } 221 | 222 | @Override 223 | public View getView(int i, View view, ViewGroup viewGroup) { 224 | ViewHolder viewHolder; 225 | // General ListView optimization code. 226 | if (view == null) { 227 | view = mInflator.inflate(R.layout.listitem_device, null); 228 | viewHolder = new ViewHolder(); 229 | viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address); 230 | viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name); 231 | view.setTag(viewHolder); 232 | } else { 233 | viewHolder = (ViewHolder) view.getTag(); 234 | } 235 | 236 | BluetoothDevice device = mLeDevices.get(i); 237 | final String deviceName = device.getName(); 238 | if (deviceName != null && deviceName.length() > 0) 239 | viewHolder.deviceName.setText(deviceName); 240 | else 241 | viewHolder.deviceName.setText(R.string.unknown_device); 242 | viewHolder.deviceAddress.setText(device.getAddress()); 243 | 244 | return view; 245 | } 246 | } 247 | 248 | // Device scan callback. 249 | private BluetoothAdapter.LeScanCallback mLeScanCallback = 250 | new BluetoothAdapter.LeScanCallback() { 251 | 252 | @Override 253 | public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { 254 | runOnUiThread(new Runnable() { 255 | @Override 256 | public void run() { 257 | mLeDeviceListAdapter.addDevice(device); 258 | mLeDeviceListAdapter.notifyDataSetChanged(); 259 | } 260 | }); 261 | } 262 | }; 263 | 264 | static class ViewHolder { 265 | TextView deviceName; 266 | TextView deviceAddress; 267 | } 268 | } -------------------------------------------------------------------------------- /app/src/main/java/id/aashari/code/miband2/Activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package id.aashari.code.miband2.Activities; 2 | 3 | import android.app.Activity; 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.BluetoothGattDescriptor; 10 | import android.bluetooth.BluetoothProfile; 11 | import android.content.BroadcastReceiver; 12 | import android.content.Context; 13 | import android.content.Intent; 14 | import android.content.IntentFilter; 15 | import android.os.Bundle; 16 | import android.util.Log; 17 | import android.view.View; 18 | import android.widget.Button; 19 | import android.widget.EditText; 20 | import android.widget.LinearLayout; 21 | import android.widget.TextView; 22 | import android.widget.Toast; 23 | 24 | import java.util.Arrays; 25 | import java.util.Set; 26 | 27 | import id.aashari.code.miband2.Helpers.CustomBluetoothProfile; 28 | import id.aashari.code.miband2.R; 29 | 30 | public class MainActivity extends Activity { 31 | 32 | 33 | Boolean isListeningHeartRate = false; 34 | 35 | public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME"; 36 | public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS"; 37 | BluetoothAdapter bluetoothAdapter; 38 | BluetoothGatt bluetoothGatt; 39 | BluetoothDevice bluetoothDevice; 40 | 41 | Button btnStartConnecting, btnGetBatteryInfo, btnGetHeartRate, btnWalkingInfo, btnStartVibrate, btnStopVibrate; 42 | EditText txtPhysicalAddress; 43 | TextView txtState, txtByte; 44 | private String mDeviceName; 45 | private String mDeviceAddress; 46 | 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | setContentView(R.layout.activity_main); 52 | 53 | initializeObjects(); 54 | initilaizeComponents(); 55 | initializeEvents(); 56 | 57 | getBoundedDevice(); 58 | 59 | } 60 | 61 | void getBoundedDevice() { 62 | 63 | mDeviceName = getIntent().getStringExtra(EXTRAS_DEVICE_NAME); 64 | mDeviceAddress = getIntent().getStringExtra(EXTRAS_DEVICE_ADDRESS); 65 | txtPhysicalAddress.setText(mDeviceAddress); 66 | 67 | Set boundedDevice = bluetoothAdapter.getBondedDevices(); 68 | for (BluetoothDevice bd : boundedDevice) { 69 | if (bd.getName().contains("MI Band 2")) { 70 | txtPhysicalAddress.setText(bd.getAddress()); 71 | } 72 | } 73 | } 74 | 75 | void initializeObjects() { 76 | bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 77 | } 78 | 79 | void initilaizeComponents() { 80 | btnStartConnecting = (Button) findViewById(R.id.btnStartConnecting); 81 | btnGetBatteryInfo = (Button) findViewById(R.id.btnGetBatteryInfo); 82 | btnWalkingInfo = (Button) findViewById(R.id.btnWalkingInfo); 83 | btnStartVibrate = (Button) findViewById(R.id.btnStartVibrate); 84 | btnStopVibrate = (Button) findViewById(R.id.btnStopVibrate); 85 | btnGetHeartRate = (Button) findViewById(R.id.btnGetHeartRate); 86 | txtPhysicalAddress = (EditText) findViewById(R.id.txtPhysicalAddress); 87 | txtState = (TextView) findViewById(R.id.txtState); 88 | txtByte = (TextView) findViewById(R.id.txtByte); 89 | } 90 | 91 | void initializeEvents() { 92 | btnStartConnecting.setOnClickListener(new View.OnClickListener() { 93 | @Override 94 | public void onClick(View v) { 95 | startConnecting(); 96 | } 97 | }); 98 | btnGetBatteryInfo.setOnClickListener(new View.OnClickListener() { 99 | @Override 100 | public void onClick(View v) { 101 | getBatteryStatus(); 102 | } 103 | }); 104 | btnStartVibrate.setOnClickListener(new View.OnClickListener() { 105 | @Override 106 | public void onClick(View v) { 107 | startVibrate(); 108 | } 109 | }); 110 | btnStopVibrate.setOnClickListener(new View.OnClickListener() { 111 | @Override 112 | public void onClick(View v) { 113 | stopVibrate(); 114 | } 115 | }); 116 | btnGetHeartRate.setOnClickListener(new View.OnClickListener() { 117 | @Override 118 | public void onClick(View v) { 119 | startScanHeartRate(); 120 | } 121 | }); 122 | } 123 | 124 | void startConnecting() { 125 | 126 | String address = txtPhysicalAddress.getText().toString(); 127 | bluetoothDevice = bluetoothAdapter.getRemoteDevice(address); 128 | 129 | Log.v("test", "Connecting to " + address); 130 | Log.v("test", "Device name " + bluetoothDevice.getName()); 131 | 132 | bluetoothGatt = bluetoothDevice.connectGatt(this, true, bluetoothGattCallback); 133 | 134 | } 135 | 136 | void stateConnected() { 137 | bluetoothGatt.discoverServices(); 138 | txtState.setText("Connected"); 139 | } 140 | 141 | void stateDisconnected() { 142 | bluetoothGatt.disconnect(); 143 | txtState.setText("Disconnected"); 144 | } 145 | 146 | void startScanHeartRate() { 147 | txtByte.setText("..."); 148 | BluetoothGattCharacteristic bchar = bluetoothGatt.getService(CustomBluetoothProfile.HeartRate.service) 149 | .getCharacteristic(CustomBluetoothProfile.HeartRate.controlCharacteristic); 150 | bchar.setValue(new byte[]{21, 2, 1}); 151 | bluetoothGatt.writeCharacteristic(bchar); 152 | } 153 | 154 | void listenHeartRate() { 155 | BluetoothGattCharacteristic bchar = bluetoothGatt.getService(CustomBluetoothProfile.HeartRate.service) 156 | .getCharacteristic(CustomBluetoothProfile.HeartRate.measurementCharacteristic); 157 | bluetoothGatt.setCharacteristicNotification(bchar, true); 158 | BluetoothGattDescriptor descriptor = bchar.getDescriptor(CustomBluetoothProfile.HeartRate.descriptor); 159 | descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 160 | bluetoothGatt.writeDescriptor(descriptor); 161 | isListeningHeartRate = true; 162 | } 163 | 164 | void getBatteryStatus() { 165 | txtByte.setText("..."); 166 | BluetoothGattCharacteristic bchar = bluetoothGatt.getService(CustomBluetoothProfile.Basic.service) 167 | .getCharacteristic(CustomBluetoothProfile.Basic.batteryCharacteristic); 168 | if (!bluetoothGatt.readCharacteristic(bchar)) { 169 | Toast.makeText(this, "Failed get battery info", Toast.LENGTH_SHORT).show(); 170 | } 171 | 172 | } 173 | 174 | void startVibrate() { 175 | BluetoothGattCharacteristic bchar = bluetoothGatt.getService(CustomBluetoothProfile.AlertNotification.service) 176 | .getCharacteristic(CustomBluetoothProfile.AlertNotification.alertCharacteristic); 177 | bchar.setValue(new byte[]{2}); 178 | if (!bluetoothGatt.writeCharacteristic(bchar)) { 179 | Toast.makeText(this, "Failed start vibrate", Toast.LENGTH_SHORT).show(); 180 | } 181 | } 182 | 183 | void stopVibrate() { 184 | BluetoothGattCharacteristic bchar = bluetoothGatt.getService(CustomBluetoothProfile.AlertNotification.service) 185 | .getCharacteristic(CustomBluetoothProfile.AlertNotification.alertCharacteristic); 186 | bchar.setValue(new byte[]{0}); 187 | if (!bluetoothGatt.writeCharacteristic(bchar)) { 188 | Toast.makeText(this, "Failed stop vibrate", Toast.LENGTH_SHORT).show(); 189 | } 190 | } 191 | 192 | final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { 193 | 194 | @Override 195 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 196 | super.onConnectionStateChange(gatt, status, newState); 197 | Log.v("test", "onConnectionStateChange"); 198 | 199 | if (newState == BluetoothProfile.STATE_CONNECTED) { 200 | stateConnected(); 201 | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 202 | stateDisconnected(); 203 | } 204 | 205 | } 206 | 207 | @Override 208 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { 209 | super.onServicesDiscovered(gatt, status); 210 | Log.v("test", "onServicesDiscovered"); 211 | listenHeartRate(); 212 | } 213 | 214 | @Override 215 | public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 216 | super.onCharacteristicRead(gatt, characteristic, status); 217 | Log.v("test", "onCharacteristicRead"); 218 | byte[] data = characteristic.getValue(); 219 | txtByte.setText(Arrays.toString(data)); 220 | } 221 | 222 | @Override 223 | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 224 | super.onCharacteristicWrite(gatt, characteristic, status); 225 | Log.v("test", "onCharacteristicWrite"); 226 | } 227 | 228 | @Override 229 | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 230 | super.onCharacteristicChanged(gatt, characteristic); 231 | Log.v("test", "onCharacteristicChanged"); 232 | byte[] data = characteristic.getValue(); 233 | txtByte.setText(Arrays.toString(data)); 234 | } 235 | 236 | @Override 237 | public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 238 | super.onDescriptorRead(gatt, descriptor, status); 239 | Log.v("test", "onDescriptorRead"); 240 | } 241 | 242 | @Override 243 | public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 244 | super.onDescriptorWrite(gatt, descriptor, status); 245 | Log.v("test", "onDescriptorWrite"); 246 | } 247 | 248 | @Override 249 | public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { 250 | super.onReliableWriteCompleted(gatt, status); 251 | Log.v("test", "onReliableWriteCompleted"); 252 | } 253 | 254 | @Override 255 | public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { 256 | super.onReadRemoteRssi(gatt, rssi, status); 257 | Log.v("test", "onReadRemoteRssi"); 258 | } 259 | 260 | @Override 261 | public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { 262 | super.onMtuChanged(gatt, mtu, status); 263 | Log.v("test", "onMtuChanged"); 264 | } 265 | 266 | }; 267 | 268 | } 269 | -------------------------------------------------------------------------------- /app/src/main/java/id/aashari/code/miband2/Helpers/CustomBluetoothProfile.java: -------------------------------------------------------------------------------- 1 | package id.aashari.code.miband2.Helpers; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * Created by aashari on 21/05/17. 7 | * This profile generated based on http://jellygom.com/2016/09/30/Mi-Band-UUID.html 8 | */ 9 | 10 | public class CustomBluetoothProfile { 11 | 12 | public static class Basic { 13 | public static UUID service = UUID.fromString("0000fee0-0000-1000-8000-00805f9b34fb"); 14 | public static UUID batteryCharacteristic = UUID.fromString("00000006-0000-3512-2118-0009af100700"); 15 | } 16 | 17 | public static class AlertNotification { 18 | public static UUID service = UUID.fromString("00001802-0000-1000-8000-00805f9b34fb"); 19 | public static UUID alertCharacteristic = UUID.fromString("00002a06-0000-1000-8000-00805f9b34fb"); 20 | } 21 | 22 | public static class HeartRate { 23 | public static UUID service = UUID.fromString("0000180d-0000-1000-8000-00805f9b34fb"); 24 | public static UUID measurementCharacteristic = UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb"); 25 | public static UUID descriptor = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 26 | public static UUID controlCharacteristic = UUID.fromString("00002a39-0000-1000-8000-00805f9b34fb"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/actionbar_indeterminate_progress.xml: -------------------------------------------------------------------------------- 1 | 16 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_device_scan.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 21 | 22 |