Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- @Override
- protected void onHandleIntent(final Intent intent) {
- // Read input parameters
- final String deviceAddress = intent.getStringExtra(EXTRA_DEVICE_ADDRESS);
- final String deviceName = intent.getStringExtra(EXTRA_DEVICE_NAME);
- final boolean disableNotification = intent.getBooleanExtra(EXTRA_DISABLE_NOTIFICATION, false);
- final boolean foregroundService = intent.getBooleanExtra(EXTRA_FOREGROUND_SERVICE, true);
- final String filePath = intent.getStringExtra(EXTRA_FILE_PATH);
- final Uri fileUri = intent.getParcelableExtra(EXTRA_FILE_URI);
- final int fileResId = intent.getIntExtra(EXTRA_FILE_RES_ID, 0);
- final String initFilePath = intent.getStringExtra(EXTRA_INIT_FILE_PATH);
- final Uri initFileUri = intent.getParcelableExtra(EXTRA_INIT_FILE_URI);
- final int initFileResId = intent.getIntExtra(EXTRA_INIT_FILE_RES_ID, 0);
- int fileType = intent.getIntExtra(EXTRA_FILE_TYPE, TYPE_AUTO);
- 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);
- // Check file type and mime-type
- if ((fileType & ~(TYPE_SOFT_DEVICE | TYPE_BOOTLOADER | TYPE_APPLICATION)) > 0 || !(MIME_TYPE_ZIP.equals(mimeType) || MIME_TYPE_OCTET_STREAM.equals(mimeType))) {
- logw("File type or file mime-type not supported");
- sendLogBroadcast(LOG_LEVEL_WARNING, "File type or file mime-type not supported");
- report(ERROR_FILE_TYPE_UNSUPPORTED);
- return;
- }
- if (MIME_TYPE_OCTET_STREAM.equals(mimeType) && fileType != TYPE_SOFT_DEVICE && fileType != TYPE_BOOTLOADER && fileType != TYPE_APPLICATION) {
- logw("Unable to determine file type");
- sendLogBroadcast(LOG_LEVEL_WARNING, "Unable to determine file type");
- report(ERROR_FILE_TYPE_UNSUPPORTED);
- return;
- }
- if (!disableNotification && getNotificationTarget() == null) {
- // This would eventually crash later...
- throw new NullPointerException("getNotificationTarget() must not return null if notifications are enabled");
- }
- if (!foregroundService && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- logw("Foreground service disabled. Android Oreo or newer may kill a background service few moments after user closes the application.\n" +
- "Consider enabling foreground service using DfuServiceInitiator#setForeground(boolean)");
- }
- UuidHelper.assignCustomUuids(intent);
- mDeviceAddress = deviceAddress;
- mDeviceName = deviceName;
- mDisableNotification = disableNotification;
- mConnectionState = STATE_DISCONNECTED;
- mError = 0;
- // The Soft Device starts where MBR ends (by default from the address 0x1000). Before there is a MBR section, which should not be transmitted over DFU.
- // Applications and bootloader starts from bigger address. However, in custom DFU implementations, user may want to transmit the whole whole data, even from address 0x0000.
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
- final String value = preferences.getString(DfuSettingsConstants.SETTINGS_MBR_SIZE, String.valueOf(DfuSettingsConstants.SETTINGS_DEFAULT_MBR_SIZE));
- int mbrSize;
- try {
- mbrSize = Integer.parseInt(value);
- if (mbrSize < 0)
- mbrSize = 0;
- } catch (final NumberFormatException e) {
- mbrSize = DfuSettingsConstants.SETTINGS_DEFAULT_MBR_SIZE;
- }
- if (foregroundService) {
- startForeground();
- }
- sendLogBroadcast(LOG_LEVEL_VERBOSE, "DFU service started");
- /*
- * First the service is trying to read the firmware and init packet files.
- */
- InputStream is = mFirmwareInputStream;
- InputStream initIs = mInitFileInputStream;
- try {
- final boolean firstRun = mFirmwareInputStream == null;
- // Prepare data to send, calculate stream size
- try {
- 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.");
- }
- // Update the file type bit field basing on the ZIP content
- 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.");
- if (fileType == TYPE_APPLICATION) {
- if (zhis.getApplicationInit() != null)
- initIs = new ByteArrayInputStream(zhis.getApplicationInit());
- } else {
- if (zhis.getSystemInit() != null)
- initIs = new ByteArrayInputStream(zhis.getSystemInit());
- }
- }
- 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");
- report(ERROR_FILE_NOT_FOUND);
- return;
- } catch (final FileNotFoundException e) {
- loge("An exception occurred while opening file", e);
- 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);
- sendLogBroadcast(LOG_LEVEL_ERROR, "Opening file failed: Firmware size must be word-aligned");
- 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());
- report(ERROR_FILE_ERROR);
- return;
- } catch (final Exception e) {
- loge("An exception occurred while opening files. Did you set the firmware file?", e);
- sendLogBroadcast(LOG_LEVEL_ERROR, "Opening file failed: " + e.getLocalizedMessage());
- report(ERROR_FILE_ERROR);
- return;
- }
- 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);
- if (mAborted) {
- logw("Upload aborted");
- sendLogBroadcast(LOG_LEVEL_WARNING, "Upload aborted");
- mProgressInfo.setProgress(PROGRESS_ABORTED);
- return;
- }
- /*
- * Now let's connect to the device.
- * All the methods below are synchronous. The mLock object is used to wait for asynchronous calls.
- */
- sendLogBroadcast(LOG_LEVEL_VERBOSE, "Connecting to DFU target...");
- mProgressInfo.setProgress(PROGRESS_CONNECTING);
- final BluetoothGatt gatt = connect(deviceAddress);
- // Are we connected?
- if (gatt == null) {
- loge("Bluetooth adapter disabled");
- sendLogBroadcast(LOG_LEVEL_ERROR, "Bluetooth adapter disabled");
- report(ERROR_BLUETOOTH_DISABLED);
- return;
- }
- if (mConnectionState == STATE_DISCONNECTED) {
- if (mError == (ERROR_CONNECTION_STATE_MASK | 133)) {
- loge("Device not reachable. Check if the device with address " + deviceAddress + " is in range, is advertising and is connectable");
- sendLogBroadcast(LOG_LEVEL_ERROR, "Error 133: Connection timeout");
- } else {
- loge("Device got disconnected before service discovery finished");
- sendLogBroadcast(LOG_LEVEL_ERROR, "Disconnected");
- }
- terminateConnection(gatt, ERROR_DEVICE_DISCONNECTED);
- return;
- }
- if (mError > 0) { // error occurred
- if ((mError & ERROR_CONNECTION_STATE_MASK) > 0) {
- final int error = mError & ~ERROR_CONNECTION_STATE_MASK;
- loge("An error occurred while connecting to the device:" + error);
- sendLogBroadcast(LOG_LEVEL_ERROR, String.format("Connection failed (0x%02X): %s", error, GattError.parseConnectionError(error)));
- } else {
- final int error = mError & ~ERROR_CONNECTION_MASK;
- loge("An error occurred during discovering services:" + error);
- sendLogBroadcast(LOG_LEVEL_ERROR, String.format("Connection failed (0x%02X): %s", error, GattError.parse(error)));
- }
- // Connection usually fails due to a 133 error (device unreachable, or.. something else went wrong).
- // Usually trying the same for the second time works.
- if (intent.getIntExtra(EXTRA_ATTEMPT, 0) == 0) {
- sendLogBroadcast(LOG_LEVEL_WARNING, "Retrying...");
- if (mConnectionState != STATE_DISCONNECTED) {
- // Disconnect from the device
- disconnect(gatt);
- }
- // Close the device
- refreshDeviceCache(gatt, true);
- close(gatt);
- logi("Restarting the service");
- final Intent newIntent = new Intent();
- newIntent.fillIn(intent, Intent.FILL_IN_COMPONENT | Intent.FILL_IN_PACKAGE);
- newIntent.putExtra(EXTRA_ATTEMPT, 1);
- startService(newIntent);
- return;
- }
- terminateConnection(gatt, mError);
- return;
- }
- if (mAborted) {
- logw("Upload aborted");
- sendLogBroadcast(LOG_LEVEL_WARNING, "Upload aborted");
- terminateConnection(gatt, 0);
- mProgressInfo.setProgress(PROGRESS_ABORTED);
- return;
- }
- sendLogBroadcast(LOG_LEVEL_INFO, "Services discovered");
- // Reset the attempt counter
- intent.putExtra(EXTRA_ATTEMPT, 0);
- DfuService dfuService = null;
- try {
- /*
- * Device services were discovered. Based on them we may now choose the implementation.
- */
- final DfuServiceProvider serviceProvider = new DfuServiceProvider();
- mDfuServiceImpl = serviceProvider; // This is required if the provider is now able read data from the device
- mDfuServiceImpl = dfuService = serviceProvider.getServiceImpl(intent, this, gatt);
- if (dfuService == null) {
- Log.w(TAG, "DFU Service not found.");
- sendLogBroadcast(LOG_LEVEL_WARNING, "DFU Service not found");
- terminateConnection(gatt, ERROR_SERVICE_NOT_FOUND);
- return;
- }
- // Begin the DFU depending on the implementation
- if (dfuService.initialize(intent, gatt, fileType, is, initIs)) {
- dfuService.performDfu(intent);
- }
- } catch (final UploadAbortedException e) {
- logw("Upload aborted");
- sendLogBroadcast(LOG_LEVEL_WARNING, "Upload aborted");
- terminateConnection(gatt, 0);
- mProgressInfo.setProgress(PROGRESS_ABORTED);
- } catch (final DeviceDisconnectedException e) {
- sendLogBroadcast(LOG_LEVEL_ERROR, "Device has disconnected");
- // TODO reconnect n times?
- loge(e.getMessage());
- close(gatt);
- report(ERROR_DEVICE_DISCONNECTED);
- } catch (final DfuException e) {
- int error = e.getErrorNumber();
- // Connection state errors and other Bluetooth GATT callbacks share the same error numbers. Therefore we are using bit masks to identify the type.
- if ((error & ERROR_CONNECTION_STATE_MASK) > 0) {
- error &= ~ERROR_CONNECTION_STATE_MASK;
- sendLogBroadcast(LOG_LEVEL_ERROR, String.format("Error (0x%02X): %s", error, GattError.parseConnectionError(error)));
- } else {
- error &= ~ERROR_CONNECTION_MASK;
- sendLogBroadcast(LOG_LEVEL_ERROR, String.format("Error (0x%02X): %s", error, GattError.parse(error)));
- }
- loge(e.getMessage());
- terminateConnection(gatt, e.getErrorNumber() /* we return the whole error number, including the error type mask */);
- } finally {
- if (dfuService != null) {
- dfuService.release();
- }
- }
- } finally {
- if (foregroundService) {
- // This will stop foreground state and, if the progress notifications were disabled
- // it will also remove the notification indicating foreground service.
- stopForeground(disableNotification);
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement