Scanning for address + 1 removed.

DFU_IN_PROGRESS no longer kept in shared preferences.
Bugs fixed.
This commit is contained in:
Aleksander Nowakowski 2014-12-08 18:25:00 +01:00
parent 2c826c7d21
commit 99bb22eebb
5 changed files with 14 additions and 262 deletions

View File

@ -23,8 +23,6 @@ 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.BootloaderScanner;
import no.nordicsemi.android.dfu.scanner.BootloaderScannerFactory;
import no.nordicsemi.android.error.GattError;
import no.nordicsemi.android.log.ILogSession;
import no.nordicsemi.android.log.LogContract;
@ -89,9 +87,6 @@ public abstract class DfuBaseService extends IntentService {
private static final String TAG = "DfuService";
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
/** A key for {@link SharedPreferences} entry that keeps information whether the upload is currently in progress. This may be used to get this information during activity creation. */
public static final String DFU_IN_PROGRESS = "no.nordicsemi.android.dfu.PREFS_DFU_IN_PROGRESS";
/** The address of the device to update. */
public static final String EXTRA_DEVICE_ADDRESS = "no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_ADDRESS";
/** The optional device name. This name will be shown in the notification. */
@ -867,10 +862,6 @@ public abstract class DfuBaseService extends IntentService {
@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(DFU_IN_PROGRESS, true);
editor.commit();
// Read input parameters
final String deviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
@ -884,7 +875,7 @@ public abstract class DfuBaseService extends IntentService {
if (filePath != null && fileType == TYPE_AUTO)
fileType = filePath.toLowerCase(Locale.US).endsWith("zip") ? TYPE_AUTO : TYPE_APPLICATION;
String mimeType = intent.getStringExtra(EXTRA_FILE_MIME_TYPE);
mimeType = mimeType != null ? mimeType : (fileType == TYPE_AUTO ? MIME_TYPE_ZIP : MIME_TYPE_OCTET_STREAM);
mimeType = mimeType != null ? mimeType : (fileType == TYPE_AUTO ? MIME_TYPE_ZIP : MIME_TYPE_OCTET_STREAM); // FIXME check if it's better
mLogSession = Logger.openSession(this, logUri);
mPartCurrent = intent.getIntExtra(EXTRA_PART_CURRENT, 1);
mPartsTotal = intent.getIntExtra(EXTRA_PARTS_TOTAL, 1);
@ -1085,7 +1076,7 @@ public abstract class DfuBaseService extends IntentService {
* In the DFU from SDK 6.1, which was also supporting the buttonless update, there was no DFU Version characteristic. In that case we may find out whether
* we are in the bootloader or application by simply checking the number of characteristics.
*/
if (version == 1 || gatt.getServices().size() > 3 /* Generic Access, Generic Attribute, DFU Service */) {
if (version == 1 || (version == 0 && gatt.getServices().size() > 3 /* No DFU Version char but more services than Generic Access, Generic Attribute, DFU Service */)) {
// the service is connected to the application, not to the bootloader
logw("Application with buttonless update found");
sendLogBroadcast(Level.WARNING, "Application with buttonless update found");
@ -1122,27 +1113,17 @@ public abstract class DfuBaseService extends IntentService {
/*
* We would like to avoid using the hack with refreshing the device (refresh method is not in the public API). The refresh method clears the cached services and causes a
* service discovery afterwards (when connected). Android, however, does it itself when receive the Service Changed indication when bonded.
* In case of unpaired device we may either refresh the services manually (using the hack), or use a different device address in bootloader more.
* Therefore, since the version 0.6 of the DFU bootloader (available in SDK 8.0), refreshing is not required as this version uses both those methods.
* If you need to use the experimental DFU bootloader in version 0.5 (SDK 7.1) you have to uncomment the following line.
* Unfortunately while being in the application mode we don't know the bootloader version.
* In case of unpaired device we may either refresh the services manually (using the hack), or include the Service Changed characteristic.
*
* According to Bluetooth Core 4.0 (and 4.1) specification:
*
* [Vol. 3, Part G, 2.5.2 - Attribute Caching]
* Note: Clients without a trusted relationship must perform service discovery on each connection if the server supports the Services Changed characteristic.
*
* However, as up to Android 5 the system does NOT respect this requirement and servers are cached for every device, even if Service Changed is enabled -> Android BUG?
* For bonded devices Android performs service re-discovery when SC indication is received.
*/
// refreshDeviceCache(gatt, true);
String bootloaderAddress = deviceAddress;
if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
logi("Scanning for the bootloader (address = " + deviceAddress + ")");
sendLogBroadcast(Level.VERBOSE, "Scanning for the bootloader...");
final BootloaderScanner scanner = BootloaderScannerFactory.getScanner();
bootloaderAddress = scanner.searchFor(deviceAddress);
logi("Bootloader device found: " + bootloaderAddress);
sendLogBroadcast(Level.APPLICATION, "Bootloader with address " + bootloaderAddress + " found");
// In case the device is not bonded and it is advertising with the same address as before we must refresh the services manually.
if (bootloaderAddress.equals(deviceAddress))
refreshDeviceCache(gatt, false /* or true, doesn't matter as not bonded */);
}
refreshDeviceCache(gatt, false);
// Close the device
close(gatt);
@ -1150,7 +1131,6 @@ public abstract class DfuBaseService extends IntentService {
logi("Starting service that will connect to the DFU bootloader");
final Intent newIntent = new Intent();
newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE);
newIntent.putExtra(EXTRA_DEVICE_ADDRESS, bootloaderAddress);
startService(newIntent);
return;
}
@ -1493,7 +1473,8 @@ public abstract class DfuBaseService extends IntentService {
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_TYPE, TYPE_APPLICATION);
newIntent.putExtra(EXTRA_FILE_MIME_TYPE, MIME_TYPE_ZIP); // ensure this is set (f.e. for scripts)
newIntent.putExtra(EXTRA_FILE_TYPE, TYPE_APPLICATION); // set the type to application only
newIntent.putExtra(EXTRA_PART_CURRENT, mPartCurrent + 1);
newIntent.putExtra(EXTRA_PARTS_TOTAL, mPartsTotal);
startService(newIntent);
@ -1554,10 +1535,6 @@ public abstract class DfuBaseService extends IntentService {
}
} finally {
try {
// upload has finished (success of fail)
editor.putBoolean(DFU_IN_PROGRESS, false);
editor.commit();
// ensure that input stream is always closed
mInputStream = null;
if (is != null)

View File

@ -1,31 +0,0 @@
package no.nordicsemi.android.dfu.scanner;
/**
* When using the buttonless jump from the application mode to the DFU bootloader the bootloader, depending on the version, may start advertising with the same device address or one
* with the last byte incremented by 1. Before the jump the application does not know which DFU bootloader version is installed on the DFU target device. The experimental version
* in the SDK 7.0 and 7.1 will advertise with the same address. Since the SDK 8.0 version, in order to be consistent with the BLE specification, it uses a different address as it is
* changing the services.
* <p>
* The scanner is being used only with unpaired devices. When bonded, the device will always keep the same address and preserve the Long Term Key while in the bootloader mode. It will notify the
* application about the DFU services using the Service Changed indication.
* </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 old Androids 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

@ -1,17 +0,0 @@
package no.nordicsemi.android.dfu.scanner;
import android.os.Build;
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

@ -1,81 +0,0 @@
package no.nordicsemi.android.dfu.scanner;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
/**
* @see BootloaderScanner
*/
public class BootloaderScannerJB implements BootloaderScanner, BluetoothAdapter.LeScanCallback {
private 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

@ -1,96 +0,0 @@
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;
import android.util.Log;
/**
* @see BootloaderScanner
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class BootloaderScannerLollipop extends ScanCallback implements BootloaderScanner {
private 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<ScanFilter>();
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();
Log.d("BootloaderScanner", "Device found: " + address);
if (mDeviceAddress.equals(address) || mDeviceAddressIncremented.equals(address)) {
mBootloaderAddress = address;
mFound = true;
// Notify the waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
}
}