From 31847573c9e472ce2cdcd682bd777e8be7754b2b Mon Sep 17 00:00:00 2001 From: paulscott56 Date: Thu, 25 Feb 2016 08:27:55 +0200 Subject: [PATCH 1/4] Initial commit with some changes Fixed BLE detection so app doesn't crash Fixed up some styles Added a few utility functions --- app/build.gradle | 28 +- app/src/main/AndroidManifest.xml | 34 +- .../java/BLEManagement/BLEDeviceInfo.java | 231 +++++++ .../java/BLEManagement/BLEDeviceInfoList.java | 85 +++ .../banc => }/BLEManagement/BLEEvent.java | 4 +- .../banc => }/BLEManagement/BLEManager.java | 608 +++++++++--------- .../main/java/BLEManagement/BLEScanner.java | 61 ++ .../BLEManagement/BluetoothHelper.java | 21 +- .../main/java/BLEManagement/Utilities.java | 30 + .../com/banc/BLEManagement/BLEDeviceInfo.java | 221 ------- .../banc/BLEManagement/BLEDeviceInfoList.java | 94 --- .../com/banc/BLEManagement/BLEScanner.java | 58 -- .../banc/sparkle_gateway/AbstractService.java | 111 ---- .../com/banc/sparkle_gateway/BLEDisplay.java | 93 --- .../banc/sparkle_gateway/BLESelection.java | 303 --------- .../com/banc/sparkle_gateway/BLEService.java | 174 ----- .../banc/sparkle_gateway/DeviceAdapter.java | 85 --- .../banc/sparkle_gateway/ParticleSocket.java | 130 ---- .../main/java/gateway/AbstractService.java | 110 ++++ app/src/main/java/gateway/BLEDisplay.java | 94 +++ app/src/main/java/gateway/BLESelection.java | 306 +++++++++ app/src/main/java/gateway/BLEService.java | 161 +++++ app/src/main/java/gateway/DeviceAdapter.java | 92 +++ .../HexAsciiHelper.java | 17 +- .../MainActivity.java | 9 +- .../MyBroadcastReceiver.java | 8 +- .../ParticleLoginDisplay.java | 24 +- app/src/main/java/gateway/ParticleSocket.java | 128 ++++ .../ServiceManager.java | 281 ++++---- app/src/main/res/values/styles.xml | 26 +- build.gradle | 19 +- .../gradle_project_sync_data.bin | Bin 583 -> 582 bytes local.properties | 13 +- 33 files changed, 1839 insertions(+), 1820 deletions(-) create mode 100644 app/src/main/java/BLEManagement/BLEDeviceInfo.java create mode 100644 app/src/main/java/BLEManagement/BLEDeviceInfoList.java rename app/src/main/java/{com/banc => }/BLEManagement/BLEEvent.java (80%) rename app/src/main/java/{com/banc => }/BLEManagement/BLEManager.java (74%) create mode 100644 app/src/main/java/BLEManagement/BLEScanner.java rename app/src/main/java/{com/banc => }/BLEManagement/BluetoothHelper.java (78%) create mode 100644 app/src/main/java/BLEManagement/Utilities.java delete mode 100644 app/src/main/java/com/banc/BLEManagement/BLEDeviceInfo.java delete mode 100644 app/src/main/java/com/banc/BLEManagement/BLEDeviceInfoList.java delete mode 100644 app/src/main/java/com/banc/BLEManagement/BLEScanner.java delete mode 100644 app/src/main/java/com/banc/sparkle_gateway/AbstractService.java delete mode 100644 app/src/main/java/com/banc/sparkle_gateway/BLEDisplay.java delete mode 100644 app/src/main/java/com/banc/sparkle_gateway/BLESelection.java delete mode 100644 app/src/main/java/com/banc/sparkle_gateway/BLEService.java delete mode 100644 app/src/main/java/com/banc/sparkle_gateway/DeviceAdapter.java delete mode 100644 app/src/main/java/com/banc/sparkle_gateway/ParticleSocket.java create mode 100644 app/src/main/java/gateway/AbstractService.java create mode 100644 app/src/main/java/gateway/BLEDisplay.java create mode 100644 app/src/main/java/gateway/BLESelection.java create mode 100644 app/src/main/java/gateway/BLEService.java create mode 100644 app/src/main/java/gateway/DeviceAdapter.java rename app/src/main/java/{com/banc/sparkle_gateway => gateway}/HexAsciiHelper.java (82%) rename app/src/main/java/{com/banc/sparkle_gateway => gateway}/MainActivity.java (91%) rename app/src/main/java/{com/banc/sparkle_gateway => gateway}/MyBroadcastReceiver.java (58%) rename app/src/main/java/{com/banc/sparkle_gateway => gateway}/ParticleLoginDisplay.java (85%) create mode 100644 app/src/main/java/gateway/ParticleSocket.java rename app/src/main/java/{com/banc/sparkle_gateway => gateway}/ServiceManager.java (64%) diff --git a/app/build.gradle b/app/build.gradle index 4161fc5..11f853f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,28 +1,42 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 19 - buildToolsVersion "23.0.0 rc2" + compileSdkVersion 23 + buildToolsVersion "23.0.2" defaultConfig { applicationId "com.banc.sparkle_gateway" minSdkVersion 18 - targetSdkVersion 18 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" } - buildTypes { release { minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } packagingOptions { + exclude 'META-INF/DEPENDENCIES.txt' exclude 'META-INF/LICENSE.txt' exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/LICENSE' + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/notice.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/dependencies.txt' + exclude 'META-INF/LGPL2.1' } } dependencies { - compile 'com.android.support:support-v4:19.1.0' + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:design:23.1.1' compile 'io.particle:cloudsdk:0.3.4' -} + compile 'org.apache.httpcomponents:httpcore:4.0-alpha6' + +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 98c9450..aa8c725 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,13 +1,7 @@ + package="com.banc.gateway"> - - @@ -16,33 +10,25 @@ android:allowBackup="true" android:icon="@drawable/bluz_icon" android:label="@string/app_name" - android:theme="@style/AppTheme" > + android:supportsRtl="true" + android:theme="@style/AppTheme"> + android:theme="@style/AppTheme.NoActionBar"> - - - - - + android:name="gateway.ParticleLoginDisplay" + android:label="Log in to the Particle Cloud" /> - - + + - + \ No newline at end of file diff --git a/app/src/main/java/BLEManagement/BLEDeviceInfo.java b/app/src/main/java/BLEManagement/BLEDeviceInfo.java new file mode 100644 index 0000000..8fce58b --- /dev/null +++ b/app/src/main/java/BLEManagement/BLEDeviceInfo.java @@ -0,0 +1,231 @@ +package BLEManagement; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattService; +import android.util.Log; + +import java.io.IOException; + +import gateway.BLEService; +import gateway.ParticleSocket; +import io.particle.android.sdk.cloud.ParticleCloud; +import io.particle.android.sdk.cloud.ParticleCloudException; +import io.particle.android.sdk.cloud.ParticleCloudSDK; +import io.particle.android.sdk.utils.Async; + +public class BLEDeviceInfo implements Runnable { + final public static int STATE_BLUETOOTH_OFF = 1; + final public static int STATE_DISCONNECTED = 2; + final public static int STATE_CONNECTING = 3; + final public static int STATE_CONNECTED = 4; + private final ParticleSocket particleSocket; + private final BluetoothDevice bleDevice; + public int State; + public BluetoothGatt mBluetoothGatt; + public BluetoothGattService mBluetoothGattService; + //internal flags so the manager can keep track of successful transmissions + boolean transmissionDone = false; + private int rssi; + private String cloudName; + private String cloudId; + private Boolean isClaimed; + //buffers for data + private byte[] dlBuffer; + private byte[] ulBuffer; + private int ulBufferLength; + //internal var to keep track of last service + private byte lastService = 0x00; + private boolean Running = false; + + public BLEDeviceInfo(BluetoothDevice device, final int rssi) { + this.bleDevice = device; + this.rssi = rssi; + cloudName = ""; + cloudId = ""; + isClaimed = false; + particleSocket = new ParticleSocket(); + ulBuffer = new byte[512]; + ulBufferLength = 0; + } + + public void UpdateRSSI(int newRSSI) { + rssi = newRSSI; + } + + public int GetRSSI() { + return rssi; + } + + public String GetName() { + return bleDevice.getName(); + } + + public String GetMAC() { + return bleDevice.getAddress(); + } + + public String GetCloudID() { + return cloudId; + } + + public String GetCloudName() { + return cloudName; + } + + public boolean IsClaimed() { + return isClaimed; + } + + public void SetClaimed() { + isClaimed = true; + } + + @Override + public void run() { + Running = true; + // TODO Auto-generated method stub + while (Running) { + + if (particleSocket.Connected()) { + try { + int bytesAvailable = particleSocket.Available(); +// Log.d("BLEService", "Connected: " + Boolean.toString(sparkSocket.Connected()) + " Bytes Available: " + bytesAvailable); + if (bytesAvailable > 0) { + dlBuffer = particleSocket.Read(); +// Log.d("BLEService", "We read some bytes from SPark Cloud: " + dlBuffer.length); + byte[] header = {0x01, 0x00}; + BLEManager.send(this, dlBuffer, header); + } + try { + Thread.sleep(10); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } + + private void stop() { + Running = false; + } + + public void disconnect() { + stop(); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + particleSocket.Disconnect(); + } catch (Exception ex) { + ex.printStackTrace(); + } + mBluetoothGatt.close(); + + mBluetoothGatt = null; + mBluetoothGattService = null; + } + + //Called when the SparkLE transmits data to us + public synchronized void processData(byte[] data) { + Log.d("BLEService", "Got data of length " + data.length); + Log.d("BLEService", "Last Service " + Byte.toString(lastService)); + + StringBuilder sb = new StringBuilder(); + for (byte b : data) { + sb.append(String.format("%02X ", b)); + } +// Log.d("BLEService", "Processing Data: " + sb.toString()); + + if (data[0] == 0x03 && data[1] == 0x04) { + if (lastService == 0x01) { + try { + if (particleSocket.Connected()) { + Log.d("BLEService", "Got a full buffer, attempting to send it up"); + byte[] tmpBuffer = new byte[ulBufferLength]; + System.arraycopy(ulBuffer, 0, tmpBuffer, 0, ulBufferLength); +// Log.d("BLEService", "About to write this many bytes " + tmpBuffer.length); + particleSocket.Write(tmpBuffer); +// Log.d("BLEManager", "Received this many bytes from BLE: " + tmpBuffer.length); + } else { + try { + Log.d("SparkLEService", "Not Connected. Attempting to connect to cloud"); + particleSocket.Connect(); + Thread rThread = new Thread(this); + //rThread.setUncaughtExceptionHandler(new ExceptionHandler()); + rThread.start(); + ulBuffer = new byte[512]; + ulBufferLength = 0; + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } else if (lastService == 0x02) { + final StringBuilder id = new StringBuilder(); + for (int i = 0; i < ulBufferLength; i++) { + id.append(String.format("%02x", ulBuffer[i])); + } + Log.d("Device ID", id.toString()); + this.cloudId = id.toString(); + Async.executeAsync(ParticleCloudSDK.getCloud(), new Async.ApiWork() { + @Override + public Object callApi(ParticleCloud sparkCloud) throws ParticleCloudException, IOException { + return ParticleCloudSDK.getCloud().getDevice(id.toString()).getName(); + } + + @Override + public void onSuccess(Object value) { + Log.d("Device Name Retreieved", (String) value); + cloudName = (String) value; + isClaimed = true; + BLEService.DeviceInfoChanged(); + } + + @Override + public void onFailure(ParticleCloudException e) { + Log.d("Device is not claimed", ""); + isClaimed = false; + BLEService.DeviceInfoChanged(); + } + }); + + } + ulBuffer = new byte[512]; + ulBufferLength = 0; + } else { + + if (ulBufferLength == 0) { + lastService = data[0]; + int headerBytes = 1; + if (lastService == 0x01) { + headerBytes = 2; + } + //this is the first packaet in the stream. check the header + System.arraycopy(data, headerBytes, ulBuffer, ulBufferLength, data.length - headerBytes); + ulBufferLength += (data.length - headerBytes); + } else { + System.arraycopy(data, 0, ulBuffer, ulBufferLength, data.length); + ulBufferLength += (data.length); + } + } + //bleManager.send(new byte[]{0x55}); + + //TO DO: Handle BLE messages + } + + public void PollParticleId() { + byte[] requestIdBuffer = {0x02, 0x00}; + BLEManager.send(this, requestIdBuffer, null); + } +} diff --git a/app/src/main/java/BLEManagement/BLEDeviceInfoList.java b/app/src/main/java/BLEManagement/BLEDeviceInfoList.java new file mode 100644 index 0000000..c05a5d8 --- /dev/null +++ b/app/src/main/java/BLEManagement/BLEDeviceInfoList.java @@ -0,0 +1,85 @@ +package BLEManagement; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +public class BLEDeviceInfoList { + + private final List bleDevices; + + public BLEDeviceInfoList() { + bleDevices = new ArrayList(); + } + + public void InsertOrUpdate(BLEDeviceInfo bleDevice) { + Iterator deviceIter = bleDevices.iterator(); + boolean found = false; + while (deviceIter.hasNext()) { + BLEDeviceInfo existingBleDevice = deviceIter.next(); + if (existingBleDevice.GetMAC().equalsIgnoreCase(bleDevice.GetMAC())) { + found = true; + existingBleDevice.UpdateRSSI(bleDevice.GetRSSI()); + break; + } + } + if (!found) { + bleDevices.add(bleDevice); + } + } + + public BLEDeviceInfo GetBLEDeviceInfo(int index) { + return bleDevices.get(index); + } + + public BLEDeviceInfo GetBLEDeviceInfoByAddress(String address) { + for (BLEDeviceInfo b : bleDevices) { + if (b.GetMAC() == address) { + return b; + } + } + return null; + } + + public int GetCount() { + return bleDevices.size(); + } + + public BLEDeviceInfoList MergeAndTakeUnique(BLEDeviceInfoList newDevices) { + BLEDeviceInfoList mergedOverDevice = new BLEDeviceInfoList(); + + Iterator deviceIter = newDevices.bleDevices.iterator(); + boolean found = false; + while (deviceIter.hasNext()) { + BLEDeviceInfo newBleDevice = deviceIter.next(); + if (!this.ContainsByAddress(newBleDevice)) { + mergedOverDevice.bleDevices.add(newBleDevice); + } + } + + return mergedOverDevice; + } + + private boolean ContainsByAddress(BLEDeviceInfo bleDevice) { + Iterator deviceIter = bleDevices.iterator(); + boolean found = false; + while (deviceIter.hasNext()) { + BLEDeviceInfo existingBleDevice = deviceIter.next(); + if (existingBleDevice.GetMAC().equalsIgnoreCase(bleDevice.GetMAC())) { + found = true; + break; + } + } + return found; + } + + public void clearNonConnected() { + Iterator iter = bleDevices.listIterator(); + while (iter.hasNext()) { + if (iter.next().State != BLEDeviceInfo.STATE_CONNECTED) { + iter.remove(); + } + } + } +} diff --git a/app/src/main/java/com/banc/BLEManagement/BLEEvent.java b/app/src/main/java/BLEManagement/BLEEvent.java similarity index 80% rename from app/src/main/java/com/banc/BLEManagement/BLEEvent.java rename to app/src/main/java/BLEManagement/BLEEvent.java index 70283d0..096227c 100644 --- a/app/src/main/java/com/banc/BLEManagement/BLEEvent.java +++ b/app/src/main/java/BLEManagement/BLEEvent.java @@ -1,7 +1,7 @@ -package com.banc.BLEManagement; +package BLEManagement; public class BLEEvent { - final public static int EVENT_DEVICE_DISCOVERED = 1; + final public static int EVENT_DEVICE_DISCOVERED = 1; final public static int EVENT_DEVICE_LOST = 2; final public static int EVENT_UPDATE = 3; final public static int EVENT_RX_DATA = 4; diff --git a/app/src/main/java/com/banc/BLEManagement/BLEManager.java b/app/src/main/java/BLEManagement/BLEManager.java similarity index 74% rename from app/src/main/java/com/banc/BLEManagement/BLEManager.java rename to app/src/main/java/BLEManagement/BLEManager.java index c7a994b..ea97daa 100644 --- a/app/src/main/java/com/banc/BLEManagement/BLEManager.java +++ b/app/src/main/java/BLEManagement/BLEManager.java @@ -1,11 +1,4 @@ -package com.banc.BLEManagement; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Observer; -import java.util.Observable; -import java.util.UUID; +package BLEManagement; import android.Manifest; import android.bluetooth.BluetoothAdapter; @@ -23,168 +16,34 @@ import android.content.IntentFilter; import android.util.Log; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Observable; +import java.util.Observer; +import java.util.UUID; + public class BLEManager extends Observable implements Observer { - - //flags for keeping track of scanning state - private boolean scanStarted; - private boolean scanning; - - //bluetooth variables - private BluetoothAdapter bluetoothAdapter; - private BluetoothDevice bluetoothDevice; - - private BluetoothManager mBluetoothManager; - private BluetoothAdapter mBluetoothAdapter; - - public final static String ACTION_CONNECTED = + + private final static String ACTION_CONNECTED = "com.banc.ACTION_CONNECTED"; - public final static String ACTION_DISCONNECTED = + private final static String ACTION_DISCONNECTED = "com.banc.ACTION_DISCONNECTED"; - public final static String ACTION_DATA_AVAILABLE = + private final static String ACTION_DATA_AVAILABLE = "com.banc.ACTION_DATA_AVAILABLE"; - public final static String EXTRA_DATA = + private final static String EXTRA_DATA = "com.banc.EXTRA_DATA"; - -// public final static UUID UUID_SERVICE = BluetoothHelper.sixteenBitUuid(0x2220); - public final static UUID UUID_SERVICE = BluetoothHelper.sixteenBitUuid(0x0223); - public final static UUID UUID_RECEIVE = BluetoothHelper.sixteenBitUuid(0x0224); - public final static UUID UUID_SEND = BluetoothHelper.sixteenBitUuid(0x0225); + // public final static UUID UUID_SERVICE = BluetoothHelper.sixteenBitUuid(0x2220); + private final static UUID UUID_SERVICE = BluetoothHelper.sixteenBitUuid(0x0223); + private final static UUID UUID_RECEIVE = BluetoothHelper.sixteenBitUuid(0x0224); + private final static UUID UUID_SEND = BluetoothHelper.sixteenBitUuid(0x0225); //public final static UUID UUID_DISCONNECT = BluetoothHelper.sixteenBitUuid(0x2223); - public final static UUID UUID_CLIENT_CONFIGURATION = BluetoothHelper.sixteenBitUuidOld(0x2902); - private static final String TAG = "SparkLE BLE Manager"; - - public BLEDeviceInfoList bleDevices; - static BLEScanner scanner; - - private Context context; - - public BLEManager(Context context) { - this.context = context; - - bleDevices = new BLEDeviceInfoList(); - - initialize(); - - //set up the bluetooth registers and start scanning - context.registerReceiver(scanModeReceiver, new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)); - context.registerReceiver(bluetoothStateReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); -// context.registerReceiver(sparkleReceiver, getIntentFilter()); - - Log.d("DEBUG", "Bluetooth Registers Setup!"); -// bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); -// updateState(bluetoothAdapter.isEnabled() ? STATE_DISCONNECTED : STATE_BLUETOOTH_OFF); - - scanner = new BLEScanner(); - } - - static public BLEDeviceInfoList GetList() {return scanner.newDevices;} - public BLEDeviceInfo GetBLEDeviceInfoByAddress(String address) - { - return scanner.newDevices.GetBLEDeviceInfoByAddress(address); - } - - public void start() { - /*bluetoothAdapter.startLeScan( - new UUID[]{ this.UUID_SERVICE }, - this); - Log.d("DEBUG", "BLE Scan Started");*/ - scanner.addObserver(this); - Thread scannerThread = new Thread(scanner); - scannerThread.start(); - } - - public void stop() { - /*bluetoothAdapter.stopLeScan(this);*/ - scanner.deleteObserver(this); - scanner.stop(); - } - -// private void upgradeState(int newState) { -// if (newState > state) { -// updateState(newState); -// } -// } -// private void downgradeState(int newState) { -// if (newState < state) { -// updateState(newState); -// } -// } - - private void updateState(BLEDeviceInfo devInfo, int newState) { - devInfo.State = newState; - //TO DO - //Send a BLEEvent with the new state - Log.d(TAG, "Changing state to " + newState); - BLEEvent e = new BLEEvent(); - e.BLEEventType = BLEEvent.EVENT_DEVICE_STATE_CHANGE; - e.State = newState; - e.Contents = scanner.newDevices; - e.DeviceInfo = devInfo; - SendEvent(e); - } - - private final BroadcastReceiver bluetoothStateReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0); - if (state == BluetoothAdapter.STATE_ON) { -// upgradeState(BLEDeviceInfo.STATE_DISCONNECTED); - } else if (state == BluetoothAdapter.STATE_OFF) { -// downgradeState(BLEDeviceInfo.STATE_BLUETOOTH_OFF); - } - } - }; - - private final BroadcastReceiver scanModeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (bluetoothAdapter == null) { - scanning = false; - } else { - scanning = (bluetoothAdapter == null || bluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_NONE); - scanStarted &= scanning; - } - //TO DO - //Send a BLEEvent with the new state -// BLEEvent e = new BLEEvent(); -// e.Type = "State Changed"; -// SendEvent(e); - } - }; - - //TO DO: DELETE - //Old code from before we created the BLEScanner class -// @Override -// public void onLeScan(BluetoothDevice device, final int rssi, final byte[] scanRecord) { -// Log.d("DEBUG", "Got scanrecord " + scanRecord); -// Log.d("DEBUG", "Got results from scan, device " + device.getName()); -// bluetoothDevice = device; -// -// BLEDeviceInfo bleDevice = new BLEDeviceInfo(bluetoothDevice, rssi); -// bleDevices.InsertOrUpdate(bleDevice); -// BLEEvent e = new BLEEvent(); -// e.BLEEventType = BLEEvent.EVENT_UPDATE; -// e.Contents = bleDevices; -// SendEvent(e); -// } - - private void SendEvent(BLEEvent e) - { -// Log.d("DEBUG", "Sending event up"); - setChanged(); - notifyObservers(e); - } - - @Override - public void update(Observable observable, Object data) { - Log.d("DEBUG", "Received event from BLEScanner"); - BLEEvent e = new BLEEvent(); - e.BLEEventType = BLEEvent.EVENT_UPDATE; - e.Contents = (BLEDeviceInfoList)data; - SendEvent(e); - } - - // Implements callback methods for GATT events that the app cares about. For example, + private final static UUID UUID_CLIENT_CONFIGURATION = BluetoothHelper.sixteenBitUuidOld(); + private static final String TAG = "SparkLE BLE Manager"; + private static BLEScanner scanner; + private final BLEDeviceInfoList bleDevices; + private final Context context; + // Implements callback methods for GATT events that the app cares about. For example, // connection change and services discovered. private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override @@ -204,58 +63,55 @@ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { BLEDeviceInfo devInfo = GetBLEDeviceInfoByAddress(gatt.getDevice().getAddress()); - Log.d("BLEManager", "Found status " + status); + Log.d("BLEManager", "Found status " + status); if (status == BluetoothGatt.GATT_SUCCESS) { devInfo.mBluetoothGattService = gatt.getService(UUID_SERVICE); Log.d("BLEManager", "Looking for service " + UUID_SERVICE); - + ArrayList services = (ArrayList) gatt.getServices(); Iterator it = services.iterator(); - while(it.hasNext()) - { - BluetoothGattService obj = it.next(); + while (it.hasNext()) { + BluetoothGattService obj = it.next(); Log.d("BLEManager", "Found GATT service " + obj.getUuid()); } - + if (devInfo.mBluetoothGattService == null) { Log.e(TAG, "Sparkle GATT service not found!"); return; } BluetoothGattCharacteristic receiveCharacteristic = - devInfo. mBluetoothGattService.getCharacteristic(UUID_RECEIVE); - + devInfo.mBluetoothGattService.getCharacteristic(UUID_RECEIVE); + ArrayList characteristics = (ArrayList) devInfo.mBluetoothGattService.getCharacteristics(); Iterator iter = characteristics.iterator(); - while(iter.hasNext()) - { - BluetoothGattCharacteristic obj = iter.next(); + while (iter.hasNext()) { + BluetoothGattCharacteristic obj = iter.next(); Log.d("BLEManager", "Found GATT characteristic " + obj.getUuid()); BluetoothGattDescriptor descr = - obj.getDescriptor(obj.getUuid()); + obj.getDescriptor(obj.getUuid()); if (descr != null) { - Log.d("BLEManager", "GATT characteristic has descriptor" + descr.toString()); + Log.d("BLEManager", "GATT characteristic has descriptor" + descr.toString()); } else { - Log.d("BLEManager", "GATT characteristic has no descriptor"); + Log.d("BLEManager", "GATT characteristic has no descriptor"); } } - - + + if (receiveCharacteristic != null) { - ArrayList descriptors = (ArrayList) receiveCharacteristic.getDescriptors(); + ArrayList descriptors = (ArrayList) receiveCharacteristic.getDescriptors(); Iterator dIter = descriptors.iterator(); - while(dIter.hasNext()) - { - BluetoothGattDescriptor obj = dIter.next(); - Log.d("BLEManager", "Found GATT descriptor " + obj.getUuid()); + while (dIter.hasNext()) { + BluetoothGattDescriptor obj = dIter.next(); + Log.d("BLEManager", "Found GATT descriptor " + obj.getUuid()); } BluetoothGattDescriptor receiveConfigDescriptor = receiveCharacteristic.getDescriptor(UUID_CLIENT_CONFIGURATION); if (receiveConfigDescriptor != null) { - gatt.setCharacteristicNotification(receiveCharacteristic, true); - receiveConfigDescriptor.setValue( - BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - gatt.writeDescriptor(receiveConfigDescriptor); + gatt.setCharacteristicNotification(receiveCharacteristic, true); + receiveConfigDescriptor.setValue( + BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + gatt.writeDescriptor(receiveConfigDescriptor); Log.d("BLEManager", "Descriptr Notified"); } else { Log.e(TAG, "Sparkle receive config descriptor not found!"); @@ -282,12 +138,12 @@ public void onCharacteristicRead(BluetoothGatt gatt, BLEDeviceInfo devInfo = GetBLEDeviceInfoByAddress(gatt.getDevice().getAddress()); // broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic); BLEEvent event = new BLEEvent(); - event.BLEEventType = BLEEvent.EVENT_RX_DATA; + event.BLEEventType = BLEEvent.EVENT_RX_DATA; event.State = devInfo.State; event.DeviceInfo = devInfo; - event.Contents = characteristic.getValue(); - Log.d(TAG, "onCharacteristicRead Data read is of size: " + ((byte[])(event.Contents)).length); - SendEvent(event); + event.Contents = characteristic.getValue(); + Log.d(TAG, "onCharacteristicRead Data read is of size: " + ((byte[]) (event.Contents)).length); + SendEvent(event); } } @@ -302,26 +158,226 @@ public void onCharacteristicChanged(BluetoothGatt gatt, event.State = devInfo.State; event.DeviceInfo = devInfo; event.Contents = characteristic.getValue(); - Log.d(TAG, "onCharacteristicChanged Data read is of size: " + ((byte[])(event.Contents)).length); + Log.d(TAG, "onCharacteristicChanged Data read is of size: " + ((byte[]) (event.Contents)).length); SendEvent(event); } - + @Override - public void onCharacteristicWrite (BluetoothGatt gatt, - BluetoothGattCharacteristic characteristic, - int status) { + public void onCharacteristicWrite(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + int status) { BLEDeviceInfo devInfo = GetBLEDeviceInfoByAddress(gatt.getDevice().getAddress()); // android.util.Log.d("BLEManager ", " Millisecinds on event " + System.currentTimeMillis()); - BluetoothGattCharacteristic writeChar = + BluetoothGattCharacteristic writeChar = devInfo.mBluetoothGattService.getCharacteristic(UUID_SEND); - if (writeChar == characteristic) { + if (writeChar == characteristic) { // android.util.Log.d("BLEManager ", " Millisecinds success " + System.currentTimeMillis()); devInfo.transmissionDone = true; - } - + } + } }; - + //flags for keeping track of scanning state + private boolean scanStarted; + //bluetooth variables + private BluetoothAdapter bluetoothAdapter; + private BluetoothDevice bluetoothDevice; + private BluetoothManager mBluetoothManager; + private BluetoothAdapter mBluetoothAdapter; + + public BLEManager(Context context) { + this.context = context; + + bleDevices = new BLEDeviceInfoList(); + + initialize(); + + //set up the bluetooth registers and start scanning + BroadcastReceiver scanModeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean scanning; + if (bluetoothAdapter == null) { + scanning = false; + } else { + scanning = (bluetoothAdapter == null || bluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_NONE); + scanStarted &= scanning; + } + //TO DO + //Send a BLEEvent with the new state +// BLEEvent e = new BLEEvent(); +// e.Type = "State Changed"; +// SendEvent(e); + } + }; + context.registerReceiver(scanModeReceiver, new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)); + BroadcastReceiver bluetoothStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0); + if (state == BluetoothAdapter.STATE_ON) { +// upgradeState(BLEDeviceInfo.STATE_DISCONNECTED); + } else if (state == BluetoothAdapter.STATE_OFF) { +// downgradeState(BLEDeviceInfo.STATE_BLUETOOTH_OFF); + } + } + }; + context.registerReceiver(bluetoothStateReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); +// context.registerReceiver(sparkleReceiver, getIntentFilter()); + + Log.d("DEBUG", "Bluetooth Registers Setup!"); +// bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); +// updateState(bluetoothAdapter.isEnabled() ? STATE_DISCONNECTED : STATE_BLUETOOTH_OFF); + + scanner = new BLEScanner(); + } + + static public BLEDeviceInfoList GetList() { + return scanner.newDevices; + } + + static public boolean send(BLEDeviceInfo devInfo, byte[] data, byte[] header) { + if (devInfo.mBluetoothGatt == null || devInfo.mBluetoothGattService == null) { + Log.w(TAG, "BluetoothGatt not initialized"); + return false; + } + + BluetoothGattCharacteristic characteristic = + devInfo.mBluetoothGattService.getCharacteristic(UUID_SEND); + + if (characteristic == null) { + Log.w(TAG, "Send characteristic not found"); + return false; + } + + boolean success = false; + int maxChunk = 960; + for (int chunkPointer = 0; chunkPointer < data.length; chunkPointer += maxChunk) { + + int chunkLength = (data.length - chunkPointer > maxChunk ? maxChunk : data.length - chunkPointer); + + if (header != null) { + characteristic.setValue(header); + characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); + devInfo.transmissionDone = false; + +// android.util.Log.d("BLEManager ", " Millisecinds before send " + System.currentTimeMillis()); + devInfo.mBluetoothGatt.writeCharacteristic(characteristic); + while (!devInfo.transmissionDone) { + } + Log.d("BLEManager ", "Sent Header"); + } + + byte[] buffer = new byte[20]; + for (int i = 0; i < chunkLength; i += 20) { + int size = (chunkLength - i > 20 ? 20 : chunkLength - i); + byte[] tmpBuffer = new byte[size]; + int originalIndex = 0; + for (int j = i; j < i + size; j++) { + tmpBuffer[originalIndex] = data[chunkPointer + j]; + originalIndex++; + } +// Log.d("BLEManager", "Sending down this many bytes on BLE: " + tmpBuffer.length); + + StringBuilder sb = new StringBuilder(); + for (byte b : tmpBuffer) { + sb.append(String.format("%02X ", b)); + } +// Log.d("BLEManager", "Sending Down: " + sb.toString()); + + characteristic.setValue(tmpBuffer); + characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); + devInfo.transmissionDone = false; + +// android.util.Log.d("BLEManager ", " Millisecinds before send " + System.currentTimeMillis()); + success = devInfo.mBluetoothGatt.writeCharacteristic(characteristic); + while (!devInfo.transmissionDone) { + } +// Log.d("BLEManager", "Success of send: " + success); + } + byte[] eosBuffer = {0x03, 0x04}; + characteristic.setValue(eosBuffer); + characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); + devInfo.transmissionDone = false; + +// android.util.Log.d("BLEManager ", " Millisecinds before send " + System.currentTimeMillis()); + success = devInfo.mBluetoothGatt.writeCharacteristic(characteristic); + while (!devInfo.transmissionDone) { + } + Log.d("BLEManager ", "Sent EOS"); + Log.d("BLEManager ", Integer.toString(chunkPointer) + " bytes have gone down so far"); + } + return success; + } + + private static IntentFilter getIntentFilter() { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_CONNECTED); + filter.addAction(ACTION_DISCONNECTED); + filter.addAction(ACTION_DATA_AVAILABLE); + return filter; + } + +// private void upgradeState(int newState) { +// if (newState > state) { +// updateState(newState); +// } +// } +// private void downgradeState(int newState) { +// if (newState < state) { +// updateState(newState); +// } +// } + + public BLEDeviceInfo GetBLEDeviceInfoByAddress(String address) { + return scanner.newDevices.GetBLEDeviceInfoByAddress(address); + } + + //TO DO: DELETE + //Old code from before we created the BLEScanner class +// @Override +// public void onLeScan(BluetoothDevice device, final int rssi, final byte[] scanRecord) { +// Log.d("DEBUG", "Got scanrecord " + scanRecord); +// Log.d("DEBUG", "Got results from scan, device " + device.getName()); +// bluetoothDevice = device; +// +// BLEDeviceInfo bleDevice = new BLEDeviceInfo(bluetoothDevice, rssi); +// bleDevices.InsertOrUpdate(bleDevice); +// BLEEvent e = new BLEEvent(); +// e.BLEEventType = BLEEvent.EVENT_UPDATE; +// e.Contents = bleDevices; +// SendEvent(e); +// } + + public void start() { + /*bluetoothAdapter.startLeScan( + new UUID[]{ this.UUID_SERVICE }, + this); + Log.d("DEBUG", "BLE Scan Started");*/ + scanner.addObserver(this); + Thread scannerThread = new Thread(scanner); + scannerThread.start(); + } + + public void stop() { + /*bluetoothAdapter.stopLeScan(this);*/ + scanner.deleteObserver(this); + scanner.stop(); + } + + private void updateState(BLEDeviceInfo devInfo, int newState) { + devInfo.State = newState; + //TO DO + //Send a BLEEvent with the new state + Log.d(TAG, "Changing state to " + newState); + BLEEvent e = new BLEEvent(); + e.BLEEventType = BLEEvent.EVENT_DEVICE_STATE_CHANGE; + e.State = newState; + e.Contents = scanner.newDevices; + e.DeviceInfo = devInfo; + SendEvent(e); + } + // private final BroadcastReceiver sparkleReceiver = new BroadcastReceiver() { // @Override // public void onReceive(Context context, Intent intent) { @@ -339,7 +395,22 @@ public void onCharacteristicWrite (BluetoothGatt gatt, // } // } // }; - + + private void SendEvent(BLEEvent e) { +// Log.d("DEBUG", "Sending event up"); + setChanged(); + notifyObservers(e); + } + + @Override + public void update(Observable observable, Object data) { + Log.d("DEBUG", "Received event from BLEScanner"); + BLEEvent e = new BLEEvent(); + e.BLEEventType = BLEEvent.EVENT_UPDATE; + e.Contents = (BLEDeviceInfoList) data; + SendEvent(e); + } + private void broadcastUpdate(final String action) { final Intent intent = new Intent(action); context.sendBroadcast(intent, Manifest.permission.BLUETOOTH); @@ -350,23 +421,24 @@ private void broadcastUpdate(final String action, if (UUID_RECEIVE.equals(characteristic.getUuid())) { final Intent intent = new Intent(action); // Log.d("BLEManager", "Got back "); - + byte[] data = characteristic.getValue(); - + // Log.d("BLEManager", "Length of data: " + data.length); String str; - try { - str = new String(data, "UTF-8"); + try { + str = new String(data, "UTF-8"); // Log.d("BLEManager", str); - } catch (UnsupportedEncodingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + } catch (UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } intent.putExtra(EXTRA_DATA, characteristic.getValue()); context.sendBroadcast(intent, Manifest.permission.BLUETOOTH); } } -// + + // // public class LocalBinder extends Binder { // GloveService getService() { // return GloveService.this; @@ -394,42 +466,40 @@ private void broadcastUpdate(final String action, // * // * @return Return true if the initialization is successful. // */ - private boolean initialize() { + private void initialize() { // For API level 18 and above, get a reference to BluetoothAdapter through // BluetoothManager. if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { Log.e(TAG, "Unable to initialize BluetoothManager."); - return false; + return; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "Unable to obtain a BluetoothAdapter."); - return false; + return; } - return true; } /** * Connects to the GATT server hosted on the Bluetooth LE device. * * @param address The device address of the destination device. - * * @return Return true if the connection is initiated successfully. The connection result - * is reported asynchronously through the - * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} - * callback. + * is reported asynchronously through the + * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} + * callback. */ - public boolean connect(final String address) { + public void connect(final String address) { BLEDeviceInfo devInfo = GetBLEDeviceInfoByAddress(address); - + if (mBluetoothAdapter == null || address == null) { Log.w(TAG, "BluetoothAdapter not initialized or unspecified address."); - return false; + return; } // Previously connected device. Try to reconnect. @@ -444,7 +514,6 @@ public boolean connect(final String address) { // parameter to false. devInfo.mBluetoothGatt = device.connectGatt(context, false, mGattCallback); Log.d(TAG, "Trying to create a new connection."); - return true; } /** @@ -462,7 +531,7 @@ public void disconnect(String address) { devInfo.mBluetoothGatt.disconnect(); } -// /** + // /** // * After using a given BLE device, the app must call this method to ensure resources are // * released properly. // */ @@ -488,91 +557,6 @@ public void disconnect(String address) { // public boolean isInitialized(String address) { BLEDeviceInfo devInfo = GetBLEDeviceInfoByAddress(address); - if (devInfo.mBluetoothGatt == null || devInfo.mBluetoothGattService == null) { - return false; - } else { - return true; - } - } - - static public boolean send(BLEDeviceInfo devInfo, byte[] data, byte[] header) { - if (devInfo.mBluetoothGatt == null || devInfo.mBluetoothGattService == null) { - Log.w(TAG, "BluetoothGatt not initialized"); - return false; - } - - BluetoothGattCharacteristic characteristic = - devInfo.mBluetoothGattService.getCharacteristic(UUID_SEND); - - if (characteristic == null) { - Log.w(TAG, "Send characteristic not found"); - return false; - } - - boolean success = false; - int maxChunk = 960; - for (int chunkPointer = 0; chunkPointer < data.length; chunkPointer += maxChunk) { - - int chunkLength = (data.length-chunkPointer > maxChunk ? maxChunk : data.length-chunkPointer); - - if (header != null) { - characteristic.setValue(header); - characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); - devInfo.transmissionDone = false; - -// android.util.Log.d("BLEManager ", " Millisecinds before send " + System.currentTimeMillis()); - devInfo.mBluetoothGatt.writeCharacteristic(characteristic); - while (!devInfo.transmissionDone) { - } - android.util.Log.d("BLEManager ", "Sent Header"); - } - - byte[] buffer = new byte[20]; - for (int i = 0; i < chunkLength; i += 20) { - int size = (chunkLength - i > 20 ? 20 : chunkLength - i); - byte[] tmpBuffer = new byte[size]; - int originalIndex = 0; - for (int j = i; j < i + size; j++) { - tmpBuffer[originalIndex] = data[chunkPointer+j]; - originalIndex++; - } -// Log.d("BLEManager", "Sending down this many bytes on BLE: " + tmpBuffer.length); - - StringBuilder sb = new StringBuilder(); - for (byte b : tmpBuffer) { - sb.append(String.format("%02X ", b)); - } -// Log.d("BLEManager", "Sending Down: " + sb.toString()); - - characteristic.setValue(tmpBuffer); - characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); - devInfo.transmissionDone = false; - -// android.util.Log.d("BLEManager ", " Millisecinds before send " + System.currentTimeMillis()); - success = devInfo.mBluetoothGatt.writeCharacteristic(characteristic); - while (!devInfo.transmissionDone) { - } -// Log.d("BLEManager", "Success of send: " + success); - } - byte[] eosBuffer = {0x03, 0x04}; - characteristic.setValue(eosBuffer); - characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE); - devInfo.transmissionDone = false; - -// android.util.Log.d("BLEManager ", " Millisecinds before send " + System.currentTimeMillis()); - success = devInfo.mBluetoothGatt.writeCharacteristic(characteristic); - while (!devInfo.transmissionDone) {} - android.util.Log.d("BLEManager ", "Sent EOS" ); - android.util.Log.d("BLEManager ", Integer.toString(chunkPointer) + " bytes have gone down so far" ); - } - return success; - } - - private static IntentFilter getIntentFilter() { - IntentFilter filter = new IntentFilter(); - filter.addAction(ACTION_CONNECTED); - filter.addAction(ACTION_DISCONNECTED); - filter.addAction(ACTION_DATA_AVAILABLE); - return filter; + return !(devInfo.mBluetoothGatt == null || devInfo.mBluetoothGattService == null); } } diff --git a/app/src/main/java/BLEManagement/BLEScanner.java b/app/src/main/java/BLEManagement/BLEScanner.java new file mode 100644 index 0000000..fcfc776 --- /dev/null +++ b/app/src/main/java/BLEManagement/BLEScanner.java @@ -0,0 +1,61 @@ +package BLEManagement; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.util.Log; + +import java.util.Observable; + +class BLEScanner extends Observable implements Runnable, BluetoothAdapter.LeScanCallback { + + private static final String TAG = "BLESCANNER"; + final BLEDeviceInfoList newDevices; + private boolean runScanner = false; + private Utilities utils; + + public BLEScanner() { + newDevices = new BLEDeviceInfoList(); + } + + @Override + public void run() { + utils = new Utilities(); + runScanner = true; + BLEDeviceInfoList devices = new BLEDeviceInfoList(); + + Log.d("BLEScanner", "Running Scan!"); + newDevices.clearNonConnected(); + + if (utils.checkForBluetooth()) { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + bluetoothAdapter.startLeScan( + //new UUID[]{ BLEManager.UUID_SERVICE }, + this); + Log.d("DEBUG", "BLE Scan Started"); + while (runScanner) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + bluetoothAdapter.stopLeScan(this); + } + } + + + public void stop() { + runScanner = false; + } + + @Override + public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { + Log.d("BLEScanner", "Found device " + device.getName()); + BLEDeviceInfo bleDevice = new BLEDeviceInfo(device, rssi); + newDevices.InsertOrUpdate(bleDevice); + setChanged(); + notifyObservers(newDevices); + } + +} diff --git a/app/src/main/java/com/banc/BLEManagement/BluetoothHelper.java b/app/src/main/java/BLEManagement/BluetoothHelper.java similarity index 78% rename from app/src/main/java/com/banc/BLEManagement/BluetoothHelper.java rename to app/src/main/java/BLEManagement/BluetoothHelper.java index 71b38aa..0270a47 100644 --- a/app/src/main/java/com/banc/BLEManagement/BluetoothHelper.java +++ b/app/src/main/java/BLEManagement/BluetoothHelper.java @@ -1,23 +1,24 @@ -package com.banc.BLEManagement; +package BLEManagement; import android.bluetooth.BluetoothDevice; import java.util.UUID; -import com.banc.sparkle_gateway.HexAsciiHelper; +import gateway.HexAsciiHelper; -public class BluetoothHelper { - public static String shortUuidFormat = "871e%04X-38ff-77b1-ed41-9fb3aa142db2"; - public static String oldUuidFormat = "0000%04X-0000-1000-8000-00805f9b34fb"; + +class BluetoothHelper { + private static final String shortUuidFormat = "871e%04X-38ff-77b1-ed41-9fb3aa142db2"; + private static final String oldUuidFormat = "0000%04X-0000-1000-8000-00805f9b34fb"; public static UUID sixteenBitUuid(long shortUuid) { assert shortUuid >= 0 && shortUuid <= 0xFFFF; return UUID.fromString(String.format(shortUuidFormat, shortUuid & 0xFFFF)); } - - public static UUID sixteenBitUuidOld(long shortUuid) { - assert shortUuid >= 0 && shortUuid <= 0xFFFF; - return UUID.fromString(String.format(oldUuidFormat, shortUuid & 0xFFFF)); + + public static UUID sixteenBitUuidOld() { + assert (long) 0x2902 >= 0 && (long) 0x2902 <= 0xFFFF; + return UUID.fromString(String.format(oldUuidFormat, (long) 0x2902 & 0xFFFF)); } public static String getDeviceInfoText(BluetoothDevice device, int rssi, byte[] scanRecord) { @@ -39,7 +40,7 @@ private static String parseScanRecord(byte[] scanRecord) { switch (scanRecord[i] & 0xFF) { // https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile case 0x0A: // Tx Power - output.append("\n Tx Power: ").append(scanRecord[i+1]); + output.append("\n Tx Power: ").append(scanRecord[i + 1]); break; case 0xFF: output.append("\n Advertisement Data: ") diff --git a/app/src/main/java/BLEManagement/Utilities.java b/app/src/main/java/BLEManagement/Utilities.java new file mode 100644 index 0000000..42dbc97 --- /dev/null +++ b/app/src/main/java/BLEManagement/Utilities.java @@ -0,0 +1,30 @@ +package BLEManagement; + +import android.bluetooth.BluetoothAdapter; +import android.util.Log; + +/** + * Created by pscot on 2/24/2016. + */ +public class Utilities { + + private static final String TAG = "BluzUtils"; + + public Utilities() { + } + + public boolean checkForBluetooth() { + BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + if (mBluetoothAdapter == null) { + Log.e(TAG, "BLE not supported"); + return false; + } else if (!mBluetoothAdapter.isEnabled()) { + Log.e(TAG, "BLE not switched on..."); + return false; + } else { + + return true; + } + } + +} diff --git a/app/src/main/java/com/banc/BLEManagement/BLEDeviceInfo.java b/app/src/main/java/com/banc/BLEManagement/BLEDeviceInfo.java deleted file mode 100644 index 666ac1c..0000000 --- a/app/src/main/java/com/banc/BLEManagement/BLEDeviceInfo.java +++ /dev/null @@ -1,221 +0,0 @@ -package com.banc.BLEManagement; - -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattService; -import android.util.Log; - -import com.banc.sparkle_gateway.BLEService; -import com.banc.sparkle_gateway.ParticleSocket; - -import java.io.IOException; - -import io.particle.android.sdk.cloud.ParticleCloud; -import io.particle.android.sdk.cloud.ParticleCloudException; -import io.particle.android.sdk.cloud.ParticleCloudSDK; -import io.particle.android.sdk.utils.Async; -import io.particle.android.sdk.utils.Toaster; - -public class BLEDeviceInfo implements Runnable { - final public static int STATE_BLUETOOTH_OFF = 1; - final public static int STATE_DISCONNECTED = 2; - final public static int STATE_CONNECTING = 3; - final public static int STATE_CONNECTED = 4; - - public int State; - public ParticleSocket particleSocket; - public BluetoothGatt mBluetoothGatt; - public BluetoothGattService mBluetoothGattService; - - private BluetoothDevice bleDevice; - private int rssi; - private String cloudName; - private String cloudId; - private Boolean isClaimed; - - - public BLEDeviceInfo(BluetoothDevice device, final int rssi) - { - this.bleDevice = device; - this.rssi = rssi; - cloudName = ""; - cloudId = ""; - isClaimed = false; - particleSocket = new ParticleSocket(); - ulBuffer = new byte[512]; - ulBufferLength = 0; - } - - public void UpdateRSSI(int newRSSI) - { - rssi = newRSSI; - } - - public int GetRSSI() { return rssi; } - public String GetName() { return bleDevice.getName(); } - public String GetMAC() { return bleDevice.getAddress(); } - public String GetCloudID() { return cloudId; } - public String GetCloudName() { return cloudName; } - public boolean IsClaimed() {return isClaimed; } - public void SetClaimed(boolean claimed) {isClaimed = claimed; } - - //buffers for data - byte[] dlBuffer, ulBuffer; - int ulBufferLength; - - //internal flags so the manager can keep track of successful transmissions - boolean transmissionDone = false; - - //internal var to keep track of last service - byte lastService = 0x00; - - public boolean Running = false; - @Override - public void run() { - Running = true; - // TODO Auto-generated method stub - while (Running) { - - if (particleSocket.Connected()) { - try { - int bytesAvailable = particleSocket.Available(); -// Log.d("BLEService", "Connected: " + Boolean.toString(sparkSocket.Connected()) + " Bytes Available: " + bytesAvailable); - if (bytesAvailable > 0) { - dlBuffer = particleSocket.Read(); -// Log.d("BLEService", "We read some bytes from SPark Cloud: " + dlBuffer.length); - byte[] header = {0x01, 0x00}; - BLEManager.send(this, dlBuffer, header); - } - try { - Thread.sleep(10); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - } - - public void stop() - { - Running = false; - } - - public void disconnect() { - stop(); - try { - Thread.sleep(10); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - try { - particleSocket.Disconnect(); - } catch (Exception ex) { - ex.printStackTrace(); - } - mBluetoothGatt.close(); - - mBluetoothGatt = null; - mBluetoothGattService = null; - } - - //Called when the SparkLE transmits data to us - public synchronized void processData(byte[] data) { - Log.d("BLEService", "Got data of length " + data.length); - Log.d("BLEService", "Last Service " + Byte.toString(lastService)); - - StringBuilder sb = new StringBuilder(); - for (byte b : data) { - sb.append(String.format("%02X ", b)); - } -// Log.d("BLEService", "Processing Data: " + sb.toString()); - - if (data[0] == 0x03 && data[1] == 0x04) { - if (lastService == 0x01) { - try { - if (particleSocket.Connected()) { - Log.d("BLEService", "Got a full buffer, attempting to send it up"); - byte[] tmpBuffer = new byte[ulBufferLength]; - System.arraycopy(ulBuffer, 0, tmpBuffer, 0, ulBufferLength); -// Log.d("BLEService", "About to write this many bytes " + tmpBuffer.length); - particleSocket.Write(tmpBuffer); -// Log.d("BLEManager", "Received this many bytes from BLE: " + tmpBuffer.length); - } else { - try { - Log.d("SparkLEService", "Not Connected. Attempting to connect to cloud"); - particleSocket.Connect(); - Thread rThread = new Thread(this); - //rThread.setUncaughtExceptionHandler(new ExceptionHandler()); - rThread.start(); - ulBuffer = new byte[512]; - ulBufferLength = 0; - } catch (Exception ex) { - ex.printStackTrace(); - } - } - } catch (IOException e) { - e.printStackTrace(); - } - } else if (lastService == 0x02) { - final StringBuilder id = new StringBuilder(); - for (int i = 0; i < ulBufferLength; i++) { - id.append(String.format("%02x", ulBuffer[i])); - } - Log.d("Device ID", id.toString()); - this.cloudId = id.toString(); - Async.executeAsync(ParticleCloudSDK.getCloud(), new Async.ApiWork() { - @Override - public Object callApi(ParticleCloud sparkCloud) throws ParticleCloudException, IOException { - return ParticleCloudSDK.getCloud().getDevice(id.toString()).getName(); - } - - @Override - public void onSuccess(Object value) { - Log.d("Device Name Retreieved", (String)value); - cloudName = (String)value; - isClaimed = true; - BLEService.DeviceInfoChanged(); - } - - @Override - public void onFailure(ParticleCloudException e) { - Log.d("Device is not claimed",""); - isClaimed = false; - BLEService.DeviceInfoChanged(); - } - }); - - } - ulBuffer = new byte[512]; - ulBufferLength = 0; - } else { - - if (ulBufferLength == 0) { - lastService = data[0]; - int headerBytes = 1; - if (lastService == 0x01) { - headerBytes = 2; - } - //this is the first packaet in the stream. check the header - System.arraycopy(data, headerBytes, ulBuffer, ulBufferLength, data.length-headerBytes); - ulBufferLength += (data.length-headerBytes); - } else { - System.arraycopy(data, 0, ulBuffer, ulBufferLength, data.length); - ulBufferLength += (data.length); - } - } - //bleManager.send(new byte[]{0x55}); - - //TO DO: Handle BLE messages - } - - public void PollParticleId() { - byte[] requestIdBuffer = {0x02, 0x00}; - BLEManager.send(this, requestIdBuffer, null); - } -} diff --git a/app/src/main/java/com/banc/BLEManagement/BLEDeviceInfoList.java b/app/src/main/java/com/banc/BLEManagement/BLEDeviceInfoList.java deleted file mode 100644 index ffce73c..0000000 --- a/app/src/main/java/com/banc/BLEManagement/BLEDeviceInfoList.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.banc.BLEManagement; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - - -public class BLEDeviceInfoList { - - private List bleDevices; - - public BLEDeviceInfoList() - { - bleDevices = new ArrayList(); - } - - public void InsertOrUpdate(BLEDeviceInfo bleDevice) - { - Iterator deviceIter = bleDevices.iterator(); - boolean found = false; - while(deviceIter.hasNext()){ - BLEDeviceInfo existingBleDevice = deviceIter.next(); - if (existingBleDevice.GetMAC().equalsIgnoreCase(bleDevice.GetMAC())) - { - found = true; - existingBleDevice.UpdateRSSI(bleDevice.GetRSSI()); - break; - } - } - if (!found) - { - bleDevices.add(bleDevice); - } - } - public BLEDeviceInfo GetBLEDeviceInfo(int index) - { - return bleDevices.get(index); - } - public BLEDeviceInfo GetBLEDeviceInfoByAddress(String address) - { - for (BLEDeviceInfo b : bleDevices) { - if (b.GetMAC() == address) { - return b; - } - } - return null; - } - - public int GetCount() - { - return bleDevices.size(); - } - - public BLEDeviceInfoList MergeAndTakeUnique(BLEDeviceInfoList newDevices) - { - BLEDeviceInfoList mergedOverDevice = new BLEDeviceInfoList(); - - Iterator deviceIter = newDevices.bleDevices.iterator(); - boolean found = false; - while(deviceIter.hasNext()) { - BLEDeviceInfo newBleDevice = deviceIter.next(); - if (!this.ContainsByAddress(newBleDevice)) - { - mergedOverDevice.bleDevices.add(newBleDevice); - } - } - - return mergedOverDevice; - } - - private boolean ContainsByAddress(BLEDeviceInfo bleDevice) - { - Iterator deviceIter = bleDevices.iterator(); - boolean found = false; - while(deviceIter.hasNext()) { - BLEDeviceInfo existingBleDevice = deviceIter.next(); - if (existingBleDevice.GetMAC().equalsIgnoreCase(bleDevice.GetMAC())) - { - found = true; - break; - } - } - return found; - } - - public void clearNonConnected() { - Iterator iter = bleDevices.listIterator(); - while (iter.hasNext()) { - if (iter.next().State != BLEDeviceInfo.STATE_CONNECTED) { - iter.remove(); - } - } - } -} diff --git a/app/src/main/java/com/banc/BLEManagement/BLEScanner.java b/app/src/main/java/com/banc/BLEManagement/BLEScanner.java deleted file mode 100644 index 62a58b6..0000000 --- a/app/src/main/java/com/banc/BLEManagement/BLEScanner.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.banc.BLEManagement; - -import java.util.Observable; -import java.util.UUID; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.util.Log; - -public class BLEScanner extends Observable implements Runnable, BluetoothAdapter.LeScanCallback { - - private boolean runScanner = false; - BLEDeviceInfoList newDevices; - - public BLEScanner() - { - newDevices = new BLEDeviceInfoList(); - } - - @Override - public void run() { - runScanner = true; - BLEDeviceInfoList devices = new BLEDeviceInfoList(); - - Log.d("BLEScanner", "Running Scan!"); - newDevices.clearNonConnected(); - BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - bluetoothAdapter.startLeScan( - //new UUID[]{ BLEManager.UUID_SERVICE }, - this); - Log.d("DEBUG", "BLE Scan Started"); - while (runScanner) - { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - bluetoothAdapter.stopLeScan(this); - } - - public void stop() - { - runScanner = false; - } - - @Override - public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { - Log.d("BLEScanner", "Found device " + device.getName()); - BLEDeviceInfo bleDevice = new BLEDeviceInfo(device, rssi); - newDevices.InsertOrUpdate(bleDevice); - setChanged(); - notifyObservers(newDevices); - } - -} diff --git a/app/src/main/java/com/banc/sparkle_gateway/AbstractService.java b/app/src/main/java/com/banc/sparkle_gateway/AbstractService.java deleted file mode 100644 index 41dc312..0000000 --- a/app/src/main/java/com/banc/sparkle_gateway/AbstractService.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.banc.sparkle_gateway; - -import java.util.ArrayList; - -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.util.Log; - -public abstract class AbstractService extends Service { - static final int MSG_REGISTER_CLIENT = 9991; - static final int MSG_UNREGISTER_CLIENT = 9992; - - static ArrayList mClients = new ArrayList(); // Keeps track of all current registered clients. - final Messenger mMessenger = new Messenger(new IncomingHandler()); // Target we publish for clients to send messages to IncomingHandler. - - private class IncomingHandler extends Handler { // Handler of incoming messages from clients. - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_REGISTER_CLIENT: - Log.i("MyService", "Client registered: "+msg.replyTo); - mClients.add(msg.replyTo); - Log.i("ServiceHandler", "Sending Message to Activity that Service is bound."); - Message startedMessage = new Message(); - Bundle b = new Bundle(); - b.putInt("info", ServiceManager.SERVICE_BOUND); - startedMessage.setData(b); - try { - msg.replyTo.send(startedMessage); - } catch (RemoteException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - break; - case MSG_UNREGISTER_CLIENT: - Log.i("MyService", "Client un-registered: "+msg.replyTo); - mClients.remove(msg.replyTo); - break; - default: - //super.handleMessage(msg); - onReceiveMessage(msg); - } - removeMessages(0); - } - } - - @Override - public void onCreate() { - super.onCreate(); - - onStartService(); - - Log.i("MyService", "Service Started."); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.i("MyService", "Received start id " + startId + ": " + intent); - return START_STICKY; // run until explicitly stopped. - } - - @Override - public IBinder onBind(Intent intent) { - return mMessenger.getBinder(); - } - - @Override - public boolean onUnbind(Intent intent) { - return true; - } - - @Override - public void onDestroy() { - super.onDestroy(); - - onStopService(); - - Log.i("MyService", "Service Stopped."); - } - - static protected void send(Message msg) { - for (int i=mClients.size()-1; i>=0; i--) { - try { - //Log.i("MyService", "Sending message to clients: "+msg); - mClients.get(i).send(msg); - } - catch (RemoteException e) { - // The client is dead. Remove it from the list; we are going through the list from back to front so this is safe to do inside the loop. - Log.e("MyService", "Client is dead. Removing from list: "+i); - mClients.remove(i); - } - } - } - - - public abstract void onStartService(); - public abstract void onStopService(); - public abstract void onReceiveMessage(Message msg); - - public Context GetContext() - { - return this.getApplicationContext(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/banc/sparkle_gateway/BLEDisplay.java b/app/src/main/java/com/banc/sparkle_gateway/BLEDisplay.java deleted file mode 100644 index 57512c9..0000000 --- a/app/src/main/java/com/banc/sparkle_gateway/BLEDisplay.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.banc.sparkle_gateway; - -import com.banc.BLEManagement.BLEDeviceInfo; -import com.banc.BLEManagement.BLEDeviceInfoList; -import com.banc.BLEManagement.BLEEvent; -import com.banc.BLEManagement.BLEManager; - -import android.graphics.Color; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.app.Activity; -import android.content.Intent; -import android.util.Log; -import android.view.Menu; -import android.widget.TextView; -import android.graphics.Typeface; - -public class BLEDisplay extends Activity { - - private ServiceManager sManager; - private String address; - TextView mainTitle; - String name; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_glove_display); - - Intent intent = getIntent(); - name = intent.getStringExtra(BLESelection.DEVICE_MESSAGE); - - mainTitle = (TextView)this.findViewById(R.id.textView1); - mainTitle.setTextColor(Color.GREEN); - mainTitle.setText("You are connected to: " + name); - mainTitle.setTypeface(null, Typeface.BOLD); - - this.sManager = new ServiceManager(this, BLEService.class, new HandlerExtension()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.main, menu); - return true; - } - @Override - protected void onDestroy() { - super.onDestroy(); // Always call the superclass method first - - Log.d("GloveDisplay", "Disconnecting from BLE device " + address); - Message msg = new Message(); - Bundle b = new Bundle(); - b.putInt("info", BLEService.DISCONNECT); - b.putString("address", address); - msg.setData(b); - try { - sManager.send(msg); - } catch (RemoteException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - sManager.unbind(); - } - - private class HandlerExtension extends Handler { - - @Override - public void handleMessage(Message msg) { - Log.d("DEBUG", "Received Message in UI"); - int type = msg.getData().getInt("BLEEventType", -1); - - if (type != -1) { - Log.d("BLEDisplay", "Received BLEEvent in UI"); - //this means it is a BLEEvent from the service - BLEEvent event = (BLEEvent) msg.obj; - if (event.BLEEventType == BLEEvent.EVENT_DEVICE_STATE_CHANGE) { - int newState = event.State; - if (newState == BLEDeviceInfo.STATE_DISCONNECTED) - { - mainTitle.setTextColor(Color.RED); - mainTitle.setText("You are disconnected from: " + name); - } - } - } - msg.recycle(); - } - } - -} diff --git a/app/src/main/java/com/banc/sparkle_gateway/BLESelection.java b/app/src/main/java/com/banc/sparkle_gateway/BLESelection.java deleted file mode 100644 index 082c235..0000000 --- a/app/src/main/java/com/banc/sparkle_gateway/BLESelection.java +++ /dev/null @@ -1,303 +0,0 @@ -package com.banc.sparkle_gateway; - -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.app.ActionBar.LayoutParams; -import android.app.Activity; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothGatt; -import android.bluetooth.BluetoothGattService; -import android.bluetooth.BluetoothManager; -import android.content.Intent; -import android.graphics.Color; -import android.util.Log; -import android.view.Menu; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemSelectedListener; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.Button; -import android.widget.TableLayout; -import android.widget.TableRow; -import android.widget.TextView; - -import io.particle.android.sdk.cloud.ParticleCloud; -import io.particle.android.sdk.cloud.ParticleCloudException; -import io.particle.android.sdk.cloud.ParticleCloudSDK; -import io.particle.android.sdk.utils.Async; -import io.particle.android.sdk.utils.Toaster; - -import java.io.IOException; -import java.util.UUID; - -import com.banc.BLEManagement.BLEDeviceInfo; -import com.banc.BLEManagement.BLEDeviceInfoList; -import com.banc.BLEManagement.BLEEvent; - -public class BLESelection extends Activity { - - public final static String DEVICE_MESSAGE = "com.banclabs.gloveapp.DeviceAddress"; - - static private ServiceManager sManager; - static BLEDeviceInfoList currentDevices; - - @Override - protected void onCreate(Bundle savedInstanceState) { - Log.d("GloveSelection", "Creating Glove Selection"); - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_ble_list); - - ParticleCloudSDK.init(this); - - this.sManager = new ServiceManager(this, BLEService.class, new HandlerExtension()); - if (!sManager.isRunning()) { - Log.d("GloveSelection", "Service is not running. Starting!"); - sManager.start(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.main, menu); - return true; - } - - @Override - protected void onStart() { - Log.d("GloveSelection", "Starting Glove Selection"); - //always do this first - super.onStart(); - - Button loginButton = (Button)findViewById(R.id.loginButton); - Button scanButton = (Button)findViewById(R.id.scanButton); - - if (ParticleCloudSDK.getCloud().isLoggedIn()) { - loginButton.setText("Logout"); - } - - sManager.bind(); - //tell the service to stop discovery - Message msg = new Message(); - Bundle b = new Bundle(); - b.putInt("info", BLEService.START_DISCOVERY); - msg.setData(b); - try { - sManager.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } - - intent = new Intent(this, BLEDisplay.class); - } - - @Override - protected void onStop() { - Log.d("GloveSelection", "Stopping Glove Selection"); - //always do this first - super.onStop(); - - //tell the service to stop discovery - Message msg = new Message(); - Bundle b = new Bundle(); - b.putInt("info", BLEService.STOP_DISCOVERY); - msg.setData(b); - try { - sManager.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - @Override - protected void onDestroy() { - Log.d("GloveSelection", "Destroying Glove Selection"); - super.onDestroy(); // Always call the superclass method first - - sManager.stop(); - sManager.unbind(); - } - - public void loginButtonPressed(View view) { - // Do something in response to button - Log.d("Clicked", "Clicked"); - if (ParticleCloudSDK.getCloud().isLoggedIn()) { - ParticleCloudSDK.getCloud().logOut(); - Button loginButton = (Button)findViewById(R.id.loginButton); - loginButton.setText("Login"); - } else { - Intent intent = new Intent(this, ParticleLoginDisplay.class); - startActivity(intent); - } - } - - public void scanButtonPressed(View view) { - // Do something in response to button - Log.d("Clicked", "Clicked"); - Button scanButton = (Button)findViewById(R.id.scanButton); - - if (scanButton.getText() == "Stop") { - Message msg = new Message(); - Bundle b = new Bundle(); - b.putInt("info", BLEService.STOP_DISCOVERY); - msg.setData(b); - try { - sManager.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } - scanButton.setText("Scan"); - } else { - Message msg = new Message(); - Bundle b = new Bundle(); - b.putInt("info", BLEService.START_DISCOVERY); - msg.setData(b); - try { - sManager.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } - scanButton.setText("Stop"); - } - - } - - Intent intent; - private void updateTable(BLEDeviceInfoList devices) - { - - currentDevices = devices; - ListView list = (ListView)findViewById(R.id.listView1); - // Getting adapter by passing xml data ArrayList - DeviceAdapter adapter=new DeviceAdapter(this, devices); - list.setAdapter(adapter); - - - // Click event for single list row -// list.setOnItemClickListener(new OnItemClickListener() { -// -// @Override -// public void onItemClick(AdapterView parent, View view, -// int position, long id) { -// android.widget.RelativeLayout item = (android.widget.RelativeLayout)view; -//// TextView addressTextView = (TextView)item.findViewById(R.id.deviceAddress); -// TextView nameTextView = (TextView)item.findViewById(R.id.deviceName); -//// String address = addressTextView.getText().toString(); -// BLEDeviceInfo devInfo = currentDevices.GetBLEDeviceInfo(position); -// String address = devInfo.GetMAC(); -// String deviceName = nameTextView.getText().toString(); -// Log.d("DEBUG", "User selected " + address); -// Message msg = new Message(); -// Bundle b = new Bundle(); -// b.putInt("info", BLEService.CONNECT); -// b.putString("address", address); -// msg.setData(b); -// try { -// sManager.send(msg); -// } catch (RemoteException e) { -// e.printStackTrace(); -// } -// sManager.unbind(); -// intent.putExtra(DEVICE_MESSAGE, deviceName); -// startActivity(intent); -// } -// -// }); - } - - private class HandlerExtension extends Handler { - - @Override - public void handleMessage(Message msg) { - int type = msg.getData().getInt("BLEEventType", -1); - - if (type != -1) - { - //this means it is a BLEEvent from the service - BLEEvent event = (BLEEvent)msg.obj; - if (event.BLEEventType == BLEEvent.EVENT_UPDATE || event.BLEEventType == BLEEvent.EVENT_DEVICE_STATE_CHANGE) - { - BLEDeviceInfoList devices = (BLEDeviceInfoList)event.Contents; - updateTable(devices); - } - } else { - //otherwise, it is a message from the ServiceManager - type = msg.getData().getInt("info", -1); - if (type == ServiceManager.SERVICE_BOUND) - { - Message discoverMessage = new Message(); - Bundle b = new Bundle(); - b.putInt("info", BLEService.START_DISCOVERY); - discoverMessage.setData(b); - try { - sManager.send(discoverMessage); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } - msg.recycle(); - } - } - - public OnClickListener connectButtonClicked = new OnClickListener() { - @Override - public void onClick(View v) { - int position = (Integer) v.getTag(); - Log.d("DEBUG","Clicked Connect Button at Position: " + position); - BLEDeviceInfo devInfo = currentDevices.GetBLEDeviceInfo(position); - String address = devInfo.GetMAC(); - Log.d("DEBUG", "User selected " + address); - Message msg = new Message(); - Bundle b = new Bundle(); - if (devInfo.State == BLEDeviceInfo.STATE_CONNECTED) { - b.putInt("info", BLEService.DISCONNECT); - } else { - b.putInt("info", BLEService.CONNECT); - } - b.putString("address", address); - msg.setData(b); - try { - sManager.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - }; - - public OnClickListener claimButtonClicked = new OnClickListener() { - @Override - public void onClick(View v) { - int position = (Integer) v.getTag(); - Log.d("DEBUG","Clicked Claim Button at Position: " + position); - final BLEDeviceInfo devInfo = currentDevices.GetBLEDeviceInfo(position); - Async.executeAsync(ParticleCloudSDK.getCloud(), new Async.ApiWork() { - @Override - public Object callApi(ParticleCloud sparkCloud) throws ParticleCloudException, IOException { - ParticleCloudSDK.getCloud().claimDevice(devInfo.GetCloudID()); - return 1; - } - - @Override - public void onSuccess(Object value) { - Log.d("Device Claimed", ""); - devInfo.SetClaimed(true); - Toaster.s(BLESelection.this, "Claimed!"); - updateTable(currentDevices); - } - - @Override - public void onFailure(ParticleCloudException e) { - Log.d("Device Not Claimed", ""); - Toaster.s(BLESelection.this, "Error Claiming Device!"); - } - }); - } - }; - -} diff --git a/app/src/main/java/com/banc/sparkle_gateway/BLEService.java b/app/src/main/java/com/banc/sparkle_gateway/BLEService.java deleted file mode 100644 index 2313aff..0000000 --- a/app/src/main/java/com/banc/sparkle_gateway/BLEService.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.banc.sparkle_gateway; - -import java.io.IOException; -import java.util.Observable; -import java.util.Observer; - -import com.banc.BLEManagement.BLEDeviceInfo; -import com.banc.BLEManagement.BLEEvent; -import com.banc.BLEManagement.BLEManager; - -import android.media.AudioManager; -import android.nfc.Tag; -import android.os.Bundle; -import android.os.Handler; -import android.content.Intent; -import android.os.Message; -import android.os.Looper; -import android.util.Log; - -public class BLEService extends AbstractService implements Observer { - private BLEManager bleManager; - - final public static int CONNECT = 2; - final public static int DISCONNECT = 3; - final public static int START_DISCOVERY = 4; - final public static int STOP_DISCOVERY = 5; - - private int MY_DATA_CHECK_CODE = 0; - - private boolean askedSMS = false; - private boolean commandMode = false; - - AudioManager audio; - - @Override - public void onStartService() { - bleManager = new BLEManager(this.GetContext()); - bleManager.addObserver(this); - } - - @Override - public void onStopService() { - bleManager.deleteObserver(this); - bleManager.stop(); - } - - @Override - public void onReceiveMessage(Message msg) { - // TODO Auto-generated method stub - Log.d("SparkLEService", "Received message"); - String address; - try { - int info = msg.getData().getInt("info"); - Log.d("SparkLEService", "Received message: " + info); - switch (info) - { -// case GET_INFO: -// BLEEvent event = new BLEEvent(); -// event.BLEEventType = BLEEvent.EVENT_UPDATE; -// event.Contents = bleManager.bleDevices; -// updateUI(event); -// break; - case CONNECT: - address = msg.getData().getString("address"); - bleManager.connect(address); - while (!bleManager.isInitialized(address)) { - Thread.sleep(100); - } - Log.d("BLEService", "We are now connected and initialized!"); -// Thread.sleep(2000); -// Log.d("BLEService", "Done Waiting 2 seconds"); -// sparkSocket.Connect(); - //bleManager.send(new byte[]{0x55}); -// readerThread = new ReaderThread(); -// Thread rThread = new Thread(readerThread); -// //rThread.setUncaughtExceptionHandler(new ExceptionHandler()); -// rThread.start(); -// ulBuffer = new byte[512]; -// ulBufferLength = 0; - break; - case DISCONNECT: - //String address = msg.getData().getString("address"); - Log.d("SparkLEService", "Disconnecting BLE"); - address = msg.getData().getString("address"); - BLEDeviceInfo devInfo = bleManager.GetBLEDeviceInfoByAddress(address); - bleManager.disconnect(address); - break; - case START_DISCOVERY: - Log.d("SparkLEService", "Starting Discovery"); - bleManager.start(); - break; - case STOP_DISCOVERY: - Log.d("SparkLEService", "Stopping Discovery"); - bleManager.stop(); - break; - - } - } - catch (Exception ex) - { - ex.printStackTrace(); - } - } - - @Override - public void update(Observable observable, Object data) { - // TODO Auto-generated method stub -// Log.d("SparkLEServices", "Received event from BLEManager"); - BLEEvent event = (BLEEvent)data; - if (event.DeviceInfo == null) { - Log.d("WTF!!!", "devInfo is null!"); - } - switch (event.BLEEventType) - { - case BLEEvent.EVENT_UPDATE: - updateUI(event); - break; - case BLEEvent.EVENT_DEVICE_STATE_CHANGE: - handleNewState(event.DeviceInfo, event.State); - updateUI(event); - break; - case BLEEvent.EVENT_RX_DATA: - Log.d("BLEService", "Calling devInfo processData"); - event.DeviceInfo.processData((byte[]) event.Contents); - break; - } - } - - private void handleNewState(final BLEDeviceInfo devInfo, int newState) - { - switch (newState) - { - case BLEDeviceInfo.STATE_DISCONNECTED: - Log.d("BLEService", "Handling BLE disconnection"); - devInfo.disconnect(); - break; - case BLEDeviceInfo.STATE_CONNECTED: - Log.d("BLEService", "Starting timer for Get ID!!"); - Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(new Runnable() { - - public void run() { - Log.d("BLEService", "Running Get ID!!"); - devInfo.PollParticleId(); - } - }, 22000); - break; - } - } - - private void updateUI(BLEEvent event) { - Message msg = Message.obtain(null, 2); - Bundle b = new Bundle(); - b.putInt("BLEEventType", event.BLEEventType); - msg.setData(b); - msg.obj = event; - send(msg); - } - - static public void DeviceInfoChanged() { - BLEEvent event = new BLEEvent(); - event.Contents = BLEManager.GetList(); - event.BLEEventType = BLEEvent.EVENT_DEVICE_STATE_CHANGE; - event.State = 0; - event.DeviceInfo = null; - Message msg = Message.obtain(null, 2); - Bundle b = new Bundle(); - b.putInt("BLEEventType", event.BLEEventType); - msg.setData(b); - msg.obj = event; - send(msg); - } - -} diff --git a/app/src/main/java/com/banc/sparkle_gateway/DeviceAdapter.java b/app/src/main/java/com/banc/sparkle_gateway/DeviceAdapter.java deleted file mode 100644 index ab6511c..0000000 --- a/app/src/main/java/com/banc/sparkle_gateway/DeviceAdapter.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.banc.sparkle_gateway; - -import com.banc.BLEManagement.BLEDeviceInfo; -import com.banc.BLEManagement.BLEDeviceInfoList; - -import android.app.Activity; -import android.content.Context; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.Button; -import android.widget.TextView; - -public class DeviceAdapter extends BaseAdapter { - - private BLEDeviceInfoList devices; - private static LayoutInflater inflater=null; - BLESelection parentActivity; - - public DeviceAdapter(Activity activity, BLEDeviceInfoList d) { - devices = d; - inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - parentActivity = (BLESelection)activity; - } - - @Override - public int getCount() { - return devices.GetCount(); - } - - @Override - public Object getItem(int position) { - return devices.GetBLEDeviceInfo(position); - } - - @Override - public long getItemId(int position) { - return devices.GetBLEDeviceInfo(position).hashCode(); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View vi=convertView; - if(convertView==null) - vi = inflater.inflate(R.layout.list_row, null); - - TextView name = (TextView)vi.findViewById(R.id.deviceName); // title -// TextView address = (TextView)vi.findViewById(R.id.deviceAddress); // artist name - TextView rssi = (TextView)vi.findViewById(R.id.deviceRssi); // duration - TextView cloudName = (TextView)vi.findViewById(R.id.deviceCloudName); // duration - TextView cloudId = (TextView)vi.findViewById(R.id.deviceCloudId); // duration - Button connectButton = (Button)vi.findViewById(R.id.connectButton); // duration - Button claimButton = (Button)vi.findViewById(R.id.claimButton); // duration - - connectButton.setTag(position); - connectButton.setOnClickListener(parentActivity.connectButtonClicked); - - claimButton.setTag(position); - claimButton.setOnClickListener(parentActivity.claimButtonClicked); - - BLEDeviceInfo device = devices.GetBLEDeviceInfo(position); - - connectButton.setText("Connect"); - if (device.State == BLEDeviceInfo.STATE_CONNECTED) { - connectButton.setText("Disconnect"); - } - - Log.d("DEBUG", "Adding device with name " + device.GetName()); - // Setting all values in listview - name.setText(device.GetName()); -// address.setText(device.GetMAC()); - rssi.setText(Integer.toString(device.GetRSSI())); - cloudId.setText(device.GetCloudID()); - cloudName.setText(device.GetCloudName()); - claimButton.setVisibility(View.INVISIBLE); - if (device.GetCloudID() != "" && !device.IsClaimed()) { - claimButton.setVisibility(View.VISIBLE); - } - - return vi; - } - -} diff --git a/app/src/main/java/com/banc/sparkle_gateway/ParticleSocket.java b/app/src/main/java/com/banc/sparkle_gateway/ParticleSocket.java deleted file mode 100644 index 696ad4d..0000000 --- a/app/src/main/java/com/banc/sparkle_gateway/ParticleSocket.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.banc.sparkle_gateway; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.Socket; -import java.net.UnknownHostException; - -import android.os.AsyncTask; -import android.util.Log; - -public class ParticleSocket { - //live server - String cloudServer = "54.208.229.4"; - //staging server -// String cloudServer = "staging-device.spark.io"; - //local server -// String cloudServer = "10.1.10.175"; - int cloudPort = 5683; - - private int CloudServiceID = 1; - - Socket socket; - InputStream inputStream; - OutputStream outputStream; - - public ParticleSocket() { - - } - - public void Connect() throws UnknownHostException, IOException { - - class Retrievedata extends AsyncTask { - @Override - protected String doInBackground(String... params) { - try{ - Log.d("SparkleCloudInterface", "Connecting to Spark Cloud"); - InetAddress serveraddress=InetAddress.getByName(cloudServer); - Log.d("ParticleSocket", "Got Here!"); - Log.d("SparkleCloudInterface", "We should have the IP Address " + serveraddress); - socket = new Socket(cloudServer, cloudPort); - Log.d("SparkleCloudInterface", "Did we connect?"); - Log.d("SparkleCloudInterface", Boolean.toString(socket.isConnected())); - inputStream = socket.getInputStream(); - outputStream = socket.getOutputStream(); - } - catch (Exception e) - { - e.printStackTrace(); - } - return null; - } - } - String params = ""; - new Retrievedata().execute(params); - } - public void Disconnect() throws UnknownHostException, IOException { - if (inputStream != null) { - inputStream.close(); - inputStream = null; - } - if (outputStream != null) { - outputStream.close(); - outputStream = null; - } - if (socket != null) { - socket.close(); - socket = null; - } - } - public void Write(byte[] data) throws IOException { -// Log.d("SparkleCloudInterface", "When writing, are we connected: " + Boolean.toString(socket.isConnected())); - outputStream.write(data); - outputStream.flush(); - StringBuilder sb = new StringBuilder(); - - for (byte b : data) { - sb.append(String.format("%02X ", b)); - } -// Log.d("SparkleCloudInterface", "Sending data: " + sb.toString()); - Log.d("SparkleCloudInterface", "Sending data to Cloud of size: " + data.length); - } - public int Available() throws IOException { - if (inputStream != null) { - return inputStream.available(); - } - return 0; - } - public boolean Connected() { - if (inputStream != null) { - return socket.isConnected(); - } else { - return false; - } - } - public byte[] Read() throws IOException { - //need to append our service ID and socket number to the beginning of the data - byte[] data = new byte[0]; - if (inputStream != null) { - int bytesAvailable = inputStream.available(); - - try { - Thread.sleep(500); - } catch (Exception ex) { - ex.printStackTrace(); - } - - bytesAvailable = inputStream.available(); - data = new byte[bytesAvailable]; - -// //copy the cloud data in with the service ID and socket number - inputStream.read(data, 0, bytesAvailable); - - -// byte[] data = new byte[inputStream.available()]; -// inputStream.read(data, 0, inputStream.available()); - - StringBuilder sb = new StringBuilder(); - for (byte b : data) { - sb.append(String.format("%02X ", b)); - } - Log.d("SparkleCloudInterface", "Got data: " + sb.toString()); - Log.d("SparkleCloudInterface", "Got data from Cloud of size: " + data.length); - } - -// System.out.println(sb.toString()); - return data; - } -} diff --git a/app/src/main/java/gateway/AbstractService.java b/app/src/main/java/gateway/AbstractService.java new file mode 100644 index 0000000..1b471b5 --- /dev/null +++ b/app/src/main/java/gateway/AbstractService.java @@ -0,0 +1,110 @@ +package gateway; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; + +public abstract class AbstractService extends Service { + static final int MSG_REGISTER_CLIENT = 9991; + static final int MSG_UNREGISTER_CLIENT = 9992; + + private static final ArrayList mClients = new ArrayList(); // Keeps track of all current registered clients. + private final Messenger mMessenger = new Messenger(new IncomingHandler()); // Target we publish for clients to send messages to IncomingHandler. + + static void send(Message msg) { + for (int i = mClients.size() - 1; i >= 0; i--) { + try { + //Log.i("MyService", "Sending message to clients: "+msg); + mClients.get(i).send(msg); + } catch (RemoteException e) { + // The client is dead. Remove it from the list; we are going through the list from back to front so this is safe to do inside the loop. + Log.e("MyService", "Client is dead. Removing from list: " + i); + mClients.remove(i); + } + } + } + + @Override + public void onCreate() { + super.onCreate(); + + onStartService(); + + Log.i("MyService", "Service Started."); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("MyService", "Received start id " + startId + ": " + intent); + return START_STICKY; // run until explicitly stopped. + } + + @Override + public IBinder onBind(Intent intent) { + return mMessenger.getBinder(); + } + + @Override + public boolean onUnbind(Intent intent) { + return true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + + onStopService(); + + Log.i("MyService", "Service Stopped."); + } + + protected abstract void onStartService(); + + protected abstract void onStopService(); + + protected abstract void onReceiveMessage(Message msg); + + Context GetContext() { + return this.getApplicationContext(); + } + + private class IncomingHandler extends Handler { // Handler of incoming messages from clients. + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REGISTER_CLIENT: + Log.i("MyService", "Client registered: " + msg.replyTo); + mClients.add(msg.replyTo); + Log.i("ServiceHandler", "Sending Message to Activity that Service is bound."); + Message startedMessage = new Message(); + Bundle b = new Bundle(); + b.putInt("info", ServiceManager.SERVICE_BOUND); + startedMessage.setData(b); + try { + msg.replyTo.send(startedMessage); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + break; + case MSG_UNREGISTER_CLIENT: + Log.i("MyService", "Client un-registered: " + msg.replyTo); + mClients.remove(msg.replyTo); + break; + default: + //super.handleMessage(msg); + onReceiveMessage(msg); + } + removeMessages(0); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/gateway/BLEDisplay.java b/app/src/main/java/gateway/BLEDisplay.java new file mode 100644 index 0000000..44cffd9 --- /dev/null +++ b/app/src/main/java/gateway/BLEDisplay.java @@ -0,0 +1,94 @@ +package gateway; + + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.graphics.Typeface; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; +import android.view.Menu; +import android.widget.TextView; + +import com.banc.gateway.R; + +import BLEManagement.BLEDeviceInfo; +import BLEManagement.BLEEvent; + +public class BLEDisplay extends Activity { + + private ServiceManager sManager; + private String address; + private TextView mainTitle; + private String name; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_glove_display); + + Intent intent = getIntent(); + name = intent.getStringExtra(BLESelection.DEVICE_MESSAGE); + + mainTitle = (TextView) this.findViewById(R.id.textView1); + mainTitle.setTextColor(Color.GREEN); + mainTitle.setText("You are connected to: " + name); + mainTitle.setTypeface(null, Typeface.BOLD); + + this.sManager = new ServiceManager(this, BLEService.class, new HandlerExtension()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + protected void onDestroy() { + super.onDestroy(); // Always call the superclass method first + + Log.d("GloveDisplay", "Disconnecting from BLE device " + address); + Message msg = new Message(); + Bundle b = new Bundle(); + b.putInt("info", BLEService.DISCONNECT); + b.putString("address", address); + msg.setData(b); + try { + sManager.send(msg); + } catch (RemoteException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + sManager.unbind(); + } + + private class HandlerExtension extends Handler { + + @Override + public void handleMessage(Message msg) { + Log.d("DEBUG", "Received Message in UI"); + int type = msg.getData().getInt("BLEEventType", -1); + + if (type != -1) { + Log.d("BLEDisplay", "Received BLEEvent in UI"); + //this means it is a BLEEvent from the service + BLEEvent event = (BLEEvent) msg.obj; + if (event.BLEEventType == BLEEvent.EVENT_DEVICE_STATE_CHANGE) { + int newState = event.State; + if (newState == BLEDeviceInfo.STATE_DISCONNECTED) { + mainTitle.setTextColor(Color.RED); + mainTitle.setText("You are disconnected from: " + name); + } + } + } + msg.recycle(); + } + } + +} diff --git a/app/src/main/java/gateway/BLESelection.java b/app/src/main/java/gateway/BLESelection.java new file mode 100644 index 0000000..7046c06 --- /dev/null +++ b/app/src/main/java/gateway/BLESelection.java @@ -0,0 +1,306 @@ +package gateway; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ListView; +import android.widget.Toast; + +import com.banc.gateway.R; + +import java.io.IOException; + +import BLEManagement.BLEDeviceInfo; +import BLEManagement.BLEDeviceInfoList; +import BLEManagement.BLEEvent; +import BLEManagement.Utilities; +import io.particle.android.sdk.cloud.ParticleCloud; +import io.particle.android.sdk.cloud.ParticleCloudException; +import io.particle.android.sdk.cloud.ParticleCloudSDK; +import io.particle.android.sdk.utils.Async; +import io.particle.android.sdk.utils.Toaster; + + +public class BLESelection extends AppCompatActivity { + + public final static String DEVICE_MESSAGE = "com.banclabs.gloveapp.DeviceAddress"; + + static private ServiceManager sManager; + private static BLEDeviceInfoList currentDevices; + public final OnClickListener connectButtonClicked = new OnClickListener() { + @Override + public void onClick(View v) { + int position = (Integer) v.getTag(); + Log.d("DEBUG", "Clicked Connect Button at Position: " + position); + BLEDeviceInfo devInfo = currentDevices.GetBLEDeviceInfo(position); + String address = devInfo.GetMAC(); + Log.d("DEBUG", "User selected " + address); + Message msg = new Message(); + Bundle b = new Bundle(); + if (devInfo.State == BLEDeviceInfo.STATE_CONNECTED) { + b.putInt("info", BLEService.DISCONNECT); + } else { + b.putInt("info", BLEService.CONNECT); + } + b.putString("address", address); + msg.setData(b); + try { + sManager.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }; + public final OnClickListener claimButtonClicked = new OnClickListener() { + @Override + public void onClick(View v) { + int position = (Integer) v.getTag(); + Log.d("DEBUG", "Clicked Claim Button at Position: " + position); + final BLEDeviceInfo devInfo = currentDevices.GetBLEDeviceInfo(position); + Async.executeAsync(ParticleCloudSDK.getCloud(), new Async.ApiWork() { + @Override + public Object callApi(ParticleCloud sparkCloud) throws ParticleCloudException, IOException { + ParticleCloudSDK.getCloud().claimDevice(devInfo.GetCloudID()); + return 1; + } + + @Override + public void onSuccess(Object value) { + Log.d("Device Claimed", ""); + devInfo.SetClaimed(); + Toaster.s(BLESelection.this, "Claimed!"); + updateTable(currentDevices); + } + + @Override + public void onFailure(ParticleCloudException e) { + Log.d("Device Not Claimed", ""); + Toaster.s(BLESelection.this, "Error Claiming Device!"); + } + }); + } + }; + private Utilities utils; + private Intent intent; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_ble_list); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + //setSupportActionBar(toolbar); + + /*FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) + .setAction("Action", null).show(); + } + });*/ + + // Find out whether device even supports Bluetooth + utils = new Utilities(); + boolean ble = utils.checkForBluetooth(); + if (!ble) { + Toast.makeText(this, "Bluetooth is either not supported on this device, " + + "or is turned off", Toast.LENGTH_LONG).show(); + } + ParticleCloudSDK.init(this); + + sManager = new ServiceManager(this, BLEService.class, new HandlerExtension()); + if (!sManager.isRunning()) { + Log.d("GloveSelection", "Service is not running. Starting!"); + sManager.start(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + protected void onStart() { + Log.d("GloveSelection", "Starting Glove Selection"); + //always do this first + super.onStart(); + + Button loginButton = (Button) findViewById(R.id.loginButton); + Button scanButton = (Button) findViewById(R.id.scanButton); + + if (ParticleCloudSDK.getCloud().isLoggedIn()) { + loginButton.setText("Logout"); + } + + sManager.bind(); + //tell the service to stop discovery + Message msg = new Message(); + Bundle b = new Bundle(); + b.putInt("info", BLEService.START_DISCOVERY); + msg.setData(b); + try { + sManager.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + + intent = new Intent(this, BLEDisplay.class); + } + + @Override + protected void onStop() { + Log.d("GloveSelection", "Stopping Glove Selection"); + //always do this first + super.onStop(); + + //tell the service to stop discovery + Message msg = new Message(); + Bundle b = new Bundle(); + b.putInt("info", BLEService.STOP_DISCOVERY); + msg.setData(b); + try { + sManager.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + protected void onDestroy() { + Log.d("GloveSelection", "Destroying Glove Selection"); + super.onDestroy(); // Always call the superclass method first + + sManager.stop(); + sManager.unbind(); + } + + public void loginButtonPressed(View view) { + // Do something in response to button + Log.d("Clicked", "Clicked"); + if (ParticleCloudSDK.getCloud().isLoggedIn()) { + ParticleCloudSDK.getCloud().logOut(); + Button loginButton = (Button) findViewById(R.id.loginButton); + loginButton.setText("Login"); + } else { + Intent intent = new Intent(this, ParticleLoginDisplay.class); + startActivity(intent); + } + } + + public void scanButtonPressed(View view) { + // Do something in response to button + Log.d("Clicked", "Clicked"); + Button scanButton = (Button) findViewById(R.id.scanButton); + + if (scanButton.getText() == "Stop") { + Message msg = new Message(); + Bundle b = new Bundle(); + b.putInt("info", BLEService.STOP_DISCOVERY); + msg.setData(b); + try { + sManager.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + scanButton.setText("Scan"); + } else { + Message msg = new Message(); + Bundle b = new Bundle(); + b.putInt("info", BLEService.START_DISCOVERY); + msg.setData(b); + try { + sManager.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + scanButton.setText("Stop"); + } + + } + + private void updateTable(BLEDeviceInfoList devices) { + + currentDevices = devices; + ListView list = (ListView) findViewById(R.id.listView1); + // Getting adapter by passing xml data ArrayList + DeviceAdapter adapter = new DeviceAdapter(this, devices); + list.setAdapter(adapter); + + + // Click event for single list row +// list.setOnItemClickListener(new OnItemClickListener() { +// +// @Override +// public void onItemClick(AdapterView parent, View view, +// int position, long id) { +// android.widget.RelativeLayout item = (android.widget.RelativeLayout)view; +//// TextView addressTextView = (TextView)item.findViewById(R.id.deviceAddress); +// TextView nameTextView = (TextView)item.findViewById(R.id.deviceName); +//// String address = addressTextView.getText().toString(); +// BLEDeviceInfo devInfo = currentDevices.GetBLEDeviceInfo(position); +// String address = devInfo.GetMAC(); +// String deviceName = nameTextView.getText().toString(); +// Log.d("DEBUG", "User selected " + address); +// Message msg = new Message(); +// Bundle b = new Bundle(); +// b.putInt("info", BLEService.CONNECT); +// b.putString("address", address); +// msg.setData(b); +// try { +// sManager.send(msg); +// } catch (RemoteException e) { +// e.printStackTrace(); +// } +// sManager.unbind(); +// intent.putExtra(DEVICE_MESSAGE, deviceName); +// startActivity(intent); +// } +// +// }); + } + + private class HandlerExtension extends Handler { + + @Override + public void handleMessage(Message msg) { + int type = msg.getData().getInt("BLEEventType", -1); + + if (type != -1) { + //this means it is a BLEEvent from the service + BLEEvent event = (BLEEvent) msg.obj; + if (event.BLEEventType == BLEEvent.EVENT_UPDATE || event.BLEEventType == BLEEvent.EVENT_DEVICE_STATE_CHANGE) { + BLEDeviceInfoList devices = (BLEDeviceInfoList) event.Contents; + updateTable(devices); + } + } else { + //otherwise, it is a message from the ServiceManager + type = msg.getData().getInt("info", -1); + if (type == ServiceManager.SERVICE_BOUND) { + Message discoverMessage = new Message(); + Bundle b = new Bundle(); + b.putInt("info", BLEService.START_DISCOVERY); + discoverMessage.setData(b); + try { + sManager.send(discoverMessage); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + // msg.recycle(); + } + } + +} diff --git a/app/src/main/java/gateway/BLEService.java b/app/src/main/java/gateway/BLEService.java new file mode 100644 index 0000000..3664c5a --- /dev/null +++ b/app/src/main/java/gateway/BLEService.java @@ -0,0 +1,161 @@ +package gateway; + +import android.media.AudioManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import java.util.Observable; +import java.util.Observer; + +import BLEManagement.BLEDeviceInfo; +import BLEManagement.BLEEvent; +import BLEManagement.BLEManager; + +public class BLEService extends AbstractService implements Observer { + final public static int CONNECT = 2; + final public static int DISCONNECT = 3; + final public static int START_DISCOVERY = 4; + final public static int STOP_DISCOVERY = 5; + AudioManager audio; + private BLEManager bleManager; + private int MY_DATA_CHECK_CODE = 0; + private boolean askedSMS = false; + private boolean commandMode = false; + + static public void DeviceInfoChanged() { + BLEEvent event = new BLEEvent(); + event.Contents = BLEManager.GetList(); + event.BLEEventType = BLEEvent.EVENT_DEVICE_STATE_CHANGE; + event.State = 0; + event.DeviceInfo = null; + Message msg = Message.obtain(null, 2); + Bundle b = new Bundle(); + b.putInt("BLEEventType", event.BLEEventType); + msg.setData(b); + msg.obj = event; + send(msg); + } + + @Override + public void onStartService() { + bleManager = new BLEManager(this.GetContext()); + bleManager.addObserver(this); + } + + @Override + public void onStopService() { + bleManager.deleteObserver(this); + bleManager.stop(); + } + + @Override + public void onReceiveMessage(Message msg) { + // TODO Auto-generated method stub + Log.d("SparkLEService", "Received message"); + String address; + try { + int info = msg.getData().getInt("info"); + Log.d("SparkLEService", "Received message: " + info); + switch (info) { +// case GET_INFO: +// BLEEvent event = new BLEEvent(); +// event.BLEEventType = BLEEvent.EVENT_UPDATE; +// event.Contents = bleManager.bleDevices; +// updateUI(event); +// break; + case CONNECT: + address = msg.getData().getString("address"); + bleManager.connect(address); + while (!bleManager.isInitialized(address)) { + Thread.sleep(100); + } + Log.d("BLEService", "We are now connected and initialized!"); +// Thread.sleep(2000); +// Log.d("BLEService", "Done Waiting 2 seconds"); +// sparkSocket.Connect(); + //bleManager.send(new byte[]{0x55}); +// readerThread = new ReaderThread(); +// Thread rThread = new Thread(readerThread); +// //rThread.setUncaughtExceptionHandler(new ExceptionHandler()); +// rThread.start(); +// ulBuffer = new byte[512]; +// ulBufferLength = 0; + break; + case DISCONNECT: + //String address = msg.getData().getString("address"); + Log.d("SparkLEService", "Disconnecting BLE"); + address = msg.getData().getString("address"); + BLEDeviceInfo devInfo = bleManager.GetBLEDeviceInfoByAddress(address); + bleManager.disconnect(address); + break; + case START_DISCOVERY: + Log.d("SparkLEService", "Starting Discovery"); + bleManager.start(); + break; + case STOP_DISCOVERY: + Log.d("SparkLEService", "Stopping Discovery"); + bleManager.stop(); + break; + + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + @Override + public void update(Observable observable, Object data) { + // TODO Auto-generated method stub +// Log.d("SparkLEServices", "Received event from BLEManager"); + BLEEvent event = (BLEEvent) data; + if (event.DeviceInfo == null) { + Log.d("WTF!!!", "devInfo is null!"); + } + switch (event.BLEEventType) { + case BLEEvent.EVENT_UPDATE: + updateUI(event); + break; + case BLEEvent.EVENT_DEVICE_STATE_CHANGE: + handleNewState(event.DeviceInfo, event.State); + updateUI(event); + break; + case BLEEvent.EVENT_RX_DATA: + Log.d("BLEService", "Calling devInfo processData"); + event.DeviceInfo.processData((byte[]) event.Contents); + break; + } + } + + private void handleNewState(final BLEDeviceInfo devInfo, int newState) { + switch (newState) { + case BLEDeviceInfo.STATE_DISCONNECTED: + Log.d("BLEService", "Handling BLE disconnection"); + devInfo.disconnect(); + break; + case BLEDeviceInfo.STATE_CONNECTED: + Log.d("BLEService", "Starting timer for Get ID!!"); + Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(new Runnable() { + + public void run() { + Log.d("BLEService", "Running Get ID!!"); + devInfo.PollParticleId(); + } + }, 22000); + break; + } + } + + private void updateUI(BLEEvent event) { + Message msg = Message.obtain(null, 2); + Bundle b = new Bundle(); + b.putInt("BLEEventType", event.BLEEventType); + msg.setData(b); + msg.obj = event; + send(msg); + } + +} diff --git a/app/src/main/java/gateway/DeviceAdapter.java b/app/src/main/java/gateway/DeviceAdapter.java new file mode 100644 index 0000000..9f695ec --- /dev/null +++ b/app/src/main/java/gateway/DeviceAdapter.java @@ -0,0 +1,92 @@ +package gateway; + + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.TextView; + +import com.banc.gateway.R; + +import BLEManagement.BLEDeviceInfo; +import BLEManagement.BLEDeviceInfoList; + +class DeviceAdapter extends BaseAdapter { + + private static LayoutInflater inflater = null; + private BLEDeviceInfoList devices; + private BLESelection parentActivity; + + public DeviceAdapter(Activity activity, BLEDeviceInfoList d) { + devices = d; + inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + parentActivity = (BLESelection) activity; + } + + public DeviceAdapter(BLESelection activity, BLEDeviceInfoList devices) { + + } + + @Override + public int getCount() { + return devices.GetCount(); + } + + @Override + public Object getItem(int position) { + return devices.GetBLEDeviceInfo(position); + } + + @Override + public long getItemId(int position) { + return devices.GetBLEDeviceInfo(position).hashCode(); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View vi = convertView; + if (convertView == null) + vi = inflater.inflate(R.layout.list_row, null); + + TextView name = (TextView) vi.findViewById(R.id.deviceName); // title +// TextView address = (TextView)vi.findViewById(R.id.deviceAddress); // artist name + TextView rssi = (TextView) vi.findViewById(R.id.deviceRssi); // duration + TextView cloudName = (TextView) vi.findViewById(R.id.deviceCloudName); // duration + TextView cloudId = (TextView) vi.findViewById(R.id.deviceCloudId); // duration + Button connectButton = (Button) vi.findViewById(R.id.connectButton); // duration + Button claimButton = (Button) vi.findViewById(R.id.claimButton); // duration + + connectButton.setTag(position); + connectButton.setOnClickListener(parentActivity.connectButtonClicked); + + claimButton.setTag(position); + claimButton.setOnClickListener(parentActivity.claimButtonClicked); + + BLEDeviceInfo device = devices.GetBLEDeviceInfo(position); + + connectButton.setText("Connect"); + if (device.State == BLEDeviceInfo.STATE_CONNECTED) { + connectButton.setText("Disconnect"); + } + + Log.d("DEBUG", "Adding device with name " + device.GetName()); + // Setting all values in listview + name.setText(device.GetName()); +// address.setText(device.GetMAC()); + rssi.setText(Integer.toString(device.GetRSSI())); + cloudId.setText(device.GetCloudID()); + cloudName.setText(device.GetCloudName()); + claimButton.setVisibility(View.INVISIBLE); + if (device.GetCloudID() != "" && !device.IsClaimed()) { + claimButton.setVisibility(View.VISIBLE); + } + + return vi; + } + +} diff --git a/app/src/main/java/com/banc/sparkle_gateway/HexAsciiHelper.java b/app/src/main/java/gateway/HexAsciiHelper.java similarity index 82% rename from app/src/main/java/com/banc/sparkle_gateway/HexAsciiHelper.java rename to app/src/main/java/gateway/HexAsciiHelper.java index 1402bae..583344a 100644 --- a/app/src/main/java/com/banc/sparkle_gateway/HexAsciiHelper.java +++ b/app/src/main/java/gateway/HexAsciiHelper.java @@ -1,21 +1,12 @@ -package com.banc.sparkle_gateway; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; +package gateway; import org.apache.http.util.ByteArrayBuffer; -import java.util.regex.Pattern; - public class HexAsciiHelper { - public static int PRINTABLE_ASCII_MIN = 0x20; // ' ' - public static int PRINTABLE_ASCII_MAX = 0x7E; // '~' + private static final int PRINTABLE_ASCII_MIN = 0x20; // ' ' + private static final int PRINTABLE_ASCII_MAX = 0x7E; // '~' - public static boolean isPrintableAscii(int c) { + private static boolean isPrintableAscii(int c) { return c >= PRINTABLE_ASCII_MIN && c <= PRINTABLE_ASCII_MAX; } diff --git a/app/src/main/java/com/banc/sparkle_gateway/MainActivity.java b/app/src/main/java/gateway/MainActivity.java similarity index 91% rename from app/src/main/java/com/banc/sparkle_gateway/MainActivity.java rename to app/src/main/java/gateway/MainActivity.java index a1b3463..cdea6a1 100644 --- a/app/src/main/java/com/banc/sparkle_gateway/MainActivity.java +++ b/app/src/main/java/gateway/MainActivity.java @@ -1,9 +1,12 @@ -package com.banc.sparkle_gateway; +package gateway; -import android.os.Bundle; import android.app.Activity; +import android.os.Bundle; import android.view.Menu; +import com.banc.gateway.R; + + public class MainActivity extends Activity { @Override @@ -19,5 +22,5 @@ public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } - + } diff --git a/app/src/main/java/com/banc/sparkle_gateway/MyBroadcastReceiver.java b/app/src/main/java/gateway/MyBroadcastReceiver.java similarity index 58% rename from app/src/main/java/com/banc/sparkle_gateway/MyBroadcastReceiver.java rename to app/src/main/java/gateway/MyBroadcastReceiver.java index f32d7b6..3afce91 100644 --- a/app/src/main/java/com/banc/sparkle_gateway/MyBroadcastReceiver.java +++ b/app/src/main/java/gateway/MyBroadcastReceiver.java @@ -1,17 +1,15 @@ -package com.banc.sparkle_gateway; +package gateway; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.Handler; -import android.os.Message; import android.util.Log; public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - Log.d("DEBUG", "Got the reboot command, starting foneRino"); - Intent startServiceIntent = new Intent(context, BLEService.class); + Log.d("DEBUG", "Got the reboot command, starting foneRino"); + Intent startServiceIntent = new Intent(context, BLEService.class); context.startService(startServiceIntent); } } diff --git a/app/src/main/java/com/banc/sparkle_gateway/ParticleLoginDisplay.java b/app/src/main/java/gateway/ParticleLoginDisplay.java similarity index 85% rename from app/src/main/java/com/banc/sparkle_gateway/ParticleLoginDisplay.java rename to app/src/main/java/gateway/ParticleLoginDisplay.java index aec6d73..4d0c12d 100644 --- a/app/src/main/java/com/banc/sparkle_gateway/ParticleLoginDisplay.java +++ b/app/src/main/java/gateway/ParticleLoginDisplay.java @@ -1,23 +1,20 @@ -package com.banc.sparkle_gateway; +package gateway; -import java.io.IOException; -import android.content.Intent; +import android.app.Activity; import android.os.Bundle; -import android.os.Message; -import android.os.RemoteException; import android.util.Log; import android.view.Menu; -import android.app.Activity; import android.view.View; -import android.widget.TextView; import android.widget.EditText; -import io.particle.android.sdk.cloud.ParticleCloudException; +import com.banc.gateway.R; + +import java.io.IOException; + import io.particle.android.sdk.cloud.ParticleCloud; +import io.particle.android.sdk.cloud.ParticleCloudException; import io.particle.android.sdk.cloud.ParticleCloudSDK; import io.particle.android.sdk.utils.Async; -import io.particle.android.sdk.utils.Async.ApiWork; -import io.particle.android.sdk.utils.Async.AsyncApiWorker; import io.particle.android.sdk.utils.Toaster; /** @@ -55,8 +52,8 @@ protected void onDestroy() { public void loginButtonPressed(View view) { // Do something in response to button - EditText emailTextView = (EditText)findViewById(R.id.emailTextField); - EditText passwordTextView = (EditText)findViewById(R.id.passwordTextField); + EditText emailTextView = (EditText) findViewById(R.id.emailTextField); + EditText passwordTextView = (EditText) findViewById(R.id.passwordTextField); email = emailTextView.getText().toString(); password = passwordTextView.getText().toString(); @@ -74,8 +71,7 @@ public void onSuccess(Object value) { Toaster.s(ParticleLoginDisplay.this, "Logged in!"); try { ParticleCloudSDK.getCloud().getDevices(); - } - catch (Exception ex) { + } catch (Exception ex) { Log.d("ParticleLoginDisplay", "Received error when getting devices"); ex.printStackTrace(); } diff --git a/app/src/main/java/gateway/ParticleSocket.java b/app/src/main/java/gateway/ParticleSocket.java new file mode 100644 index 0000000..06f2a97 --- /dev/null +++ b/app/src/main/java/gateway/ParticleSocket.java @@ -0,0 +1,128 @@ +package gateway; + +import android.os.AsyncTask; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; + +public class ParticleSocket { + //live server + private final String cloudServer = "54.208.229.4"; + //staging server +// String cloudServer = "staging-device.spark.io"; + //local server +// String cloudServer = "10.1.10.175"; + private final int cloudPort = 5683; + + private int CloudServiceID = 1; + + private Socket socket; + private InputStream inputStream; + private OutputStream outputStream; + + public ParticleSocket() { + + } + + public void Connect() { + + class Retrievedata extends AsyncTask { + @Override + protected String doInBackground(String... params) { + try { + Log.d("SparkleCloudInterface", "Connecting to Spark Cloud"); + InetAddress serveraddress = InetAddress.getByName(cloudServer); + Log.d("ParticleSocket", "Got Here!"); + Log.d("SparkleCloudInterface", "We should have the IP Address " + serveraddress); + socket = new Socket(cloudServer, cloudPort); + Log.d("SparkleCloudInterface", "Did we connect?"); + Log.d("SparkleCloudInterface", Boolean.toString(socket.isConnected())); + inputStream = socket.getInputStream(); + outputStream = socket.getOutputStream(); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + } + String params = ""; + new Retrievedata().execute(params); + } + + public void Disconnect() throws IOException { + if (inputStream != null) { + inputStream.close(); + inputStream = null; + } + if (outputStream != null) { + outputStream.close(); + outputStream = null; + } + if (socket != null) { + socket.close(); + socket = null; + } + } + + public void Write(byte[] data) throws IOException { +// Log.d("SparkleCloudInterface", "When writing, are we connected: " + Boolean.toString(socket.isConnected())); + outputStream.write(data); + outputStream.flush(); + StringBuilder sb = new StringBuilder(); + + for (byte b : data) { + sb.append(String.format("%02X ", b)); + } +// Log.d("SparkleCloudInterface", "Sending data: " + sb.toString()); + Log.d("SparkleCloudInterface", "Sending data to Cloud of size: " + data.length); + } + + public int Available() throws IOException { + if (inputStream != null) { + return inputStream.available(); + } + return 0; + } + + public boolean Connected() { + return inputStream != null && socket.isConnected(); + } + + public byte[] Read() throws IOException { + //need to append our service ID and socket number to the beginning of the data + byte[] data = new byte[0]; + if (inputStream != null) { + int bytesAvailable = inputStream.available(); + + try { + Thread.sleep(500); + } catch (Exception ex) { + ex.printStackTrace(); + } + + bytesAvailable = inputStream.available(); + data = new byte[bytesAvailable]; + +// //copy the cloud data in with the service ID and socket number + inputStream.read(data, 0, bytesAvailable); + + +// byte[] data = new byte[inputStream.available()]; +// inputStream.read(data, 0, inputStream.available()); + + StringBuilder sb = new StringBuilder(); + for (byte b : data) { + sb.append(String.format("%02X ", b)); + } + Log.d("SparkleCloudInterface", "Got data: " + sb.toString()); + Log.d("SparkleCloudInterface", "Got data from Cloud of size: " + data.length); + } + +// System.out.println(sb.toString()); + return data; + } +} diff --git a/app/src/main/java/com/banc/sparkle_gateway/ServiceManager.java b/app/src/main/java/gateway/ServiceManager.java similarity index 64% rename from app/src/main/java/com/banc/sparkle_gateway/ServiceManager.java rename to app/src/main/java/gateway/ServiceManager.java index 0227ab3..40f437b 100644 --- a/app/src/main/java/com/banc/sparkle_gateway/ServiceManager.java +++ b/app/src/main/java/gateway/ServiceManager.java @@ -1,143 +1,140 @@ -package com.banc.sparkle_gateway; - -import android.app.ActivityManager; -import android.app.ActivityManager.RunningServiceInfo; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; -import android.util.Log; - -public class ServiceManager { - private Class mServiceClass; - private Context mActivity; - private boolean mIsBound; - private Messenger mService = null; - private Handler mIncomingHandler = null; - private final Messenger mMessenger = new Messenger(new IncomingHandler()); - - final public static int SERVICE_BOUND = 10; - - private class IncomingHandler extends Handler { - @Override - public void handleMessage(Message msg) { - if (mIncomingHandler != null) { - //Log.i("ServiceHandler", "Incoming message. Passing to handler: "+msg); - mIncomingHandler.handleMessage(msg); - } - } - } - - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - mService = new Messenger(service); - - //textStatus.setText("Attached."); - Log.i("ServiceHandler", "Attached."); - try { - Message msg = Message.obtain(null, AbstractService.MSG_REGISTER_CLIENT); - msg.replyTo = mMessenger; - mService.send(msg); - } catch (RemoteException e) { - // In this case the service has crashed before we could even do anything with it - } - } - - public void onServiceDisconnected(ComponentName className) { - // This is called when the connection with the service has been unexpectedly disconnected - process crashed. - mService = null; - //textStatus.setText("Disconnected."); - Log.i("ServiceHandler", "Disconnected."); - } - }; - - public ServiceManager(Context context, Class serviceClass, Handler incomingHandler) { - this.mActivity = context; - this.mServiceClass = serviceClass; - this.mIncomingHandler = incomingHandler; - if (isRunning()) { - doBindService(); - } - } - - public void start() { - doStartService(); - doBindService(); - } - - public void stop() { - doUnbindService(); - doStopService(); - } - - /** - * Use with caution (only in Activity.onDestroy())! - */ - public void unbind() { - doUnbindService(); - } - - public void bind() { - doBindService(); - } - - public boolean isRunning() { - ActivityManager manager = (ActivityManager) mActivity.getSystemService(Context.ACTIVITY_SERVICE); - - for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { - if (mServiceClass.getName().equals(service.service.getClassName())) { - return true; - } - } - - return false; - } - - public void send(Message msg) throws RemoteException { - if (mIsBound) { - if (mService != null) { - mService.send(msg); - } - } - } - - private void doStartService() { - mActivity.startService(new Intent(mActivity, mServiceClass)); - } - - private void doStopService() { - mActivity.stopService(new Intent(mActivity, mServiceClass)); - } - - private void doBindService() { - mActivity.bindService(new Intent(mActivity, mServiceClass), mConnection, Context.BIND_AUTO_CREATE); - mIsBound = true; - } - - private void doUnbindService() { - if (mIsBound) { - // If we have received the service, and hence registered with it, then now is the time to unregister. - if (mService != null) { - try { - Message msg = Message.obtain(null, AbstractService.MSG_UNREGISTER_CLIENT); - msg.replyTo = mMessenger; - mService.send(msg); - } catch (RemoteException e) { - // There is nothing special we need to do if the service has crashed. - } - } - - // Detach our existing connection. - mActivity.unbindService(mConnection); - mIsBound = false; - //textStatus.setText("Unbinding."); - Log.i("ServiceHandler", "Unbinding."); - } - } +package gateway; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +class ServiceManager { + final public static int SERVICE_BOUND = 10; + private final Class mServiceClass; + private final Context mActivity; + private final Messenger mMessenger = new Messenger(new IncomingHandler()); + private boolean mIsBound; + private Messenger mService = null; + private final ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + mService = new Messenger(service); + + //textStatus.setText("Attached."); + Log.i("ServiceHandler", "Attached."); + try { + Message msg = Message.obtain(null, AbstractService.MSG_REGISTER_CLIENT); + msg.replyTo = mMessenger; + mService.send(msg); + } catch (RemoteException e) { + // In this case the service has crashed before we could even do anything with it + } + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been unexpectedly disconnected - process crashed. + mService = null; + //textStatus.setText("Disconnected."); + Log.i("ServiceHandler", "Disconnected."); + } + }; + private Handler mIncomingHandler = null; + + public ServiceManager(Context context, Class serviceClass, Handler incomingHandler) { + this.mActivity = context; + this.mServiceClass = serviceClass; + this.mIncomingHandler = incomingHandler; + if (isRunning()) { + doBindService(); + } + } + + public void start() { + doStartService(); + doBindService(); + } + + public void stop() { + doUnbindService(); + doStopService(); + } + + /** + * Use with caution (only in Activity.onDestroy())! + */ + public void unbind() { + doUnbindService(); + } + + public void bind() { + doBindService(); + } + + public boolean isRunning() { + ActivityManager manager = (ActivityManager) mActivity.getSystemService(Context.ACTIVITY_SERVICE); + + for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { + if (mServiceClass.getName().equals(service.service.getClassName())) { + return true; + } + } + + return false; + } + + public void send(Message msg) throws RemoteException { + if (mIsBound) { + if (mService != null) { + mService.send(msg); + } + } + } + + private void doStartService() { + mActivity.startService(new Intent(mActivity, mServiceClass)); + } + + private void doStopService() { + mActivity.stopService(new Intent(mActivity, mServiceClass)); + } + + private void doBindService() { + mActivity.bindService(new Intent(mActivity, mServiceClass), mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + private void doUnbindService() { + if (mIsBound) { + // If we have received the service, and hence registered with it, then now is the time to unregister. + if (mService != null) { + try { + Message msg = Message.obtain(null, AbstractService.MSG_UNREGISTER_CLIENT); + msg.replyTo = mMessenger; + mService.send(msg); + } catch (RemoteException e) { + // There is nothing special we need to do if the service has crashed. + } + } + + // Detach our existing connection. + mActivity.unbindService(mConnection); + mIsBound = false; + //textStatus.setText("Unbinding."); + Log.i("ServiceHandler", "Unbinding."); + } + } + + private class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + if (mIncomingHandler != null) { + //Log.i("ServiceHandler", "Incoming message. Passing to handler: "+msg); + mIncomingHandler.handleMessage(msg); + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 6ce89c7..9a9c15f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,20 +1,18 @@ - - - - - + 128dp \ No newline at end of file diff --git a/app/build/intermediates/manifests/androidTest/debug/AndroidManifest.xml b/app/build/intermediates/manifests/androidTest/debug/AndroidManifest.xml deleted file mode 100644 index 5eb5561..0000000 --- a/app/build/intermediates/manifests/androidTest/debug/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - diff --git a/app/build/intermediates/manifests/full/debug/AndroidManifest.xml b/app/build/intermediates/manifests/full/debug/AndroidManifest.xml index 0c6a844..372bb74 100644 --- a/app/build/intermediates/manifests/full/debug/AndroidManifest.xml +++ b/app/build/intermediates/manifests/full/debug/AndroidManifest.xml @@ -11,6 +11,14 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/build/intermediates/res/debug/drawable-hdpi/arrow_right.png b/app/build/intermediates/res/debug/drawable-hdpi/arrow_right.png deleted file mode 100644 index 9f8834499671029faf236f7fc3a6f80c4bcd68ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 930 zcmeAS@N?(olHy`uVBq!ia0vp^YCx>R!3-qrDy)7nFfh&w@Ck7Rav{Lj*toE;u&k^s zH#aveEv=-a#M9F=G&Iz~!NJ(r7p4k9>*$#K`T1#UYX=4fUcY|*_U+pzVIU|dJRu<= zG_(PzdiU<#|6$4o;P(gQh_xJDL|NQyW(9mFMX&D)r0u=1*>;(z}85R~cqN2)w{{OMGbOej{n3`I} z#>N8u5)=e7zp1GeX#SHYPxABgfm{cN1~0Er8ylcvq5Jpm1C3p>WC_sZfPe%|O#^jx zJ)lQ`E}1%Y22e2rL#T?14n%PByoVP zh9c}d!s3$RLTtSEX4wGKkEW-KV~EA+20^Y5tCNz_5>u1YA3u0Lw65fJ}te%@8!8FO*)aIpd1`bxab+OyT&#jP>K_4tvjBM-ww z=TG1WN}ZN6ea@kla8Uz?pv!YE>*{s{Of~cgHEchgE);T9VEWX#levRZ+x$hH=f&4G zPU@Q%S0eN-fb~{nL`Y0fR9IMCU}kHyD6{iaw^HBS*jnctWxG(G7~iwHYjfYqD$NzE m@OD&tXQS|7TSAJ&MFxgB`*Ne-g{ZTEQh=wcpUXO@geCyonpT?t diff --git a/app/build/intermediates/res/debug/drawable-hdpi/bluz_icon.png b/app/build/intermediates/res/debug/drawable-hdpi/bluz_icon.png deleted file mode 100644 index 179aae164b80397bbbbb0f161d30709b067fbdee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2585 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 v;MK`5E%iY-Z6S$H+F1b;Ft~&U - - - \ No newline at end of file diff --git a/app/build/intermediates/res/debug/drawable-hdpi/gradient_bg_hover.xml b/app/build/intermediates/res/debug/drawable-hdpi/gradient_bg_hover.xml deleted file mode 100644 index 36d1c05..0000000 --- a/app/build/intermediates/res/debug/drawable-hdpi/gradient_bg_hover.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/build/intermediates/res/debug/drawable-hdpi/ic_launcher.png b/app/build/intermediates/res/debug/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 46486b60da89835cb781013bf6fb084f92245488..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4537 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 - - - - - - - - \ No newline at end of file diff --git a/app/build/intermediates/res/debug/drawable-hdpi/list_selector.xml b/app/build/intermediates/res/debug/drawable-hdpi/list_selector.xml deleted file mode 100644 index f413627..0000000 --- a/app/build/intermediates/res/debug/drawable-hdpi/list_selector.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/build/intermediates/res/debug/drawable-mdpi/bluz_icon.png b/app/build/intermediates/res/debug/drawable-mdpi/bluz_icon.png deleted file mode 100644 index e870365186a9fcbdb0eef39f3ba633bdd9db712f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1618 zcmaJ>c~H|=5RTv=prIn8V(b8QDjsDlB8SDQpaB6XLA=1Kg%%`$AlBFlqH+ldC~^dW z5bl5wuEYQVA|c@lAjEJABp?JL1_)Pxkn`7{ozD122Y2S}e((Es-oAOW`!KF9=b+j! zZ3qMcb##EcfzkBy(o_e%Cr_&y0#T#8I$yE_w`4LoCnqO6JDWrzu~@A7`g$6T2J!#^ zfF3ltTp^XoK_FUM&8q6iAhYn4jto*eU%0TgE=wdfe-bS(udb()+GZBS4c**#BU96J z%S3uler@;SiX?;FUdim6_%xqg-8nV0m__MOs#KJg0S;FHp2_N;cv;cfIml-Z3-YL4 zp&8T#@fyf4iX|Nb{IciiBcjUQL%u1@wUX!9y zDJX2!m_T$XEFW9m8k$k_5Jw#q%=yNZJ;|yIN+d6>NF-9Z52ox+A_=_G-DGm#==5WJ zokS*Y>KSu?Lg*Tr9GVbXBe8vB(~WH2>y`o9+mYX6iAtsFT0{{FOYugRq6v){LStMZ zBfg07tcVemND9fQ2}!RJudNFg#1GPI-gI#ry14Xi9-YlY6X^F+C{a1I8rFN<>-Lu3 z4;MmnSJ$NqrPBT`uBe_Jl}j6$oEe&!Ng_65%UT>!_>woAs1PraQ`DU&PK zr1Ek`ueBf6Ei}){C;6m*DvsPPS13ywI9DGNq;h#%|2UzpoAGXx!R9%k@M9lE=mNT5 ze0jk0iZC1%9*leYe)1Nk>=o@DSgW{!Q_1WHflwq?0e}yt^l4th5P#8)1OP3tdp9!e|Aeg@C2YiR+er8-5B;L*e1RLHZvbHX|3qB?IBbt_b=$m4e-G^F zlIkirzaEZo+e;3tKRosE;YnKB=}E|2=0}Z@{p49`s@X<9?n0tRZIXLN-+g{Uv zcXopD<|gMuJIZYx_sp5@%(o~emZ&>If5F45I8UqZqgGd-+sH_cT7YTOb}P%H7RRWh zmVVFZv+A1e9^9d1LPQ^XjHwGhq4g83w!TDfkRhD+Vs{9I0+CmsXmWNM;jZp$g`Awhb1%j?Ko6|$D8>H4_cdS$NY&uru|tk*mjrn}Ok+>nSnByc!+&pq06$ zqm>mk+~yty6|ncr*=4 z*}N-SyBl}X_XtrIBHnEhZ9HNZFN&CQCdKTp-4`T$EvWh7N`;y+cYGjVutGh~gjwme z9Jz|WvUgVyugXvSvYdNd%YTR-We6AXszW6P#UGaZhiG?B$LQ-o+qUQ!SYHpGI(Q%n zX7XZF>ih>3UN0?151D@e5#i9S?rx5U1RQ^E>>sQF&psJz*X&mYWB&#@H=>qr;eBKC z?yxlUAR|QT^nPIN+I=3ZA}?yzDrl@e25Mu$GlqfgJD80*ui>0|?be>)93-U2b7g#@ zfQ8VEWW6vr6^=cR@ICcV#ouqc$d^(w5FJSHn>hBU;dWqT*n`iL=V<2wuQ=lw`443^ B%Q*l5 diff --git a/app/build/intermediates/res/debug/drawable-mdpi/ic_launcher.png b/app/build/intermediates/res/debug/drawable-mdpi/ic_launcher.png deleted file mode 100644 index 3f766b3155dd2a5d627b5cf7119a7668afb3c7fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2140 zcmV-i2&4CjP)&korsECy1JG5pCQlr{|if7+R~P`w52U=Y5%jyl|Lj?@SuPw z;I2I_2%xGr7m^4>Q11bQh)1Of5+i1XKY*L~i^{)q`rlA|^aqTKLlYmLX6~ILG|K$!yQNR?UTCX#= z`0B$YXbh6o5%RSo7Sz_>Uqn!AhggV=56(#q!o;XCh!{j7R2@V~__z{GT!T_j7aUPr z-;)4b>wTmFTHL6L_-0kvIq;|z7WF>-&=+ANb#>a!Lc0Az&|=hkBwY_+brt}5izFE! zp$v_NwOv_9)`jPy1`;(;n}>b00?jW2bmepVi*qC&qJ9<@j(hV=`5dAYS$T*w`=Lz4qJBmspydRd#aB7x6VzF74oO=` zXAR9Ks4Jix1t)xUE%H|#h;MI7det{AVRj65hW56I`j63S4Ck-VVhk=vI@=%_nh!uN zrrk%<4x}BlvqlstaVx?NPw;OJ;p_-C36K--{s3>MP>cfjY6{-frOd6RbB6=|W(T-* zOu$Z&^XIs7K=SZCqOD!*=`r@`8?+c=)(;@rl4$P$H#mh}n{;&zO)`?TKJ9}&Ffr4~1YJ31<m?(M%}dJ?*km@peI zK)Wh*A@3G4;^m$;dQo##{aI|6F-ip1f$`uyqMq5&HymTx{mBy^udndUWK91TzhQrG zi&k%iPP4(suLivLqdt=Ea`@rr4Bj479DNDJkf_;4(-gd-)fCDZ_$qw&w%*&nyM&C1 z>Ux{korqaDJ~kX=TOpMT8=3?+E3ngoleZ^q>_6o9zx^e951JfgF?+3yPsRm%58C{7 zRPgHCF}A8`^_rN?Z3;KQo)6GGM~e~SE$Yhoyt(H9-*CWtccX=>`uTWw3dTT`p{DNR zvk_KdDov`!EA)7qPmg!_V7tTnYi&e?hQQzQir9KMA5*8HPlYS z9;HO|GPz<1$fn-FhTQv8+%j)BYJYH?niVT%1P8AiQ zD+Zb5G>1bHHPGx+X%Aa<(9)olgQ|oo2bXaKJkNHvYqeK>F@C9!3xixe#IYNzvlh@9ricZd0IN2eS6BbbG$hnQ=E+PqX9`X z#jJL5$qxRs#A_Y~p)2YZ?(YPCj{~7y1$MANs!Ri-G&m7O2o|J-YJ?U;dcB0MEBNS- ze?qqV%$`5v@OK|zI$fHb4ujWc9RK5UHk&Or*BiV(K4*OR8SeB2e)2Yyg%L=b5XFJ6 z*b>y#INSnxEbeeXl0|;J(0^;LUf)s?L;?}Z`kQngK4S9qGhY4qQ=;wf;fF(b{eq*v ze1uC{#GM`u2OJ&o*}wiyr`x8rv4QV*nEDl<#81y)b_TYXM_VoexS&z=UO{8@hKMk= zCWta5YJnJ33(Wcs$<|{`98(>BiFS#^;G~rI}2A|)g9=n)VzmIU)KZm0ZzI|)~g4M zh2lIcE*W4ElLocrtB}1YMY0ak=%L<1F+!pkX|%yK&?*l^(>gRipGUi~8uW#)f@3bP zC$YQI))%S7Jr0;4@~W5EG?r3};uk)o~)buIOa179rY>Ev4L^x;j(rLelmNUw3B3s3E}(zt~i z-9AccC#!nD3r$>lhkA+IK53pNwu{p| zPhH!gdAx#gycIV#s(}E)U<-l)A+ZU8B(x%-eIIE?qnUg9L9zm9n?b{9WbXgdAkKa7 zIq(0T^KR!HP@q780tE^bD7YCRInpRAE9;$>mX@Em;)Oy8BuN_d`+DRI3}Os2T+x@I z5F8x*?wU1gemZ6Xva_?_ZEbDcFz7)^8c@QOzlR&lwQYv&&=m@C*;nR-%%S>u?RZ8S z`;{;l3|sEH>#m<%Hvz|v9ec06z5U16Gl-$uj?~#}3A+&lj3#@i{*86q0RaK;WoBkR z;xqw;g@u3Y?(Y84wO_$#Q}x<*BYi!ND8SJr$B`w;nRkyij?u<_C47B-KS)kaUhfnE z!j+$(0;FNsQz@z$Unm491SkY3PzX>6P%uu=Oq7n=WkE-m1-mR0p!Y_nQ7Z(v1J#Y~ z9L&4O`N{??RvVHep;k%6ObX)Glvw5@M7lJG`pM&08Hz5Ntk0oH|Ukdp$?;@Egv8=g$Cf+*DAcFFX zs`)JE9AQCvp1ki?bXv`XBfuk#aLJ`64&;}zFm(#+mZT0+e*b_-Pm1R6R?MZo#l)YF z7AanktFZTY5y1gItiC;Y)V9;3L%B62hGT_QbaeG71dJ7(-940EZe~GB92&J_p3Z_< zamcdZd}X~tz*te++<}l`%4CDX&x8i*@$>bfwy8rQV65n|+5qtJa?Y#q)@kVJwJQXS z6@I>60JNFR4nHFcfvLL(y^l^IV62D;4Me9=aiOZw;b$sqTd`Pe#2SMX0>%n2t(w&7 z5ga{NNxRA7P{4K||02HLIuc{T6#`s=yEBsj*tX}yfK3<<`%jcme5r}$83_|IKje`F zgar8V@Uq!d)wlBMr-vx5X&Tg~-DKwD>{DdtmS7C_WB!cj2^olabW#3dbrT=&KSf)o znJ|MNQDK2-H7c6gyQr#fMJK$mDkhIW@KbES zNT{lB<+bgHF6{ z4rbZB1eVU7?i9H<4FLcJWp%ufbric@=8m+ftXrBk>_Dp<+Ss!D1jUyc5wbvBp>Tvi z{`TKjkRXI0Jjjp7@5^NVj2MN0p>XJQIUnuIM+W}pjue*7PaM?d$k__E?8?Q`YXbo) z3DTxVlAaVvd_)LA{=TRriH2geF>i`+|FTRCG?wL30EW7Cl0Qfwo zfZg9*Kms0Jox!{-qU8g57umKa54$XbfQ;lA9$cD6Sde~1nO$Zp+Yg-PPhX#<*Jeiu zxH}_}r`IhUad*Z|Nq`W7cX#IU&ACdnY8B5sxQK-4FwU0N@}HmNV3!4ImBfaX^H`KN z)hXG9<@Nk}>pogK%m@J+R?g+&Wpl=#fNqPGoKt0-F0G}p)r3l=Vsc0T>4}kK&Wv^1 zD;XtI3QCiJ#wpJ9Cqwu1j`BT?^0rekX06W>hMP z%}?J;Qf&D65%AT?Qr^lshRNJRRCplKlMEmWDjQm9Zto^6NY4}Z-pZ`_$!;Z~%VK5I z`v)EB&27NSxOFVah^;hA-d@bS{{(Y}1Q8|4d?4G2J|Q8Pa|#$|G2jRq2R^kA$WS-LZ-T@dL_#;>tFl?v(-j@i|Y$EwauLaiMo(L zz59%CdSWy_URttp&N=Kg?K*NEqrs0jW3bzduu7#OGAs~T78F%B;Ob1v*|J&$pw(!I z4i9u60lwZ^?q6~X*(XXkbh>QR_F0DuDJZYw{w24dQn~kNQOXn}vJlj_by0Gu$z^i8 zExiNo=d5_6QwK#{5{ax|KAYsYa9-HDpIt}J59x*2?J^(lKgC;FM_Di{j-_*_yFCuL zJvDA%nb>i-z-4lG96C#vr7uN&*F5LOIR)IAK8+Zo z0U-osH7y*>zer6}2Y1b#&OeTly1h~8Gs1lyQO?P7zMnaRl&Qus$!zZEX3MS<2muCv zUshx$I`zCulX0>T?9VOXPhXv(xVi};KqX0N)GB7h8+l+!Dj6<}K%GI}g=&8DUN$zn zJm8Xs8~k`};~EV9KG!AFX0x;LwJeUGuSOtv?x8z)@XoaH%Z6Tw&h8%STTQ4W31hH7 zzTR5K3uTv^dH%n5(bdySMshU9V1GW(DL{xmFK*LgcM}qzcS@$U*T%2j*~@{vQUrq8 z(<6D~7i&=ulZv@13o-yrZCyP5`~Rb%t&7B{P=5F1N;Dc3|N8b`PF$!4AS6J~vkxwG zI6qW&xtZT=$)>QP0f8VQG=Q!D^1z7h=S@jKcaN3lHt(dgrkOB%Mh#nMF%CexKypA0?1?)LiM6XRoAczSMV)L{25)m@8 z>v_`=V7J@()jRt*aiJQ$kCy*>YBh0_g9mwLTX*O3@%~fT?DBx3y>uF;PBsu4Sj8+EdxeeAh$ z9CHn8n>+dXR4J!QE>m&2g_i#8sh^LI=dL&8^3&O50?Htq0c|O zctTubV?8GQm-Ih$O(LkHY%s>5MH?19JVvPn~ zd~~_PwTW?GnmpMC96nRdZ$CIlo5@U4OgKp~VQ4jKsvFwLFRsBR+j(F~8UYc3 z{dhC$C>phz7k|2J)Z)fNOVe1GI)&Ze6w&QOFF7<5^ze08#vH4Bq5^yh6x!{4q* zr=!cl;WOn50b@nUrA91P8yPcW9e!qJoDsc`mV&Z6g@Cc5&169c2o3agd=M%LgTF7W zZro{iqX`(d2uAO%Ll%NI2X0CkaGCpw^YHtECPpe^dUPlf3=~ToL0L@`Egjt?#Dslk z2pG2+5aj1WMshTt9z28DL9gbId-Kt1RD5rCJQEEb>4rYBb^#5oCSKaMzwfx{2>3Fu zgwMV%;D^g*Uw3g`A>bOMO^e{~*DfII&^eyn`~@wY-GkcL>@pwh&EvVvyGV^U^5lIB zn1~o|sZrT%wsF6L@clh`yt@4mTD6MB>C>1QHwiDThKjnreMZ%d9o#u5o=uOfK=0$_ z(Tdh+G-^P0n1G(19*?*Kva+s~_xI%S^~tingAY>QcF6R^DAp}Wb^3Vec)`od%P9iX zU0q!^Pa1#_&~53V*DmAl>ox5D8&8E^uh#&BKI&l;Exo;-msingROmD+WV;>NKJhMT z9dc@ToB=2^Kp{XOK*2Z?V6)j2?Tn)lmh9gVH*cb*BGf<_ z5C;Uf_M00000NkvXXu0mjfOLO%1 diff --git a/app/build/intermediates/res/debug/drawable-xhdpi/ic_launcher.png b/app/build/intermediates/res/debug/drawable-xhdpi/ic_launcher.png deleted file mode 100644 index c8db86a1b0eb458a342a3ec7cedc898db305b753..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7356 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 OVrYW(RKF2h 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 S^WXR`*e^*%iCQtk;Qs@k{!1?a diff --git a/app/build/intermediates/res/debug/layout/activity_glove_display.xml b/app/build/intermediates/res/debug/layout/activity_glove_display.xml deleted file mode 100644 index f219666..0000000 --- a/app/build/intermediates/res/debug/layout/activity_glove_display.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/build/intermediates/res/debug/layout/list_row.xml b/app/build/intermediates/res/debug/layout/list_row.xml deleted file mode 100644 index 9da9abc..0000000 --- a/app/build/intermediates/res/debug/layout/list_row.xml +++ /dev/null @@ -1,118 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - -