Java tutorial
/** * Hexiwear application is used to pair with Hexiwear BLE devices * and send sensor readings to WolkSense sensor data cloud * * Copyright (C) 2016 WolkAbout Technology s.r.o. * * Hexiwear is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Hexiwear is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package com.wolkabout.hexiwear.service; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothProfile; import android.content.Intent; import android.graphics.Color; import android.os.Binder; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.wolkabout.hexiwear.R; import com.wolkabout.hexiwear.model.Characteristic; import com.wolkabout.hexiwear.model.otap.Command; import com.wolkabout.hexiwear.model.otap.Image; import com.wolkabout.hexiwear.model.otap.ImageBlock; import com.wolkabout.hexiwear.model.otap.response.ErrorNotification; import com.wolkabout.hexiwear.model.otap.response.ImageBlockRequest; import com.wolkabout.hexiwear.model.otap.response.ImageTransferComplete; import com.wolkabout.hexiwear.util.ByteUtils; import org.androidannotations.annotations.EService; import org.androidannotations.annotations.Receiver; import org.androidannotations.annotations.SystemService; import java.util.UUID; @EService public class FirmwareUpdateService extends Service { public static final String UPDATE_INITIATED = "updateStarted"; public static final String UPDATE_CANCELED = "updateCanceled"; public static final String UPDATE_FINISHED = "updateFinished"; public static final String UPDATE_ERROR = "updateError"; public static final String UPDATE_PROGRESS = "updateProgress"; public static final String UPDATE_PROGRESS_VALUE = "progress"; public static final String CANCEL_UPDATE = "cancelUpdate"; private static final String TAG = FirmwareUpdateService.class.getSimpleName(); private static final int NOTIFICATION_ID = 1; @SystemService static NotificationManager notificationManager; private static NotificationCompat.Builder notificationBuilder; private static BluetoothGatt bluetoothGatt; @Override public int onStartCommand(final Intent intent, final int flags, final int startId) { return START_STICKY; } @Nullable @Override public IBinder onBind(final Intent intent) { return new ServiceBinder(this); } public void updateFirmware(final BluetoothDevice device, final Image image) { notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_bluetooth_connected_white_48dp) .setContentTitle(getResources().getString(R.string.app_name)).setLights(Color.GREEN, 100, 5000); startForeground(NOTIFICATION_ID, notificationBuilder.build()); bluetoothGatt = device.connectGatt(this, true, new FirmwareUpdater(image)); } @Receiver(actions = CANCEL_UPDATE) public void cancelUpdate() { sendBroadcast(UPDATE_CANCELED); stopService(); } private void stopService() { if (bluetoothGatt != null) { bluetoothGatt.close(); } stopForeground(true); stopSelf(); } private void sendBroadcast(final String event) { sendBroadcast(new Intent(event)); } public void sendBroadcast(final Intent intent) { LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } private class FirmwareUpdater extends BluetoothGattCallback { private static final String OTAP_SERVICE_UUID = "01ff5550-ba5e-f4ee-5ca1-eb1e5e4b1ce0"; private static final String CONTROL_POINT_DESCRIPTOR_UUID = "00002902-0000-1000-8000-00805f9b34fb"; private final Image image; private BluetoothGattCharacteristic controlPoint; private BluetoothGattCharacteristic data; private BluetoothGattCharacteristic state; private ImageBlock currentBlock; public FirmwareUpdater(final Image image) { this.image = image; } @Override public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) { if (BluetoothProfile.STATE_CONNECTED == newState) { Log.i(TAG, "GATT connected."); gatt.discoverServices(); } else { Log.i(TAG, "GATT disconnected."); gatt.connect(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { Log.i(TAG, "Services discovered."); if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) { gatt.disconnect(); gatt.getDevice().createBond(); return; } // Get GATT characteristics from the device. final BluetoothGattService otapService = gatt.getService(UUID.fromString(OTAP_SERVICE_UUID)); controlPoint = otapService.getCharacteristic(UUID.fromString(Characteristic.CONTROL_POINT.getUuid())); data = otapService.getCharacteristic(UUID.fromString(Characteristic.DATA.getUuid())); state = otapService.getCharacteristic(UUID.fromString(Characteristic.STATE.getUuid())); gatt.readCharacteristic(state); } @Override public void onCharacteristicRead(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { if (characteristic != state) { return; } final int value = ByteUtils.parseInt(characteristic.getValue()); if (value == 0) { Log.e(TAG, "OTAP mode is not enabled."); return; } final boolean wrongModeForMK64 = value == 1 && image.getType() == Image.Type.MK64; final boolean wrongModeForKW40 = value == 2 && image.getType() == Image.Type.KW40; if (wrongModeForMK64 || wrongModeForKW40) { Log.e(TAG, "Wrong OTAP mode for the selected image."); sendBroadcast(UPDATE_ERROR); return; } Log.i(TAG, "The device is in the correct OTAP mode. Starting communication."); gatt.setCharacteristicNotification(controlPoint, true); final BluetoothGattDescriptor descriptor = controlPoint .getDescriptor(UUID.fromString(CONTROL_POINT_DESCRIPTOR_UUID)); gatt.readDescriptor(descriptor); } @Override public void onDescriptorRead(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) { descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE); gatt.writeDescriptor(descriptor); } @Override public void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) { final byte[] value = characteristic.getValue(); final Command command = Command.byCommandByte(value[0]); Log.i(TAG, "Received command: " + command); switch (command) { case NEW_IMAGE_INFO_REQUEST: controlPoint.setValue(image.getNewImageInfoResponse()); gatt.writeCharacteristic(controlPoint); Log.i(TAG, "Writing command: NEW_IMAGE_INFO_RESPONSE"); final PendingIntent pendingIntent = PendingIntent.getBroadcast(FirmwareUpdateService.this, 0, new Intent(CANCEL_UPDATE), 0); notificationBuilder.addAction(R.drawable.ic_clear_white_24dp, getString(R.string.firmware_update_cancel), pendingIntent); setNotificationText(R.string.firmware_update_start); sendBroadcast(UPDATE_INITIATED); break; case IMAGE_BLOCK_REQUEST: final ImageBlockRequest imageBlockRequest = new ImageBlockRequest(value); Log.i(TAG, "Block request is: " + imageBlockRequest); currentBlock = image.getBlock(imageBlockRequest); setProgress(imageBlockRequest.getStartPosition()); writeChunk(gatt); break; case IMAGE_TRANSFER_COMPLETE: final ImageTransferComplete imageTransferComplete = new ImageTransferComplete(value); Log.i(TAG, "Image transfer completed: " + imageTransferComplete); setNotificationText(R.string.firmware_update_complete); sendBroadcast(UPDATE_FINISHED); stopService(); break; case ERROR_NOTIFICATION: final ErrorNotification errorNotification = new ErrorNotification(value); Log.e(TAG, "Error during firmware update: " + errorNotification); setNotificationText(R.string.firmware_update_error); sendBroadcast(UPDATE_ERROR); stopService(); break; default: break; } } @Override public void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) { if (characteristic == controlPoint) { Log.i(TAG, "Successfully written to control point."); } else if (characteristic == data && !currentBlock.isCompleted()) { writeChunk(gatt); } } private synchronized void writeChunk(BluetoothGatt gatt) { // The first block will block and stop OTAP if the chunks are sent too fast. try { wait(50); } catch (InterruptedException e) { e.printStackTrace(); } Log.v(TAG, "Writing chunk " + currentBlock.getPosition() + " / " + currentBlock.getNumberOfChunks()); data.setValue(currentBlock.getNextChunk()); gatt.writeCharacteristic(data); } private void setProgress(final int value) { notificationBuilder.setContentText(getString(R.string.firmware_update_notification_text)); final int progress = (int) (((double) value / image.getSize()) * 100); notificationBuilder.setProgress(100, progress, false); notificationBuilder.setOngoing(true); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); final Intent progressUpdated = new Intent(UPDATE_PROGRESS); progressUpdated.putExtra(UPDATE_PROGRESS_VALUE, progress); sendBroadcast(progressUpdated); } private void setNotificationText(final int stringResource) { notificationBuilder.setContentText(FirmwareUpdateService.this.getString(stringResource)); notificationBuilder.setProgress(0, 0, false); notificationBuilder.setOngoing(false); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } } public class ServiceBinder extends Binder { private FirmwareUpdateService service; public ServiceBinder(final FirmwareUpdateService service) { this.service = service; } public FirmwareUpdateService getService() { return service; } } }