Merge branch 'master' into release

This commit is contained in:
Aleksander Nowakowski 2015-06-04 17:02:16 +02:00
commit 1205a04c17
10 changed files with 360 additions and 20 deletions

View File

@ -6,6 +6,7 @@
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="C:\Program Files\Android\Android Studio\gradle\gradle-2.2.1" />
<option name="gradleJvm" value="1.7" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@ -16,4 +17,3 @@
</option>
</component>
</project>

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<module external.linked.project.id="DFULibrary" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="false" />
</configuration>
</facet>
</component>
@ -16,4 +17,3 @@
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -8,7 +8,7 @@ ext {
android {
compileSdkVersion 22
buildToolsVersion '22.0.0'
buildToolsVersion '22.0.1'
defaultConfig {
minSdkVersion 18
@ -26,7 +26,7 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:support-v4:22.0.0'
compile 'com.android.support:support-v4:22.1.1'
compile 'com.google.code.gson:gson:2.3.1'
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="DFULibrary" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<module external.linked.project.id=":dfu" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="DFULibrary" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
@ -12,8 +12,9 @@
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
@ -87,8 +88,7 @@
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="gson-2.3.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-22.0.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-22.0.0" level="project" />
<orderEntry type="library" exported="" name="support-v4-22.1.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-22.1.1" level="project" />
</component>
</module>

View File

@ -66,6 +66,7 @@ import no.nordicsemi.android.dfu.exception.HexFileValidationException;
import no.nordicsemi.android.dfu.exception.RemoteDfuException;
import no.nordicsemi.android.dfu.exception.UnknownResponseException;
import no.nordicsemi.android.dfu.exception.UploadAbortedException;
import no.nordicsemi.android.dfu.scanner.BootloaderScannerFactory;
import no.nordicsemi.android.error.GattError;
/**
@ -1727,14 +1728,32 @@ public abstract class DfuBaseService extends IntentService {
updateProgressNotification(PROGRESS_COMPLETED);
} else {
/*
* The current service handle will try to upload Soft Device and/or Bootloader.
* We need to enqueue another Intent that will try to send application only.
* In case when the Soft Device has been upgraded, and the application should be send in the following connection, we have to
* make sure that we know the address the device is advertising with. Depending on the method used to start the DFU bootloader the first time
* the new Bootloader may advertise with the same address or one incremented by 1.
* When the buttonless update was used, the bootloader will use the same address as the application. The cached list of services on the Android device
* should be cleared thanks to the Service Changed characteristic (the fact that it exists if not bonded, or the Service Changed indication on bonded one).
* In case of forced DFU mode (using a button), the Bootloader does not know whether there was the Service Changed characteristic present in the list of
* application's services so it must advertise with a different address. The same situation applies when the new Soft Device was uploaded and the old
* application has been removed in this process.
*
* We could have save the fact of jumping as a parameter of the service but it ma be that some Android devices must first scan a device before connecting to it.
* It a device with the address+1 has never been detected before the service could have failed on connection.
*/
sendLogBroadcast(LOG_LEVEL_VERBOSE, "Scanning for the DFU bootloader...");
final String newAddress = BootloaderScannerFactory.getScanner().searchFor(mDeviceAddress);
sendLogBroadcast(LOG_LEVEL_INFO, "The Bootloader found (" + newAddress + ")");
/*
* The current service instance has uploaded the Soft Device and/or Bootloader.
* We need to start another instance that will try to send application only.
*/
logi("Starting service that will upload application");
final Intent newIntent = new Intent();
newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE);
newIntent.putExtra(EXTRA_FILE_MIME_TYPE, MIME_TYPE_ZIP); // ensure this is set (f.e. for scripts)
newIntent.putExtra(EXTRA_FILE_MIME_TYPE, MIME_TYPE_ZIP); // ensure this is set (e.g. for scripts)
newIntent.putExtra(EXTRA_FILE_TYPE, TYPE_APPLICATION); // set the type to application only
newIntent.putExtra(EXTRA_DEVICE_ADDRESS, newAddress);
newIntent.putExtra(EXTRA_PART_CURRENT, mPartCurrent + 1);
newIntent.putExtra(EXTRA_PARTS_TOTAL, mPartsTotal);
startService(newIntent);

View File

@ -0,0 +1,54 @@
/*************************************************************************************************************************************************
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************************************************************************************/
package no.nordicsemi.android.dfu.scanner;
/**
* <p>
* The DFU Bootloader may advertise with the same address as an application (in case of the buttonless update) or one incremented by 1 (in case of jumping to the DFU mode with a button,
* or after flashing the new Soft Device (flashing new SD removes the old application)).
* </p>
* <p>
* The DFU service always connects to the address given as a parameter. However, when flashing SD+BL+App it will first send the SD+BL as part one followed by the App in the second connection.
* As the service does not know which address was used in the first connection (normal, when buttonless update, or +1 when with-button update) we have to scan for the advertising device
* after SD+BL part is completed.
* </p>
*/
public interface BootloaderScanner {
/**
* After the buttonless jump from the application mode to the bootloader mode the service will wait this long for the advertising bootloader (in milliseconds).
*/
public final static long TIMEOUT = 2000l; // ms
/** The bootloader may advertise with the same address or one with the last byte incremented by this value. F.e. 00:11:22:33:44:55 -> 00:11:22:33:44:56. FF changes to 00. */
public final static int ADDRESS_DIFF = 1;
/**
* Searches for the advertising bootloader. The bootloader may advertise with the same device address or one with the last byte incremented by 1.
* This method is a blocking one and ends when such device is found. There are two implementations of this interface - one for Androids 4.3 and 4.4.x and one for
* the Android 5+ devices.
*
* @param deviceAddress
* the application device address
* @return the address of the advertising DFU bootloader. If may be the same as the application address or one with the last byte incremented by 1 (AA:BB:CC:DD:EE:45/FF -> AA:BB:CC:DD:EE:46/00).
*/
public String searchFor(final String deviceAddress);
}

View File

@ -0,0 +1,42 @@
/*************************************************************************************************************************************************
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************************************************************************************/
package no.nordicsemi.android.dfu.scanner;
import android.os.Build;
/**
* The factory should be used to create the {@link BootloaderScanner} instance appropriate for the Android version.
*/
public class BootloaderScannerFactory {
/**
* Returns the scanner implementation.
*
* @return the bootloader scanner
*/
public static BootloaderScanner getScanner() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
return new BootloaderScannerLollipop();
return new BootloaderScannerJB();
}
}

View File

@ -0,0 +1,104 @@
/*************************************************************************************************************************************************
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************************************************************************************/
package no.nordicsemi.android.dfu.scanner;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
/**
* @see BootloaderScanner
*/
public class BootloaderScannerJB implements BootloaderScanner, BluetoothAdapter.LeScanCallback {
private final Object mLock = new Object();
private String mDeviceAddress;
private String mDeviceAddressIncremented;
private String mBootloaderAddress;
private boolean mFound;
@SuppressWarnings("deprecation")
@Override
public String searchFor(final String deviceAddress) {
final String fistBytes = deviceAddress.substring(0, 15);
final String lastByte = deviceAddress.substring(15); // assuming that the device address is correct
final String lastByteIncremented = String.format("%02X", (Integer.valueOf(lastByte, 16) + ADDRESS_DIFF) & 0xFF);
mDeviceAddress = deviceAddress;
mDeviceAddressIncremented = fistBytes + lastByteIncremented;
mBootloaderAddress = null;
mFound = false;
// Add timeout
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(BootloaderScanner.TIMEOUT);
} catch (final InterruptedException e) {
// do nothing
}
if (mFound)
return;
mBootloaderAddress = null;
mFound = true;
// Notify the waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
}, "Scanner timer").start();
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
adapter.startLeScan(this);
try {
synchronized (mLock) {
while (!mFound)
mLock.wait();
}
} catch (final InterruptedException e) {
// do nothing
}
adapter.stopLeScan(this);
return mBootloaderAddress;
}
@Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
final String address = device.getAddress();
if (mDeviceAddress.equals(address) || mDeviceAddressIncremented.equals(address)) {
mBootloaderAddress = address;
mFound = true;
// Notify the waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
}
}

View File

@ -0,0 +1,117 @@
/*************************************************************************************************************************************************
* Copyright (c) 2015, Nordic Semiconductor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************************************************************************************/
package no.nordicsemi.android.dfu.scanner;
import java.util.ArrayList;
import java.util.List;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.os.Build;
/**
* @see BootloaderScanner
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class BootloaderScannerLollipop extends ScanCallback implements BootloaderScanner {
private final Object mLock = new Object();
private String mDeviceAddress;
private String mDeviceAddressIncremented;
private String mBootloaderAddress;
private boolean mFound;
@Override
public String searchFor(final String deviceAddress) {
final String fistBytes = deviceAddress.substring(0, 15);
final String lastByte = deviceAddress.substring(15); // assuming that the device address is correct
final String lastByteIncremented = String.format("%02X", (Integer.valueOf(lastByte, 16) + ADDRESS_DIFF) & 0xFF);
mDeviceAddress = deviceAddress;
mDeviceAddressIncremented = fistBytes + lastByteIncremented;
mBootloaderAddress = null;
mFound = false;
// Add timeout
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(BootloaderScanner.TIMEOUT);
} catch (final InterruptedException e) {
// do nothing
}
if (mFound)
return;
mBootloaderAddress = null;
mFound = true;
// Notify the waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
}, "Scanner timer").start();
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
final BluetoothLeScanner scanner = adapter.getBluetoothLeScanner();
final List<ScanFilter> filters = new ArrayList<>();
filters.add(new ScanFilter.Builder().setDeviceAddress(mDeviceAddress).build());
filters.add(new ScanFilter.Builder().setDeviceAddress(mDeviceAddressIncremented).build());
final ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
scanner.startScan(filters, settings, this);
try {
synchronized (mLock) {
while (!mFound)
mLock.wait();
}
} catch (final InterruptedException e) {
// do nothing
}
scanner.stopScan(this);
return mBootloaderAddress;
}
@Override
public void onScanResult(final int callbackType, final ScanResult result) {
final String address = result.getDevice().getAddress();
if (mDeviceAddress.equals(address) || mDeviceAddressIncremented.equals(address)) {
mBootloaderAddress = address;
mFound = true;
// Notify the waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
}
}

View File

@ -135,12 +135,16 @@ public class GattError {
return "GATT ENCRYPTED NO MITM";
case 0x008e:
return "GATT NOT ENCRYPTED";
case 0x01FF:
case 0x008f:
return "GATT CONGESTED";
case 0x00FD:
return "GATT CCCD CFG ERROR";
case 0x00FE:
return "GATT PROCEDURE IN PROGRESS";
case 0x00FF:
return "GATT VALUE OUT OF RANGE";
case 0x0101:
return "TOO MANY OPEN CONNECTIONS";
case 0x00FF:
return "DFU SERVICE DISCOVERY NOT STARTED";
case DfuBaseService.ERROR_DEVICE_DISCONNECTED:
return "DFU DEVICE DISCONNECTED";
case DfuBaseService.ERROR_FILE_ERROR:
@ -152,7 +156,7 @@ public class GattError {
case DfuBaseService.ERROR_FILE_NOT_FOUND:
return "DFU FILE NOT FOUND";
case DfuBaseService.ERROR_SERVICE_DISCOVERY_NOT_STARTED:
return "DFU ERROR WHILE SERVICE DISCOVERY";
return "DFU SERVICE DISCOVERY NOT STARTED";
case DfuBaseService.ERROR_SERVICE_NOT_FOUND:
return "DFU SERVICE NOT FOUND";
case DfuBaseService.ERROR_CHARACTERISTICS_NOT_FOUND: