1 package com.bluekitchen.sppclient; 2 3 import java.io.UnsupportedEncodingException; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import android.annotation.SuppressLint; 8 import android.app.Activity; 9 import android.os.Bundle; 10 import android.util.Log; 11 import android.view.Menu; 12 import android.widget.TextView; 13 14 import com.bluekitchen.btstack.RFCOMMDataPacket; 15 import com.bluekitchen.btstack.BD_ADDR; 16 import com.bluekitchen.btstack.BTstack; 17 import com.bluekitchen.btstack.Packet; 18 import com.bluekitchen.btstack.PacketHandler; 19 import com.bluekitchen.btstack.Util; 20 import com.bluekitchen.btstack.event.BTstackEventState; 21 import com.bluekitchen.btstack.event.HCIEventCommandComplete; 22 import com.bluekitchen.btstack.event.HCIEventDisconnectionComplete; 23 import com.bluekitchen.btstack.event.HCIEventHardwareError; 24 import com.bluekitchen.btstack.event.HCIEventInquiryComplete; 25 import com.bluekitchen.btstack.event.HCIEventInquiryResultWithRssi; 26 import com.bluekitchen.btstack.event.HCIEventRemoteNameRequestComplete; 27 import com.bluekitchen.btstack.event.RFCOMMEventOpenChannelComplete; 28 import com.bluekitchen.btstack.event.SDPQueryComplete; 29 import com.bluekitchen.btstack.event.SDPQueryRFCOMMService; 30 31 32 public class MainActivity extends Activity implements PacketHandler { 33 34 // minimal Android text UI 35 36 private static final String BTSTACK_TAG = "BTstack"; 37 38 private TextView tv; 39 private String onScreenMessage = ""; 40 41 @Override 42 protected void onCreate(Bundle savedInstanceState) { 43 super.onCreate(savedInstanceState); 44 setContentView(R.layout.activity_main); 45 46 tv = new TextView(this); 47 setContentView(tv); 48 49 test(); 50 } 51 52 void addMessage(final String message){ 53 onScreenMessage = onScreenMessage + "\n" + message; 54 Log.d(BTSTACK_TAG, message); 55 runOnUiThread(new Runnable(){ 56 public void run(){ 57 tv.setText(onScreenMessage); 58 } 59 }); 60 } 61 62 void addTempMessage(final String message){ 63 Log.d(BTSTACK_TAG, message); 64 runOnUiThread(new Runnable(){ 65 public void run(){ 66 tv.setText(onScreenMessage +"\n" + message); 67 } 68 }); 69 } 70 71 void clearMessages(){ 72 onScreenMessage = ""; 73 runOnUiThread(new Runnable(){ 74 public void run(){ 75 tv.setText(onScreenMessage); 76 } 77 }); 78 } 79 80 81 @Override 82 public boolean onCreateOptionsMenu(Menu menu) { 83 // Inflate the menu; this adds items to the action bar if it is present. 84 getMenuInflater().inflate(R.menu.main, menu); 85 return true; 86 } 87 88 89 // helper class to store inquiry results 90 private static class RemoteDevice{ 91 private enum NAME_STATE { 92 REMOTE_NAME_REQUEST, REMOTE_NAME_INQUIRED, REMOTE_NAME_FETCHED 93 }; 94 95 private BD_ADDR bdAddr; 96 private int pageScanRepetitionMode; 97 private int clockOffset; 98 private String name; 99 private NAME_STATE state; 100 101 public RemoteDevice(BD_ADDR bdAddr, int pageScanRepetitionMode, int clockOffset) { 102 this.bdAddr = bdAddr; 103 this.state = NAME_STATE.REMOTE_NAME_REQUEST; 104 } 105 106 public boolean nameRequest() { 107 return this.state == NAME_STATE.REMOTE_NAME_REQUEST; 108 } 109 110 public void inquireName(BTstack btstack) { 111 this.state = NAME_STATE.REMOTE_NAME_INQUIRED; 112 btstack.HCIRemoteNameRequest(bdAddr, pageScanRepetitionMode, 0, clockOffset); 113 } 114 115 public BD_ADDR getBDAddress() { 116 return this.bdAddr; 117 } 118 119 private String getName() { 120 return name; 121 } 122 123 private void setName(String name) { 124 this.state = NAME_STATE.REMOTE_NAME_FETCHED; 125 this.name = name; 126 } 127 } 128 129 // constants 130 131 private static final int HCI_INQUIRY_LAP = 0x9E8B33; // 0x9E8B33: General/Unlimited Inquiry Access Code (GIAC) 132 private static final int INQUIRY_INTERVAL = 5; 133 private static final int SPP_UUID = 0x1002; 134 135 private enum STATE { 136 w4_btstack_working, w4_write_inquiry_mode, w4_scan_result, w4_remote_name, w4_sdp_query_result, w4_connected, active 137 }; 138 139 private static final String REMOTE_DEVICE_NAME_PREFIX = "BTstack SPP"; 140 private static final String RFCOMM_SERVICE_PREFIX = "SPP"; 141 142 // state 143 144 private BTstack btstack; 145 private STATE state; 146 private int testHandle; 147 148 private int rfcommChannelID = 0; 149 private int mtu = 0; 150 private BD_ADDR remoteBDAddr; 151 152 List<SDPQueryRFCOMMService> services = new ArrayList<SDPQueryRFCOMMService>(10); 153 List<RemoteDevice> devices = new ArrayList<RemoteDevice>(10); 154 155 private int counter; 156 157 158 private boolean hasMoreRemoteNameRequests() { 159 for (RemoteDevice device:devices){ 160 if (device.nameRequest()){ 161 return true; 162 } 163 } 164 return false; 165 } 166 167 private void doNextRemoteNameRequest() { 168 for (RemoteDevice device:devices){ 169 if (device.nameRequest()){ 170 addMessage("Get remote name of " + device.getBDAddress()); 171 device.inquireName(btstack); 172 return; 173 } 174 } 175 } 176 177 private void restartInquiry(){ 178 clearMessages(); 179 addMessage("Restart Inquiry"); 180 state = STATE.w4_scan_result; 181 services.clear(); 182 devices.clear(); 183 counter = 0; 184 btstack.HCIInquiry(HCI_INQUIRY_LAP, INQUIRY_INTERVAL, 0); 185 } 186 187 @SuppressLint("DefaultLocale") 188 public void handlePacket(Packet packet){ 189 if (packet instanceof HCIEventHardwareError){ 190 clearMessages(); 191 addMessage("Received HCIEventHardwareError, \nhandle power cycle of the Bluetooth \nchip of the device."); 192 } 193 194 if (packet instanceof HCIEventDisconnectionComplete){ 195 HCIEventDisconnectionComplete event = (HCIEventDisconnectionComplete) packet; 196 testHandle = event.getConnectionHandle(); 197 addMessage(String.format("Received disconnect, status %d, handle %x", event.getStatus(), testHandle)); 198 restartInquiry(); 199 return; 200 } 201 202 switch (state){ 203 case w4_btstack_working: 204 if (packet instanceof BTstackEventState){ 205 BTstackEventState event = (BTstackEventState) packet; 206 if (event.getState() == 2) { 207 addMessage("BTstack working. Set write inquiry mode."); 208 state = STATE.w4_write_inquiry_mode; 209 btstack.HCIWriteInquiryMode(1); // with RSSI 210 } 211 } 212 break; 213 214 case w4_write_inquiry_mode: 215 if (packet instanceof HCIEventCommandComplete){ 216 state = STATE.w4_scan_result; 217 btstack.HCIInquiry(HCI_INQUIRY_LAP, INQUIRY_INTERVAL, 0); 218 } 219 break; 220 221 case w4_scan_result: 222 if (packet instanceof HCIEventInquiryResultWithRssi){ 223 HCIEventInquiryResultWithRssi result = (HCIEventInquiryResultWithRssi) packet; 224 devices.add(new RemoteDevice(result.getBdAddr(), result.getPageScanRepetitionMode(), result.getClockOffset())); 225 addMessage("Found device: " + result.getBdAddr()); 226 } 227 228 if (packet instanceof HCIEventInquiryComplete){ 229 state = STATE.w4_remote_name; 230 if (hasMoreRemoteNameRequests()){ 231 doNextRemoteNameRequest(); 232 break; 233 } 234 } 235 break; 236 237 case w4_remote_name: 238 if (packet instanceof HCIEventRemoteNameRequestComplete){ 239 HCIEventRemoteNameRequestComplete result = (HCIEventRemoteNameRequestComplete) packet; 240 if (result.getStatus() == 0){ 241 // store name on success 242 setNameForDeviceWithBDAddr(result.getRemoteName(), result.getBdAddr()); 243 } 244 245 if (hasMoreRemoteNameRequests()){ 246 doNextRemoteNameRequest(); 247 break; 248 } 249 250 // discovery done, connect to device with remote name prefix 251 252 RemoteDevice remoteDevice = null; 253 for (RemoteDevice device : devices){ 254 if (device.getName() != null && device.getName().startsWith(REMOTE_DEVICE_NAME_PREFIX)){ 255 remoteDevice = device; 256 } 257 } 258 259 // try first one otherwise 260 if (remoteDevice == null && devices.size() > 0){ 261 remoteDevice = devices.get(0); 262 } 263 264 // no device, restart inquiry 265 if (remoteDevice == null){ 266 restartInquiry(); 267 break; 268 } 269 270 // start SDP query for RFCOMMM services 271 remoteBDAddr = remoteDevice.getBDAddress(); 272 if (remoteDevice.getName() == null){ 273 addMessage("Start SDP Query of " + remoteDevice.getBDAddress()); 274 } else { 275 addMessage("Start SDP Query of " + remoteDevice.getName()); 276 } 277 state = STATE.w4_sdp_query_result; 278 byte[] serviceSearchPattern = Util.serviceSearchPatternForUUID16(SPP_UUID); 279 btstack.SDPClientQueryRFCOMMServices(remoteBDAddr, serviceSearchPattern); 280 break; 281 } 282 break; 283 284 case w4_sdp_query_result: 285 if (packet instanceof SDPQueryRFCOMMService){ 286 SDPQueryRFCOMMService service = (SDPQueryRFCOMMService) packet; 287 services.add(service); 288 addMessage(String.format("Found \"%s\", channel %d", service.getName(), service.getRFCOMMChannel())); 289 } 290 if (packet instanceof SDPQueryComplete){ 291 // find service with "SPP" prefix 292 SDPQueryRFCOMMService selectedService = null; 293 for (SDPQueryRFCOMMService service : services){ 294 if (service.getName().startsWith(RFCOMM_SERVICE_PREFIX)){ 295 selectedService = service; 296 break; 297 } 298 } 299 // restart demo, if no service with prefix found 300 if (selectedService == null){ 301 restartInquiry(); 302 break; 303 } 304 305 // connect 306 state = STATE.w4_connected; 307 clearMessages(); 308 addMessage("SPP Test Application / Part 2"); 309 addMessage("Connect to channel nr " + selectedService.getRFCOMMChannel()); 310 btstack.RFCOMMCreateChannel(remoteBDAddr, selectedService.getRFCOMMChannel()); 311 } 312 313 break; 314 315 case w4_connected: 316 if (packet instanceof RFCOMMEventOpenChannelComplete){ 317 RFCOMMEventOpenChannelComplete e = (RFCOMMEventOpenChannelComplete) packet; 318 if (e.getStatus() != 0) { 319 addMessage("RFCOMM channel open failed, status " + e.getStatus()); 320 } else { 321 state = STATE.active; 322 rfcommChannelID = e.getRFCOMMCid(); 323 mtu = e.getMaxFrameSize(); 324 addMessage(String.format("RFCOMM channel open succeeded. \nNew RFCOMM Channel ID %d,\n max frame size %d", rfcommChannelID, mtu)); 325 326 counter = 0; 327 new Thread(new Runnable(){ 328 @Override 329 public void run() { 330 try { 331 while(state == STATE.active){ 332 Thread.sleep(1000); 333 byte[] data; 334 try { 335 data = String.format("BTstack SPP Counter %d\n", counter).getBytes("utf8"); 336 if (counter < Integer.MAX_VALUE){ 337 counter++; 338 } else { 339 counter = 0; 340 } 341 btstack.RFCOMMSendData(rfcommChannelID, data); 342 } catch (UnsupportedEncodingException e) { 343 // TODO Auto-generated catch block 344 e.printStackTrace(); 345 } 346 } 347 } catch (InterruptedException e) {} 348 } 349 }).start(); 350 } 351 } 352 break; 353 354 case active: 355 if (packet instanceof RFCOMMDataPacket){ 356 addTempMessage("Received RFCOMM data packet: \n" + packet.toString()); 357 } 358 break; 359 360 default: 361 break; 362 } 363 } 364 365 private RemoteDevice deviceForBDAddr(BD_ADDR bdAddr){ 366 for (RemoteDevice device:devices){ 367 if (device.getBDAddress().equals(bdAddr) ) 368 return device; 369 } 370 return null; 371 } 372 373 private void setNameForDeviceWithBDAddr(String remoteName, BD_ADDR bdAddr) { 374 RemoteDevice device = deviceForBDAddr(bdAddr); 375 if (device != null){ 376 addMessage("Found " + remoteName); 377 device.setName(remoteName); 378 } 379 } 380 381 void test(){ 382 counter = 0; 383 addMessage("SPP Test Application"); 384 385 btstack = new BTstack(); 386 btstack.registerPacketHandler(this); 387 388 boolean ok = btstack.connect(); 389 if (!ok) { 390 addMessage("Failed to connect to BTstack Server"); 391 return; 392 } 393 394 addMessage("BTstackSetPowerMode(1)"); 395 396 state = STATE.w4_btstack_working; 397 btstack.BTstackSetPowerMode(1); 398 } 399 } 400 401