+ ArchiveInputStream can be reused with different content types
+ Reusing input stream between service runs
This commit is contained in:
Aleksander Nowakowski 2017-11-17 15:50:25 +01:00
parent 90c7694b94
commit 2a3eac37c5
2 changed files with 122 additions and 91 deletions

View File

@ -622,6 +622,7 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
private boolean mAborted;
private DfuCallback mDfuServiceImpl;
private InputStream mFirmwareInputStream, mInitFileInputStream;
private final BroadcastReceiver mDfuActionReceiver = new BroadcastReceiver() {
@Override
@ -879,6 +880,20 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
unregisterReceiver(mDfuActionReceiver);
unregisterReceiver(mConnectionStateBroadcastReceiver);
unregisterReceiver(mBondStateBroadcastReceiver);
try {
// Ensure that input stream is always closed
if (mFirmwareInputStream != null)
mFirmwareInputStream.close();
if (mInitFileInputStream != null)
mInitFileInputStream.close();
} catch (final IOException e) {
// do nothing
} finally {
mFirmwareInputStream = null;
mInitFileInputStream = null;
}
logi("DFU service destroyed");
}
@Override
@ -946,53 +961,65 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
/*
* First the service is trying to read the firmware and init packet files.
*/
InputStream is = null;
InputStream initIs = null;
int imageSizeInBytes;
InputStream is = mFirmwareInputStream;
InputStream initIs = mInitFileInputStream;
try {
final boolean firstRun = mFirmwareInputStream == null;
// Prepare data to send, calculate stream size
try {
sendLogBroadcast(LOG_LEVEL_VERBOSE, "Opening file...");
if (fileUri != null) {
is = openInputStream(fileUri, mimeType, mbrSize, fileType);
} else if (filePath != null) {
is = openInputStream(filePath, mimeType, mbrSize, fileType);
} else if (fileResId > 0) {
is = openInputStream(fileResId, mimeType, mbrSize, fileType);
if (firstRun) {
// The files are opened only once, when DFU service is first started.
// In case the service needs to be restarted (for example a buttonless service
// was found or to send Application in the second connection) the input stream
// is kept as a global service field. This is to avoid SecurityException
// when the URI was granted with one-time read permission.
// See: Intent#FLAG_GRANT_READ_URI_PERMISSION (https://developer.android.com/reference/android/content/Intent.html#FLAG_GRANT_READ_URI_PERMISSION).
sendLogBroadcast(LOG_LEVEL_VERBOSE, "Opening file...");
if (fileUri != null) {
is = openInputStream(fileUri, mimeType, mbrSize, fileType);
} else if (filePath != null) {
is = openInputStream(filePath, mimeType, mbrSize, fileType);
} else if (fileResId > 0) {
is = openInputStream(fileResId, mimeType, mbrSize, fileType);
}
// The Init file Input Stream is kept global only in case it was provided
// as an argument (separate file for HEX/BIN and DAT files).
// If a ZIP file was given with DAT file(s) inside it will be taken from the ZIP
// ~20 lines below.
if (initFileUri != null) {
// Try to read the Init Packet file from URI
initIs = getContentResolver().openInputStream(initFileUri);
} else if (initFilePath != null) {
// Try to read the Init Packet file from path
initIs = new FileInputStream(initFilePath);
} else if (initFileResId > 0) {
// Try to read the Init Packet file from given resource
initIs = getResources().openRawResource(initFileResId);
}
final int imageSizeInBytes = is.available();
if ((imageSizeInBytes % 4) != 0)
throw new SizeValidationException("The new firmware is not word-aligned.");
}
if (initFileUri != null) {
// Try to read the Init Packet file from URI
initIs = getContentResolver().openInputStream(initFileUri);
} else if (initFilePath != null) {
// Try to read the Init Packet file from path
initIs = new FileInputStream(initFilePath);
} else if (initFileResId > 0) {
// Try to read the Init Packet file from given resource
initIs = getResources().openRawResource(initFileResId);
}
imageSizeInBytes = is.available();
if ((imageSizeInBytes % 4) != 0)
throw new SizeValidationException("The new firmware is not word-aligned.");
// Update the file type bit field basing on the ZIP content
if (fileType == TYPE_AUTO && MIME_TYPE_ZIP.equals(mimeType)) {
final ArchiveInputStream zhis = (ArchiveInputStream) is;
fileType = zhis.getContentType();
}
// Set the Init packet stream in case of a ZIP file
if (MIME_TYPE_ZIP.equals(mimeType)) {
final ArchiveInputStream zhis = (ArchiveInputStream) is;
if (fileType == TYPE_AUTO) {
fileType = zhis.getContentType();
} else {
fileType = zhis.setContentType(fileType);
}
// Validate sizes
if ((fileType & TYPE_APPLICATION) > 0 && (zhis.applicationImageSize() % 4) != 0)
throw new SizeValidationException("Application firmware is not word-aligned.");
if ((fileType & TYPE_BOOTLOADER) > 0 && (zhis.bootloaderImageSize() % 4) != 0)
throw new SizeValidationException("Bootloader firmware is not word-aligned.");
if ((fileType & TYPE_SOFT_DEVICE) > 0 && (zhis.softDeviceImageSize() % 4) != 0)
throw new SizeValidationException("Soft Device firmware is not word-aligned.");
// Validate sizes
if ((fileType & TYPE_APPLICATION) > 0 && (zhis.applicationImageSize() % 4) != 0)
throw new SizeValidationException("Application firmware is not word-aligned.");
if ((fileType & TYPE_BOOTLOADER) > 0 && (zhis.bootloaderImageSize() % 4) != 0)
throw new SizeValidationException("Bootloader firmware is not word-aligned.");
if ((fileType & TYPE_SOFT_DEVICE) > 0 && (zhis.softDeviceImageSize() % 4) != 0)
throw new SizeValidationException("Soft Device firmware is not word-aligned.");
if (fileType == TYPE_APPLICATION) {
if (zhis.getApplicationInit() != null)
@ -1002,7 +1029,9 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
initIs = new ByteArrayInputStream(zhis.getSystemInit());
}
}
sendLogBroadcast(LOG_LEVEL_INFO, "Firmware file opened (" + imageSizeInBytes + " bytes in total)");
mFirmwareInputStream = is;
mInitFileInputStream = initIs;
sendLogBroadcast(LOG_LEVEL_INFO, "Firmware file opened successfully");
} catch (final SecurityException e) {
loge("A security exception occurred while opening file", e);
sendLogBroadcast(LOG_LEVEL_ERROR, "Opening file failed: Permission required");
@ -1013,11 +1042,11 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
sendLogBroadcast(LOG_LEVEL_ERROR, "Opening file failed: File not found");
report(ERROR_FILE_NOT_FOUND);
return;
} catch (final SizeValidationException e) {
loge("Firmware not word-aligned", e);
} catch (final SizeValidationException e) {
loge("Firmware not word-aligned", e);
sendLogBroadcast(LOG_LEVEL_ERROR, "Opening file failed: Firmware size must be word-aligned");
report(ERROR_FILE_SIZE_INVALID);
return;
report(ERROR_FILE_SIZE_INVALID);
return;
} catch (final IOException e) {
loge("An exception occurred while calculating file size", e);
sendLogBroadcast(LOG_LEVEL_ERROR, "Opening file failed: " + e.getLocalizedMessage());
@ -1030,10 +1059,12 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
return;
}
// Wait a second... If we were connected before it's good to give some time before we start reconnecting.
waitFor(1000);
// Looks like a second is not enough. The ACL_DISCONNECTED broadcast sometimes comes later (on Android 7.0)
waitFor(1000);
if (!firstRun) {
// Wait a second... If we were connected before it's good to give some time before we start reconnecting.
waitFor(1000);
// Looks like a second is not enough. The ACL_DISCONNECTED broadcast sometimes comes later (on Android 7.0)
waitFor(1000);
}
mProgressInfo = new DfuProgressInfo(this);
@ -1163,13 +1194,6 @@ public abstract class DfuBaseService extends IntentService implements DfuProgres
}
}
} finally {
try {
// Ensure that input stream is always closed
if (is != null)
is.close();
} catch (final IOException e) {
// do nothing
}
if (foregroundService) {
// This will stop foreground state and, if the progress notifications were disabled
// it will also remove the notification indicating foreground service.

View File

@ -77,6 +77,7 @@ public class ArchiveInputStream extends ZipInputStream {
private byte[] systemInitBytes;
private byte[] applicationInitBytes;
private byte[] currentSource;
private int type;
private int bytesReadFromCurrentSource;
private int softDeviceSize;
private int bootloaderSize;
@ -251,6 +252,7 @@ public class ArchiveInputStream extends ZipInputStream {
}
mark(0);
} finally {
type = getContentType();
super.close();
}
}
@ -372,9 +374,6 @@ public class ArchiveInputStream extends ZipInputStream {
@Override
public void reset() throws IOException {
if (applicationBytes != null && (softDeviceBytes != null || bootloaderBytes != null || softDeviceAndBootloaderBytes != null))
throw new UnsupportedOperationException("Application must be sent in a separate connection.");
currentSource = markedSource;
bytesRead = bytesReadFromCurrentSource = bytesReadFromMarkedSource;
@ -409,7 +408,7 @@ public class ArchiveInputStream extends ZipInputStream {
* TYPE_APPLICATION}
*/
public int getContentType() {
byte type = 0;
type = 0;
// In Secure DFU the softDeviceSize and bootloaderSize may be 0 if both are in the ZIP file. The size of each part is embedded in the Init packet.
if (softDeviceAndBootloaderBytes != null)
type |= DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER;
@ -431,33 +430,40 @@ public class ArchiveInputStream extends ZipInputStream {
* @return the final type after truncating
*/
public int setContentType(final int type) {
if (bytesRead > 0)
throw new UnsupportedOperationException("Content type must not be change after reading content");
this.type = type;
// If the new type has Application, but there is no application fw, remove this type bit
if ((type & DfuBaseService.TYPE_APPLICATION) > 0 && applicationBytes == null)
this.type &= ~DfuBaseService.TYPE_APPLICATION;
// If the new type has SD+BL
if ((type & (DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER)) == (DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER)) {
// but there is no SD, remove the softdevice type bit
if (softDeviceBytes == null && softDeviceAndBootloaderBytes == null)
this.type &= ~DfuBaseService.TYPE_SOFT_DEVICE;
// or there is no BL, remove the bootloader type bit
if (bootloaderBytes == null && softDeviceAndBootloaderBytes == null)
this.type &= ~DfuBaseService.TYPE_SOFT_DEVICE;
} else {
// If at least one of SD or B: bit is cleared, but the SD+BL file is set, remove both bits.
if (softDeviceAndBootloaderBytes != null)
this.type &= ~(DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER);
}
final int t = getContentType() & type;
if ((t & DfuBaseService.TYPE_SOFT_DEVICE) == 0) {
softDeviceBytes = null;
if (softDeviceAndBootloaderBytes != null) {
softDeviceAndBootloaderBytes = null;
bootloaderSize = 0;
}
softDeviceSize = 0;
if ((type & (DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER)) > 0 && softDeviceAndBootloaderBytes != null)
currentSource = softDeviceAndBootloaderBytes;
else if ((type & DfuBaseService.TYPE_SOFT_DEVICE) > 0)
currentSource = softDeviceBytes;
else if ((type & DfuBaseService.TYPE_BOOTLOADER) > 0)
currentSource = bootloaderBytes;
else if ((type & DfuBaseService.TYPE_APPLICATION) > 0)
currentSource = applicationBytes;
bytesReadFromCurrentSource = 0;
try {
mark(0);
reset();
} catch (final IOException e) {
// not available
}
if ((t & DfuBaseService.TYPE_BOOTLOADER) == 0) {
bootloaderBytes = null;
if (softDeviceAndBootloaderBytes != null) {
softDeviceAndBootloaderBytes = null;
softDeviceSize = 0;
}
bootloaderSize = 0;
}
if ((t & DfuBaseService.TYPE_APPLICATION) == 0) {
applicationBytes = null;
applicationSize = 0;
}
mark(0);
return t;
return this.type;
}
/**
@ -467,9 +473,9 @@ public class ArchiveInputStream extends ZipInputStream {
*/
private byte[] startNextFile() {
byte[] ret;
if (currentSource == softDeviceBytes && bootloaderBytes != null) {
if (currentSource == softDeviceBytes && bootloaderBytes != null && (type & DfuBaseService.TYPE_BOOTLOADER) > 0) {
ret = currentSource = bootloaderBytes;
} else if (currentSource != applicationBytes && applicationBytes != null) {
} else if (currentSource != applicationBytes && applicationBytes != null && (type & DfuBaseService.TYPE_APPLICATION) > 0) {
ret = currentSource = applicationBytes;
} else {
ret = currentSource = null;
@ -488,11 +494,12 @@ public class ArchiveInputStream extends ZipInputStream {
// This method then is just used to log file size.
// In case of SD+BL in Secure DFU:
if (softDeviceAndBootloaderBytes != null && softDeviceSize == 0 && bootloaderSize == 0)
return softDeviceAndBootloaderBytes.length + applicationSize - bytesRead;
if (softDeviceAndBootloaderBytes != null && softDeviceSize == 0 && bootloaderSize == 0
&& (type & (DfuBaseService.TYPE_SOFT_DEVICE | DfuBaseService.TYPE_BOOTLOADER)) > 0)
return softDeviceAndBootloaderBytes.length + applicationImageSize() - bytesRead;
// Otherwise:
return softDeviceSize + bootloaderSize + applicationSize - bytesRead;
return softDeviceImageSize() + bootloaderImageSize() + applicationImageSize() - bytesRead;
}
/**
@ -500,7 +507,7 @@ public class ArchiveInputStream extends ZipInputStream {
* @return the size of the SoftDevice firmware (BIN part)
*/
public int softDeviceImageSize() {
return softDeviceSize;
return (type & DfuBaseService.TYPE_SOFT_DEVICE) > 0 ? softDeviceSize : 0;
}
/**
@ -508,7 +515,7 @@ public class ArchiveInputStream extends ZipInputStream {
* @return the size of the Bootloader firmware (BIN part)
*/
public int bootloaderImageSize() {
return bootloaderSize;
return (type & DfuBaseService.TYPE_BOOTLOADER) > 0 ? bootloaderSize : 0;
}
/**
@ -516,7 +523,7 @@ public class ArchiveInputStream extends ZipInputStream {
* @return the size of the Application firmware (BIN part)
*/
public int applicationImageSize() {
return applicationSize;
return (type & DfuBaseService.TYPE_APPLICATION) > 0 ? applicationSize : 0;
}
/**