Android Open Source - metawear-antifitness Dfu Service






From Project

Back to project page metawear-antifitness.

License

The source code is released under:

GNU General Public License

If you think the Android project metawear-antifitness listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*******************************************************************************
 * Copyright (c) 2013 Nordic Semiconductor. All Rights Reserved.
 * /*from w w  w.  j  av  a 2  s .  c o  m*/
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 * Licensees are granted free, non-transferable use of the information. NO WARRANTY of ANY KIND is provided. 
 * This heading must NOT be removed from the file.
 ******************************************************************************/

/*
 * NORDIC SEMICONDUTOR EXAMPLE CODE AND LICENSE AGREEMENT
 *
 * You are receiving this document because you have obtained example code (?Software?) 
 * from Nordic Semiconductor ASA * (?Licensor?). The Software is protected by copyright 
 * laws and international treaties. All intellectual property rights related to the 
 * Software is the property of the Licensor. This document is a license agreement governing 
 * your rights and obligations regarding usage of the Software. Any variation to the terms 
 * of this Agreement shall only be valid if made in writing by the Licensor.
 * 
 * == Scope of license rights ==
 * 
 * You are hereby granted a limited, non-exclusive, perpetual right to use and modify the 
 * Software in order to create your own software. You are entitled to distribute the 
 * Software in original or modified form as part of your own software.
 *
 * If distributing your software in source code form, a copy of this license document shall 
 * follow with the distribution.
 *   
 * The Licensor can at any time terminate your rights under this license agreement.
 * 
 * == Restrictions on license rights ==
 * 
 * You are not allowed to distribute the Software on its own, without incorporating it into 
 * your own software.  
 * 
 * You are not allowed to remove, alter or destroy any proprietary, 
 * trademark or copyright markings or notices placed upon or contained with the Software.
 *     
 * You shall not use Licensor?s name or trademarks without Licensor?s prior consent.
 * 
 * == Disclaimer of warranties and limitation of liability ==
 * 
 * YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT USE OF THE SOFTWARE IS AT YOUR OWN RISK AND THAT THE 
 * SOFTWARE IS PROVIDED *AS IS" WITHOUT ANY WARRANTIES OR CONDITIONS WHATSOEVER. NORDIC SEMICONDUCTOR ASA 
 * DOES NOT WARRANT THAT THE FUNCTIONS OF THE SOFTWARE WILL MEET YOUR REQUIREMENTS OR THAT THE 
 * OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR FREE. YOU ASSUME RESPONSIBILITY FOR 
 * SELECTING THE SOFTWARE TO ACHIEVE YOUR INTENDED RESULTS, AND FOR THE *USE AND THE RESULTS 
 * OBTAINED FROM THE SOFTWARE.
 * 
 * NORDIC SEMICONDUCTOR ASA DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 * TO WARRANTIES RELATED TO: NON-INFRINGEMENT, LACK OF VIRUSES, ACCURACY OR COMPLETENESS OF RESPONSES 
 * OR RESULTS, IMPLIED  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 * 
 * IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL OR 
 * CONSEQUENTIAL DAMAGES OR FOR ANY DAMAGES WHATSOEVER (INCLUDING BUT NOT LIMITED TO DAMAGES FOR 
 * LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, PERSONAL INJURY, 
 * LOSS OF PRIVACY OR OTHER PECUNIARY OR OTHER LOSS WHATSOEVER) ARISING OUT OF USE OR INABILITY TO 
 * USE THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * REGARDLESS OF THE FORM OF ACTION, NORDIC SEMICONDUCTOR ASA AGGREGATE LIABILITY ARISING OUT OF 
 * OR RELATED TO THIS AGREEMENT SHALL NOT EXCEED THE TOTAL AMOUNT PAYABLE BY YOU UNDER THIS AGREEMENT. 
 * THE FOREGOING LIMITATIONS, EXCLUSIONS AND DISCLAIMERS SHALL APPLY TO THE MAXIMUM EXTENT ALLOWED BY 
 * APPLICABLE LAW.
 * 
 * == Dispute resolution and legal venue ==
 * 
 * Any and all disputes arising out of the rights and obligations in this license agreement shall be 
 * submitted to ordinary court proceedings. You accept the Oslo City Court as legal venue under this agreement.
 * 
 * This license agreement shall be governed by Norwegian law.
 * 
 * == Contact information ==
 * 
 * All requests regarding the Software or the API shall be directed to: 
 * Nordic Semiconductor ASA, P.O. Box 436, Sk?yen, 0213 Oslo, Norway.
 * 
 * http://www.nordicsemi.com/eng/About-us/Contact-us
 */
package no.nordicsemi.android.nrftoolbox.dfu;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.UUID;

import za.co.paulscott.antifitness.BuildConfig;
import za.co.paulscott.antifitness.R;

import com.mbientlab.metawear.api.characteristic.MetaWear;
import com.mbientlab.metawear.api.GATT.GATTService;
import com.mbientlab.metawear.api.controller.Debug;
import com.mbientlab.metawear.api.util.Registers;

import no.nordicsemi.android.log.LogContract.Log.Level;
import no.nordicsemi.android.log.LogSession;
import no.nordicsemi.android.log.Logger;
import no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsFragment;
import no.nordicsemi.android.nrftoolbox.utility.GattError;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
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.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

public class DfuService extends IntentService {
  private static final String TAG = "DfuService";

  public static final String EXTRA_DEVICE_ADDRESS = "no.nordicsemi.android.extra.dfu.EXTRA_DEVICE_ADDRESS";
  public static final String EXTRA_DEVICE_NAME = "no.nordicsemi.android.extra.dfu.EXTRA_DEVICE_NAME";
  public static final String EXTRA_LOG_URI = "no.nordicsemi.android.extra.dfu.EXTRA_LOG_URI";
  public static final String EXTRA_FILE_PATH = "no.nordicsemi.android.extra.dfu.EXTRA_FILE_PATH";
  public static final String EXTRA_FILE_URI = "no.nordicsemi.android.extra.dfu.EXTRA_FILE_URI";
  public static final String EXTRA_DATA = "no.nordicsemi.android.extra.dfu.EXTRA_DATA";

  public static final String PREFS_DFU_IN_PROGRESS = "no.nordicsemi.android.nrftoolbox.dfu.PREFS_DFU_IN_PROGRESS";

  public static final String BROADCAST_ERROR = "no.nordicsemi.android.broadcast.dfu.BROADCAST_ERROR";
  public static final int ERROR_MASK = 0x0100;
  public static final int ERROR_DEVICE_DISCONNECTED = ERROR_MASK | 0x00;
  public static final int ERROR_FILE_NOT_FOUND = ERROR_MASK | 0x01;
  public static final int ERROR_FILE_CLOSED = ERROR_MASK | 0x02;
  public static final int ERROR_FILE_INVALID = ERROR_MASK | 0x03;
  public static final int ERROR_FILE_IO_EXCEPTION = ERROR_MASK | 0x04;
  public static final int ERROR_SERVICE_DISCOVERY_NOT_STARTED = ERROR_MASK | 0x05;
  public static final int ERROR_SERVICE_NOT_FOUND = ERROR_MASK | 0x06;
  public static final int ERROR_CHARACTERISTICS_NOT_FOUND = ERROR_MASK | 0x07;
  public static final int ERROR_INVALID_RESPONSE = ERROR_MASK | 0x08;
  /** Look for DFU specification to get error codes */
  public static final int ERROR_REMOTE_MASK = 0x0200;
  public static final int ERROR_CONNECTION_MASK = 0x0400;

  public static final String BROADCAST_PROGRESS = "no.nordicsemi.android.broadcast.dfu.BROADCAST_PROGRESS";
  public static final int PROGRESS_CONNECTING = -1;
  public static final int PROGRESS_STARTING = -2;
  public static final int PROGRESS_VALIDATING = -4;
  public static final int PROGRESS_DISCONNECTING = -5;
  public static final int PROGRESS_COMPLETED = -6;
  public static final int PROGRESS_ABORTED = -7;

  /**
   * The log events are only broadcasted when there is no LogView application
   * installed
   */
  public static final String BROADCAST_LOG = "no.nordicsemi.android.broadcast.dfu.BROADCAST_LOG";
  public static final String EXTRA_LOG_MESSAGE = "no.nordicsemi.android.extra.dfu.EXTRA_LOG_INFO";
  public static final String EXTRA_LOG_LEVEL = "no.nordicsemi.android.extra.dfu.EXTRA_LOG_LEVEL";

  /**
   * Activity may broadcast this broadcast in order to pause, resume or abort
   * DFU process
   */
  public static final String BROADCAST_ACTION = "no.nordicsemi.android.broadcast.dfu.BROADCAST_ACTION";
  public static final String EXTRA_ACTION = "no.nordicsemi.android.extra.dfu.EXTRA_ACTION";
  public static final int ACTION_PAUSE = 0;
  public static final int ACTION_RESUME = 1;
  public static final int ACTION_ABORT = 2;

  public static final int NOTIFICATION_ID = 283; // a random number

  private BluetoothAdapter mBluetoothAdapter;
  private String mDeviceAddress;
  private String mDeviceName;
  private LogSession mLogSession;
  /** Lock used in synchronization purposes */
  private final Object mLock = new Object();

  /**
   * The number of the last error that has occurred or 0 if there was no error
   */
  private int mErrorState;
  /**
   * The current connection state. If its value is > 0 than an error has
   * occurred. Error number is a negative value of mConnectionState
   */
  private int mConnectionState;
  private final static int STATE_DISCONNECTED = 0;
  private final static int STATE_CONNECTING = -1;
  private final static int STATE_CONNECTED = -2;
  private final static int STATE_CONNECTED_AND_READY = -3; // indicates that
                                // services were
                                // discovered
  private final static int STATE_DISCONNECTING = -4;
  private final static int STATE_CLOSED = -5;

  /**
   * Flag set when we got confirmation from the device that notifications are
   * enabled.
   */
  private boolean mNotificationsEnabled;

  private final static int MAX_PACKET_SIZE = 20; // the maximum number of
                          // bytes in one packet is
                          // 20. May be less.
  /**
   * The number of packets of firmware data to be send before receiving a new
   * Packets receipt notification. 0 disables the packets notifications
   */
  private int mPacketsBeforeNotification = 10;

  private byte[] mBuffer = new byte[MAX_PACKET_SIZE];
  private HexInputStream mHexInputStream;
  private int mImageSizeInBytes;
  private int mImageSizeInPackets;
  private int mBytesSent;
  private int mBytesConfirmed;
  private int mPacketsSendSinceNotification;
  private boolean mPaused;
  private boolean mAborted;

  /**
   * Flag indicating whether the image size has been already transfered or not
   */
  private boolean mImageSizeSent;
  /** Flag indicating whether the request was completed or not */
  private boolean mRequestCompleted;

  /** Latest data received from device using notification. */
  private byte[] mReceivedData = null;
  private static final int OP_CODE_RECEIVE_START_DFU_KEY = 0x01; // 1
  private static final int OP_CODE_RECEIVE_FIRMWARE_IMAGE_KEY = 0x03; // 3
  private static final int OP_CODE_RECEIVE_VALIDATE_KEY = 0x04; // 4
  private static final int OP_CODE_RECEIVE_ACTIVATE_AND_RESET_KEY = 0x05; // 5
  private static final int OP_CODE_RECEIVE_RESET_KEY = 0x06; // 6
  // private static final int OP_CODE_PACKET_REPORT_RECEIVED_IMAGE_SIZE_KEY =
  // 0x07; // 7
  private static final int OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY = 0x08; // 8
  private static final int OP_CODE_RESPONSE_CODE_KEY = 0x10; // 16
  private static final int OP_CODE_PACKET_RECEIPT_NOTIF_KEY = 0x11; // 11
  private static final byte[] OP_CODE_START_DFU = new byte[] { OP_CODE_RECEIVE_START_DFU_KEY };
  private static final byte[] OP_CODE_RECEIVE_FIRMWARE_IMAGE = new byte[] { OP_CODE_RECEIVE_FIRMWARE_IMAGE_KEY };
  private static final byte[] OP_CODE_VALIDATE = new byte[] { OP_CODE_RECEIVE_VALIDATE_KEY };
  private static final byte[] OP_CODE_ACTIVATE_AND_RESET = new byte[] { OP_CODE_RECEIVE_ACTIVATE_AND_RESET_KEY };
  private static final byte[] OP_CODE_RESET = new byte[] { OP_CODE_RECEIVE_RESET_KEY };
  // private static final byte[] OP_CODE_REPORT_RECEIVED_IMAGE_SIZE = new
  // byte[] { OP_CODE_PACKET_REPORT_RECEIVED_IMAGE_SIZE_KEY };
  private static final byte[] OP_CODE_PACKET_RECEIPT_NOTIF_REQ = new byte[] {
      OP_CODE_PACKET_RECEIPT_NOTIF_REQ_KEY, 0x00, 0x00 };

  public static final int DFU_STATUS_SUCCESS = 1;
  public static final int DFU_STATUS_INVALID_STATE = 2;
  public static final int DFU_STATUS_NOT_SUPPORTED = 3;
  public static final int DFU_STATUS_DATA_SIZE_EXCEEDS_LIMIT = 4;
  public static final int DFU_STATUS_CRC_ERROR = 5;
  public static final int DFU_STATUS_OPERATION_FAILED = 6;

  public static final UUID DFU_SERVICE_UUID = new UUID(0x000015301212EFDEl,
      0x1523785FEABCD123l);
  private static final UUID DFU_CONTROL_POINT_UUID = new UUID(
      0x000015311212EFDEl, 0x1523785FEABCD123l);
  private static final UUID DFU_PACKET_UUID = new UUID(0x000015321212EFDEl,
      0x1523785FEABCD123l);
  private static final UUID CLIENT_CHARACTERISTIC_CONFIG = new UUID(
      0x0000290200001000l, 0x800000805f9b34fbl);

  private final BroadcastReceiver mDfuActionReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(final Context context, final Intent intent) {
      final int action = intent.getIntExtra(EXTRA_ACTION, 0);

      switch (action) {
      case ACTION_PAUSE:
        mPaused = true;
        break;
      case ACTION_RESUME:
        mPaused = false;

        // notify waiting thread
        synchronized (mLock) {
          mLock.notifyAll();
        }
        break;
      case ACTION_ABORT:
        mPaused = false;
        mAborted = true;

        // notify waiting thread
        synchronized (mLock) {
          mLock.notifyAll();
        }
        break;
      }
    }
  };

  public static String ACTION_RECONNECT_BOOTLOADER = "ACTION_RECONNECT_BOOTLOADER";
  private BluetoothGattCallback normalCallback = new BluetoothGattCallback() {

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
        int newState) {
      switch (newState) {
      case BluetoothProfile.STATE_CONNECTED:
        gatt.discoverServices();
        break;
      case BluetoothProfile.STATE_DISCONNECTED:
        mConnectionState = STATE_DISCONNECTED;
        synchronized (mLock) {
          mLock.notifyAll();
        }
        break;
      }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
      BluetoothGattService mwBleService = gatt
          .getService(GATTService.METAWEAR.uuid());
      BluetoothGattCharacteristic cmdRegister = mwBleService
          .getCharacteristic(MetaWear.COMMAND.uuid());
      cmdRegister.setValue(Registers
          .buildWriteCommand(Debug.Register.JUMP_TO_BOOTLOADER));

      gatt.writeCharacteristic(cmdRegister);
    }

  };

  private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(final BluetoothGatt gatt,
        final int status, final int newState) {
      // check whether an error occurred
      if (status == BluetoothGatt.GATT_SUCCESS) {
        if (newState == BluetoothGatt.STATE_CONNECTED) {
          logi("Connected to GATT server");
          mConnectionState = STATE_CONNECTED;

          // Attempts to discover services after successful
          // connection.
          // do not refresh the gatt device here!
          final boolean success = gatt.discoverServices();
          logi("Attempting to start service discovery... "
              + (success ? "succeed" : "failed"));

          if (!success) {
            mErrorState = ERROR_SERVICE_DISCOVERY_NOT_STARTED;
          } else {
            // just return here, lock will be notified when service
            // discovery finishes
            return;
          }
        } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
          logi("Disconnected from GATT server");
          mConnectionState = STATE_DISCONNECTED;
        }
      } else {
        loge("Connection state change error: " + status + " newState: "
            + newState);
        mErrorState = ERROR_CONNECTION_MASK | status;
      }

      // notify waiting thread
      synchronized (mLock) {
        mLock.notifyAll();
        return;
      }
    }

    @Override
    public void onServicesDiscovered(final BluetoothGatt gatt,
        final int status) {
      if (status == BluetoothGatt.GATT_SUCCESS) {
        logi("Services discovered");
        mConnectionState = STATE_CONNECTED_AND_READY;
      } else {
        loge("Service discovery error: " + status);
        mErrorState = ERROR_CONNECTION_MASK | status;
      }

      // notify waiting thread
      synchronized (mLock) {
        mLock.notifyAll();
        return;
      }
    }

    @Override
    public void onDescriptorWrite(final BluetoothGatt gatt,
        final BluetoothGattDescriptor descriptor, final int status) {
      if (status == BluetoothGatt.GATT_SUCCESS) {
        if (CLIENT_CHARACTERISTIC_CONFIG.equals(descriptor.getUuid())) {
          // we have enabled or disabled characteristic
          mNotificationsEnabled = descriptor.getValue()[0] == 1;
        }
      } else {
        loge("Descriptor write error: " + status);
        mErrorState = ERROR_CONNECTION_MASK | status;
      }

      // notify waiting thread
      synchronized (mLock) {
        mLock.notifyAll();
        return;
      }
    };

    @Override
    public void onCharacteristicWrite(final BluetoothGatt gatt,
        final BluetoothGattCharacteristic characteristic,
        final int status) {
      if (status == BluetoothGatt.GATT_SUCCESS) {
        /*
         * This method is called when either a CONTROL POINT or PACKET
         * characteristic has been written. If it is the CONTROL POINT
         * characteristic, just set the flag to true. If the PACKET
         * characteristic was written we must: - if the image size was
         * written in DFU Start procedure, just set flag to true - else
         * - send the next packet, if notification is not required at
         * that moment - do nothing, because we have to wait for the
         * notification to confirm the data received
         */
        if (DFU_PACKET_UUID.equals(characteristic.getUuid())) {
          if (mImageSizeSent) {
            // if the PACKET characteristic was written with image
            // data, update counters
            mBytesSent += characteristic.getValue().length;
            mPacketsSendSinceNotification++;

            // if a packet receipt notification is expected, or the
            // last packet was sent, do nothing. There
            // onCharacteristicChanged listener will catch either
            // a packet confirmation (if there are more bytes to
            // send) or the image received notification (it upload
            // process was completed)
            final boolean notificationExpected = mPacketsBeforeNotification > 0
                && mPacketsSendSinceNotification == mPacketsBeforeNotification;
            final boolean lastPacketTransfered = mBytesSent == mImageSizeInBytes;

            if (notificationExpected || lastPacketTransfered)
              return;

            // when neither of them is true, send the next packet
            try {
              waitIfPaused();
              if (mAborted) {
                // notify waiting thread
                synchronized (mLock) {
                  mLock.notifyAll();
                  return;
                }
              }

              final byte[] buffer = mBuffer;
              final int size = mHexInputStream.readPacket(buffer);
              writePacket(gatt, characteristic, buffer, size);
              updateProgressNotification();
              return;
            } catch (final HexFileValidationException e) {
              loge("Invalid HEX file");
              mErrorState = ERROR_FILE_INVALID;
            } catch (final IOException e) {
              loge("Error while reading the input stream", e);
              mErrorState = ERROR_FILE_IO_EXCEPTION;
            }
          } else {
            // we've got confirmation that the image size was sent
            mImageSizeSent = true;
          }
        } else {
          // if the CONTROL POINT characteristic was written just set
          // the flag to true
          mRequestCompleted = true;
        }
      } else {
        loge("Characteristic write error: " + status);
        mErrorState = ERROR_CONNECTION_MASK | status;
      }

      // notify waiting thread
      synchronized (mLock) {
        mLock.notifyAll();
        return;
      }
    };

    @Override
    public void onCharacteristicChanged(final BluetoothGatt gatt,
        final BluetoothGattCharacteristic characteristic) {
      final int responseType = characteristic.getIntValue(
          BluetoothGattCharacteristic.FORMAT_UINT8, 0);

      switch (responseType) {
      case OP_CODE_PACKET_RECEIPT_NOTIF_KEY:
        final BluetoothGattCharacteristic packetCharacteristic = gatt
            .getService(DFU_SERVICE_UUID).getCharacteristic(
                DFU_PACKET_UUID);

        try {
          mBytesConfirmed = characteristic.getIntValue(
              BluetoothGattCharacteristic.FORMAT_UINT32, 1);
          mPacketsSendSinceNotification = 0;

          waitIfPaused();
          if (mAborted)
            break;

          final byte[] buffer = mBuffer;
          final int size = mHexInputStream.readPacket(buffer);
          writePacket(gatt, packetCharacteristic, buffer, size);
          updateProgressNotification();
          return;
        } catch (final HexFileValidationException e) {
          loge("Invalid HEX file");
          mErrorState = ERROR_FILE_INVALID;
        } catch (final IOException e) {
          loge("Error while reading the input stream", e);
          mErrorState = ERROR_FILE_IO_EXCEPTION;
        }
        break;
      default:
        mReceivedData = characteristic.getValue();
        break;
      }

      // notify waiting thread
      synchronized (mLock) {
        mLock.notifyAll();
        return;
      }
    };
  };

  public DfuService() {
    super(TAG);
  }

  @Override
  public void onCreate() {
    super.onCreate();

    final LocalBroadcastManager manager = LocalBroadcastManager
        .getInstance(this);
    manager.registerReceiver(mDfuActionReceiver,
        makeDfuActionIntentFilter());
  }

  @Override
  public void onDestroy() {
    super.onDestroy();

    final LocalBroadcastManager manager = LocalBroadcastManager
        .getInstance(this);
    manager.unregisterReceiver(mDfuActionReceiver);
  }

  @Override
  protected void onHandleIntent(final Intent intent) {
    final SharedPreferences preferences = PreferenceManager
        .getDefaultSharedPreferences(this);
    // In order to let DfuActivity know whether DFU is in progress, we have
    // to use Shared Preferences
    final SharedPreferences.Editor editor = preferences.edit();
    editor.putBoolean(PREFS_DFU_IN_PROGRESS, true);
    editor.commit();

    initialize();

    final String deviceAddress = intent
        .getStringExtra(EXTRA_DEVICE_ADDRESS);
    final String deviceName = intent.getStringExtra(EXTRA_DEVICE_NAME);
    final String filePath = intent.getStringExtra(EXTRA_FILE_PATH);
    final Uri fileUri = intent.getParcelableExtra(EXTRA_FILE_URI);
    final Uri logUri = intent.getParcelableExtra(EXTRA_LOG_URI);
    mLogSession = Logger.openSession(this, logUri);

    mDeviceAddress = deviceAddress;
    mDeviceName = deviceName;
    mConnectionState = STATE_DISCONNECTED;

    // read preferences
    final boolean packetReceiptNotificationEnabled = preferences
        .getBoolean(
            SettingsFragment.SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED,
            true);
    final String value = preferences
        .getString(
            SettingsFragment.SETTINGS_NUMBER_OF_PACKETS,
            String.valueOf(SettingsFragment.SETTINGS_NUMBER_OF_PACKETS_DEFAULT));
    int numberOfPackets = SettingsFragment.SETTINGS_NUMBER_OF_PACKETS_DEFAULT;
    try {
      numberOfPackets = Integer.parseInt(value);
      if (numberOfPackets < 0 || numberOfPackets > 0xFFFF)
        numberOfPackets = SettingsFragment.SETTINGS_NUMBER_OF_PACKETS_DEFAULT;
    } catch (final NumberFormatException e) {
      numberOfPackets = SettingsFragment.SETTINGS_NUMBER_OF_PACKETS_DEFAULT;
    }
    if (!packetReceiptNotificationEnabled)
      numberOfPackets = 0;
    mPacketsBeforeNotification = numberOfPackets;

    sendLogBroadcast(Level.VERBOSE, "Starting DFU service");

    HexInputStream his = null;
    try {
      // Prepare data to send, calculate stream size
      try {
        sendLogBroadcast(Level.VERBOSE, "Opening file...");
        if (fileUri != null)
          his = openInputStream(fileUri);
        else
          his = openInputStream(filePath);

        mImageSizeInBytes = his.sizeInBytes();
        mImageSizeInPackets = his.sizeInPackets(MAX_PACKET_SIZE);
        mHexInputStream = his;
        sendLogBroadcast(Level.INFO, "Image file opened ("
            + mImageSizeInBytes + " bytes)");
      } catch (final FileNotFoundException e) {
        loge("An exception occured while opening file", e);
        sendErrorBroadcast(ERROR_FILE_NOT_FOUND);
        return;
      } catch (final IOException e) {
        loge("An exception occured while calculating file size", e);
        sendErrorBroadcast(ERROR_FILE_CLOSED);
        return;
      }

      // Let's connect to the device
      sendLogBroadcast(Level.VERBOSE, "Connecting to DFU target...");
      updateProgressNotification(PROGRESS_CONNECTING);

      final BluetoothGatt gatt = connect(deviceAddress);
      // Are we connected?
      if (mErrorState > 0) { // error occurred
        final int error = mErrorState & ~ERROR_CONNECTION_MASK;
        loge("An error occurred while connecting to the device:"
            + error);
        sendLogBroadcast(Level.ERROR, String.format(
            "Connection failed (0x%02X): %s", error,
            GattError.parse(error)));
        terminateConnection(gatt, mErrorState);
        return;
      }
      if (mAborted) {
        logi("Upload aborted");
        sendLogBroadcast(Level.WARNING, "Upload aborted");
        terminateConnection(gatt, PROGRESS_ABORTED);
        return;
      }

      // We have connected to DFU device and services are discoverer
      final BluetoothGattService dfuService = gatt
          .getService(DFU_SERVICE_UUID); // there was a case when the
                          // service was null. I don't
                          // know why
      if (dfuService == null) {
        loge("DFU service does not exists on the device");
        sendLogBroadcast(Level.WARNING,
            "Connected. DFU Service not found");
        terminateConnection(gatt, ERROR_SERVICE_NOT_FOUND);
        return;
      }
      final BluetoothGattCharacteristic controlPointCharacteristic = dfuService
          .getCharacteristic(DFU_CONTROL_POINT_UUID);
      final BluetoothGattCharacteristic packetCharacteristic = dfuService
          .getCharacteristic(DFU_PACKET_UUID);
      if (controlPointCharacteristic == null
          || packetCharacteristic == null) {
        loge("DFU characteristics not found in the DFU service");
        sendLogBroadcast(Level.WARNING,
            "Connected. DFU Characteristics not found");
        terminateConnection(gatt, ERROR_CHARACTERISTICS_NOT_FOUND);
        return;
      }

      sendLogBroadcast(Level.INFO, "Connected. Services discovered");
      try {
        // enable notifications
        updateProgressNotification(PROGRESS_STARTING);
        setCharacteristicNotification(gatt, controlPointCharacteristic,
            true);
        sendLogBroadcast(Level.INFO, "Notifications enabled");

        try {
          // set up the temporary variable that will hold the
          // responses
          byte[] response = null;

          // send Start DFU command to Control Point
          logi("Sending Start DFU command (Op Code = 1)");
          writeOpCode(gatt, controlPointCharacteristic,
              OP_CODE_START_DFU);
          sendLogBroadcast(Level.INFO, "DFU Start sent (Op Code 1) ");

          // send image size in bytes to DFU Packet
          logi("Sending image size in bytes to DFU Packet");
          writeImageSize(gatt, packetCharacteristic,
              mImageSizeInBytes);
          sendLogBroadcast(Level.INFO, "Firmware image size sent");

          // a notification will come with confirmation. Let's wait
          // for it a bit
          response = readNotificationResponse();

          /*
           * The response received from the DFU device contains:
           * +------
           * ---+--------+--------------------------------------
           * --------------+ | byte no | value | description |
           * +--------
           * -+--------+--------------------------------------
           * --------------+ | 0 | 16 | Response code | | 1 | 1 | The
           * Op Code of a request that this response is for | | 2 |
           * STATUS | See DFU_STATUS_* for status codes |
           * +---------+--
           * ------+--------------------------------------
           * --------------+
           */
          int status = getStatusCode(response,
              OP_CODE_RECEIVE_START_DFU_KEY);
          sendLogBroadcast(Level.INFO, "Responce received (Op Code: "
              + response[1] + " Status: " + status + ")");
          if (status != DFU_STATUS_SUCCESS)
            throw new RemoteDfuException("Starting DFU failed",
                status);

          // Send the number of packets of firmware before receiving a
          // receipt notification
          final int numberOfPacketsBeforeNotification = mPacketsBeforeNotification;
          if (numberOfPacketsBeforeNotification > 0) {
            logi("Sending the number of packets before notifications (Op Code = 8)");
            setNumberOfPackets(OP_CODE_PACKET_RECEIPT_NOTIF_REQ,
                numberOfPacketsBeforeNotification);
            writeOpCode(gatt, controlPointCharacteristic,
                OP_CODE_PACKET_RECEIPT_NOTIF_REQ);
            sendLogBroadcast(Level.INFO,
                "Packet Receipt Notif Req (Op Code 8) sent (value: "
                    + mPacketsBeforeNotification + ")");
          }

          // Initialize firmware upload
          logi("Sending Receive Firmware Image request (Op Code = 3)");
          writeOpCode(gatt, controlPointCharacteristic,
              OP_CODE_RECEIVE_FIRMWARE_IMAGE);
          sendLogBroadcast(Level.INFO,
              "Receive Firmware Image request sent");

          // This allow us to calculate upload time
          final long startTime = System.currentTimeMillis();
          updateProgressNotification();
          try {
            sendLogBroadcast(Level.INFO, "Starting upload...");
            response = uploadFirmwareImage(gatt,
                packetCharacteristic, his);
          } catch (final DeviceDisconnectedException e) {
            loge("Disconnected while sending data");
            throw e;
            // TODO reconnect?
          }

          final long endTime = System.currentTimeMillis();
          logi("Transfer of " + mBytesSent + " bytes has taken "
              + (endTime - startTime) + " ms");
          sendLogBroadcast(Level.INFO, "Upload completed in "
              + (endTime - startTime) + " ms");

          // Check the result of the operation
          status = getStatusCode(response,
              OP_CODE_RECEIVE_FIRMWARE_IMAGE_KEY);
          sendLogBroadcast(Level.INFO, "Responce received (Op Code: "
              + response[1] + " Status: " + status + ")");
          logi("Response received. Op Code: " + response[0]
              + " Req Op Code: " + response[1] + " status: "
              + response[2]);
          if (status != DFU_STATUS_SUCCESS)
            throw new RemoteDfuException(
                "Device returned error after sending file",
                status);

          // Send Validate request
          logi("Sending Validate request (Op Code = 4)");
          writeOpCode(gatt, controlPointCharacteristic,
              OP_CODE_VALIDATE);
          sendLogBroadcast(Level.INFO, "Validate request sent");

          // A notification will come with status code. Let's wait for
          // it a bit.
          response = readNotificationResponse();
          status = getStatusCode(response,
              OP_CODE_RECEIVE_VALIDATE_KEY);
          sendLogBroadcast(Level.INFO, "Responce received (Op Code: "
              + response[1] + " Status: " + status + ")");
          if (status != DFU_STATUS_SUCCESS)
            throw new RemoteDfuException(
                "Device returned validation error", status);

          // Disable notifications locally (we don't need to disable
          // them on the device, it will reset)
          updateProgressNotification(PROGRESS_DISCONNECTING);
          gatt.setCharacteristicNotification(
              controlPointCharacteristic, false);
          sendLogBroadcast(Level.INFO, "Notifications disabled");

          // Send Activate and Reset signal.
          logi("Sending Activate and Reset request (Op Code = 5)");
          writeOpCode(gatt, controlPointCharacteristic,
              OP_CODE_ACTIVATE_AND_RESET);
          sendLogBroadcast(Level.INFO,
              "Activate and Reset request sent");

          // The device will reset so we don't have to send Disconnect
          // signal.
          waitUntilDisconnected();
          sendLogBroadcast(Level.INFO,
              "Disconnected by remote device");

          // Close the device
          refreshDeviceCache(gatt);
          close(gatt);
          updateProgressNotification(PROGRESS_COMPLETED);
        } catch (final UnknownResponseException e) {
          final int error = ERROR_INVALID_RESPONSE;
          loge(e.getMessage());
          sendLogBroadcast(Level.ERROR, e.getMessage());

          // This causes GATT_ERROR 0x85 on Nexus 4 (4.4.2)
          // logi("Sending Reset command (Op Code = 6)");
          // writeOpCode(gatt, controlPointCharacteristic,
          // OP_CODE_RESET);
          // sendLogBroadcast(Level.INFO, "Reset request sent");
          terminateConnection(gatt, error);
        } catch (final RemoteDfuException e) {
          final int error = ERROR_REMOTE_MASK | e.getErrorNumber();
          loge(e.getMessage());
          sendLogBroadcast(
              Level.ERROR,
              String.format("Remote DFU error: %s",
                  GattError.parse(error)));

          // This causes GATT_ERROR 0x85 on Nexus 4 (4.4.2)
          // logi("Sending Reset command (Op Code = 6)");
          // writeOpCode(gatt, controlPointCharacteristic,
          // OP_CODE_RESET);
          // sendLogBroadcast(Level.INFO, "Reset request sent");
          terminateConnection(gatt, error);
        }
      } catch (final UploadAbortedException e) {
        logi("Upload aborted");
        sendLogBroadcast(Level.WARNING, "Upload aborted");
        if (mConnectionState == STATE_CONNECTED_AND_READY)
          try {
            mAborted = false;
            logi("Sending Reset command (Op Code = 6)");
            writeOpCode(gatt, controlPointCharacteristic,
                OP_CODE_RESET);
            sendLogBroadcast(Level.INFO, "Reset request sent");
          } catch (final Exception e1) {
            // do nothing
          }
        terminateConnection(gatt, PROGRESS_ABORTED);
      } catch (final DeviceDisconnectedException e) {
        sendLogBroadcast(Level.ERROR, "Device has disconneted");
        // TODO reconnect n times?
        loge(e.getMessage());
        if (mNotificationsEnabled)
          gatt.setCharacteristicNotification(
              controlPointCharacteristic, false);
        close(gatt);
        updateProgressNotification(ERROR_DEVICE_DISCONNECTED);
        return;
      } catch (final DfuException e) {
        final int error = e.getErrorNumber() & ~ERROR_CONNECTION_MASK;
        sendLogBroadcast(
            Level.ERROR,
            String.format("Error (0x%02X): %s", error,
                GattError.parse(error)));
        loge(e.getMessage());
        if (mConnectionState == STATE_CONNECTED_AND_READY)
          try {
            logi("Sending Reset command (Op Code = 6)");
            writeOpCode(gatt, controlPointCharacteristic,
                OP_CODE_RESET);
          } catch (final Exception e1) {
            // do nothing
          }
        terminateConnection(gatt, e.getErrorNumber());
      }
    } finally {
      try {
        // upload has finished (success of fail)
        editor.putBoolean(PREFS_DFU_IN_PROGRESS, false);
        editor.commit();

        // ensure that input stream is always closed
        mHexInputStream = null;
        if (his != null)
          his.close();
        his = null;
      } catch (IOException e) {
        // do nothing
      }
    }
  }

  /**
   * Sets number of data packets that will be send before the notification
   * will be received
   * 
   * @param data
   *            control point data packet
   * @param value
   *            number of packets before receiving notification. If this value
   *            is 0, then the notification of packet receipt will be disabled
   *            by the DFU target.
   */
  private void setNumberOfPackets(final byte[] data, final int value) {
    data[1] = (byte) (value & 0xFF);
    data[2] = (byte) ((value >> 8) & 0xFF);
  }

  /**
   * Opens the binary input stream from a HEX file. A Path to the HEX file is
   * given
   * 
   * @param filePath
   *            the path to the HEX file
   * @return the binary input stream with Intel HEX data
   * @throws FileNotFoundException
   */
  private HexInputStream openInputStream(final String filePath)
      throws FileNotFoundException, IOException {
    final InputStream is = new FileInputStream(filePath);
    return new HexInputStream(is);
  }

  /**
   * Opens the binary input stream from a HEX file. A Uri to the stream is
   * given
   * 
   * @param stream
   *            the Uri to the stream
   * @return the binary input stream with Intel HEX data
   * @throws FileNotFoundException
   */
  private HexInputStream openInputStream(final Uri stream)
      throws FileNotFoundException, IOException {
    final InputStream is = getContentResolver().openInputStream(stream);
    return new HexInputStream(is);
  }

  /**
   * Connects to the BLE device with given address. This method is
   * SYNCHRONOUS, it wait until the connection status change from
   * {@link #STATE_CONNECTING} to {@link #STATE_CONNECTED_AND_READY} or an
   * error occurs.
   * 
   * @param address
   *            the device address
   * @return the GATT device
   */
  private BluetoothGatt connect(final String address) {
    mConnectionState = STATE_CONNECTING;

    logi("Connecting to the device...");
    final BluetoothDevice device = mBluetoothAdapter
        .getRemoteDevice(address);
    BluetoothGatt gatt = device.connectGatt(this, false, normalCallback);

    // We have to wait until the device is connected and services are
    // discovered
    // Connection error may occur as well.
    try {
      synchronized (mLock) {
        while (((mConnectionState == STATE_CONNECTING || mConnectionState == STATE_CONNECTED)
            && mErrorState == 0 && !mAborted)
            || mPaused)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }

    mConnectionState = STATE_CONNECTING;
    refreshDeviceCache(gatt);
    gatt = device.connectGatt(this, false, mGattCallback);
    try {
      synchronized (mLock) {
        while (((mConnectionState == STATE_CONNECTING || mConnectionState == STATE_CONNECTED)
            && mErrorState == 0 && !mAborted)
            || mPaused)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }
    return gatt;
  }

  /**
   * Disconnects from the device and cleans local variables in case of error.
   * This method is SYNCHRONOUS and wait until the disconnecting process will
   * be completed.
   * 
   * @param gatt
   *            the GATT device to be disconnected
   * @param error
   *            error number
   */
  private void terminateConnection(final BluetoothGatt gatt, final int error) {
    if (mConnectionState != STATE_DISCONNECTED) {
      updateProgressNotification(PROGRESS_DISCONNECTING);

      // disable notifications
      try {
        final BluetoothGattService dfuService = gatt
            .getService(DFU_SERVICE_UUID);
        if (dfuService != null) {
          final BluetoothGattCharacteristic controlPointCharacteristic = dfuService
              .getCharacteristic(DFU_CONTROL_POINT_UUID);
          setCharacteristicNotification(gatt,
              controlPointCharacteristic, false);
          sendLogBroadcast(Level.INFO, "Notifications disabled");
        }
      } catch (final DeviceDisconnectedException e) {
        // do nothing
      } catch (final DfuException e) {
        // do nothing
      } catch (final Exception e) {
        // do nothing
      }

      // Disconnect from the device
      disconnect(gatt);
      sendLogBroadcast(Level.INFO, "Disconnected");
    }

    // Close the device
    refreshDeviceCache(gatt);
    close(gatt);
    updateProgressNotification(error);
  }

  /**
   * Disconnects from the device. This is SYNCHRONOUS method and waits until
   * the callback returns new state. Terminates immediately if device is
   * already disconnected. Do not call this method directly, use
   * {@link #terminateConnection(BluetoothGatt, int)} instead.
   * 
   * @param gatt
   *            the GATT device that has to be disconnected
   */
  private void disconnect(final BluetoothGatt gatt) {
    if (mConnectionState == STATE_DISCONNECTED)
      return;

    mConnectionState = STATE_DISCONNECTING;

    logi("Disconnecting from the device...");
    gatt.disconnect();

    // We have to wait until device gets disconnected or an error occur
    waitUntilDisconnected();
  }

  /**
   * Wait until the connection state will change to
   * {@link #STATE_DISCONNECTED} or until an error occurs.
   */
  private void waitUntilDisconnected() {
    try {
      synchronized (mLock) {
        while (mConnectionState != STATE_DISCONNECTED
            && mErrorState == 0)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }
  }

  /**
   * Closes the GATT device and cleans up.
   * 
   * @param gatt
   *            the GATT device to be closed
   */
  private void close(final BluetoothGatt gatt) {
    logi("Cleaning up...");
    gatt.close();
    mConnectionState = STATE_CLOSED;
  }

  /**
   * Clears the device cache. After uploading new firmware the DFU target will
   * have other services than before.
   * 
   * @param gatt
   *            the GATT device to be refreshed
   */
  private void refreshDeviceCache(final BluetoothGatt gatt) {
    /*
     * There is a refresh() method in BluetoothGatt class but for now it's
     * hidden. We will call it using reflections.
     */
    try {
      final Method refresh = gatt.getClass().getMethod("refresh");
      if (refresh != null) {
        final boolean success = (Boolean) refresh.invoke(gatt);
        logi("Refreshing result: " + success);
      }
    } catch (Exception e) {
      loge("An exception occured while refreshing device", e);
    }
  }

  /**
   * Checks whether the response received is valid and returns the status
   * code.
   * 
   * @param response
   *            the response received from the DFU device.
   * @param request
   *            the expected Op Code
   * @return the status code
   * @throws UnknownResponseException
   *             if response was not valid
   */
  private int getStatusCode(final byte[] response, final int request)
      throws UnknownResponseException {
    if (response == null || response.length != 3
        || response[0] != OP_CODE_RESPONSE_CODE_KEY
        || response[1] != request || response[2] < 1 || response[2] > 6)
      throw new UnknownResponseException("Invalid response received",
          response, request);
    return response[2];
  }

  /**
   * Enables or disables the notifications for given characteristic. This
   * method is SYNCHRONOUS and wait until the
   * {@link BluetoothGattCallback#onDescriptorWrite(BluetoothGatt, BluetoothGattDescriptor, int)}
   * will be called or the connection state will change from
   * {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or
   * an error will occur, an exception will be thrown.
   * 
   * @param gatt
   *            the GATT device
   * @param characteristic
   *            the characteristic to enable or disable notifications for
   * @param enable
   *            <code>true</code> to enable notifications, <code>false</code>
   *            to disable them
   * @throws DfuException
   * @throws UploadAbortedException
   */
  private void setCharacteristicNotification(final BluetoothGatt gatt,
      final BluetoothGattCharacteristic characteristic,
      final boolean enable) throws DeviceDisconnectedException,
      DfuException, UploadAbortedException {
    if (mConnectionState != STATE_CONNECTED_AND_READY)
      throw new DeviceDisconnectedException(
          "Unable to set notifications state", mConnectionState);
    mErrorState = 0;

    if (mNotificationsEnabled == enable)
      return;

    logi((enable ? "Enabling " : "Disabling") + " notifications...");

    // enable notifications locally
    gatt.setCharacteristicNotification(characteristic, enable);

    // enable notifications on the device
    final BluetoothGattDescriptor descriptor = characteristic
        .getDescriptor(CLIENT_CHARACTERISTIC_CONFIG);
    descriptor
        .setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
    gatt.writeDescriptor(descriptor);

    // We have to wait until device gets disconnected or an error occur
    try {
      synchronized (mLock) {
        while ((mNotificationsEnabled != enable
            && mConnectionState == STATE_CONNECTED_AND_READY
            && mErrorState == 0 && !mAborted)
            || mPaused)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }
    if (mAborted)
      throw new UploadAbortedException();
    if (mErrorState != 0)
      throw new DfuException("Unable to set notifications state",
          mErrorState);
    if (mConnectionState != STATE_CONNECTED_AND_READY)
      throw new DeviceDisconnectedException(
          "Unable to set notifications state", mConnectionState);
  }

  /**
   * Writes the operation code to the characteristic. This method is
   * SYNCHRONOUS and wait until the
   * {@link BluetoothGattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)}
   * will be called or the connection state will change from
   * {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or
   * an error will occur, an exception will be thrown.
   * 
   * @param gatt
   *            the GATT device
   * @param characteristic
   *            the characteristic to write to. Should be the DFU CONTROL
   *            POINT
   * @param value
   *            the value to write to the characteristic
   * @throws DeviceDisconnectedException
   * @throws DfuException
   * @throws UploadAbortedException
   */
  private void writeOpCode(final BluetoothGatt gatt,
      final BluetoothGattCharacteristic characteristic, final byte[] value)
      throws DeviceDisconnectedException, DfuException,
      UploadAbortedException {
    mReceivedData = null;
    mErrorState = 0;
    mRequestCompleted = false;

    characteristic.setValue(value);
    gatt.writeCharacteristic(characteristic);

    // We have to wait for confirmation
    try {
      synchronized (mLock) {
        while ((mRequestCompleted == false
            && mConnectionState == STATE_CONNECTED_AND_READY
            && mErrorState == 0 && !mAborted)
            || mPaused)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }
    if (mAborted)
      throw new UploadAbortedException();
    if (mErrorState != 0)
      throw new DfuException("Unable to write Op Code " + value[0],
          mErrorState);
    if (mConnectionState != STATE_CONNECTED_AND_READY)
      throw new DeviceDisconnectedException("Unable to write Op Code "
          + value[0], mConnectionState);
  }

  /**
   * Writes the image size to the characteristic. This method is SYNCHRONOUS
   * and wait until the
   * {@link BluetoothGattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)}
   * will be called or the connection state will change from
   * {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or
   * an error will occur, an exception will be thrown.
   * 
   * @param gatt
   *            the GATT device
   * @param characteristic
   *            the characteristic to write to. Should be the DFU PACKET
   * @param imageSize
   *            the image size in bytes
   * @throws DeviceDisconnectedException
   * @throws DfuException
   * @throws UploadAbortedException
   */
  private void writeImageSize(final BluetoothGatt gatt,
      final BluetoothGattCharacteristic characteristic,
      final int imageSize) throws DeviceDisconnectedException,
      DfuException, UploadAbortedException {
    mReceivedData = null;
    mErrorState = 0;
    mImageSizeSent = false;

    characteristic
        .setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
    characteristic.setValue(imageSize,
        BluetoothGattCharacteristic.FORMAT_UINT32, 0);
    gatt.writeCharacteristic(characteristic);

    // We have to wait for confirmation
    try {
      synchronized (mLock) {
        while ((mImageSizeSent == false
            && mConnectionState == STATE_CONNECTED_AND_READY
            && mErrorState == 0 && !mAborted)
            || mPaused)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }
    if (mAborted)
      throw new UploadAbortedException();
    if (mErrorState != 0)
      throw new DfuException("Unable to write Image Size", mErrorState);
    if (mConnectionState != STATE_CONNECTED_AND_READY)
      throw new DeviceDisconnectedException("Unable to write Image Size",
          mConnectionState);
  }

  /**
   * Starts sending the data. This method is SYNCHRONOUS and terminates when
   * the whole file will be uploaded or the connection status will change from
   * {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or
   * an error will occur, an exception will be thrown.
   * 
   * @param gatt
   *            the GATT device (DFU target)
   * @param packetCharacteristic
   *            the characteristic to write file content to. Must be the DFU
   *            PACKET
   * @return The response value received from notification with Op Code = 3
   *         when all bytes will be uploaded successfully.
   * @throws DeviceDisconnectedException
   *             Thrown when the device will disconnect in the middle of the
   *             transmission. The error core will be saved in
   *             {@link #mConnectionState}.
   * @throws DfuException
   *             Thrown if DFU error occur
   * @throws UploadAbortedException
   */
  private byte[] uploadFirmwareImage(final BluetoothGatt gatt,
      final BluetoothGattCharacteristic packetCharacteristic,
      final HexInputStream inputStream)
      throws DeviceDisconnectedException, DfuException,
      UploadAbortedException {
    mReceivedData = null;
    mErrorState = 0;

    final byte[] buffer = mBuffer;
    try {
      final int size = inputStream.readPacket(buffer);
      writePacket(gatt, packetCharacteristic, buffer, size);
    } catch (final HexFileValidationException e) {
      throw new DfuException("HEX file not valid", ERROR_FILE_INVALID);
    } catch (final IOException e) {
      throw new DfuException("Error while reading file",
          ERROR_FILE_IO_EXCEPTION);
    }

    try {
      synchronized (mLock) {
        while ((mReceivedData == null
            && mConnectionState == STATE_CONNECTED_AND_READY
            && mErrorState == 0 && !mAborted)
            || mPaused)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }
    if (mAborted)
      throw new UploadAbortedException();
    if (mErrorState != 0)
      throw new DfuException("Uploading Fimrware Image failed",
          mErrorState);
    if (mConnectionState != STATE_CONNECTED_AND_READY)
      throw new DeviceDisconnectedException(
          "Uploading Fimrware Image failed: device disconnected",
          mConnectionState);

    return mReceivedData;
  }

  /**
   * Writes the buffer to the characteristic. The maximum size of the buffer
   * is 20 bytes. This method is ASYNCHRONOUS and returns immediately after
   * adding the data to TX queue.
   * 
   * @param gatt
   *            the GATT device
   * @param characteristic
   *            the characteristic to write to. Should be the DFU PACKET
   * @param buffer
   *            the buffer with 1-20 bytes
   * @param size
   *            the number of bytes from the buffer to send
   */
  private void writePacket(final BluetoothGatt gatt,
      final BluetoothGattCharacteristic characteristic,
      final byte[] buffer, final int size) {
    byte[] locBuffer = buffer;
    if (buffer.length != size) {
      locBuffer = new byte[size];
      System.arraycopy(buffer, 0, locBuffer, 0, size);
    }
    characteristic.setValue(locBuffer);
    gatt.writeCharacteristic(characteristic);
    // FIXME BLE buffer overflow
    // after writing to the device with WRITE_NO_RESPONSE property the
    // onCharacteristicWrite callback is received immediately after writing
    // data to a buffer.
    // The real sending is much slower than adding to the buffer. This
    // method does not return false if writing didn't succeed.. just the
    // callback is not invoked.
    //
    // More info: this works fine on Nexus 5 (Andorid 4.4) (4.3 seconds) and
    // on Samsung S4 (Android 4.3) (20 seconds) so this is a driver issue.
    // Nexus 4 and 7 uses Qualcomm chip, Nexus 5 and Samsung uses Broadcom
    // chips.
  }

  private void waitIfPaused() {
    synchronized (mLock) {
      try {
        while (mPaused)
          mLock.wait();
      } catch (final InterruptedException e) {
        loge("Sleeping interrupted", e);
      }
    }
  }

  /**
   * Waits until the notification will arrive. Returns the data returned by
   * the notification. This method will block the thread if response is not
   * ready or connection state will change from
   * {@link #STATE_CONNECTED_AND_READY}. If connection state will change, or
   * an error will occur, an exception will be thrown.
   * 
   * @return the value returned by the Control Point notification
   * @throws DeviceDisconnectedException
   * @throws DfuException
   * @throws UploadAbortedException
   */
  private byte[] readNotificationResponse()
      throws DeviceDisconnectedException, DfuException,
      UploadAbortedException {
    mErrorState = 0;
    try {
      synchronized (mLock) {
        while ((mReceivedData == null
            && mConnectionState == STATE_CONNECTED_AND_READY
            && mErrorState == 0 && !mAborted)
            || mPaused)
          mLock.wait();
      }
    } catch (final InterruptedException e) {
      loge("Sleeping interrupted", e);
    }
    if (mAborted)
      throw new UploadAbortedException();
    if (mErrorState != 0)
      throw new DfuException("Unable to write Op Code", mErrorState);
    if (mConnectionState != STATE_CONNECTED_AND_READY)
      throw new DeviceDisconnectedException("Unable to write Op Code",
          mConnectionState);
    return mReceivedData;
  }

  /**
   * Stores the last progress percent. Used to lower number of calls of
   * {@link #updateProgressNotification(int)}.
   */
  private int mLastProgress = -1;

  /**
   * Creates or updates the notification in the Notification Manager. Sends
   * broadcast with current progress to the activity.
   */
  private void updateProgressNotification() {
    final int progress = (int) (100.0f * mBytesSent / mImageSizeInBytes);
    if (mLastProgress == progress)
      return;

    mLastProgress = progress;
    updateProgressNotification(progress);
  }

  /**
   * Creates or updates the notification in the Notification Manager. Sends
   * broadcast with given progress or error state to the activity.
   * 
   * @param progress
   *            the current progress state or an error number, can be one of
   *            {@link #PROGRESS_CONNECTING}, {@link #PROGRESS_STARTING},
   *            {@link #PROGRESS_VALIDATING}, {@link #PROGRESS_DISCONNECTING},
   *            {@link #PROGRESS_COMPLETED} or {@link #ERROR_FILE_CLOSED},
   *            {@link #ERROR_FILE_INVALID} , etc
   */
  private void updateProgressNotification(final int progress) {
    final String deviceAddress = mDeviceAddress;
    final String deviceName = mDeviceName != null ? mDeviceName
        : getString(R.string.dfu_unknown_name);

    final Bitmap largeIcon = BitmapFactory.decodeResource(getResources(),
        R.drawable.stat_dfu);

    final Notification.Builder builder = new Notification.Builder(this)
        .setSmallIcon(android.R.drawable.stat_sys_upload)
        .setOnlyAlertOnce(true).setLargeIcon(largeIcon);
    switch (progress) {
    case PROGRESS_CONNECTING:
      builder.setOngoing(true)
          .setContentTitle(getString(R.string.dfu_status_connecting))
          .setContentText(
              getString(R.string.dfu_status_connecting_msg,
                  deviceName)).setProgress(100, 0, true);
      break;
    case PROGRESS_STARTING:
      builder.setOngoing(true)
          .setContentTitle(getString(R.string.dfu_status_starting))
          .setContentText(
              getString(R.string.dfu_status_starting_msg,
                  deviceName)).setProgress(100, 0, true);
      break;
    case PROGRESS_VALIDATING:
      builder.setOngoing(true)
          .setContentTitle(getString(R.string.dfu_status_validating))
          .setContentText(
              getString(R.string.dfu_status_validating_msg,
                  deviceName)).setProgress(100, 0, true);
      break;
    case PROGRESS_DISCONNECTING:
      builder.setOngoing(true)
          .setContentTitle(
              getString(R.string.dfu_status_disconnecting))
          .setContentText(
              getString(R.string.dfu_status_disconnecting_msg,
                  deviceName)).setProgress(100, 0, true);
      break;
    case PROGRESS_COMPLETED:
      builder.setOngoing(false)
          .setContentTitle(getString(R.string.dfu_status_completed))
          .setContentText(
              getString(R.string.dfu_status_completed_msg))
          .setAutoCancel(true);
      break;
    case PROGRESS_ABORTED:
      builder.setOngoing(false)
          .setContentTitle(getString(R.string.dfu_status_abored))
          .setContentText(getString(R.string.dfu_status_aborted_msg))
          .setAutoCancel(true);
      break;
    default:
      if (progress >= ERROR_MASK) {
        // progress is an error number
        builder.setOngoing(false)
            .setContentTitle(getString(R.string.dfu_status_error))
            .setContentText(
                getString(R.string.dfu_status_error_msg))
            .setAutoCancel(true);
      } else {
        // progress is in percents
        builder.setOngoing(true)
            .setContentTitle(
                getString(R.string.dfu_status_uploading))
            .setContentText(
                getString(R.string.dfu_status_uploading_msg,
                    deviceName))
            .setProgress(100, progress, false);
      }
    }
    // send progress or error broadcast
    if (progress < ERROR_MASK)
      sendProgressBroadcast(progress);
    else
      sendErrorBroadcast(progress);

    // We cannot set two activities at once (using
    // PendingIntent.getActivities(...)) because we have to start the
    // BluetoothLeService first. Service is created in DeviceListActivity.
    // When creating activities the parent Activity is not created, it's
    // just inserted to the history stack.
    final Intent intent = new Intent(this, NotificationActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.putExtra(DfuActivity.EXTRA_DEVICE_ADDRESS, deviceAddress);
    intent.putExtra(DfuActivity.EXTRA_DEVICE_NAME, deviceName);
    intent.putExtra(DfuActivity.EXTRA_PROGRESS, progress); // this may
                                // contains
                                // ERROR_CONNECTION_MASK
                                // bit!
    if (mLogSession != null)
      intent.putExtra(DfuActivity.EXTRA_LOG_URI,
          mLogSession.getSessionUri());
    final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
        intent, PendingIntent.FLAG_UPDATE_CURRENT);
    builder.setContentIntent(pendingIntent);

    final NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(NOTIFICATION_ID, builder.build());
  }

  private void sendProgressBroadcast(final int progress) {
    final Intent broadcast = new Intent(BROADCAST_PROGRESS);
    broadcast.putExtra(EXTRA_DATA, progress);
    broadcast.putExtra(EXTRA_DEVICE_ADDRESS, mDeviceAddress);
    if (mLogSession != null)
      broadcast.putExtra(EXTRA_LOG_URI, mLogSession.getSessionUri());
    LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
  }

  private void sendErrorBroadcast(final int error) {
    final Intent broadcast = new Intent(BROADCAST_ERROR);
    broadcast.putExtra(EXTRA_DATA, error & ~ERROR_CONNECTION_MASK);
    broadcast.putExtra(EXTRA_DEVICE_ADDRESS, mDeviceAddress);
    if (mLogSession != null)
      broadcast.putExtra(EXTRA_LOG_URI, mLogSession.getSessionUri());
    LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
  }

  private void sendLogBroadcast(final int level, final String message) {
    final LogSession session = mLogSession;
    final String fullMessage = "[DFU] " + message;
    if (session == null) {
      // the log provider is not installed, use broadcast action
      final Intent broadcast = new Intent(BROADCAST_LOG);
      broadcast.putExtra(EXTRA_LOG_MESSAGE, fullMessage);
      broadcast.putExtra(EXTRA_LOG_LEVEL, level);
      broadcast.putExtra(EXTRA_DEVICE_ADDRESS, mDeviceAddress);
      LocalBroadcastManager.getInstance(this).sendBroadcast(broadcast);
    } else {
      // the log provider is installed, we can use logger
      Logger.log(session, level, fullMessage);
    }
  }

  /**
   * Initializes bluetooth adapter
   * 
   * @return <code>true</code> if initialization was successful
   */
  private boolean initialize() {
    // For API level 18 and above, get a reference to BluetoothAdapter
    // through
    // BluetoothManager.
    final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    if (bluetoothManager == null) {
      loge("Unable to initialize BluetoothManager.");
      return false;
    }

    mBluetoothAdapter = bluetoothManager.getAdapter();
    if (mBluetoothAdapter == null) {
      loge("Unable to obtain a BluetoothAdapter.");
      return false;
    }

    return true;
  }

  private static IntentFilter makeDfuActionIntentFilter() {
    final IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(DfuService.BROADCAST_ACTION);
    return intentFilter;
  }

  private void loge(final String message) {
    if (BuildConfig.DEBUG)
      Log.e(TAG, message);
  }

  private void loge(final String message, final Throwable e) {
    if (BuildConfig.DEBUG)
      Log.e(TAG, message, e);
  }

  private void logi(final String message) {
    if (BuildConfig.DEBUG)
      Log.i(TAG, message);
  }
}




Java Source Code List

no.nordicsemi.android.nrftoolbox.AppHelpFragment.java
no.nordicsemi.android.nrftoolbox.dfu.DeviceDisconnectedException.java
no.nordicsemi.android.nrftoolbox.dfu.DfuActivity.java
no.nordicsemi.android.nrftoolbox.dfu.DfuException.java
no.nordicsemi.android.nrftoolbox.dfu.DfuInitiatorActivity.java
no.nordicsemi.android.nrftoolbox.dfu.DfuService.java
no.nordicsemi.android.nrftoolbox.dfu.HexFileValidationException.java
no.nordicsemi.android.nrftoolbox.dfu.HexInputStream.java
no.nordicsemi.android.nrftoolbox.dfu.NotificationActivity.java
no.nordicsemi.android.nrftoolbox.dfu.RemoteDfuException.java
no.nordicsemi.android.nrftoolbox.dfu.UnknownResponseException.java
no.nordicsemi.android.nrftoolbox.dfu.UploadAbortedException.java
no.nordicsemi.android.nrftoolbox.dfu.adapter.FileBrowserAppsAdapter.java
no.nordicsemi.android.nrftoolbox.dfu.fragment.UploadCancelFragment.java
no.nordicsemi.android.nrftoolbox.dfu.settings.AboutDfuPreference.java
no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsActivity.java
no.nordicsemi.android.nrftoolbox.dfu.settings.SettingsFragment.java
no.nordicsemi.android.nrftoolbox.scanner.DeviceListAdapter.java
no.nordicsemi.android.nrftoolbox.scanner.ExtendedBluetoothDevice.java
no.nordicsemi.android.nrftoolbox.scanner.ScannerFragment.java
no.nordicsemi.android.nrftoolbox.scanner.ScannerServiceParser.java
no.nordicsemi.android.nrftoolbox.utility.DebugLogger.java
no.nordicsemi.android.nrftoolbox.utility.GattError.java
za.co.paulscott.adapters.InteractiveArrayAdapter.java
za.co.paulscott.antifitness.DeviceInfoFragment.java
za.co.paulscott.antifitness.MainActivity.java
za.co.paulscott.antifitness.MetawearReceiver.java
za.co.paulscott.antifitness.ModuleActivity.java
za.co.paulscott.antifitness.ModuleFragment.java
za.co.paulscott.models.DStvHighlights.java
za.co.paulscott.networkhelpers.URLFetcherAsync.java