-
Notifications
You must be signed in to change notification settings - Fork 263
/
DfuBaseService.java
2041 lines (1897 loc) · 88.9 KB
/
DfuBaseService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2018, 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;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.IntentService;
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.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Locale;
import no.nordicsemi.android.dfu.internal.ArchiveInputStream;
import no.nordicsemi.android.dfu.internal.HexInputStream;
import no.nordicsemi.android.dfu.internal.exception.DeviceDisconnectedException;
import no.nordicsemi.android.dfu.internal.exception.DfuException;
import no.nordicsemi.android.dfu.internal.exception.SizeValidationException;
import no.nordicsemi.android.dfu.internal.exception.UploadAbortedException;
import no.nordicsemi.android.error.GattError;
/**
* The DFU Service provides full support for Over-the-Air (OTA) Device Firmware Update (DFU)
* by Nordic Semiconductor.
* With the Soft Device 7.0.0+ it allows to upload a new Soft Device, new Bootloader and a
* new Application. For older soft devices only the Application update is supported.
* <p>
* To run the service to your application extend it in your project and overwrite the missing method.
* Remember to add your service class to the AndroidManifest.xml file.
* <p>
* The {@link DfuServiceInitiator} object should be used to start the DFU Service.
* <pre>
* final DfuServiceInitiator starter = new DfuServiceInitiator(mSelectedDevice.getAddress())
* .setDeviceName(mSelectedDevice.getName())
* .setKeepBond(keepBond)
* .setZip(mFileStreamUri, mFilePath) // where one, URI or path, should be null
* .start(this, DfuService.class);
* </pre>
* <p>
* You may register the progress and log listeners using the {@link DfuServiceListenerHelper}
* helper class. See {@link DfuProgressListener} and {@link DfuLogListener} for more information.
* <p>
* The service will show its progress on the notification bar and will send local broadcasts to the
* application.
*/
@SuppressWarnings("deprecation")
public abstract class DfuBaseService extends IntentService implements DfuProgressInfo.ProgressListener {
private static final String TAG = "DfuBaseService";
/* package */ static boolean DEBUG = false;
public static final int NOTIFICATION_ID = 283; // a random number
public static final String NOTIFICATION_CHANNEL_DFU = "dfu";
/**
* 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.
*/
public static final String EXTRA_DEVICE_NAME = "no.nordicsemi.android.dfu.extra.EXTRA_DEVICE_NAME";
/**
* A boolean indicating whether to disable the progress notification in the status bar.
* Defaults to false.
*/
public static final String EXTRA_DISABLE_NOTIFICATION = "no.nordicsemi.android.dfu.extra.EXTRA_DISABLE_NOTIFICATION";
/**
* A boolean indicating whether the DFU service should be set as a foreground service.
* It is recommended to have it as a background service at least on Android Oreo or newer as
* the background service will be killed by the system few moments after the user closed the
* foreground app.
* <p>
* Read more here: <a href="https://developer.android.com/about/versions/oreo/background.html">https://developer.android.com/about/versions/oreo/background.html</a>
*/
public static final String EXTRA_FOREGROUND_SERVICE = "no.nordicsemi.android.dfu.extra.EXTRA_FOREGROUND_SERVICE";
/**
* An extra private field indicating which reconnection attempt is being performed.
* In case of error 133 the service will retry to connect 2 more times.
*/
private static final String EXTRA_RECONNECTION_ATTEMPT = "no.nordicsemi.android.dfu.extra.EXTRA_RECONNECTION_ATTEMPT";
/**
* An extra private field indicating which DFU attempt is being performed.
* If the target device will disconnect for some unknown reason during DFU, the service will
* retry to connect and continue. In case of Legacy DFU it will reconnect and restart process.
*/
/* package */ static final String EXTRA_DFU_ATTEMPT = "no.nordicsemi.android.dfu.extra.EXTRA_DFU_ATTEMPT";
/**
* Maximum number of DFU attempts. Default value is 0.
*/
public static final String EXTRA_MAX_DFU_ATTEMPTS = "no.nordicsemi.android.dfu.extra.EXTRA_MAX_DFU_ATTEMPTS";
/**
* If the new firmware (application) does not share the bond information with the old one,
* the bond information is lost. Set this flag to <code>true</code> to make the service create
* new bond with the new application when the upload is done (and remove the old one).
* When set to <code>false</code> (default), the DFU service assumes that the LTK is shared
* between them. Note: currently it is not possible to remove the old bond without creating
* a new one so if your old application supported bonding while the new one does not you have
* to modify the source code yourself.
* <p>
* In case of updating the soft device the application is always removed together with the
* bond information.
* <p>
* Search for occurrences of EXTRA_RESTORE_BOND in this file to check the implementation and
* get more details.
* <p>
* This flag is ignored when Secure DFU Buttonless Service is used.
* It will keep or will not restore the bond depending on the Buttonless service type.
*/
public static final String EXTRA_RESTORE_BOND = "no.nordicsemi.android.dfu.extra.EXTRA_RESTORE_BOND";
/**
* This flag indicated whether the bond information should be kept or removed after an upgrade
* of the Application. If an application is being updated on a bonded device with the DFU
* Bootloader that has been configured to preserve the bond information for the new application,
* set it to <code>true</code>.
* <p>
* By default the Legacy DFU Bootloader clears the whole application's memory. It may be,
* however, configured in the \Nordic\nrf51\components\libraries\bootloader_dfu\dfu_types.h
* file (sdk 11, line 76: <code>#define DFU_APP_DATA_RESERVED 0x0000</code>) to preserve some pages.
* The BLE_APP_HRM_DFU sample app stores the LTK and System Attributes in the first
* two pages, so in order to preserve the bond information this value should be changed to
* 0x0800 or more. For Secure DFU this value is by default set to 3 pages.
* When those data are preserved, the new Application will notify the app with the
* Service Changed indication when launched for the first time. Otherwise this service will
* remove the bond information from the phone and force to refresh the device cache
* (see {@link #refreshDeviceCache(android.bluetooth.BluetoothGatt, boolean)}).
* <p>
* In contrast to {@link #EXTRA_RESTORE_BOND} this flag will not remove the old bonding and
* recreate a new one, but will keep the bond information untouched.
* <p>
* The default value of this flag is <code>false</code>.
* <p>
* This flag is ignored when Secure DFU Buttonless Service is used. It will keep or remove the
* bond depending on the Buttonless service type.
*/
public static final String EXTRA_KEEP_BOND = "no.nordicsemi.android.dfu.extra.EXTRA_KEEP_BOND";
/**
* This property must contain a boolean value.
* <p>
* The {@link DfuBaseService}, when connected to a DFU target will check whether it is in
* application or in DFU bootloader mode. For DFU implementations from SDK 7.0 or newer
* this is done by reading the value of DFU Version characteristic.
* If the returned value is equal to 0x0100 (major = 0, minor = 1) it means that we are in the
* application mode and jump to the bootloader mode is required.
* <p>
* However, for DFU implementations from older SDKs, where there was no DFU Version
* characteristic, the service must guess. If this option is set to false (default) it will count
* number of device's services. If the count is equal to 3 (Generic Access, Generic Attribute,
* DFU Service) it will assume that it's in DFU mode. If greater than 3 - in app mode.
* This guessing may not be always correct. One situation may be when the nRF chip is used to
* flash update on external MCU using DFU. The DFU procedure may be implemented in the
* application, which may (and usually does) have more services.
* In such case set the value of this property to true.
*/
public static final String EXTRA_FORCE_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_FORCE_DFU";
/**
* This flag indicates whether the service should scan for bootloader in Legacy DFU after
* switching using buttonless service. The default value is false.
*/
public static final String EXTRA_FORCE_SCANNING_FOR_BOOTLOADER_IN_LEGACY_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_FORCE_SCANNING_FOR_BOOTLOADER_IN_LEGACY_DFU";
/**
* This options allows to disable the resume feature in Secure DFU. When the extra value is set
* to true, the DFU will send Init Packet and Data again, despite the firmware might have been
* send partially before. By default, without setting this extra, or by setting it to false,
* the DFU will resume the previously cancelled upload if CRC values match.
* <p>
* It is ignored when Legacy DFU is used.
* <p>
* This feature seems to help in some cases:
* <a href="https://github.com/NordicSemiconductor/Android-DFU-Library/issues/71">#71</a>.
*/
public static final String EXTRA_DISABLE_RESUME = "no.nordicsemi.android.dfu.extra.EXTRA_DISABLE_RESUME";
/**
* The MBR size.
*
* @see DfuServiceInitiator#setMbrSize(int)
*/
public static final String EXTRA_MBR_SIZE = "no.nordicsemi.android.dfu.extra.EXTRA_MBR_SIZE";
/**
* This extra allows you to control the MTU that will be requested (on Lollipop or newer devices).
* If the field is null, the service will not request higher MTU and will use MTU = 23
* (even if it has been set to a higher value before).
*/
public static final String EXTRA_MTU = "no.nordicsemi.android.dfu.extra.EXTRA_MTU";
/**
* This extra value will be used when MTU request returned with an error. That means, that
* MTU has been requested before and may not be changed again. This value will be used instead.
*/
public static final String EXTRA_CURRENT_MTU = "no.nordicsemi.android.dfu.extra.EXTRA_CURRENT_MTU";
/**
* Set this flag to true to enable experimental buttonless feature in Secure DFU from SDK 12.
* When the experimental Buttonless DFU Service is found on a device, the service will use it to
* switch the device to the bootloader mode, connect to it in that mode and proceed with DFU.
* <p>
* <b>Please, read the information below before setting it to true.</b>
* <p>
* In the SDK 12.x the Buttonless DFU feature for Secure DFU was experimental.
* It is NOT recommended to use it: it was not properly tested, had implementation bugs
* (e.g. https://devzone.nordicsemi.com/question/100609/sdk-12-bootloader-erased-after-programming/)
* and does not require encryption and therefore may lead to DOS attack (anyone can use it
* to switch the device to bootloader mode). However, as there is no other way to trigger
* bootloader mode on devices without a button, this DFU Library supports this service,
* but the feature must be explicitly enabled here.
* Be aware, that setting this flag to false will not protect your devices from this kind of
* attacks, as an attacker may use another app for that purpose. To be sure your device is
* secure remove this experimental service from your device.
* <p>
* <b>Spec:</b><br>
* Buttonless DFU Service UUID: 8E400001-F315-4F60-9FB8-838830DAEA50<br>
* Buttonless DFU characteristic UUID: 8E400001-F315-4F60-9FB8-838830DAEA50 (the same)<br>
* Enter Bootloader Op Code: 0x01<br>
* Correct return value: 0x20-01-01 , where:<br>
* 0x20 - Response Op Code<br>
* 0x01 - Request Code<br>
* 0x01 - Success<br>
* The device should disconnect and restart in DFU mode after sending the notification.
* <p>
* In SDK 14 this issue was fixed by Buttonless Service With Bonds.
*/
public static final String EXTRA_UNSAFE_EXPERIMENTAL_BUTTONLESS_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_UNSAFE_EXPERIMENTAL_BUTTONLESS_DFU";
/**
* The duration of a delay that will be added before sending each data packet in Secure DFU,
* in milliseconds. This defaults to 0 for backwards compatibility reason.
*/
public static final String EXTRA_DATA_OBJECT_DELAY = "no.nordicsemi.android.dfu.extra.EXTRA_DATA_OBJECT_DELAY";
/**
* This property must contain a boolean value.
* <p>
* If true the Packet Receipt Notification procedure will be enabled.
* See DFU documentation on http://infocenter.nordicsemi.com for more details.
* The number of packets before receiving a Packet Receipt Notification is set with property
* {@link #EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE}.
* The PRNs by default are enabled on devices running Android 4.3, 4.4.x and 5.x and
* disabled on 6.x and newer.
*
* @see #EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE
*/
public static final String EXTRA_PACKET_RECEIPT_NOTIFICATIONS_ENABLED = "no.nordicsemi.android.dfu.extra.EXTRA_PRN_ENABLED";
/**
* This property must contain a positive integer value, usually from range 1-200.
* <p>
* The default value is {@link DfuServiceInitiator#DEFAULT_PRN_VALUE}.
* Setting it to 0 will disable the Packet Receipt Notification procedure.
* When sending a firmware using the DFU procedure the service will send this number of packets
* before waiting for a notification. Packet Receipt Notifications are used to synchronize
* the sender with receiver.
* <p>
* On Android, calling
* {@link android.bluetooth.BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic)}
* simply adds the packet to outgoing queue before returning the callback. Adding the next
* packet in the callback is much faster than the real transmission (also the speed depends on
* the device chip manufacturer) and the queue may reach its limit. When does, the transmission
* stops and Android Bluetooth hangs (see Note below). Using PRN procedure eliminates this
* problem as the notification is send when all packets were delivered the queue is empty.
* <p>
* Note: this bug has been fixed on Android 6.0 Marshmallow and now no notifications are required.
* The onCharacteristicWrite callback will be postponed until half of the queue is empty and
* upload will be resumed automatically. Disabling PRNs speeds up the upload process on those
* devices.
*
* @see #EXTRA_PACKET_RECEIPT_NOTIFICATIONS_ENABLED
*/
public static final String EXTRA_PACKET_RECEIPT_NOTIFICATIONS_VALUE = "no.nordicsemi.android.dfu.extra.EXTRA_PRN_VALUE";
/**
* A path to the file with the new firmware. It may point to a HEX, BIN or a ZIP file.
* Some file manager applications return the path as a String while other return a Uri.
* Use the {@link #EXTRA_FILE_URI} in the later case. For files included
* in /res/raw resource directory please use {@link #EXTRA_FILE_RES_ID} instead.
*/
public static final String EXTRA_FILE_PATH = "no.nordicsemi.android.dfu.extra.EXTRA_FILE_PATH";
/**
* See {@link #EXTRA_FILE_PATH} for details.
*/
public static final String EXTRA_FILE_URI = "no.nordicsemi.android.dfu.extra.EXTRA_FILE_URI";
/**
* See {@link #EXTRA_FILE_PATH} for details.
*/
public static final String EXTRA_FILE_RES_ID = "no.nordicsemi.android.dfu.extra.EXTRA_FILE_RES_ID";
/**
* The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+).
* Must point to a 'dat' file corresponding with the selected firmware.
* The Init packet may contain just the CRC (in case of older versions of DFU) or the
* Extended Init Packet in binary format (SDK 7.0+).
*/
public static final String EXTRA_INIT_FILE_PATH = "no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_PATH";
/**
* The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+).
* Must point to a 'dat' file corresponding with the selected firmware.
* The Init packet may contain just the CRC (in case of older versions of DFU) or the
* Extended Init Packet in binary format (SDK 7.0+).
*/
public static final String EXTRA_INIT_FILE_URI = "no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_URI";
/**
* The Init packet URI. This file is required if the Extended Init Packet is required (SDK 7.0+).
* Must point to a 'dat' file corresponding with the selected firmware.
* The Init packet may contain just the CRC (in case of older versions of DFU) or the
* Extended Init Packet in binary format (SDK 7.0+).
*/
public static final String EXTRA_INIT_FILE_RES_ID = "no.nordicsemi.android.dfu.extra.EXTRA_INIT_FILE_RES_ID";
/**
* The input file mime-type. Currently only "application/zip" (ZIP) or "application/octet-stream"
* (HEX or BIN) are supported. If this parameter is empty the "application/octet-stream" is assumed.
*/
public static final String EXTRA_FILE_MIME_TYPE = "no.nordicsemi.android.dfu.extra.EXTRA_MIME_TYPE";
// Since the DFU Library version 0.5 both HEX and BIN files are supported.
// As both files have the same MIME TYPE the distinction is made based on the file extension.
public static final String MIME_TYPE_OCTET_STREAM = "application/octet-stream";
public static final String MIME_TYPE_ZIP = "application/zip";
/**
* This optional extra parameter may contain a file type. Currently supported are:
* <ul>
* <li>{@link #TYPE_SOFT_DEVICE} - only Soft Device update</li>
* <li>{@link #TYPE_BOOTLOADER} - only Bootloader update</li>
* <li>{@link #TYPE_APPLICATION} - only application update</li>
* <li>{@link #TYPE_AUTO} - the file is a ZIP file that may contain more than one HEX/BIN + DAT files.
* Since SDK 8.0 the ZIP Distribution packet is a recommended way of delivering firmware files.
* Please, see the DFU documentation for more details. A ZIP distribution packet may be created
* using the 'nrf util' Python application, available at
* <a href="https://github.com/NordicSemiconductor/pc-nrfutil">https://github.com/NordicSemiconductor/pc-nrfutil</a>.
* The ZIP file MAY contain only the following files: <b>softdevice.hex/bin</b>,
* <b>bootloader.hex/bin</b>, <b>application.hex/bin</b> to determine the type based on its name.
* At lease one of them MUST be present.
* </li>
* </ul>
* If this parameter is not provided the type is assumed as follows:
* <ol>
* <li>If the {@link #EXTRA_FILE_MIME_TYPE} field is <code>null</code> or is equal to
* {@value #MIME_TYPE_OCTET_STREAM} - the {@link #TYPE_APPLICATION} is assumed.</li>
* <li>If the {@link #EXTRA_FILE_MIME_TYPE} field is equal to {@value #MIME_TYPE_ZIP}
* - the {@link #TYPE_AUTO} is assumed.</li>
* </ol>
*/
public static final String EXTRA_FILE_TYPE = "no.nordicsemi.android.dfu.extra.EXTRA_FILE_TYPE";
/**
* <p>
* The file contains a new version of Soft Device.
* <p>
* Since DFU Library 7.0 all firmware may contain an Init packet. The Init packet is required
* if Extended Init Packet is used by the DFU bootloader (SDK 7.0+)..
* The Init packet for the bootloader must be placed in the .dat file.
*
* @see #EXTRA_FILE_TYPE
*/
public static final int TYPE_SOFT_DEVICE = 0x01;
/**
* <p>
* The file contains a new version of Bootloader.
* <p>
* Since DFU Library 7.0 all firmware may contain an Init packet. The Init packet is required
* if Extended Init Packet is used by the DFU bootloader (SDK 7.0+).
* The Init packet for the bootloader must be placed in the .dat file.
*
* @see #EXTRA_FILE_TYPE
*/
public static final int TYPE_BOOTLOADER = 0x02;
/**
* <p>
* The file contains a new version of Application.
* <p>
* Since DFU Library 0.5 all firmware may contain an Init packet. The Init packet is required
* if Extended Init Packet is used by the DFU bootloader (SDK 7.0+).
* The Init packet for the application must be placed in the .dat file.
*
* @see #EXTRA_FILE_TYPE
*/
public static final int TYPE_APPLICATION = 0x04;
/**
* <p>
* A ZIP file that consists of more than 1 file. Since SDK 8.0 the ZIP Distribution packet is
* a recommended way of delivering firmware files. Please, see the DFU documentation for
* more details. A ZIP distribution packet may be created using the 'nrf utility' command line
* application, that is a part of Master Control Panel 3.8.0.
* For backwards compatibility this library supports also ZIP files without the manifest file.
* Instead they must follow the fixed naming convention:
* The names of files in the ZIP must be: <b>softdevice.hex</b> (or .bin), <b>bootloader.hex</b>
* (or .bin), <b>application.hex</b> (or .bin) in order to be read correctly. Using the
* Soft Device v7.0.0+ the Soft Device and Bootloader may be updated and sent together.
* In case of additional application file included, the service will try to send Soft Device,
* Bootloader and Application together (which is not supported currently) and if it fails,
* send first SD+BL, reconnect and send the application in the following connection.
* <p>
* Since the DFU Library 0.5 you may specify the Init packet, that will be send prior to the
* firmware. The init packet contains some verification data, like a device type and revision,
* application version or a list of supported Soft Devices. The Init packet is required if
* Extended Init Packet is used by the DFU bootloader (SDK 7.0+).
* In case of using the compatibility ZIP files the Init packet for the Soft Device and Bootloader
* must be in the 'system.dat' file while for the application in the 'application.dat' file
* (included in the ZIP). The CRC in the 'system.dat' must be a CRC of both BIN contents if
* both a Soft Device and a Bootloader is present.
*
* @see #EXTRA_FILE_TYPE
*/
public static final int TYPE_AUTO = 0x00;
/**
* An extra field with progress and error information used in broadcast events.
*/
public static final String EXTRA_DATA = "no.nordicsemi.android.dfu.extra.EXTRA_DATA";
/**
* An extra field to send the progress or error information in the DFU notification.
* The value may contain:
* <ul>
* <li>Value 0 - 100 - percentage progress value</li>
* <li>One of the following status constants:
* <ul>
* <li>{@link #PROGRESS_CONNECTING}</li>
* <li>{@link #PROGRESS_STARTING}</li>
* <li>{@link #PROGRESS_ENABLING_DFU_MODE}</li>
* <li>{@link #PROGRESS_VALIDATING}</li>
* <li>{@link #PROGRESS_DISCONNECTING}</li>
* <li>{@link #PROGRESS_COMPLETED}</li>
* <li>{@link #PROGRESS_ABORTED}</li>
* </ul>
* </li>
* <li>An error code with {@link #ERROR_MASK} if initialization error occurred</li>
* <li>An error code with {@link #ERROR_REMOTE_MASK} if remote DFU target returned an error</li>
* <li>An error code with {@link #ERROR_CONNECTION_MASK} if connection error occurred
* (e.g. GATT error (133) or Internal GATT Error (129))</li>
* </ul>
* To check if error occurred use:<br>
* {@code boolean error = progressValue >= DfuBaseService.ERROR_MASK;}
*/
public static final String EXTRA_PROGRESS = "no.nordicsemi.android.dfu.extra.EXTRA_PROGRESS";
/**
* The number of currently transferred part. The SoftDevice and Bootloader may be send
* together as one part. If user wants to upload them together with an application it has to be
* sent in another connection as the second part.
*
* @see no.nordicsemi.android.dfu.DfuBaseService#EXTRA_PARTS_TOTAL
*/
public static final String EXTRA_PART_CURRENT = "no.nordicsemi.android.dfu.extra.EXTRA_PART_CURRENT";
/**
* Number of parts in total.
*
* @see no.nordicsemi.android.dfu.DfuBaseService#EXTRA_PART_CURRENT
*/
public static final String EXTRA_PARTS_TOTAL = "no.nordicsemi.android.dfu.extra.EXTRA_PARTS_TOTAL";
/**
* The current upload speed in bytes/millisecond.
*/
public static final String EXTRA_SPEED_B_PER_MS = "no.nordicsemi.android.dfu.extra.EXTRA_SPEED_B_PER_MS";
/**
* The average upload speed in bytes/millisecond for the current part.
*/
public static final String EXTRA_AVG_SPEED_B_PER_MS = "no.nordicsemi.android.dfu.extra.EXTRA_AVG_SPEED_B_PER_MS";
/**
* The broadcast message contains the following extras:
* <ul>
* <li>{@link #EXTRA_DATA} - the progress value (percentage 0-100) or:
* <ul>
* <li>{@link #PROGRESS_CONNECTING}</li>
* <li>{@link #PROGRESS_STARTING}</li>
* <li>{@link #PROGRESS_ENABLING_DFU_MODE}</li>
* <li>{@link #PROGRESS_VALIDATING}</li>
* <li>{@link #PROGRESS_DISCONNECTING}</li>
* <li>{@link #PROGRESS_COMPLETED}</li>
* <li>{@link #PROGRESS_ABORTED}</li>
* </ul>
* </li>
* <li>{@link #EXTRA_DEVICE_ADDRESS} - the target device address</li>
* <li>{@link #EXTRA_PART_CURRENT} - the number of currently transmitted part</li>
* <li>{@link #EXTRA_PARTS_TOTAL} - total number of parts that are being sent, e.g. if a ZIP
* file contains a Soft Device, a Bootloader and an Application, the SoftDevice and Bootloader
* will be send together as one part. Then the service will disconnect and reconnect to the
* new Bootloader and send the application as part number two.</li>
* <li>{@link #EXTRA_SPEED_B_PER_MS} - current speed in bytes/millisecond as float</li>
* <li>{@link #EXTRA_AVG_SPEED_B_PER_MS} - the average transmission speed in bytes/millisecond
* as float</li>
* </ul>
*/
public static final String BROADCAST_PROGRESS = "no.nordicsemi.android.dfu.broadcast.BROADCAST_PROGRESS";
/**
* Service is connecting to the remote DFU target.
*/
public static final int PROGRESS_CONNECTING = -1;
/**
* Service is enabling notifications and starting transmission.
*/
public static final int PROGRESS_STARTING = -2;
/**
* Service has triggered a switch to bootloader mode. Now the service waits for the link loss
* event (this may take up to several seconds) and will connect again to the same device,
* now started in the bootloader mode.
*/
public static final int PROGRESS_ENABLING_DFU_MODE = -3;
/**
* Service is sending validation request to the remote DFU target.
*/
public static final int PROGRESS_VALIDATING = -4;
/**
* Service is disconnecting from the DFU target.
*/
public static final int PROGRESS_DISCONNECTING = -5;
/**
* The connection is successful.
*/
public static final int PROGRESS_COMPLETED = -6;
/**
* The upload has been aborted. Previous software version will be restored on the target.
*/
public static final int PROGRESS_ABORTED = -7;
/**
* The broadcast error message contains the following extras:
* <ul>
* <li>{@link #EXTRA_DATA} - the error number. Use {@link GattError#parse(int)} to get String
* representation.</li>
* <li>{@link #EXTRA_DEVICE_ADDRESS} - the target device address</li>
* </ul>
*/
public static final String BROADCAST_ERROR = "no.nordicsemi.android.dfu.broadcast.BROADCAST_ERROR";
/**
* The type of the error. This extra contains information about that kind of error has occurred.
* Connection state errors and other errors may share the same numbers. For example, the
* {@link BluetoothGattCallback#onCharacteristicWrite(BluetoothGatt, BluetoothGattCharacteristic, int)}
* method may return a status code 8 (GATT INSUF AUTHORIZATION), while the status code 8
* returned by {@link BluetoothGattCallback#onConnectionStateChange(BluetoothGatt, int, int)}
* is a GATT CONN TIMEOUT error.
*/
public static final String EXTRA_ERROR_TYPE = "no.nordicsemi.android.dfu.extra.EXTRA_ERROR_TYPE";
public static final int ERROR_TYPE_OTHER = 0;
public static final int ERROR_TYPE_COMMUNICATION_STATE = 1;
public static final int ERROR_TYPE_COMMUNICATION = 2;
public static final int ERROR_TYPE_DFU_REMOTE = 3;
/**
* If this bit is set than the progress value indicates an error. Use {@link GattError#parse(int)}
* to obtain error name.
*/
public static final int ERROR_MASK = 0x1000;
public static final int ERROR_DEVICE_DISCONNECTED = ERROR_MASK; // | 0x00;
public static final int ERROR_FILE_NOT_FOUND = ERROR_MASK | 0x01;
/**
* Thrown if service was unable to open the file ({@link java.io.IOException} has been thrown).
*/
public static final int ERROR_FILE_ERROR = ERROR_MASK | 0x02;
/**
* Thrown when input file is not a valid HEX or ZIP file.
*/
public static final int ERROR_FILE_INVALID = ERROR_MASK | 0x03;
/**
* Thrown when {@link java.io.IOException} occurred when reading from file.
*/
public static final int ERROR_FILE_IO_EXCEPTION = ERROR_MASK | 0x04;
/**
* Error thrown when {@code gatt.discoverServices();} returns false.
*/
public static final int ERROR_SERVICE_DISCOVERY_NOT_STARTED = ERROR_MASK | 0x05;
/**
* Thrown when the service discovery has finished but the DFU service has not been found.
* The device does not support DFU of is not in DFU mode.
*/
public static final int ERROR_SERVICE_NOT_FOUND = ERROR_MASK | 0x06;
/**
* Thrown when unknown response has been obtained from the target. The DFU target must follow
* specification.
*/
public static final int ERROR_INVALID_RESPONSE = ERROR_MASK | 0x08;
/**
* Thrown when the the service does not support given type or mime-type.
*/
public static final int ERROR_FILE_TYPE_UNSUPPORTED = ERROR_MASK | 0x09;
/**
* Thrown when the the Bluetooth adapter is disabled.
*/
public static final int ERROR_BLUETOOTH_DISABLED = ERROR_MASK | 0x0A;
/**
* DFU Bootloader version 0.6+ requires sending the Init packet. If such bootloader version is
* detected, but the init packet has not been set this error is thrown.
*/
public static final int ERROR_INIT_PACKET_REQUIRED = ERROR_MASK | 0x0B;
/**
* Thrown when the firmware file is not word-aligned. The firmware size must be dividable by
* 4 bytes.
*/
public static final int ERROR_FILE_SIZE_INVALID = ERROR_MASK | 0x0C;
/**
* Thrown when the received CRC does not match with the calculated one. The service will try
* 3 times to send the data, and if the CRC fails each time this error will be thrown.
*/
public static final int ERROR_CRC_ERROR = ERROR_MASK | 0x0D;
/**
* Thrown when device had to be paired before the DFU process was started.
*/
public static final int ERROR_DEVICE_NOT_BONDED = ERROR_MASK | 0x0E;
/**
* Thrown when the DFU library lost track of what is going on. Reported number of bytes is
* not equal to the number of bytes sent and due to some other events the library cannot recover.
* <p>
* Check https://github.com/NordicSemiconductor/Android-DFU-Library/issues/229
*/
public static final int ERROR_PROGRESS_LOST = ERROR_MASK | 0x0F;
/**
* Flag set when the DFU target returned a DFU error. Look for DFU specification to get error
* codes. The error code is binary OR-ed with one of: {@link #ERROR_REMOTE_TYPE_LEGACY},
* {@link #ERROR_REMOTE_TYPE_SECURE} or {@link #ERROR_REMOTE_TYPE_SECURE_EXTENDED}.
*/
public static final int ERROR_REMOTE_MASK = 0x2000;
public static final int ERROR_REMOTE_TYPE_LEGACY = 0x0100;
public static final int ERROR_REMOTE_TYPE_SECURE = 0x0200;
public static final int ERROR_REMOTE_TYPE_SECURE_EXTENDED = 0x0400;
public static final int ERROR_REMOTE_TYPE_SECURE_BUTTONLESS = 0x0800;
/**
* The flag set when one of {@link android.bluetooth.BluetoothGattCallback} methods was called
* with status other than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}.
*/
public static final int ERROR_CONNECTION_MASK = 0x4000;
/**
* The flag set when the
* {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
* method was called with status other than {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}.
*/
public static final int ERROR_CONNECTION_STATE_MASK = 0x8000;
/**
* The log events are only broadcast when there is no nRF Logger installed.
* The broadcast contains 2 extras:
* <ul>
* <li>{@link #EXTRA_LOG_LEVEL} - The log level, one of following: {@link #LOG_LEVEL_DEBUG},
* {@link #LOG_LEVEL_VERBOSE}, {@link #LOG_LEVEL_INFO}, {@link #LOG_LEVEL_APPLICATION},
* {@link #LOG_LEVEL_WARNING}, {@link #LOG_LEVEL_ERROR}</li>
* <li>{@link #EXTRA_LOG_MESSAGE} - The log message</li>
* </ul>
*/
public static final String BROADCAST_LOG = "no.nordicsemi.android.dfu.broadcast.BROADCAST_LOG";
public static final String EXTRA_LOG_MESSAGE = "no.nordicsemi.android.dfu.extra.EXTRA_LOG_INFO";
public static final String EXTRA_LOG_LEVEL = "no.nordicsemi.android.dfu.extra.EXTRA_LOG_LEVEL";
/*
* Note:
* The nRF Logger API library has been excluded from the DfuLibrary.
* All log events are now being sent using local broadcasts and may be logged into nRF Logger
* in the app module. This is to make the Dfu module independent from logging tool.
*
* The log levels below are equal to log levels in nRF Logger API library, v 2.0.
* @see https://github.com/NordicSemiconductor/nRF-Logger-API
*/
/**
* Level used just for debugging purposes. It has lowest level
*/
public final static int LOG_LEVEL_DEBUG = 0;
/**
* Log entries with minor importance
*/
public final static int LOG_LEVEL_VERBOSE = 1;
/**
* Default logging level for important entries
*/
public final static int LOG_LEVEL_INFO = 5;
/**
* Log entries level for applications
*/
public final static int LOG_LEVEL_APPLICATION = 10;
/**
* Log entries with high importance
*/
public final static int LOG_LEVEL_WARNING = 15;
/**
* Log entries with very high importance, like errors
*/
public final static int LOG_LEVEL_ERROR = 20;
/**
* Activity may broadcast this broadcast in order to pause, resume or abort DFU process.
* Use {@link #EXTRA_ACTION} extra to pass the action.
*/
public static final String BROADCAST_ACTION = "no.nordicsemi.android.dfu.broadcast.BROADCAST_ACTION";
/**
* The action extra. It may have one of the following values: {@link #ACTION_PAUSE},
* {@link #ACTION_RESUME}, {@link #ACTION_ABORT}.
*/
public static final String EXTRA_ACTION = "no.nordicsemi.android.dfu.extra.EXTRA_ACTION";
/**
* Pauses the upload. The service will wait for broadcasts with the action set to
* {@link #ACTION_RESUME} or {@link #ACTION_ABORT}.
*/
public static final int ACTION_PAUSE = 0;
/** Resumes the upload that has been paused before using {@link #ACTION_PAUSE}. */
public static final int ACTION_RESUME = 1;
/**
* Aborts the upload. The service does not need to be paused before.
* After sending {@link #BROADCAST_ACTION} with extra {@link #EXTRA_ACTION} set to this value
* the DFU bootloader will restore the old application (if there was already an application).
* Be aware, that uploading the Soft Device will erase the application in order to make space
* in the memory. In case there is no application, or the application has been removed, the
* DFU bootloader will be started and user may try to send the application again.
* The bootloader may advertise with the address incremented by 1 to prevent caching services.
*/
public static final int ACTION_ABORT = 2;
public static final String EXTRA_CUSTOM_UUIDS_FOR_LEGACY_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_CUSTOM_UUIDS_FOR_LEGACY_DFU";
public static final String EXTRA_CUSTOM_UUIDS_FOR_SECURE_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_CUSTOM_UUIDS_FOR_SECURE_DFU";
public static final String EXTRA_CUSTOM_UUIDS_FOR_EXPERIMENTAL_BUTTONLESS_DFU = "no.nordicsemi.android.dfu.extra.EXTRA_CUSTOM_UUIDS_FOR_EXPERIMENTAL_BUTTONLESS_DFU";
public static final String EXTRA_CUSTOM_UUIDS_FOR_BUTTONLESS_DFU_WITHOUT_BOND_SHARING = "no.nordicsemi.android.dfu.extra.EXTRA_CUSTOM_UUIDS_FOR_BUTTONLESS_DFU_WITHOUT_BOND_SHARING";
public static final String EXTRA_CUSTOM_UUIDS_FOR_BUTTONLESS_DFU_WITH_BOND_SHARING = "no.nordicsemi.android.dfu.extra.EXTRA_CUSTOM_UUIDS_FOR_BUTTONLESS_DFU_WITH_BOND_SHARING";
/**
* Lock used in synchronization purposes
*/
private final Object mLock = new Object();
private BluetoothAdapter mBluetoothAdapter;
private String mDeviceAddress;
private String mDeviceName;
private boolean mDisableNotification;
/**
* The current connection state. If its value is > 0 than an error has occurred.
* Error number is a negative value of mConnectionState
*/
protected int mConnectionState;
protected final static int STATE_DISCONNECTED = 0;
protected final static int STATE_CONNECTING = -1;
protected final static int STATE_CONNECTED = -2;
protected final static int STATE_CONNECTED_AND_READY = -3; // indicates that services were discovered
protected final static int STATE_DISCONNECTING = -4;
protected final static int STATE_CLOSED = -5;
/**
* The number of the last error that has occurred or 0 if there was no error
*/
private int mError;
/**
* Stores the last progress percent. Used to prevent from sending progress notifications with
* the same value.
*/
private int mLastProgress = -1;
/* package */ DfuProgressInfo mProgressInfo;
private long mLastNotificationTime;
/** Flag set to true if sending was aborted. */
private boolean mAborted;
private DfuCallback mDfuServiceImpl;
private InputStream mFirmwareInputStream, mInitFileInputStream;
private final BroadcastReceiver mDfuActionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final int action = intent.getIntExtra(EXTRA_ACTION, 0);
logi("User action received: " + action);
switch (action) {
case ACTION_PAUSE:
sendLogBroadcast(LOG_LEVEL_WARNING, "[Broadcast] Pause action received");
if (mDfuServiceImpl != null)
mDfuServiceImpl.pause();
break;
case ACTION_RESUME:
sendLogBroadcast(LOG_LEVEL_WARNING, "[Broadcast] Resume action received");
if (mDfuServiceImpl != null)
mDfuServiceImpl.resume();
break;
case ACTION_ABORT:
sendLogBroadcast(LOG_LEVEL_WARNING, "[Broadcast] Abort action received");
mAborted = true;
if (mDfuServiceImpl != null)
mDfuServiceImpl.abort();
break;
}
}
};
private final BroadcastReceiver mBluetoothStateBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
final int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.STATE_ON);
logw("Action received: android.bluetooth.adapter.action.STATE_CHANGED [state: " + state + ", previous state: " + previousState + "]");
if (previousState == BluetoothAdapter.STATE_ON
&& (state == BluetoothAdapter.STATE_TURNING_OFF || state == BluetoothAdapter.STATE_OFF)) {
sendLogBroadcast(LOG_LEVEL_WARNING, "Bluetooth adapter disabled");
mConnectionState = STATE_DISCONNECTED;
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onDisconnected();
// Notify waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
}
};
private final BroadcastReceiver mBondStateBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
// Obtain the device and check if this is the one that we are connected to
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device == null || !device.getAddress().equals(mDeviceAddress))
return;
// Read bond state
final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
if (bondState == BluetoothDevice.BOND_BONDING)
return;
if (mDfuServiceImpl != null)
mDfuServiceImpl.onBondStateChanged(bondState);
}
};
private final BroadcastReceiver mConnectionStateBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
// Obtain the device and check it this is the one that we are connected to
final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device == null || !device.getAddress().equals(mDeviceAddress))
return;
final String action = intent.getAction();
logi("Action received: " + action);
sendLogBroadcast(LOG_LEVEL_DEBUG, "[Broadcast] Action received: " + action);
/*
Handling the disconnection event here could lead to race conditions, as it also may (most probably will)
be delivered to onConnectionStateChange below.
See: https://github.com/NordicSemiconductor/Android-DFU-Library/issues/55
Note: This broadcast is now received on all 3 ACL events!
Don't assume DISCONNECT here.
mConnectionState = STATE_DISCONNECTED;
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onDisconnected();
// Notify waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
*/
}
};
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");
sendLogBroadcast(LOG_LEVEL_INFO, "Connected to " + mDeviceAddress);
mConnectionState = STATE_CONNECTED;
/*
* The onConnectionStateChange callback is called just after establishing connection and before sending Encryption Request BLE event in case of a paired device.
* In that case and when the Service Changed CCCD is enabled we will get the indication after initializing the encryption, about 1600 milliseconds later.
* If we discover services right after connecting, the onServicesDiscovered callback will be called immediately, before receiving the indication and the following
* service discovery and we may end up with old, application's services instead.
*
* This is to support the buttonless switch from application to bootloader mode where the DFU bootloader notifies the master about service change.
* Tested on Nexus 4 (Android 4.4.4 and 5), Nexus 5 (Android 5), Samsung Note 2 (Android 4.4.2). The time after connection to end of service discovery is about 1.6s
* on Samsung Note 2.
*
* NOTE: We are doing this to avoid the hack with calling the hidden gatt.refresh()
* method, at least for bonded devices.
*
* IMPORTANT: BluetoothDevice.getBondState() returns true if the bond information
* is present on Android, not necessarily when the link is established or even
* encrypted. This is a security issue, but in here it does not matter.
*/
if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_BONDED) {
logi("Waiting 1600 ms for a possible Service Changed indication...");
waitFor(1600);
// After 1.6s the services are already discovered so the following gatt.discoverServices() finishes almost immediately.
// NOTE: This also works with shorted waiting time. The gatt.discoverServices() must be called after the indication is received which is
// about 600ms after establishing connection. Values 600 - 1600ms should be OK.
}
// Attempts to discover services after successful connection.
sendLogBroadcast(LOG_LEVEL_VERBOSE, "Discovering services...");
sendLogBroadcast(LOG_LEVEL_DEBUG, "gatt.discoverServices()");
final boolean success = gatt.discoverServices();
logi("Attempting to start service discovery... " + (success ? "succeed" : "failed"));
if (!success) {
mError = 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;
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onDisconnected();
}
} else {
if (status == 0x08 /* GATT CONN TIMEOUT */ || status == 0x13 /* GATT CONN TERMINATE PEER USER */)
logw("Target device disconnected with status: " + status);
else
loge("Connection state change error: " + status + " newState: " + newState);
mError = ERROR_CONNECTION_STATE_MASK | status;
if (newState == BluetoothGatt.STATE_DISCONNECTED) {
mConnectionState = STATE_DISCONNECTED;
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onDisconnected();
}
}
// Notify waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
@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);
mError = ERROR_CONNECTION_MASK | status;
}
// Notify waiting thread
synchronized (mLock) {
mLock.notifyAll();
}
}
// Other methods just pass the parameters through
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onCharacteristicWrite(gatt, characteristic, status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onCharacteristicRead(gatt, characteristic, status);
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onCharacteristicChanged(gatt, characteristic);
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onDescriptorWrite(gatt, descriptor, status);
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (mDfuServiceImpl != null)
mDfuServiceImpl.getGattCallback().onDescriptorRead(gatt, descriptor, status);
}
@SuppressLint("NewApi")
@Override
public void onMtuChanged(final BluetoothGatt gatt, final int mtu, final int status) {