package com.firstbuild.androidapp.paragon; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.util.Log; import com.firstbuild.androidapp.FirstBuildApplication; import com.firstbuild.androidapp.OpalValues; import com.firstbuild.androidapp.opal.OpalMainActivity; import com.firstbuild.commonframework.blemanager.BleManager; import com.firstbuild.tools.MainQueue; import com.firstbuild.tools.MathTools; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; /** * Created by Hollis on 11/17/15. */ public class OtaManager { private static OtaManager ourInstance = new OtaManager(); private String TAG = OtaManager.class.getSimpleName(); private static final int OTA_STEP_NONE = 0; private static final int OTA_STEP_COMPARE_VERSION = 1; private static final int OTA_STEP_SEND_IMAGE_SIZE = 2; private static final int OTA_STEP_SEND_IMAGE_DATA = 3; private static final int OTA_STEP_SEND_IMAGE_DONE = 4; private static final int OTA_STEP_REBOOT = 5; private static final int OTA_BLE_STEP_NONE = OTA_STEP_REBOOT + 1; private static final int OTA_BLE_PREPARE_DOWNLOAD = OTA_BLE_STEP_NONE + 1; private static final int OTA_BLE_STEP_LOAD_IMAGE_AND_SEND_IMAGE_SIZE = OTA_BLE_PREPARE_DOWNLOAD + 1; private static final int OTA_BLE_STEP_SEND_IMAGE_DATA = OTA_BLE_STEP_LOAD_IMAGE_AND_SEND_IMAGE_SIZE + 1; private static final int OTA_BLE_STEP_SEND_IMAGE_DONE = OTA_BLE_STEP_SEND_IMAGE_DATA + 1; private static final int OTA_BLE_STEP_REBOOT = OTA_BLE_STEP_SEND_IMAGE_DONE + 1; private static final int OTA_OPAL_STEP_NONE = OTA_BLE_STEP_REBOOT + 1; private static final int OTA_OPAL_PREPARE_DOWNLOAD = OTA_OPAL_STEP_NONE + 1; private static final int OTA_OPAL_STEP_LOAD_IMAGE_AND_SEND_IMAGE_HEADER = OTA_OPAL_PREPARE_DOWNLOAD + 1; private static final int OTA_OPAL_STEP_SEND_IMAGE_DATA = OTA_OPAL_STEP_LOAD_IMAGE_AND_SEND_IMAGE_HEADER + 1; private static final int OTA_OPAL_STEP_SEND_IMAGE_DONE = OTA_OPAL_STEP_SEND_IMAGE_DATA + 1; private static final int OTA_OPAL_STEP_BINARY_INSTALL_IN_PROGRESS = OTA_OPAL_STEP_SEND_IMAGE_DONE + 1; private static final int OTA_OPAL_STEP_REBOOT = OTA_OPAL_STEP_BINARY_INSTALL_IN_PROGRESS + 1; private static final int SIZE_CHUNK = 20; private static final int SIZE_CHECKSUM = 160; private Context context; private int versionMajor = 0; private int versionMinor = 0; private int versionBuild = 0; private int currentStep; private byte[] imageChunk = null; private int transferTotalCount = 0; private int transferOffset = 0; private int currentInstallProgress = -1; private BluetoothDevice currentDevice; private int otaImageSize; // in byte OpalMainActivity.OTAResultDelegate delegate; public static OtaManager getInstance() { return ourInstance; } private OtaManager() { } public int getTransferCount() { return transferTotalCount; } public int getTransferOffset() { return transferOffset; } /** * Read image file from asset and get majoro version and minor version. */ public void readImageFile(Context context) { // Read image file file form asset and get current version. this.context = context; AssetManager assetManager = context.getAssets(); InputStream inputStream; byte[] versionChunk = new byte[4]; try { String[] fileNames =assetManager.list("image"); inputStream = assetManager.open("image/"+fileNames[0]); // Image file contained its version number in for four byte from 5th data. inputStream.skip(4); inputStream.read(versionChunk); inputStream.close(); versionMajor = versionChunk[0]; versionMinor = versionChunk[1]; versionBuild = (short) versionChunk[2]; } catch (Exception e) { Log.d(TAG, "Not found image file, Skip OTA"); versionMajor = 0; versionMinor = 0; versionBuild = 0; } Log.d(TAG, "readImageFile version is :" + versionMajor + ", " + versionMinor); } /** * Compare the version number between get from Paragon Master and image file. * * @param versionMajor Major version number get from Paragon. * @param versionMinor Minor version number bet from Paragon. * @return If need to ge update then return true, other than false. */ public boolean compareVersion(int versionMajor, int versionMinor, int versionBuild) { Log.d(TAG, "compareVersion : Paragon version :" + versionMajor + "." + versionMinor + "." + versionBuild); Log.d(TAG, "compareVersion : Image version :" + this.versionMajor + "." + this.versionMinor + "." + this.versionBuild); boolean isNeedUpdate; if (this.versionMajor > versionMajor || this.versionMinor > versionMinor || this.versionBuild > versionBuild) { isNeedUpdate = true; } else { isNeedUpdate = false; } return isNeedUpdate; } public void startProcess(){ currentStep = OTA_STEP_COMPARE_VERSION; ByteBuffer valueBuffer = ByteBuffer.allocate(3); valueBuffer.put((byte) 1); // BleManager.getInstance().writeCharacteristics(ParagonValues.CHARACTERISTIC_OTA_COMMAND, valueBuffer.array()); Log.d(TAG, "ParagonValues.CHARACTERISTIC_OTA_COMMAND Send:" + valueBuffer.toString()); } public void startBleOtaProcess(BluetoothDevice device, OpalMainActivity.OTAResultDelegate resultDelegate){ resetOtaStates(); currentStep = OTA_BLE_PREPARE_DOWNLOAD; currentDevice = device; delegate = resultDelegate; ByteBuffer valueBuffer = ByteBuffer.allocate(1); Log.d(TAG, "startBleOtaProcess : Sending image type : " + OpalValues.OPAL_BLE_IMAGE_TYPE); valueBuffer.put(OpalValues.OPAL_BLE_IMAGE_TYPE); BleManager.getInstance().writeCharacteristics(device, OpalValues.OPAL_IMG_TYPE_UUID, valueBuffer.array()); valueBuffer.clear(); Log.d(TAG, "startBleOtaProcess : Sending Prepare Download Control Command : " + OpalValues.OPAL_CONTROL_COMMAND_PREPARE_DOWNLOAD); valueBuffer.put(OpalValues.OPAL_CONTROL_COMMAND_PREPARE_DOWNLOAD); BleManager.getInstance().writeCharacteristics(device, OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID, valueBuffer.array()); // Should wait for the notification value 0x00 for OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID Log.d(TAG, "startBleOtaProcess : Should wait for the notification value 0x00 for OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID"); } public void startOpalOtaProcess(BluetoothDevice device, OpalMainActivity.OTAResultDelegate resultDelegate){ resetOtaStates(); currentStep = OTA_OPAL_PREPARE_DOWNLOAD; currentDevice = device; delegate = resultDelegate; ByteBuffer valueBuffer = ByteBuffer.allocate(1); Log.d(TAG, "startOpalOtaProcess : Sending image type : " + OpalValues.OPAL_OPAL_IMAGE_TYPE); valueBuffer.put(OpalValues.OPAL_OPAL_IMAGE_TYPE); BleManager.getInstance().writeCharacteristics(device, OpalValues.OPAL_IMG_TYPE_UUID, valueBuffer.array()); valueBuffer.clear(); Log.d(TAG, "startOpalOtaProcess : Sending Prepare Download Control Command : " + OpalValues.OPAL_CONTROL_COMMAND_PREPARE_DOWNLOAD); valueBuffer.put(OpalValues.OPAL_CONTROL_COMMAND_PREPARE_DOWNLOAD); BleManager.getInstance().writeCharacteristics(device, OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID, valueBuffer.array()); // Should wait for the notification value 0x00 for OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID Log.d(TAG, "startOpalOtaProcess : Should wait for the notification value 0x00 for OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID"); } /** * Prepare to update. Show the popup, modify image file with pad if need. */ public void prepareImageFile() { currentStep = OTA_STEP_SEND_IMAGE_DATA; AssetManager assetManager = context.getAssets(); InputStream inputStream; int imageSize = 0; try { // Open image file String[] fileNames = assetManager.list("image"); inputStream = assetManager.open("image/"+fileNames[0]); // get file image file size and get the size to be padded. imageSize = inputStream.available(); int padSize = imageSize % SIZE_CHUNK; // If the image size is not divide by 20byte then pad the remained byte. // The image size is contained data and checksum chunk. The checksum chunk is be the last // 160byte of the image file. if (padSize > 0) { padSize = SIZE_CHUNK - padSize; } else { //do nothing. } // Allocate image buffer. byte[] dataChunk = new byte[imageSize - SIZE_CHECKSUM + padSize]; byte[] checksumChunk = new byte[SIZE_CHECKSUM]; imageChunk = new byte[dataChunk.length + checksumChunk.length]; // Read data chunk except last 160 byte of checksum chunk. inputStream.read(dataChunk, 0, imageSize - SIZE_CHECKSUM); inputStream.read(checksumChunk, 0, SIZE_CHECKSUM); inputStream.close(); System.arraycopy(dataChunk, 0, imageChunk, 0, dataChunk.length); System.arraycopy(checksumChunk, 0, imageChunk, imageChunk.length-checksumChunk.length, checksumChunk.length); transferTotalCount = (int)(imageChunk.length / (float)SIZE_CHUNK); transferOffset = 0; } catch (Exception e) { Log.d(TAG, "readImageFile :" + e); } if(transferTotalCount > 0) { ByteBuffer valueBuffer = ByteBuffer.allocate(3); valueBuffer.put((byte) 2); valueBuffer.put(1, (byte) (imageSize & 0xff)); valueBuffer.put(2, (byte) ((imageSize >> 8) & 0xff)); // BleManager.getInstance().writeCharacteristics(ParagonValues.CHARACTERISTIC_OTA_COMMAND, valueBuffer.array()); Log.d(TAG, "ParagonValues.CHARACTERISTIC_OTA_COMMAND Send OTA_STEP_SEND_IMAGE_DATA:" + valueBuffer.toString()); } else{ Log.d(TAG, "OTA image file read failed"); ((ParagonMainActivity) context).failedOta(); } } private void sendBleImageSize() { currentStep = OTA_BLE_STEP_LOAD_IMAGE_AND_SEND_IMAGE_SIZE; Log.d(TAG, "sendBleImageSize : image name : " + OpalValues.LATEST_OPAL_BLE_FIRMWARE_NAME); // load ble image into Memory, checksum is available boolean loadSuccess = loadImageToMemory(OpalValues.LATEST_OPAL_BLE_FIRMWARE_NAME, true); if( loadSuccess == false) { Log.d(TAG, "sendBleImageSize : load failed !"); onBLEUpdateFailed(); } else { ByteBuffer valueBuffer = ByteBuffer.allocate(3); // As we put image size into 2byte bucket => // max image size should be 2 to the 16th power = 65,536 // => 64KByte valueBuffer.put((byte) 0x02); valueBuffer.put(1, (byte) (otaImageSize & 0xff)); valueBuffer.put(2, (byte) ((otaImageSize >> 8) & 0xff)); Log.d(TAG, "sendBleImageSize : OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID : " + " Image size to send : " + MathTools.byteArrayToHex(valueBuffer.array())); BleManager.getInstance().writeCharacteristics(currentDevice, OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID, valueBuffer.array()); } } private void sendOpalImageHeader() { currentStep = OTA_OPAL_STEP_LOAD_IMAGE_AND_SEND_IMAGE_HEADER; Log.d(TAG, "sendOpalImageHeader : image name : " + OpalValues.LATEST_OPAL_FIRMWARE_NAME); // load Opal image into Memory , checksum is not available boolean loadSuccess = loadImageToMemory(OpalValues.LATEST_OPAL_FIRMWARE_NAME, false); if( loadSuccess == false) { Log.d(TAG, "sendOpalImageHeader : load failed !"); onBLEUpdateFailed(); } else { ByteBuffer valueBuffer = ByteBuffer.allocate(20); // Image type: 1 byte (should be 0x01 : Opal image type), valueBuffer.put((byte) 0x01); // Opal firmware version:4 bytes, byte[] version = MathTools.hexToByteArray(OpalValues.LATEST_OPAL_FIRMWARE_VERSION); valueBuffer.put(version[0]); valueBuffer.put(version[1]); valueBuffer.put(version[2]); valueBuffer.put(version[3]); // file size: 4 bytes + random value 11 bytes // As we put image size into 2byte bucket => // max image size should be 2 to the 16th power = 65,536 // => 64KByte String hex = String.format("%08x", otaImageSize); byte[] imageSize = MathTools.hexToByteArray(hex); // img size => reverse order valueBuffer.put(imageSize[3]); valueBuffer.put(imageSize[2]); valueBuffer.put(imageSize[1]); valueBuffer.put(imageSize[0]); Log.d(TAG, "sendOpalImageHeader : OpalValues.OPAL_IMAGE_DATA_CHAR_UUID : " + " Image header to send : " + MathTools.byteArrayToHex(valueBuffer.array())); BleManager.getInstance().writeCharacteristics(currentDevice, OpalValues.OPAL_IMAGE_DATA_CHAR_UUID, valueBuffer.array()); } } private void sendOpalImageData() { if (transferOffset < transferTotalCount) { currentStep = OTA_OPAL_STEP_SEND_IMAGE_DATA; ByteBuffer valueBuffer; // if it is last chunk to send and it is dividable by 20, // then send only available bytes if(transferOffset == transferTotalCount - 1) { int lastChunkLength = imageChunk.length % SIZE_CHUNK; if (lastChunkLength != 0) { valueBuffer = ByteBuffer.allocate(lastChunkLength); valueBuffer.put(imageChunk, transferOffset * SIZE_CHUNK, lastChunkLength); } else { valueBuffer = ByteBuffer.allocate(SIZE_CHUNK); valueBuffer.put(imageChunk, transferOffset * SIZE_CHUNK, SIZE_CHUNK); } } else { valueBuffer = ByteBuffer.allocate(SIZE_CHUNK); valueBuffer.put(imageChunk, transferOffset * SIZE_CHUNK, SIZE_CHUNK); } byte[] data = valueBuffer.array(); BleManager.getInstance().writeCharacteristics(currentDevice, OpalValues.OPAL_IMAGE_DATA_CHAR_UUID, data); Log.d(TAG, "sendOpalImageData() : Sending 20 bytes image chunk : OpalValues.OPAL_IMAGE_DATA_CHAR_UUID :" + transferOffset + ", " + MathTools.byteArrayToHex(data)); transferOffset++; if(delegate != null) { delegate.onOTAProgressChanged(transferOffset); } } else { currentStep = OTA_OPAL_STEP_SEND_IMAGE_DONE; ByteBuffer valueBuffer = ByteBuffer.allocate(1); valueBuffer.put((byte) 0x03); BleManager.getInstance().writeCharacteristics(currentDevice, OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID, valueBuffer.array()); Log.d(TAG, "sendOpalImageData() : Sending verification command(0x03) for completion of sending binary !"); } } private void sendBleImageData() { if (transferOffset < transferTotalCount) { currentStep = OTA_BLE_STEP_SEND_IMAGE_DATA; ByteBuffer valueBuffer = ByteBuffer.allocate(SIZE_CHUNK); valueBuffer.put(imageChunk, transferOffset * SIZE_CHUNK, SIZE_CHUNK); byte[] data = valueBuffer.array(); BleManager.getInstance().writeCharacteristics(currentDevice, OpalValues.OPAL_IMAGE_DATA_CHAR_UUID, data); Log.d(TAG, "sendBleImageData() : Sending 20 bytes image chunk : OpalValues.OPAL_IMAGE_DATA_CHAR_UUID :" + transferOffset + ", " + MathTools.byteArrayToHex(data)); transferOffset++; if(delegate != null) { delegate.onOTAProgressChanged(transferOffset); } } else { currentStep = OTA_BLE_STEP_SEND_IMAGE_DONE; ByteBuffer valueBuffer = ByteBuffer.allocate(1); valueBuffer.put((byte) 0x03); BleManager.getInstance().writeCharacteristics(currentDevice, OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID, valueBuffer.array()); Log.d(TAG, "sendBleImageData() : Sending verification command(0x03) for completion of sending binary !"); } } private boolean loadImageToMemory(String imageName, boolean isChecksumAvailable) { Log.d(TAG, "loadImageToMemory() : In Image name to load : " + imageName); boolean loadSuccess = false; AssetManager assetManager = FirstBuildApplication.getInstance().getContext().getAssets(); try { AssetFileDescriptor fd = assetManager.openFd("image/" + imageName); Long imageSize = fd.getLength(); otaImageSize = imageSize.intValue(); Log.d(TAG, "Binary Image size in byte: " + otaImageSize); } catch (IOException e) { e.printStackTrace(); } if(otaImageSize == 0) { loadSuccess = false; } else { try { // Open BLE image file InputStream inputStream = assetManager.open("image/"+ imageName); int padSize = otaImageSize % SIZE_CHUNK; // If the image size is not divide by 20byte then pad the remained byte. // The image size is contained data and checksum chunk. The checksum chunk is be the last // 160byte of the image file. if (padSize > 0) { padSize = SIZE_CHUNK - padSize; } else { //do nothing. } // Allocate image buffer. if(isChecksumAvailable == true) { byte[] dataChunk = new byte[otaImageSize - SIZE_CHECKSUM + padSize]; byte[] checksumChunk = new byte[SIZE_CHECKSUM]; imageChunk = new byte[dataChunk.length + checksumChunk.length]; // Read data chunk except last 160 byte of checksum chunk. inputStream.read(dataChunk, 0, otaImageSize - SIZE_CHECKSUM ); inputStream.read(checksumChunk, 0, SIZE_CHECKSUM); inputStream.close(); System.arraycopy(dataChunk, 0, imageChunk, 0, dataChunk.length); System.arraycopy(checksumChunk, 0, imageChunk, imageChunk.length-checksumChunk.length, checksumChunk.length); } else { // Opal Firmware image should not add padding to meet SIZE_CHUNK * N byte[] dataChunk = new byte[otaImageSize]; imageChunk = new byte[dataChunk.length]; // Read data chunk inputStream.read(dataChunk, 0, otaImageSize); inputStream.close(); System.arraycopy(dataChunk, 0, imageChunk, 0, dataChunk.length); } int totalImgSize = imageChunk.length; if(totalImgSize % SIZE_CHUNK != 0) { totalImgSize += padSize; } transferTotalCount = totalImgSize / SIZE_CHUNK; transferOffset = 0; } catch (Exception e) { Log.d(TAG, "loadImageToMemory :" + e); loadSuccess = false; } if(transferTotalCount > transferOffset) { loadSuccess = true; // Let the UI to know the max progress value if(delegate != null) { delegate.onOTAProgressMax(transferTotalCount); } } } return loadSuccess; } /** * Send image data to Paragon Master 20 byte every transfer. */ private void transferData() { Log.d(TAG, "transferData : transferOffset :"+transferOffset + ", transferCount :"+transferTotalCount); if (transferOffset < transferTotalCount) { currentStep = OTA_STEP_SEND_IMAGE_DATA; ByteBuffer valueBuffer = ByteBuffer.allocate(SIZE_CHUNK); valueBuffer.put(imageChunk, transferOffset * SIZE_CHUNK, SIZE_CHUNK); // BleManager.getInstance().writeCharacteristics(ParagonValues.CHARACTERISTIC_OTA_DATA, valueBuffer.array()); Log.d(TAG, "ParagonValues.CHARACTERISTIC_OTA_DATA Send OTA_STEP_SEND_IMAGE_DATA:" + transferOffset + ", " + valueBuffer.toString()); transferOffset++; } else { currentStep = OTA_STEP_SEND_IMAGE_DONE; ByteBuffer valueBuffer = ByteBuffer.allocate(3); valueBuffer.put((byte) 3); // BleManager.getInstance().writeCharacteristics(ParagonValues.CHARACTERISTIC_OTA_COMMAND, valueBuffer.array()); Log.d(TAG, "ParagonValues.CHARACTERISTIC_OTA_COMMAND Send OTA_STEP_SEND_IMAGE_DONE:" + valueBuffer.toString()); } } private void successfulDone() { Log.d(TAG, "successfulDone"); ((ParagonMainActivity) context).succeedOta(); } private void onBLEUpdateSuccess() { Log.d(TAG, "onBLEUpdateSuccess"); if(delegate != null) { delegate.onOTASuccessful(); } resetOtaStates(); } private void onBLEUpdateFailed() { Log.d(TAG, "onBLEUpdateFailed"); if(delegate != null) { delegate.onOTAFailed(); } resetOtaStates(); } private void resetOtaStates() { context = null; versionMajor = 0; versionMinor = 0; versionBuild = 0; currentStep = OTA_STEP_NONE; imageChunk = null; transferTotalCount = 0; transferOffset = 0; currentInstallProgress = -1; currentDevice = null; otaImageSize = 0; delegate = null; } public void getResponse(byte response) { if (response == 0) { switch (currentStep) { case OTA_STEP_NONE: //do nothging break; case OTA_STEP_COMPARE_VERSION: prepareImageFile(); break; case OTA_STEP_SEND_IMAGE_SIZE: transferData(); break; case OTA_STEP_SEND_IMAGE_DATA: transferData(); break; case OTA_STEP_SEND_IMAGE_DONE: successfulDone(); break; case OTA_STEP_REBOOT: break; case OTA_BLE_PREPARE_DOWNLOAD: sendBleImageSize(); break; case OTA_BLE_STEP_LOAD_IMAGE_AND_SEND_IMAGE_SIZE: sendBleImageData(); break; case OTA_BLE_STEP_SEND_IMAGE_DATA: sendBleImageData(); break; case OTA_BLE_STEP_SEND_IMAGE_DONE: onBLEUpdateSuccess(); break; case OTA_BLE_STEP_REBOOT: break; case OTA_OPAL_PREPARE_DOWNLOAD: sendOpalImageHeader(); break; case OTA_OPAL_STEP_LOAD_IMAGE_AND_SEND_IMAGE_HEADER: sendOpalImageData(); break; case OTA_OPAL_STEP_SEND_IMAGE_DATA: sendOpalImageData(); break; case OTA_OPAL_STEP_SEND_IMAGE_DONE: // prepare install progress UI from BLE to Opal Device prepareOpalImageInstallProgress(); break; case OTA_OPAL_STEP_REBOOT: break; } } else { Log.d(TAG, "getResponse : get error response :" + response + ", step :" + currentStep); if(currentStep >= OTA_BLE_STEP_NONE && currentStep <= OTA_OPAL_STEP_REBOOT) { onBLEUpdateFailed(); } } } private void prepareOpalImageInstallProgress() { if(delegate != null) { currentStep = OTA_OPAL_STEP_BINARY_INSTALL_IN_PROGRESS; delegate.onOpalBinaryInstallPrepare(); currentInstallProgress = 0; } } public void responseWriteData() { Log.d(TAG, "responseWriteData"); transferData(); } public void onHandleWriteResponse(String uuid, final int status) { switch(uuid.toUpperCase()) { case OpalValues.OPAL_IMAGE_DATA_CHAR_UUID: // Let it run on the Main Thread MainQueue.post(new Runnable() { @Override public void run() { getResponse((byte)status); } }); break; default : } } public void onHandleNotification(String uuid, final byte[] value) { switch (uuid.toUpperCase()) { case OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID: Log.d(TAG, "onHandleNotification : uuid : OpalValues.OPAL_OTA_CONTROL_COMMAND_CHAR_UUID : value : "+ MathTools.byteArrayToHex(value)); MainQueue.post(new Runnable() { @Override public void run() { getResponse(value[0]); } }); break; case OpalValues.OPAL_UPDATE_PROGRESS_UUID: Log.d(TAG, "onHandleNotification : uuid : OpalValues.OPAL_UPDATE_PROGRESS_UUID : value : " + MathTools.byteArrayToHex(value) ); final int progress = MathTools.hexByteToInt(value[0]); if(currentStep == OTA_OPAL_STEP_BINARY_INSTALL_IN_PROGRESS && progress != currentInstallProgress) { currentInstallProgress = progress; MainQueue.post(new Runnable() { @Override public void run() { if(delegate != null) { delegate.onOpalBinaryInstallProgress(progress); if(progress == 100) { currentStep = OTA_OPAL_STEP_REBOOT; resetOtaStates(); } } } }); } break; default: Log.d(TAG, "onHandleNotification : Not Handled ! " + uuid + " value : " + MathTools.byteArrayToHex(value)); break; } } }