From 6a568a027fc640e10eba012200605407fd4bc60c Mon Sep 17 00:00:00 2001 From: shengMR <954164687@qq.com> Date: Mon, 30 Mar 2020 15:59:36 +0800 Subject: [PATCH] =?UTF-8?q?1,=E6=AD=A3=E5=BC=8F=E7=89=88=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 11 + .idea/caches/gradle_models.ser | Bin 0 -> 192608 bytes .idea/codeStyles/Project.xml | 116 +++ .idea/encodings.xml | 4 + .idea/gradle.xml | 22 + .idea/misc.xml | 47 + .idea/runConfigurations.xml | 12 + .idea/vcs.xml | 6 + README.md | 216 +++++ app/.gitignore | 1 + app/build.gradle | 31 + app/proguard-rules.pro | 21 + app/src/main/AndroidManifest.xml | 27 + .../com/cys/DiscoverServicesActivity.java | 348 +++++++ app/src/main/java/com/cys/MainActivity.java | 245 +++++ .../java/com/cys/adapter/DeviceAdapter.java | 66 ++ .../com/cys/adapter/DeviceViewHolder.java | 20 + .../drawable-v24/ic_launcher_foreground.xml | 34 + .../res/drawable-xxhdpi/ic_arrow_down.png | Bin 0 -> 380 bytes .../main/res/drawable-xxhdpi/ic_arrow_up.png | Bin 0 -> 375 bytes .../res/drawable/ic_launcher_background.xml | 170 ++++ .../res/layout/activity_discover_services.xml | 18 + app/src/main/res/layout/activity_main.xml | 50 + .../layout/adapter_item_characteristic.xml | 77 ++ .../main/res/layout/adapter_item_device.xml | 28 + .../main/res/layout/adapter_item_service.xml | 31 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2963 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/values/colors.xml | 6 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 11 + build.gradle | 28 + fastblegatt/.gitignore | 1 + fastblegatt/build.gradle | 26 + fastblegatt/proguard-rules.pro | 21 + fastblegatt/src/main/AndroidManifest.xml | 2 + .../java/com/cys/fastblegatt/FastBleGatt.java | 869 ++++++++++++++++++ .../com/cys/fastblegatt/FastBleManager.java | 139 +++ .../cys/fastblegatt/callback/BleCallback.java | 24 + .../callback/BleCallbackAdapterListener.java | 47 + .../fastblegatt/callback/RequestCallback.java | 16 + .../com/cys/fastblegatt/request/Request.java | 126 +++ .../java/com/cys/fastblegatt/util/Logger.java | 17 + .../cys/fastblegatt/util/PrintHelpper.java | 68 ++ fastblegatt/src/main/res/values/strings.xml | 3 + gradle.properties | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++ gradlew.bat | 84 ++ settings.gradle | 1 + 61 files changed, 3298 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/caches/gradle_models.ser create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/cys/DiscoverServicesActivity.java create mode 100644 app/src/main/java/com/cys/MainActivity.java create mode 100644 app/src/main/java/com/cys/adapter/DeviceAdapter.java create mode 100644 app/src/main/java/com/cys/adapter/DeviceViewHolder.java create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable-xxhdpi/ic_arrow_down.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_arrow_up.png create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/layout/activity_discover_services.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/adapter_item_characteristic.xml create mode 100644 app/src/main/res/layout/adapter_item_device.xml create mode 100644 app/src/main/res/layout/adapter_item_service.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 build.gradle create mode 100644 fastblegatt/.gitignore create mode 100644 fastblegatt/build.gradle create mode 100644 fastblegatt/proguard-rules.pro create mode 100644 fastblegatt/src/main/AndroidManifest.xml create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/FastBleGatt.java create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/FastBleManager.java create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallback.java create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/callback/BleCallbackAdapterListener.java create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/callback/RequestCallback.java create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/request/Request.java create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/util/Logger.java create mode 100644 fastblegatt/src/main/java/com/cys/fastblegatt/util/PrintHelpper.java create mode 100644 fastblegatt/src/main/res/values/strings.xml create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd45b12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser new file mode 100644 index 0000000000000000000000000000000000000000..d071ee88f688dcf3e39a9fa2a8d0801b4a4aa006 GIT binary patch literal 192608 zcmdRX3A`LdoqxZ1R{}{0HwXl|FEg2*p7RBSM?x-MLd*+S7{#7GUM87~nSRN8$RZv@ z*Bg*yMG+Ad7ZhC2^*~e<*K1W2T~UM;!Ii(a>xuCHR#jIYGd-_fdb*XxkDWKwRrUMT zud2TFtE%7c*LVFw8g93w^;)BrRO@=HQPGoDqfu=qE4ra3r&_9BHImI%V~3$x$@Xkr zOV+27nyPKD;NMmF*U&e!w;LO}QEf}?zeGYxOh^aTX3w6kRQ1hj%c^LVrdqe!mb7$Y zhq_B$F>O_RcdOXPd@l(v?J>i>R0!@f^U!Q&sCzD<-WLdW1-bNaE z*GNYu$JuD`@JZx=|Aq#oMcbsqYP09JL2(lQuNoJqtqMAK660e+T3Va63)>^ZlJXNE zy}}+~D=@TH;CHQ2U%>|03QBUtdfo6B#K<_n9zsM4?XzdexUhwBRBiU08LOqPHR?u7 zwG4f-G2POPHRv{@g<<9$Jd*|)Wj^tdTK1Wco>!YaZ{u!Z6Vk)qhYPCv^J~Oc1v2qxT-W(tf^EDWH`7s zJ84uw(a`OV;9^1pm(*rGiF9=ANBhW5^>S~Ip2Zm4MKV@6$4Q&xq@Uv?8z~(evr!@q z(9t|pn_aKlq6Q~h3{_XlYKaB+d8{ytW_1dl@GTLd!kE>1V zyrzX(FrrWL&7v|vR-;_+I$gC^HR@(%%97G^ zv;a+a=M)N9D*CAf4l$!v&(_siMO$O2)-<5mCJSs}GjMgKX4HwO`5S=f2CNb`Q;q81)8z&(pKd`L_EKN~C1Gs&iz_~&C z7Nbqkb#u!wD>HWgB7L$lRmTdz`j_X&=qD?|`gVKT=_|5c)X{Z~>MlcHZ2$pvqpnq) z_W>}4frY8UVT$(oOiOwJW~8I4%rQf$=$qO#zpU3+CHV40MkSv9DpjadaSg&(b@??^QEHH)_0bgRn;;< zw_UQ~e|fsV|4i<{WF)_R~%A52*S3N}=*7ki=o`3tUO z1Nf)^^US*;jFfl8&N1&DPF)#{ZX*lwYNI?oHAP07PbAwUEK?im756nw*;s)tID1Z= zNc`+ZL2iLtC;Y1O_(~qsW(?cpfNB^ zaa*#`$2!&g#+%S8G%O>H`g(nG+H~Rh(Aq4@x2J1H3!OAIt+sTcNilGs-RLO1RaG;{ ze#h=cY&F38Ml9SA%Z$xB8JSBbB>xfy*#*^&QHB-c0<~%{0BkR4FG>WpJdCnl{>p=Q z9me)9?i`TN?b&&t2R3s(!6R{rG_=#0rHx*3i8Qjy;b{d>gU>d}{R{9ltdpTa-kk*w zmQkpq`%PDIY9D`2t!d4I^2ND>uHN>;C%;J{ra5rm4e!30_1EA)z?AlF!RF|E|4ay& z*Ml7+0qf^DHrXb&utdIuc`PeklaZv`^XiZ%AABt~6PMa>cc}&C`BE-7JYGAzr{kq-# zjwopMevixwA{Z#X{?UE7e`nzFU!ma<^I;Ugo zu(5>Y=fzPhKkb&rmo~CxqG(o9(MfC@L}cO4LWlIF@sXyg(453eu@T=oEPJLWXKHMW;}p_{IA;R%nsJxJ#$3xIg@E}M-;i$^ zb5kzI*JO^0ZvO|gJigJ_X1ME8Klh67-FcEF?R)8Ty<*vGAi=*)o38)h?p{OUf~e#} z*4%lb??o}t6vOWM(Nh)S?LUBI4E-rY*o$M4Y!|c|(Ua+osSBO)+-v4+jh-C=ipDUU zMDQ8eis6W}1d>dfbdf6nH)9x6U4Xf1`-k51y4^dHMbWlU z1crZSauN5R5CkKeoUH#K%`N~`qwTO>;74?ZHNB?A=4g2kAYQ~{Muc`3M%=i0Azj1+ zrwiqZFIoM{U(xP7Qh4Z1&7LKEFI_D&=Yf6va@w#Jrocpj1^tpF@ z|Ck3zi7RNen=ZfMghP9cmq>NJEsR+)m~M-zBgwMmP!&UJWCO@cR|Ks85Tdd4s}N)( zRY!Qg20`sq90RBq;9}3_>F-`MDN@azw_Sk0u?#H{3P*NUj(XBOg5?42*31>ojm)x@hD>$cufJw2#WpuwjpY+qpC5c@6F}^Sb6)mUPM-CMrJKFc|2%g|wto=P2&KShQ~|ul}#p^Ew80g8n}D{MAohPR9L4 z`g7Bd&)k#m$mrZ)MW%n*U9uI5?%z?b_E8lt7mz<$4*7O1 zACc+RUP9wNZ^riZWa~APB2#}nv`T{YB$lBiT2ms^t9=cf*SZqR`0}nT3_th$=qDfb zoJ(AE^OM89dOfm}C9=s717~Ck1S2O#%*f8^C_xca{(Y%M+jl~6j7*c~qU=J&$S#Q~ zh2C&DMBewB5$8pv*;6J202u9n5g}h>5YL)6FUX5_)cCNjC1t{>&aPeCk`B8z=)^wT zOM;=qk{gz$+#SJALe7oq%%r~4;|y>d^_qZ{shey`C(fZ2yZ$85sHx2D>!k@44{L{=@#E8vHUyF?#&Ju6c#}gC(#+Cu2#QZ6{6m5rMlY_z_X3!1-GD zy4`7yFvnIKZ>afj$8f{A!W-u1XUh}b5u{CZZtuyg2T|XF(xGp8nS&2fzOO)>Yqq`(J(uw}x%f zFttX8XM#Vg?U8f9f8TohA7AmB0SzFRlvh*@1hfUM9veDXe*Y!v3`_@Na@9}XM zl#?kskv3iQ<*v<12@L5%TX5oUb-A3CNSuXOqb)-=3$H!nJ!0ZdUZL1%ycy-?ius-*xk_O59%&N zorj@VSkI|z4IMsWgch_q;$ltPyb2z6&}_;Txbo5kh`nU`Quj-?Ivw}TTd#pz$@yE? zEH5B@fSk;ef%pixpRWs|$GOzQ_BNubt=evAJBjMW7;Jaw4e)ayzQ@Gr+2iMc<#ZB0 zN-Z?PaepUGO*lHlY=&qE!%8xfOgSMBj;4!GurCbp;k*13hkoSr_ucXcU5n5>;-Bg$ z86I~R*Iza1^H<&Rt99S{@n=u|NkW3B(}EhjO?Op{-NZ|J5+3si$zTypVBGPooHVAW zTOeT_o|TJh8`T|+mYr%zXZiGL&=>j39&f9Pb`4a0+*r|C4LgB!+Y^*d1umTa#6_H4 z9A7_69h=Nhwcm{Tc8LtK7`94Nt)@FDV!WU|#ENt@y74!=$w zYycDE0@*jMc9lkzs=z)7}g2-ty*9LFp;;rg6I39pbpc1o)&Z8*nz9oOMmNIgJp`EkcH z$~Rmrt5G|9x{l~QTh;bX zXO~M)9fG#+JX8-mPFPbhsyeL@TXBdmuqK2~akgt)?Bg9f`U(*s1_Hcp?SV$v>~L%M zz;S0+yM@rpAmbh?puf;-9ZiD&AB&^F_Jyv8A%bh1H*Gbi+3_713ieC&fs;p6WRawc zKAPD<64Qb(;sl1s%y$%fO09%5aDJZShb-a2gJ(bA;GsuD?&zQaKs4Tvg_-hkLm2qiZPS`0D)g8l|)8r zZpTT`TmcfQ#P>SAqO%Yv?aBN?7XRXiiEqPLKxVe?z2@Zh zkAPZ-x@sI0%bkW!;?bbp->-ge+x0&>bs16L_~8Xr_U!kcTz&QRH+gGz*6Ct)slCJZ+l_0OwU`IM!I%R2e7>+UpZURTLjvKK0Z%j~SJs6mD3JgV!S zt3>|>-fmD^&Nc&Mh(!G0n$ukIlvt}d!2rz=Rny20PBbb1*vD&IN<ZM5uQCCv?et(Z7>&Zv~nJp0dLLIFdpd#dW$3vBd*`!cT1|H{7a8svt z%~XIAi7bc-U1PIidAekhh!xAzNtw)MXC&EFkmks@#N=DjCRf)q!7X6@%=AFcwrna>0No?i}ZC0xq zMDs%gyD>S5|9N^9%}gBi@Jn9&3N)T&K^=z#*Fo|g1MQO5@tdhli+wVB^WMuSm<$me zSS?X9rF1H<<`9<4$f-&;n>Cbl4$_j%e69?+=|wfI6t%Ku*rk@!QXQhnUw_3tXx6bl zdK$Jvb4ukMOCMQBntU>f?%!3E3WGdOy?5K0kN70`K`)v`GD?W(0)_B9>&h>qCBCg0pZNB_%hh_l!EV9=0Dv_1 zs8y#tb{ghTKmh_1iRY8YG30SG$F<&<@%`{H@2>Gn16lbOsT082DEj zu_|%YZ6~aKIpzku_vjslrH8KUCySol&;C9 zVH(9^!7M15f}GNG1v8y57;3g)Xspa~Ql?{K-1*&?eICs_rO}#7ZdYp>F4?Uy{>O5n zwE*bI#VBw?;i!>VNgAFw>U9s?breJslpS?0^44jK0!N9;L!W&5%RWiGWA7#5t-2IM zAK0OkvbvGdFdz(5E)?awrWdk$O2s@Xm(xl?F60VXz^MK_cJ%k7j4x@HfI!ex=q9KnpS6U~3+9#RoqmqnGkV++!2W}`@T2|7j zVj-0-q|KtKC_2z3oy(}XVk(o$Dh1P(id{NDBF%R;pMgY<4VDN6*nn}fc;wfQSkhug z93)?-EYy2;%05|ySY5naxvin#N`^-!t){cOrljR!CS5dB21Z9NmC9jhkyWjnDVEKY zoKjQ%q5)F)?pOY4HKcGv-9X4;Gr1E%JI2T(J(k88a#ScjZEpR*H6H*_&>SeXucsIt z3wTG%{q)s0{hLn&uiX3L;HkSr36^NpB@HWPKCfm?y_m`yW+8_Gfl-hx=Cqud&6uW| zF^ZW?niYaRgLZVxhT~8E@Y})uF$AL9mmyl0G2I;7fi1#roYEUDwB4g<@V0Mzq*R58 zxSI_%Eb`XiN&uxB|Gj;gPXce;dkJ_eryDQE9^?Q5rCi=LGg&ARMk;M&3z!9Fv8-g2 zw3;=_Wh0fBZZDxJ^uA#RacIf@hYG{_;pkjk6er$y$4Ak4 zA_|^~P684rmS|Z6^3@-`^)8B7uJEjbXL~B;+IySQHu^sJ}lFDK{q+G?5 z3t{9{V?To8$+Do9!+O=?jtkT>sl1x(l3dN=n!OiHFG3~83&sZsm9lb9ODW~FtY=ed zE|bxLj9697l#(lIP{oRcB9;|1WtYO(K&cJ^z3j?6x1nj{gd*1>ZT$n=O=5opdjt!b zDF1UgPOlr&)^fV}Yf+8eS$hZm#Ma)`_wIg{iYvGf=q;O@9v3JX%gP;(Juu=E=%>O7 zw0HBMmo8V!AhuXE8n{3Ea+_{14ki?j^JA<8)8(B;iNVmS`` zc^a&c_Zf~Jc)U}#x$U|Qm|z-?d&)B0ah8icIJx-Upi&Dugr_6tLk&%>B8BdxacGI- zC7gWF=#E?XNfL76ly&oT8Id8KBTFzX9e^X5`Ci$LvmHG<`nP$+WyPWn9&r*x&XX_bwLj)Nk0CE#NMG$FM~vTxID60{O>29h}Z?USD#z$o_F9dP&|2TDnV z-SI={XD4KS)2VOdZMRds`EA*lUEASJEk^v6DY zAQx>oq$KJTHgh-}%tF0NYZ*DM-~glyj4G6kvK)kzA@%8?)|6O-q2@s*C9_a43OJs_ z#?#Cx&}Yb*Tp^n-$T_`;9cVFI(8`9Ew@X2(L#c=$Q7DKQAW=lqH=P1&bR9@MYVtCiPXM9-zu_QJNGldd6jF^C62SA)u9m|tWFSo{Q^;g9I5Ec-zEA`PD(P|%0={kTWo5n>e%^-hO9~vkW(&|%aoCp1sD)fU zrR0k^io@OWXid01{b8bSPa-a z-x>`dcC?LSq7E3;ltNioirCF-&OWL&>T*>CCA` zHG_S7&cH2oKFf#xD47_bQA9HWXcUo%7aE1sV}eE@T?ZO3 zdeMHL=2I#hGzw|O0*ykd5ksSxUJx`Ezx@n>MxRpw-!J3)S2gmD^yRx~P@Uzv9aX0X zME?1mjnj!kPmfZaP`TmBrGva*!$BoZ57VlwW??Lr#@U@>%H^_FC}v1EU+k~8Zj)2=>@@J z;`<458SRT{$Tdno8d#O8#1G@8fg5%R4_(7kCk8;W|;MG?)iOqdZ zEoicl$&@oGSl=7vY%%a2=DpEqk=@PX_?Rx1q0HuqPK(QFqYNKIIVTs&D$u%+NyAV( zpDq;BY6m_NiAD>NLg~Z+ks_KAK%|I7ybvj*9uq_g={gYk9}hf2{R_j}>j-JZ0+B+h z5ksVyUJyhsANV%8q|VKC9yz}~_u9A1Z?U99IgoS|>IBOd=duO9@P*SP^+G`{mz83< zoYJypCU2%pvkb?zRHkg^(yAJyN!}AJSa!6LBW3}nMEL@+(#&RYnO?}~1=tlCS$MFd zWwWTjO{17rblpMBj(VbnO`)7(fK3t22w+o0B3{@OQjZBXg>)U*OkVZ6bNK`dr&$VV z#R8i`su9Dcm|hTU{^#YV-3Zt`AqY0B4cwT(-<8JX;0`m~!10E$S3dx&Z@`fxqJ#+b-ghC>;C=X~hD6LaGtNpO{_{{5^U7OUM;VDhU2G9*?ilVxby(D1ZBZ z_5IIB=&-KropAW<)U0JYMK%Rhqp%q zhh44Xu$V_&5pbd@nT(Rpk?<<8F;`$x1jp?h4m`_&As1(~@FJFh|}1sBfgRetq?ao*d}LT(w!-*rl!NpQ7)P#Z{cX6P$-cYpio3J z0w@%bh!+Zl)MJ7|AzcRw@A-enEap=wq^2mEO0htpu!Rvrp_pC}6z2X=BUdVcnqs?J zAz$Tbs&!*dP4Vgz7F-TZu`7EgD878p`<$4>;WR}BQ69@0kWka1AZ8IMw2T07IGro1 zh+}31f?`CPVpr=pGNx0RT*1H|x_}Tqx>kUXc1}lV-=b+EavXR=Ktes0#g%iXIHQF} zp=@G+M-j~k;88>(UU(Ezj|m=ybRBs7&Uxp4zCU;r(uxHhg;XPkM=`x1c$~WNzCQvU zgM#LaY18;9QH>mn_1Hn@uf0M?yihk^Cm=q4?HwBLyKsPrP!M^b3(Ns^IiJmHhOFf^ zAWj-vXH}L1WbR7tM&AT3bJD*13G(;h-SRhbHHDU-9(+h&Y zrMJERP(Wa?UE=Oa(`Xqcx?=mBD&i+TzHvWdm)MoR6B0jEe~I%|xR8*T$!2q=s;1H~ zOHAjpnM^^^3I(&MWe}&K5NNa=m0e<2^Efn?O%>+jN=h*iA_qQ!3eXW|H>PTmYoMZ% zLogpRPh0`Js)!gOMbe1_A_X*~fJgy}m?2U`JuZk8(RCoQw)#C6_X&|ATJb=nh-%aj zDWn$!kuN@NpZ5SF13ylpwrA`3>=lWLU8$Q@_yzW|w)yq>Pn?2}lLQp#1j*Yj{^|F4 zZ-xZPVm7B4hz72hs%B(Otystu3t9n&+W<_%47zfOCIT&L?h*(`NkxU1O$J{DfiX}H z!QEB842Qs65q!c1(aaPyeBS|IMhU7TVwe=kCk~ht(2N2m1temINfGt9U{XZafyqC- zxA-MKu|jH+B3kjlq=;(NFe#)L1e3qG>`fmBOd{BH?{E@$hnR~!Hq^JDe>M&pmvF0X zmbVie)uUehH1ET3;0T2gpV!fePc#6ji0w7Lv!UnIoQ4?PJ%Y~O63wRC-!zVkdOC}c zx0#fo7ZmKL5#$12e#sipB@uKM@x$>EEyVv(vyP26lT5UbD3nSJkSL-V0VIk@#0!Z+ z>M=p0kgfxXlRrG=T0Wh^L86dWERZOq8Zjh_=>QjZXm~Dz}MCA$(#4rabu2LLr6kf(6tOEldjK69Y_& zXhr~&A`y?{Fe#+#z@+(!pWfFWObThm0+T|j5yPaIUJy)f9J=DCfXNpGX^z@- ztKDd+y4vi$wf@L;-+K&Se(`1PgvK9z_`aKY4~EklVK80L@J$yzqa(CV5nnpZX(YfG zzSJm}b7r7F%iE*T8+|R~ps2&Q&&=i#pu1?~aAU5Q3os!v5acIi;)Aq$E>Das^F`dp zctjX2913L;100HIMgWH*67j;Jka|pTD5UGa;VZAXZC^f_!fA~{TCu>PkZQzmD5e(# zhX-s}bs*sIWYzu-IP^pB$FlL!601@(=(qRePM^Mm&OZa+y&bjlP!K%DiAj6vmfMc} z4ez&bAczPHsWc)in23FtE@#tu@>yfJ^r<-=pJ>zrPZmRbu_d_aGZpNGCqJ|V4GkWWZ8V&oIk3qrp4FMIukK)zth?S?j8GwPOE z>A6v!diM#dh~>5?XQvvt?w%tZbJK7Ls3Oue;)EgeY}Ukg^fLH5Gd|R-t9b*yBl%1q z0!C%I?P(a-0%Zg+Gcs^lF%fAZn<}SBI9%A~AU?OQmhrvhl%nOzYO&;%O0-I#P$Drv zp@?P#P$(i1FBA%?#{`8!x(*a>eZ}L;`h!9ttyrK?NHt<86w?cW!k@LjLoS_tUzoM8 z55}tGyS3#OVmoMR+p4McIozEmuG@H=B^?}Cq!S!ZR&U*h_h>k9L?muAo6=w*Ef>-V z#ihY2M^<&j=E|ht@DgaScvm!gD~49j!)F0qj=AO@{QRK>iW zDnz!3S|@nT#9H$0GA>X@xrB$dQ5OBr0c-tpB9{YBA;C0G)Ez=Sm07f zHDb6F(+h&jcWs+60GG!H9U(SbwC&rZ4;g-!r9II^poygPSVvv2bvxsQ?^& zFGW${M1qgSXRlrB~f^JjZ5DgsC7IFB~;m({YAzcUVzOwCY4L*6o!JUv+ zEN~~J8Zq37=>@^v*G?%s1-Ls^4L%|yUMu(vs8J0*B>eLg|M4w51P;yBc})1LcVFQI zFA4{Iid-NEg@$TmWZf*txFXR3gxPYcY!r1Z5csa&d*BOhK8fhL#3kF}o!l7V6fsu-;sm|hSR{`iMqcmT+2}z`>ksm;f7mG%ugZ<8x2=QeL`@4QZgm zUWl)l1~3O*bm|p0{E)5R?v(zV7UN)YK45#G@H@^i;9lBl5E;37pWpB6h#c^ zC!!ev`iV%yi+)1tF`=K3u7iH>Dt+}VK8M1gpO988=qIEaG5U$=1)<+-7yV>Epr7*v z(++DH4ghdVY4xy~$sTp*XJJ0*%h`#8rK{Fl!22*94rbsPoL4j&UNEQVxG2e|GWb*g ztoF<@E^mX*0^bqYme|)g8w?eAoET}C&1B(Uf(vAPYYNAMiVk2_%2_C?h=f>98@Lp) zR}d=0ND)yelo$|EL^A?J6p@G*5rx!aLPQ~52N8eMme=#y6b=!Ev|>R-A=QWxQA{rg z5#N6EHEkf`aXyuZ!rz>xgAa{<=5d$`4!xFQU&^%_Jm8-ohVBAPEDW zjH<{0Kzz&|R)oA*q);c6K@3nQq8S0yiAcl?bwcVfL7kAU19dO__sr}08^-8`E&S8&i_0 zWSW~%C+O$B5=S*Y{*p0#w7FLyijZ$`5b{me{>NtzA3D{WYRTXfKFR8#K zC7soahHUh#XF07WA|q^})MCI?5zPoNRYW3QOchd(2~&l19ZWq|KKCv@-NIq2kX9_1 zDx?}Qri$qWVd_y^?jUg-#)7ayYt-9TOT`5NUh_Yf+p)pqq?}YZlJbvCeR20`8Ty@p zj@%SNXB-HfJ?*gHP4hkt2caobF9Lw_Mk-|#(h7F883V@83O>V^E95hQUo4Hr_7LB? z=B+Eaf7)W9x~Eb?$ixE7!x4hVNEeInp#gfTB;GAP3{$}82A~eZDk#%ko+_bAM1U!j zQ4D}7q8R~Tib%u@m_q6?0j7|y1DN+b{f)oy$rTPTg|uP;Od-{X0aHvb2r%!z@oVP- zFliLosfx9Ix}0opH|kRxw~VcUefHT^V^UM=W4mOprpfW~15d3#+miP6WJFa55bX6= zMUfI;>tnSXY+Q8+@6d2)nO2OlqQZd~pPEA% zv(EW+rE*(q+TMH=2!1 zR?ZuF1XVDMEcpb1p#+{whPa#VZhy&X7=u8$1g%xiz(gEwC`ClM(Q`PC#D|&52VpWe z0l>Ajfe*tNx?7R~U{wChLQ%v3fFha^0HBCOyZ|Vq9uoix={f*-|3ObK=W{3=019cv z0)RrQ5d)x@UJwAz{N~nwv7|HCjg=;eLRi|0BgvB8=abg7UTJJ@HFg-9)h=zET}xHR zQlpOZNrP)TYgNm#q!ZPaRY53Mt35VmLZw8kuqnJ4o36K;hE_2vhJF^r41-4RYoI09 zGXMAX&;PCCEr5BUQ!N43W1xn5Dm>%IM|_2kjBEOALCM2(sXSe&>deNlX6O}!%x#y< z>1y>-w4kIL<>{%C)-sU128kQg)@qtWawJKPfUBa{J$KmiLDtRB9lmzl}3^({qSHO!zj;PwEz#`gCrg z|0Tzo>c-~~t34N->z_+Z`d29`O!}~-WzbTah^;~4)*j4cy_%%gWMpt84sN%kJW*y< z+wG{L8#w!{)f)BW6k@bhjU;uuNv_vXP1C6BMqQg-+ftj`+Y5T@3yc=V&~PXv#Y8^rKHka zHq$)(@L#@|{+)FC;#k1m>3i2OAFs1^vj!8=K|btTFOAe@x6U>Jf{V~TOeZF6G6?pW zkoHHKt@n@XI@W5+7hWuJU#tOQlk5kuR5ZkD`zl%Lpes~qo*Q)lbLcQYVL50XOf$@WUHv7mkL856Og$Z|p;QmInUNNz( z*qP9KfbgklwWV)CXK@D5^v!gsa>ksUpLCsP{`fcmL1gzOL9COrwQ{5CY57t}a+0)H zV`FBP%8xA!L)R}ng3Z2dbDwqN613hn8?9P555Ym1R1{0$Ml)-vmq>?iuvdZ zP=(b;1*k$o-GDm0DJ-B0iFLOs+;A5Y3J<8_y1hphkpZfZY=nR+q!k6Aiiz|Ms6rCq z0aaXect91_?FH07-22_D`vKG}KCxaTAF0&f_6i@^3@nf{FiA5MwOH25sS*R!lfBy? z9)^%aOKEn59t5fm-W(QH#mw}Ds>15K@h6;`DkRj6s)KOC4ySwziFLOs9IA>5g-2C! z-LasmkZgpgDx?(!s)~v9jjBQt;Zapwb$C=2*6l^r-~8`uSNDUe!mmX!RNcEz<&n)S z!dr(s0T6T47qkki?*^f8&?+R<4Xs0K!$PZ&Sa++!L93WhcxV;Z9SgJy$wmmRLRwKk ztC&dN&?+Pm9$Lj!hlf^S-Ck(j{FM{W?FU+wVoEN+U|G#(G<;4tS1cnKe>R;;%Z8DG z@v^P3l50ulnvyH5_P;EQ!F0ucx;nTrEVhbS>I++i)pw&%IBXRX>c-Z=Gs9x5kXU!C z!eOhJP;7l4Cgeawg!KmrKRpRTDB*k8*`DI2YfXwu!{NV3s{BK zcSBG(U=w7-K`1-tYSjpfmK|0EWj!x8zHa?X+;67Vj_J5tB^!^U=>## z9$1BSdx7<5?|9d>{Q#?0%;q$s0GARNaT{4vD-OU7SKcqT`oULW+}t@7S8DUv9V{Hc zIy@K_SjGJG1+2pAyCEnXunGxv1MBc;SYQuyyzU=!1qwnhnT!qnnnbzuDW6dwJ zIZ<4du-GbQsV{65R^N?6;jmRms2f{{^I@@7NUXb6;jmRqC_J``>y8Cmg=8bdRw1n@ zuvJW?Z)_Ek2#>Aes>5Teux>B5{`R_Sp5G6)7K?d=sx`8DMlY6?qF$DB2sLgL3iuLX zIcIA2iR%l36jyD!)o#F@8D6itS%pD` znbsOfNOq#jmdQgU4w0x6xsfP=woMI6M(jgi9wOz&?@e^|&OdJIUiR^1yNv z&Y4ii0QJcpu7&mb6V~q?a@U{H{zX4SPWVZ9Hsmrvi+Po}pqE<>__!c&R!yyUJFEzW~{G8U08kh@)vGV7ZM6s)bYuza1JX%V%@C@Hwndr!Y}ILx?@??g=8aK)P=O7 zSk%Qt`d-w9B*HK1;;O?h>cYCc2)plK;<0`ZHixM6X$=w@axYgw-02mR_lok1Yq9|9ve$Z9RWV1O_ zRa5D-l1t}d@>ftaeD<@bWeT}`f%~k;LFn4vU1=IE!$gFR?LqK5bYNI`6?4@Wyb7!D z2BL8ADkRhmuS181g;yc5?pB3^S23aR@G7o57I+nsjSyaiw4#7lF_FIERY)Q{yo#$1 z53ji4Ul=Q_z8j6gVXTl)H^vS&!eXqDSa++!VXT-? zc#IX-9Sg<^$wr8=LRwK^te8mO7%LlkFQZe*29;FUBJcc^|gQttM6Xw!Y$xJLfs4a@ReZ~a3QhoR)t%@ z#e~8y;NrStS-^#4BV53Rw4zwR#YFmEz=b5jFW};;!!O{%y1h92>vbol`oUR+d}~&N zL%*iuU_UFT5cp5c%Q^V^7tEBw7x2In{&uy31OBO|S~r4@pay>u7G1?m^@Xm&>br3$ z9J&e#b))OxlVQy8Cog=8Z{S0Sw^&{a&NZ*&!s2#>Dfs>7qJ zux>B9*0vq=n|{!>n3mPNk|~x8hM}bar)f>o3dM|B)J@&YaC6fW0=7qERoJF<+L;c% zDlDXmIq3^hh1GY1PdG>w5(M13q>vA5S05@bUubCM$4U-gAmho*~ z8Z(7{bzQZ;N5%vFY13`1Q6urGI}Y;y8WvW?-1G&j!s@#LC>*Q`2?fCFvteOXNUXb6 z;b2uvC_Jo+>y8Chg=8ayRUxe?U{y?{Z&($Q2oI~`s>8#oux>A`K5_8kZGFM2YNSmC zCVfgK2P+g4|HH3*O3urvf|f1XuzGxurrK;FSc)G}haL=#s6s~iLsU`q-PjWnQALEh z5q0Q$p%GO?th-eq5miX24@4E#9S@?4$VQ2%B3h9ks*p(kh$or!RFY|mkWf*zf zhnlhq`+T*a$wnqq&ZNruoPh|vX&YFB>*hQLWK3mLo!Mwe5%3!O5`jD(0#$cokOP z4MgGKRY<5CUI#A>3$H?A-K`1-uVO;s;ZUT8<857ZffIkC>ajU{zRsHvol$RUx5nSRKYjE~Lx8 zs0+JW6%JN~t?J`b=;FF#fmI>d2w_!7D+*W@6X_dPg(Skms<`U#uqv$E3#+d<{r#`& z2Ub<3oWrNj5Q{f!rn1>g(M;t{Q&;l_PNVV}{!NL%;{d&(O_PwkW~I~BY3SWyfmO^; zU%)D?z8iwV0jrQuH?R)9FD$SMiFLOs9I%QBh1XZbb;km%Lb4G8tB_U{z$zxvH?Rsx zga=k})!~6vShp8gr>_{kr5|9`aTt&;rd3nc3h8n-oi}ut_Q6g%r#MS-efB7LK(kVJS?6;~Y|RfTnXQT1hqyyyM>pein^(yFXx5sf!p zgyo4Tm&?RLnk2&wh2N6q18zyj)Ow|c@1E%bFa6`ff~=UgzCc!3eK#D116d)VZjc>3 zDJ;keiFLOs9LS0Zg$G%2-LZhIkZgn?E2I?#$cl;d4YEQK;Xzhhb$E~!*6jt^%YIV5 zp)Zilhr9V&=Ro@LiAwg9{s2fy=KNK2NMZ~&W6%tg1g!%wgVcqe7s)%fqpemvj38)H* z^be{c5`BQGuhOSzw??7Pqh_J9K=B6)L6;|I3K;d9jNGJeSj}8l~LSo&m3J0rVLg8Um zTz4$6DkK{rtO{vG0jpvneZ#7dM0i*gR~;T!g>`#j^~Uu7EbIqX3u>WYlo696lY^ED zT-DPEOp(E*R;H+_GPk-)1|0~r3~g4c8ZF}C*KN`Ia9C^=v(y*13ajr%p>WtLB-D+q zi64i>Rw1$OR)xb>F`@9-Dy};gY!#A?5L<<`qQF)$k-o82NFqG8imMKft-`v!*m}m1 zUpTTKY|UtLE@KqUVm6!C^9au;rg`UORPF#Qg}_S5_b|; zjDIDM4~BI?5%bmUf^s*Z_-|odP=pk17nDx~x}bc7(EcQOd^)TPijV>3g7UGjE-1qK zz2j=r^0CwU8CS61BbTEF4wq!zEXeqno(^ZyY&lgnin_*~M^Ejs6(Y-Zt)UxL-(G0w zsj%y{n4P}XYhm@>D_gkrT1cpSy&n2i*!5aSth-g=)@w1L@awg>?pW4qA=wDmYay*D z)@w15zSnCZiSX;Sxa#ohwXkj%SEb}MmI#T&*Im+XNj^u z@a|THe&dwds$k-yKtdx`j{f%c#V@L}V$3qT(U=1F8rVsimAYP@o@$TnXt$J9b}S7~ zRrxHtI9<<1fBT#7{_qM`{MbfoD!E;)Y2elx<9{$8S2?Nha+8egAC|89Fe{f1+WFZL zUY2_hkvrVj^z*Eq(zY8n+3W|?M<@dWU1C)lbu32?8*5qdm5tL^u;M`>jS-EuT1CWo zly&@lZ1Tsk@}Hghe;2XxN7M}~**23qXKK|k@<>Bo@a^U8zJwKc<+|S<&I%l~V;aCX z#-5p^oaQ;tWI2almLXr$Lw_DeBsAsen5i}zLm%m6A(`a`U&#ulKQXp1D|j^N`^y3az=;1T({uL{9tZ!8(4o@T~FGjY#n%e@9S-JDFmwl0yJ0V#9RW|0m zvfs#xe&fH#Zf8Zu1BL*GV7+a%R1%89{c|}BOu4+>sP3YQtEWpZXVoUgHF6FNxt-)| zNws3KYHha@k?R7rRZ;5}O<*7}{Gw~VjK&XI(w22&rAgvnQQC_6Sh82FNo!iK zH0;XSrH!*|sV7FMQLk3&M#*->SgTsrmuOk*j_>(4;c-e&y1VmKihXM4(u6XM0VfwVKpp^FsKtq{E1iwb^#kLyvAwNX6?c z>5%et9eCPVu%o*Y^!MSGRPD3bCAd(R%0&95DFb^WVvkY+qLo_hl0BTLJgGo-C#2F0 zr`(Q>veCXktxki79t^Z3pU+~ik%~oVJ*E@}%$8{a`Q*QW?low?+Uz9rcAT_SYr4Hn zI@n1u^s}9HX@V?W>+5EtV;y?z;6D=o_XLZJjxKpsbb=vZ-l`dT1>MqS-L$*{7P=+l zd6@(l)1I=V!+|7qOd~_@98VZ+1CMtMDuMy0&j$k_S$j( ztihkUu62ytSVHDhDi2JUS41~q8gv{2Ihs2I<_NHEIR!H4S$eBc(FwFAk=LFsLmS7} zZ=zhes_%fq>cCxghb>qhm`8iv%l}4}hXtpRcCI6jHFymF4AaV|^vyA#2}9>KsRHJ9 z?cg@)Aj}_XEl5Y9q$xd z$@)rK`H>SIxUwa!@PYvy?MBO1^aG3AEK3JS60PVoOBz~t-sIL!aX}!8x9T|rLbeec zWL$|PI+_l|-q_G~5~*yis+QSk)izMApKTTr3qN`I&<4UqV#)4BsAlPzXR*p=Ea_m? z1||xT=R-#&ZKEt>Y+fJT*z|)rQB}1P9gUo32{LJ7ljCEA2;f2}E#zOVq*V#GZD%1) z9Q5ozeeUxe@?Ah%df2vy-|$N^9ur5>=SrG-)cGHLXNe>pO%tC_{v`7^~l6w`RlLC(JV)N#&os4v&lQ`^USAOnMnq}I}xa*F`e)I+>Bh`NshaHjp;+tre ze{wTkdG+ZBKH_A2CH_hj$>Thl)*-H=sbKc})9Oq1r#0N`*7Mn09{Bm8H2dKwIeIRY zJE`akZsLTK_<)-orn{F|;rveQdDRPL#{Dvv`=j5wWz`u@ z_R*Vpor82{NwnwR?j)Y%=DdX`c5**@8-3$)Kl)ylAa8!&AKmh|^70<;N8e2o-AuPT zzY}}b{;+l?l|7rlD~X8nPg~XNo$MnDuXB)s2<3hx<0R4zJWI^+#7^!<#^@WD`;iy2 zgx@Z`>nARy0x$33eq<$0bTggq{7&qdSe2u*c95Y8<^H@ihcEt$lYR6OUgsb^i(#@a zIf)m#IbXsPJGmb%(>E^nqb5r@P}zUlE&ozp-oyQJJ>?1DsH@k^0 z_wRHQdvZV0$^D4Uef0-#yw)ur#QjJI_ak2JW$79=Yd`36fA{)}m*w3$KgA_7_+dBk z(@x^Y-Nbu&VyEm!K2P7cvLE>pOIUo(RZOcRKsIt8FYl54$QNj$OXPm%cVf>czyE$V zYro`j|G?6dYb)IBt9YG*Uv(2#JBk14Ca&R$UEFV=Z#>+yWx4s63%AnqP&UKX@$z2o z*;4NkVXHj(o!E1gdMlf?kGS0b_{+a}c$t%Zbdc9M=z`nmkdyeNn{$LGc5*+u4}Ifu zKe~h^9QDa7pLWYH;N?BskM2to-As#|--$gRO8j0Xd=LJ?<^GS$|Mu`Z-0a8kItTyi zCLZr3KIG`Kb5}maGzue-&%a&LvHz#d3i7Qr_n?=(+iy6i9OpE|L{eG@1Z3Q z_e1;r<-e}E%E>C|&Z9%9^o?C$Sh=avuBb4DI?xMjoZ zJI?RKp7-8x#*ZoYu0B0<(ooRWk2!~ zmT=boHT#qb^^aV{%X?%$atTdz*(o`{6MJ$${M>IS_b+s1f9Aiw_7gcbyEAfzR=J7J z$Qkm?x}GCvq;uqq*dym1YfE2qD-9YsBON1W#5-~d%b$EX<^BSf`wI^_W*=Af!@uJa z8M?$FH2ixf@lrSO4?M9`_QQXnZ(P|AKf@C4IsUM}xaI%E%X?%${8yUjW_sHBo!E0j zBJGmcu8-3$)KXMC8`0CT&J;$x| zCSKmd{m9KU(arRB=XYYyB_G|jk#fJs<^DBqU%c5J;-jzUbq>AWsblmFPU4&0#5eK8 zPVPsqrEgsBM_EIg`fdMwv0MHvyu63|Q6@Y$6O$YHo!Il+f4=t`%Ka@a_wT&+i1Xah zJo++T=g_;|I<9aM-{&S?$rHP{ehnM%XYvjW;(arP`=XYYy&%gNcchX+@hTALux$dfW zyQ&(HF9W1Hbt7_q*i_yu63|(UmmO&2+l+ zJF(~Bw^mP}+&|%R|73B=-5!nhC%n#~r`$RocM^Z)CO*LvySV>3edFQ&mn@;UYP)B> z{~0gu<^Cy}=o0yV&hNyY%kO#vn-R~r+&}xOzwGpA#2T-2c+jb1M0XNL-9(coc5*+m zgT8UOAE~p1>I=W|e7F2|Uf#p~$WEH*W~w^B6MIfs`-e{vzK0KSxF0@r`3q|wbFznzi}!2cDrvS9EC~4bq)Qzd&=kMDBNfC-!`6 z=8*N2`=rbLiuXJ<`xYnr2!jLCLd8vFm_QQqZX$yiBzAH?@;b(>o0Y+CbL3oJo0-d@k%%6A9!LH_nkV#$TKu+^Do;I8Ml0p4l(jqn$yknwDUW$XG!AOtEueY z=*s?0FZt$&H#^x!*|Z^p!@&CO45yMI?4|KiXom2@oOXw~q@k4In2YF%__noWN zsJ&V(zUp~XZuy|qYP4gu8uhMLpD#W5Ad%pG zB1t2M^21+x&4%-a7*$KUrr)awkHvv~ET?-uq%{yQ8S{vUi>89rpgLKHkGRg9eR}PStbmMb~Rj-*Y6)NxW zzHdw;gsG-u=F2C)Ell@*e}wZLE`%&%|LXaiCKt2mTsEIArc&ihCY6>IJzpr8nQ|!* zh5DnJG0G{#aVyG3s;m~(j4aED>!xeEs;5(CE*l5Ulw342x|T1LbtRQj%6ToDg3pAJ z)6{ax&{J{HGz;Zo(a^GH1|dL;nqnwsDg)w0MK|*qU7nlf8&{2$&Tqr)r$oapmuy4j zlBR0gVN3-p2v{$+m#0f6uH5l&QZ7{*NvwIWf8AxwBwI7&iauRp|CZ88lA~6wg~&8g_|l$KYsMo})nb;K@(T2acG%)Hxg-Fw!Eu_@;bW}=2#gNdHa z>vEnz{hCTjS}Eo-nv%uJ95oC}s z*_du=)M{$MI6?X!l2&!tRSfW!R{BM(AoIjF{J;!_*Zz!KrcLz`Go~^#M!T zpQ$n2JY)lu0`M!rwBw5Cc=PK~jK4LFD!zYA%r>?dET!GaF5zVOsa03~oE@&XptKAx zMu`Q{=tU&Uukjcm#u+0^$%CBMj1sfhQDS8?k|K|@$YV1}do6j~M1H=MJeuTj6?wdc zJnq0_lsra0LH?qTFO#42@qLo^DEawo@|Q#&OUdK;8H_j~Vi~oV<~)XiklN%vreA zxn~i~aO-Vi3FM#Sc@<$U@~IqrymFbeqLlG@Yol0}3vehj@P?a>In#jfnHc%uF*wM$u=DF84Qt;HkYB$&M*DoI{`9Zuo~@KKn&FutR;_i zLyUD6TtFU|lE-%PxQsk5Cy!T=2QkxGKt8jz;1=?DA9>tE9{)}r zWbd=!tK{(=^7s*XJV746B9A}ev5*Xsg=7pZJcvAwB99ZvV+DB>$b-PoLc-<3N%A0O zP2I~odDDAXS<-=CjB~MVA>lm`+noKF9$gQ+rq>>9qqbo_)hfa<Bw$`dNt$7J$ zi>H7ioPcHMATDsak+#PfZ_4iIM$&v&BLCnTe(IUmjb71#4*xe6A+m0G)}J_*9!naz zv<6G?OPddFU9~LG?c=}zw+}Eja1$QHt7Bh}S4W4K9aoN*MmGxhU~^nK{M}8opQ+fW zIqn;Nq6?PYefQxH6SG@KJB)4IQ{-Tt5|oJ`?+CIebS-a`wFM`9VAI?6nhvfs;w%&8$pUHdLMrjq5(v8mVb6vv=Tj@^}wZTK{QHR{?NUK*%%ubnAz2m&hXllTa8AworGn%$~{zZVnJNWYy2ZO9qsf*=!(tl-UNfle3-4IlS%%d*L*=~i+w>Ra@aD+ouq`9Hw8LnB3*CT~zKXuOR z7q-Bb(c+m;^ki0>6{ps9Mzu-g*5(;~v6d(ooF!7sKE*_!Q1}jz9<-#4r{=0!pPE*u zz&2xym`DW^yb-OG#hY$u1|S0Qx{WPdfv=L(B6EUOfPO^J7px5j-9VGdXpdD^!@wb^_kiUAT)qyX0yM4WE*UcN$!3KjWKM|hQY5RVT z92<2YCc2$-PxkcFE#~RwAM*CJqq~0Ou(|N(v8NsPZT6rf&U0*cW3|&4 zc8lhyW`CMn_sw%wwUY)%^mb)xJBcUY%WRQqxv+VeT@{V{YqNEkEnK=mUf2R+)dcKf z`I_c`Wj6vI=9Iks5^)xwE48TB?96e#7wtsracV-qp`}+YhL&_RSQ49;y|Q9eVQ~ZI zk&%yhwwU&+(HY20I{s`n`wW46uY5eS@$7rQ24e|Bd!u2gB!V&mcTgb)<=ILOu`A?4 z3NrvAWs9jVr>EJ$Z+Tl;SNY014P-#(d&g%p?1SD=kFb9de}Z^2E&S1=aI4}-B%;q zRL~k(7N=6r8#|5nj6DEI&)9D|#7`=uijwhw{1gMn=U1B0fDSPAwZ z;b7oR3uuh@HQz2X!Fu0daw1GsTdiUcb5+cKZ?nZU27fBAk%7Peoxe_Y`p_hMc5I{g z4hp|J)eHMQ8xj1yoybJV6FH*TFWFWNFKv$PWHwseqMRA6Y};m(ta zDZQDPCAGJ6G{wOr3@X~4E!Y73MHllOe~(;T{j?;ug!IVPHCDX&97;?@@V;`Z=R$LN z_0-8Gg|{G=k5jj=ye(VjC0jx5^7c!YG?zUkZNK#pX8`Lj+(n)b9C`)}p|ZUR3gJZ2 zq6b@S2BHUX5Jx+m#~ook!yz~*gUu6HW?2@t8c!M)RX2zwf^S|erz_Mvvf41lTG=bV zor&RbuS^?BbUFI~saGDSDRWoFHWExB?^PAaWAnv!mUCrv=rV9`!Da>_@4P!PHZig7 zsnbe1X0ZGu*{*`WVDLm<;8>MabRmrk>Eo%_dtYBMTnfnQaLsVW&as7#AwI_1R87P< zz^G$jY?0GxOJWL)MUP5ZE6CrJJ3f5*{*Jt`X}Pc1uF|n{u}{5-4G2~%I)aUst3c%F zc$;5tU*p+86&7?vk@6GTgw66U5+<-sINL}TIJUAm@$oEa<=z(YF__+q(hirIxe85M zFaV$(F*#c+H>y3Q=x9aZxDOFE4!?sXZL z9#cuCF-hqTWxNVCg{`Jenmu6|K>JgDjbLi!xBBU&xmy#V((OY#2!+=%)jdWvpD$zp61_1}js+9oX{@yF<85X(VE zEv~8U_JlM(O_()4_2CM3GiJv#gQC42m%aY7h{#9lW56!0FG!4vj{;RFeVOp5im^+n*r$=SNLzTHNs zOIkg<-reVul7s+hhweakBoWN5nuI#J)6hxOg{9DpF{azQBiOx;$h}rG1H}ol3KGtP`(g z%lJ*F-1ni+g8K<+6xVm`ChdR;Qgj8mYh7_(ncUK^gfLpt3pE5wRoTExc8}j08K*GR zgrGJ!gV)omm<8lN1pQnP%bfc!jXkct!OXe;LHH zd1-0hQ2SG1HuGN&8(wWt@bKJ27fldB?xMnqJME3XZA^h4#bYS-Jdx5@h_(Gmm7~ zu7sJt6;C|>H-DuZobiel*0Ex!YWCls=ngjRU@a~%-8T>cMq|;g1xu3i8>%S(g z*+^^Lbna}G`GdL{9TPMo%GS198kZ=Y*2LhUq~bo-?cD$t`nJUL45Lyo3-rWoPA2*` zW)?EBqh5KAa}fVnAh0Yt;Q&{ha>PtNz`<+_TmTHfxfsV@7yfQ~_r_nX=+*t(qV9fuewuUS z!_KBF2HK0`X|V`o|3PG9>R%zaMn1V&{|2eReY@{;uG@@MFHj|#%$i=)>7sZBmjFp) z8e1Y1y)>rA&I>_f8em=MS$y*MDnrjR9=+qr$2*TV9wNhm_?b!Tn$n|pyzZDD#~bX_ z;@qEHzMi;Yb5;1Gg^5P5p%jZcYTsapmEm&KeM?qIhq@T0BOhB{`Xa3UhZ2vq--Wmx zi)O_8JqW!UaSpUz7>;vf&v@uH17q2oa9Ic-J=P&80`|yH(>%hSr-B^oIP3!XO}Ab4 zvz{t)|m`M!xkIPwALP!9C_Dy>5EYLA4)vd zeix#19>%7h*vvg~(i5Iyd;^jzy)Ul2{5n4yC3Hus8FT{>@ z_<2#AcK*5f4Ns7OZTy5{;e!9E^%@nCA3Kq4f*6M*U%DAtF#tz?xJL<#koO-$HJ-i| zVr}F)I2Umj$VPq*q(zDVEtiA=VZdlJ zJ<~JO!zC>%#PKAeNL+$7qNk7DL9?^VY_C{3#K;B{2)02;U?;W^gi{7{JYyi(fO$5C z1cz9`2SG%DgMo*h6Tp5L2uFUu>aLoun(nQ2)2;o(@Lp-B``7iY>r+=(SCODds?~Ig zm8?EZydm+TI-!a*ow6m?pz>|FR$sMw)v&io6WMobjxzV!e}sLv)RH&3Ocn;FzatkZ zji;}x?C+f^67rHV@j3uMq@9HN-IJY!`ke^x_WFH7s_EqI?yuizWYe$TiPCuW`!{x5 z{Z4|iyX$vPRI^Su2H*Lf}a1-Q~ox~wG zUV@i^>oso|#aH+>>uB^UG5RfeQwYDkjgMR2z*W(%(n3~QlV1w_$`D(;*9O0+pXdv2 z6xs4>(<*Xe|4!c@?|K;T{{j8?-d4k6(Zj`UxBbzGw?%LFT6_J7J5;yH$`yPXcLZ;F z4}DaE>(}wBcOYV;(P*U`I=adp+U;Q8K6to^lT>r?S zGw<1o8)mnXQzct*e|K`^Q0pPj-bj*`e6i7bD5geh*VGwry+Iy6Jm|%a^FRM3ynO_@ zcqH@UsJHvAP4LU}f}HW#+Eu)MQ-Rp|$CeR~585(oCvG_Idy#2)@z}X%SBb!yVoLbWJ8BN#n zTE@&cnVeoKWb?UfSvX#)^~Y2$-WerQIsE#A7AdyxJ;wd1NfP!y7ozHwbW+);i|#YP z&M)$+L&)9L>FCn&&2ZnQL;iur(M(5+WG!xT6U^7fi9+RcRAE~MvJqIfJH~Gpe?dxm z{5e0Erma_eYuV8kA-hw^85s1awMTc|gkNP&=@79#?bwI@nPx^jtZk!^adJBBaG1;J zIYTRD3VNngE?JfhU~3rx>nVFuxp-Jh<)W~jvezUDCxW#!+9_cz)n!=U{KFI9r70W_ zYpGT$SW8tC##*iyhV^A9s2?Yp6!PRL*5f z1wH4e<+7=lG`w6?3k4Bt(r3aiDdS-+m5apMdpElKg%iPA8ts&@mg+LB)fM~yk(Q)* zSWC51!CI=CFxGOtFsx60ac~i7t-XG{-?^{voc|zVt&3QH=vJ?%nGp}`Tq&dKst#wi zX{KE)6iVg1>g2Ovv||{ogc&<6$k;N(F1FYQk8{^}?`T)>|NB>nYyI-N(9XQO9azYK4gP(*0)sE6t2} zSgRQu&ZV@5c8$1i1jKe7mqHfTqM?KPm*vVSWBaw64p{( zhV`7>>`|J+@vxR^rGm9oHDRpfdSO_v8*KlCwAKzG*vI!fL)&nBex8FR#_7{nV zwWaGuxtuGN%NaZnw&-LWr>qxp84ZqP%^HS?^;(+u@vxT4MPluJiW9pN9$PpOtflHH zVJ+2VSYN#B(XY^~jfb^VD;2DzstIE)*9*hC>$m4`Cs=cbdy+OqK(%#u` z(aea4bt$VFML2=AtlPGk&DdJjG;wV0lq|<`G}umu?}T4Q<>Fy2m5aps?>Yhi=%&tSbwt#>*t=h zm}W*itn(I}+zuzwXHBD&SK%F(p=UL#sMgst# zni=u1hC|b{SrewqY&BCbb;HmyPS(~7<&s)1nPyIGt-nX*;$bb7i^Te&NfJ&3YiYDo z!dj}!u%5YbgZGsO40GaPE!9c|YpH6&Sj+XouzuwEZQhjGPlH(R_w+9=MXa9|v7U9r zp!d}=WJWx!^G@E%7BtH#85u*b0sZbDCG2vp=u)5&wM(%q;ip1|NA5fCxW#! z+9_cz)n!YW^X~_wboL(NUUF{@!ZYAiC`^_ zc1l=Fbs5$NJ@Um5X$r@~TB?-_)>74kv6kzFVSRt=A5>&_%DX|Vi`QL(%U7qoCt|(u zqa9mlX2io9R*DuKE0fDumRii`OrW<0I1#L+ z(M}0#sV>8M`^-7sh8VFs;Zm(su$HPOjI~@Z4C`}ezWCS3Zd+#%>xUm&z87AcC1U-- ze{S4Bi+fD0wS2zd7)B-wudTDiyr$_nQ`eo6sp*BHE-puDn?vPdVl9)4!n$qlBnc;j zwJh3cVJ*{TSU-60^P6Z2$7`)knN~Vj%T$xbTB;X@_3bwt{}REP`;g_WGhf56SK7P} zSpuJW?0Ry)*)%g^VqMA?Ok0N~;f`V2dB-g2T1hu`Sc(FO+m6s$w~?=9;;%9@Wpa^N zvpIyA^+{aeWU!X0r-ijlmtnnl_Mmr$OZ?Varj-uXGS#H9mg8SVQBi7jz5Pfg5(Q1l@7R(X4_|FmnalE{mP;wq7b1k1nZP zB-U&~XEzHcg0(c-DPb+uWms=Hx$kE*YvZ-nQms_5mZ~OBXSf3+ez3*rC_>`?Re6D4~w6|rHoO}UhzAeot7D{SS&zo7Nm@SH}^?5Y!xE%`!_a;63D&hB)(>p@ z^(%<=u!!|%=l%Ydw7AFXgqKQ1-7MO9OS9l3u98(&3#Osjx}LYya={T-nYE2jxp-Jh z%GrB9=8H)yOLZan?#Up6ps4#%oS{~8xLy>KJ>_zvWBCYdbXU;7HtdGlEUk)f>pNi zR@2z}YMS@)=#t7sVtvgd2`7TJG}2)DbFQ6$L4{NDbDp*TZ6UJJu7l!ql zPhPqe*=>6^i1kNb|MBne;?G5_PuXw_`}QIp)}~=+og6Iw&S|hWis``r@T-_ni)uzU z^CfZ0>^YkE@vxT4MPmKyNfJ&3YiYDo!dj}!u%7eezip!_91m-$Rw`IaRTIWqt`~;& zKAWox2-dF!v3~!Of7y;$zdlNc+E!9c|YpH6&Sj+Xous-tS zcYlEFw(lFn`lVg3Y{iRRBGwB}7<`Zx_jp)aYT2@Kc0skBT+VQ^YB5vHTBU5Um@Svf zmbl8S{QxQ#4{NDh6xQwYCrLOFtfkRT32Uh?!}`sqKK=qt;dod}wNk-as+us?a=kFD zw~y_6D#5xth;{y;yB zQBB)uwEL(`d8B)eHA<5-V3e_{MqWg;JRk!d2A=?cvw3*)hNR_+RPVtqO-bn)tw*usfmEmcnmYpE{7`pU!lpQI@q4{NDb zDp*TZ6UJJu7l!p0?*6Lxg6rcT)|K}bZNk=CAr}KLxGwu(?N*u@@vzo&Dl9QG4Ap`i z|BIHEwd_IxUSH=lGYh}Ov32_tDi@C~saz!1(JqqJ`$x2~nFb^CV?I%6GM_7x9nM=Lm) zY}s~TJ3?E_t3|u0XL7ck$1kw$TvKcPU7Gjtu$Ib2VtxN42`7TJG}u*9a*knD)^hVGmQ@u_kZW<3`84CiqtpEIPAk8wlaDihf|^%i zmJU@R_INJZN_vFrIGNGD>xf-1e4PZbQH>k`v73emkxe@Xoc6cCnW~^O3mxqIPaDI( zGUzNuX9Zrn5}j}2&kN8QMrQ*$*P?SLIMdLXil^vJLucyK=sb@Vwv|4{GW0>#)B`Jp}g1Sw?jf!V(>jj(MB6O+5lgYGogUrVa|JrxsRf&QNo}`WiRc z323gepE*>k*(Nj*V!43K_YR*_8vKys7CRQ`H@{(j0{>z8Sj_A?j_j;{AKz2^*WjAz1_ZKTcx^~*17&%kg-1Uw3}jU|JVOtz>{uui|yD0g% z(!}RFaacVEdT&jwHw1C|muUuEo_wJA(iGi53*p2N{h77QmkjTD`GZ(NOeejO)~U5$ z{e~QABorY9IGV|EM6>T}X=h)6AC4O-FLQ#tOoS6Q*r_HTr%+Bv)z6I=Qb^5)@^}Lr zE@?xDGO6~QJVw1PF&BWcb1*(nJR;ds7Y4KKbR4(sOC@C2gXWAD_N| zUiIGe2Zmogtn1a#5y_m85s9l{Wg-qkpxVR{$ew`_2+7w*hl&k529nQbTCCWxgU?Xx zs12pA%7z-ec*ndmX3y9J!wZFV8dERnjS+<3FKk?3Lkb*?MvWUf7Y+OrLyWl^95*c6 za{s!~18FU;?#l zeeTX4a%g&B;$|w|xD}n-(77F*JJ7ikoo(pch0Z8jSo)^&BfzFHQ{1-Yq(ZPhC{xUkayTJ6{ zxXR&JpZJ5mZc5E9@**r=jW9Ij$-M9&K+OGO#+Ps=3kFOwB5hWV*4)aV?fVlE zeBX%qCFVC~nt!tgvyCr9=74Rkb(Jg^338FB1`>P8=y0`S!po0doXkKj=SMuW+^AI? zFmRR6G+X#VY1jzi0U)3X;c+qX;)$b`s?}>i*u*j&95mrLz*Uambpu5#5FyW0orozn zaC4Q+M4kzDwq50O6M1CSfPwELCTi`yC4-7N(r+Q$e1#WGHa@U?i4EGZ5RPL)gVh}w zEQ+KMyy#q`5BNh7vC{HGxC|Pfrc&wUJp~}TbodrRkUkq02j7%WGag|-M7||UwQ-GT zx`mc5-zS<7LNNJikQb+qu(X@o6sMsYv;1YGG%c7PeNO<=R#yt=pNUFYD%bF>NeCoOGm5K(+uy8OH*ir7Jh0q$q<|6L*`loIuPI!%-3__ zu(*B)A4Ya>{i(H)O8)>9H68+L;Pyh9qe&|Iv94m;Tzc}&uVtfwpO}2V$Fj%#hJepN zxXz->Hxd4*(67j0Qrd7UL$#)S_RUt*hx$j2x`ngRV8iF6Z{rK~)B>LIH5(n`iLFNu zIge1RI-{(;x}Llrb``!JiFyQ@Q|}ehpYQ-Bj&Jd!6c|8!w)5tGH2IpAGvVUKmvYXI zycTekL%9OVTQs6Pz7>rp&o}26iyU7Gb2hXlp?Nt@&QsGs|L@4?h)`=52)>@AfuN<& zPh-Ar7L2%54v)9*ouAI(IYw<7DJ`?3X&s&;zJDyDe8nxqfhG%0Z@zOJVH`;D@ci+E zKs{$cna$^`u2SR+VTd&iy*RAk zP5r343ZKlOMhzeNgs75E#lBqP;5EN4-n;3>(h3av`C1+ASy*!16N5&j1`7$6RYu$u z(Dj3VVASjN_{T|+LA&1HWJHU}U-ANBrl-{^Frx7mfHZYCLp=`^D<0%fYu4f62O|Mn zpJBe{ZQO^R0CouKLZigDZw+%HK70k;FbhP2ZQ%QEJqF(@4&rhH{j zy!1;2%{soR3tB`Z2;WKv9}tp+uTg`BfkX*OH$Q+ZS@_Y~;E-j*aEs3tc& zmyRQjBqTWBhVVALWM~|H-vUX<_t}Q)Hfe~&xhA+S)}p>0 zKVe1f{F{6x(wnsA@{RbRGZ^`S^luCCV7CQd_^BW8W!R|OIKU#kW*!dyE-Z;!feTPa z2E}{ffAK!+_Vy+y)YE7@K`7@VLLtnUs&jmo5;hfTv!B73fijmgXN(r%?($H$U{C0LLO)O#R=&{8Ei_t(x3jM zOqDD6UY2>yZzIAHC13g^mtzqHSL#WF;fTnO=L3s;M4_L+vkiTIW^fcLMl$;CsX~XB vZ!XzAQhvB(;Kw)XjB3RKNLP9FY_mDv7g%T4E6#d9QY`Fa_#SzW?PLEBM=W)# literal 0 HcmV?d00001 diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..88d1552 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c3c9f49 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..14f1af3 --- /dev/null +++ b/README.md @@ -0,0 +1,216 @@ +# FastBleGatt + +## 版本更新信息 + +### v1.0.0 + +- 正式版提交 + +## 具体使用: + +### 1,引入库 + +添加JitPack仓库到项目中 + +项目根 build.gradle + +```groovy +allprojects { + repositories { + ... + maven { url 'https://jitpack.io' } + } +} +``` + +添加依赖,app模块中 + +```groovy +dependencies { + implementation 'com.github.shengMR:FastBleGatt:v1.0.0' +} +``` + +### 2,使用 + +1. 初始化蓝牙管理器 + + ```java + FastBleManager.getInstance().init(this); + ``` + +2. 获取一个蓝牙关联器 FastBleGatt,之后所有操作都是对这个蓝牙关联器进行操作 + + ```java + // 参数:device,扫描到的蓝牙BluetoothDevice对象 + FastBleGatt fastBleGatt = FastBleManager.getInstance().with(device); + ``` + +3. 通过蓝牙关联器设置回调并开始连接 + + ```java + FastBleManager.getInstance() + .with(device) + .setBleCallback(new BleCallback() { + @Override + public void onConnecting(BluetoothDevice device) { + // 设备连接中 + } + + @Override + public void onConnected(BluetoothDevice device) { + // 设备连接成功 + } + + @Override + public void onDeviceReady(BluetoothDevice device) { + // 设备准备完毕,此时可以发送命令了 + } + + @Override + public void onDisconnecting(BluetoothDevice device) { + // 设备断开中 + } + + @Override + public void onDisconnectByUser(BluetoothDevice device) { + // 设备主动断开 + } + + @Override + public void onDisconnected(BluetoothDevice device) { + // 设备被动断开 + } + + @Override + public void onConnectTimeout(BluetoothDevice device) { + // 设备连接超时 + } + }) + .connect(); + ``` + +4. 通过蓝牙关联器对指定蓝牙设备进行操作 + + - 开启通知 + + ```java + // 创建一条命令 + Request request = Request.newEnableNotifyRequest( + serviceUUID, // 服务UUID + characteristicUuid, // 特征UUID + new RequestCallback() { // 请求命令回调 + @Override + public void success(FastBleGatt fastBleGatt, Request request, Object data) { + // 发送成功 + } + + @Override + public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) { + // 发送失败 + } + + @Override + public boolean timeout(FastBleGatt fastBleGatt, Request request) { + // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令 + return false; + } + }); + + // 对指定设备做操作 + FastBleManager.getInstance() + .with(device) + .sendRequest(request); + ``` + + - 写操作 + + ```java + // 创建一条命令 + Request request = Request.newReadRequest( + serviceUUID, // 服务UUID + characteristicUuid, // 特征UUID + new RequestCallback() { // 请求命令回调 + @Override + public void success(FastBleGatt fastBleGatt, Request request, Object data) { + // 发送成功 + } + + @Override + public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) { + // 发送失败 + } + + @Override + public boolean timeout(FastBleGatt fastBleGatt, Request request) { + // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令 + return false; + } + }); + + // 对指定设备做操作 + FastBleManager.getInstance() + .with(device).sendRequest(request); + ``` + + - 读操作 + + ```java + // 创建一条命令 + Request request = Request.newReadRequest( + serviceUUID, // 服务UUID + characteristicUuid, // 特征UUID + data, // 需要写的数据 + new RequestCallback() { // 请求命令回调 + @Override + public void success(FastBleGatt fastBleGatt, Request request, Object data) { + // 发送成功 + } + + @Override + public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) { + // 发送失败 + } + + @Override + public boolean timeout(FastBleGatt fastBleGatt, Request request) { + // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令 + return false; + } + }); + + // 对指定设备做操作 + FastBleManager.getInstance() + .with(device).sendRequest(request); + ``` + +## 基本概念 + +### 1,Request消息对象 + +​ 我们要发送一个消息,需要创建一个请求对象,即Request,可用类方法直接构造出来,然后通过蓝牙关联器对指定设备进行发送 + +```java +Request.newWriteRequest(); // 写操作 +Request.newReadRequest(); // 读操作 +Request.newMtuRequest(); // 申请MTU操作 +Request.newEnableNotifyRequest(); // 开启通知 +Request.newDisableNotifyRequest(); // 关闭通知 +``` + +每个Request消息都会要求输入一个RequestCallback对象,用来反馈消息的发送成功与否 + +#### 消息特性: + +* 延时发送 + + 每一个Request对象都可以设置延时的时间,不过延时的时间相对于上一条命令 + + 如:request(1)延时200ms,request(2)延时300ms,然后两条一起发送,命令执行如下: + + sendRequest ---- 间隔200ms ---- request(1)发送 ---- 成功/失败 ---- 延时300ms --- request(2)发送 + +* 消息Tag + + 每一个Request对象都可以携带一个Tag变量,用来发送成功失败判断是哪一个包的失败与发送 + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..822343f --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.cys.fastblegatt" + minSdkVersion 19 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'com.google.code.gson:gson:2.8.5' + + implementation 'com.github.shengMR:FastBleScan:master-SNAPSHOT' + implementation 'com.github.shengMR:BasisUtil:v1.0.1' + implementation project(path: ':fastblegatt') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ff27d7f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/cys/DiscoverServicesActivity.java b/app/src/main/java/com/cys/DiscoverServicesActivity.java new file mode 100644 index 0000000..5625045 --- /dev/null +++ b/app/src/main/java/com/cys/DiscoverServicesActivity.java @@ -0,0 +1,348 @@ +package com.cys; + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattService; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.Button; +import android.widget.ExpandableListView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.cys.basis.view.helper.adapter.BaseViewHolder; +import com.cys.fastblegatt.FastBleGatt; +import com.cys.fastblegatt.FastBleManager; +import com.cys.fastblegatt.callback.BleCallback; +import com.cys.fastblegatt.callback.RequestCallback; +import com.cys.fastblegatt.request.Request; +import com.cys.fastblegatt.util.Logger; +import com.cys.fastblegatt.util.PrintHelpper; +import com.cys.fastblescan.bean.ScanDevice; + +import java.util.ArrayList; +import java.util.List; + +public class DiscoverServicesActivity extends AppCompatActivity { + + private ExpandableListView idElvService; + private ElvAdapter adapterService; + private FastBleGatt currentGatt; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_discover_services); + + + if (getIntent().hasExtra("CurrentDevice")) { + final ScanDevice scanDevice = getIntent().getParcelableExtra("CurrentDevice"); + currentGatt = FastBleManager.getInstance().with(scanDevice.device); + currentGatt + .setBleCallback(new BleCallback() { + @Override + public void onConnecting(BluetoothDevice device) { + Logger.d("连接中 = " + device.getAddress()); + } + + @Override + public void onConnected(BluetoothDevice device) { + Logger.d("连接成功 = " + device.getAddress()); + } + + @Override + public void onDeviceReady(BluetoothDevice device) { + Logger.d("设备准备完毕 = " + device.getAddress()); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (currentGatt == null) { + return; + } + if (adapterService != null) { + adapterService.replaceData(currentGatt.getGattServices()); + } + } + }); + } + + @Override + public void onDisconnecting(BluetoothDevice device) { + Logger.d("设备断开中 = " + device.getAddress()); + } + + @Override + public void onDisconnectByUser(BluetoothDevice device) { + Logger.d("设备主动断开 = " + device.getAddress()); + } + + @Override + public void onDisconnected(BluetoothDevice device) { + Logger.d("设备被动断开 = " + device.getAddress()); + finish(); + } + + @Override + public void onConnectTimeout(BluetoothDevice device) { + finish(); + } + }) + .connect(); + } + + idElvService = findViewById(R.id.id_elv); + idElvService.setGroupIndicator(null); + adapterService = new ElvAdapter(this); + idElvService.setAdapter(adapterService); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (currentGatt != null) { + currentGatt.disconnect(); + } + } + + class ElvAdapter extends BaseExpandableListAdapter { + + public Context mContext; + public LayoutInflater mInflater; + public List mService = new ArrayList<>(); + + public void replaceData(List services) { + if (mService != services) { + mService.clear(); + mService.addAll(services); + } + notifyDataSetChanged(); + } + + public ElvAdapter(Context context) { + this.mContext = context.getApplicationContext(); + this.mInflater = LayoutInflater.from(this.mContext); + } + + @Override + public int getGroupCount() { + return mService.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return mService.get(groupPosition).getCharacteristics().size(); + } + + @Override + public BluetoothGattService getGroup(int groupPosition) { + return mService.get(groupPosition); + } + + @Override + public BluetoothGattCharacteristic getChild(int groupPosition, int childPosition) { + return mService.get(groupPosition).getCharacteristics().get(childPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + BaseViewHolder viewHolder = BaseViewHolder.getViewHolder(this.mContext, R.layout.adapter_item_service, groupPosition, convertView, parent); + viewHolder.setText(R.id.id_tv_service_uuid, "服务 -> " + adapterService.mService.get(groupPosition).getUuid().toString()); + if (isExpanded) { + viewHolder.setImageResource(R.id.id_iv_arrow, R.drawable.ic_arrow_down); + } else { + viewHolder.setImageResource(R.id.id_iv_arrow, R.drawable.ic_arrow_up); + } + return viewHolder.getItemView(); + } + + @SuppressLint("SetTextI18n") + @Override + public View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + final BluetoothGattCharacteristic bean = adapterService.mService.get(groupPosition).getCharacteristics().get(childPosition); + BaseViewHolder viewHolder = BaseViewHolder.getViewHolder(this.mContext, R.layout.adapter_item_characteristic, childPosition, convertView, parent); + viewHolder.setText(R.id.id_tv_characteritic_uuid, "特征 -> " + bean.getUuid().toString()); + TextView readTv = viewHolder.getView(R.id.id_tv_read); + Button readBt = viewHolder.getView(R.id.id_read); + Button writeBt = viewHolder.getView(R.id.id_write); + Button writeNRBt = viewHolder.getView(R.id.id_write_no_response); + Button notifyBt = viewHolder.getView(R.id.id_notify); + readTv.setVisibility(View.GONE); + readBt.setVisibility(View.GONE); + writeBt.setVisibility(View.GONE); + writeNRBt.setVisibility(View.GONE); + notifyBt.setVisibility(View.GONE); + + // 读按钮 + readBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Request request = Request.newReadRequest(adapterService.mService.get(groupPosition).getUuid(), bean.getUuid(), new RequestCallback() { + @Override + public void success(FastBleGatt baseFastGatt, Request request, Object data) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "读取成功", Toast.LENGTH_SHORT).show(); + adapterService.notifyDataSetChanged(); + } + }); + } + + @Override + public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "读取失败", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public boolean timeout(FastBleGatt baseFastGatt, Request request) { + return false; + } + }); + currentGatt.sendRequest(request); + } + }); + + writeBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + byte[] data = new byte[]{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00/*, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06*/ + }; + List requests = FastBleManager.getInstance().calculRequest( + adapterService.mService.get(groupPosition).getUuid(), + bean.getUuid(), + data, + currentGatt.getBleDataSize(), + new RequestCallback() { + @Override + public void success(FastBleGatt baseFastGatt, Request request, Object data) { + Logger.d("发送成功:" + "Tag = " + request.tag + " " + PrintHelpper.bytesToHexString(request.data, ",")); + } + + @Override + public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) { + + } + + @Override + public boolean timeout(FastBleGatt baseFastGatt, Request request) { + return false; + } + }); + Logger.d("request length = " + requests.size()); + for (int i = 0; i < requests.size(); i++) { + Logger.d(String.format("request data[%d] = %s", i, PrintHelpper.bytesToHexString(requests.get(i).data, ","))); + } + + currentGatt.sendRequests(requests); + } + }); + + // 通知按钮 + notifyBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Request request = Request.newEnableNotifyRequest(adapterService.mService.get(groupPosition).getUuid(), bean.getUuid(), new RequestCallback() { + @Override + public void success(FastBleGatt baseFastGatt, Request request, Object data) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "成功", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "失败", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public boolean timeout(FastBleGatt baseFastGatt, Request request) { + return false; + } + }); + currentGatt.sendRequest(request); + } + }); + + + // 解析属性 + int properties = bean.getProperties(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_BROADCAST) == BluetoothGattCharacteristic.PROPERTY_BROADCAST) { + + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == BluetoothGattCharacteristic.PROPERTY_READ) { + readBt.setVisibility(View.VISIBLE); + if (bean.getValue() != null) { + readTv.setVisibility(View.VISIBLE); + + readTv.setText("[" + PrintHelpper.bytesToHexString(bean.getValue(), ",") + "]" + new String(bean.getValue())); + } + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) { + writeNRBt.setVisibility(View.VISIBLE); + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) == BluetoothGattCharacteristic.PROPERTY_WRITE) { + writeBt.setVisibility(View.VISIBLE); + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { + notifyBt.setVisibility(View.VISIBLE); + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { + + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) == BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) { + + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) == BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) { + + } + + + return viewHolder.getItemView(); + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } + } +} diff --git a/app/src/main/java/com/cys/MainActivity.java b/app/src/main/java/com/cys/MainActivity.java new file mode 100644 index 0000000..f2ca8d5 --- /dev/null +++ b/app/src/main/java/com/cys/MainActivity.java @@ -0,0 +1,245 @@ +package com.cys; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.cys.adapter.DeviceAdapter; +import com.cys.fastblegatt.FastBleManager; +import com.cys.fastblegatt.util.Logger; +import com.cys.fastblescan.FastBleScanner; +import com.cys.fastblescan.bean.ScanDevice; +import com.cys.fastblescan.callback.FastBleScanCallback; + +import java.util.ArrayList; +import java.util.List; + +public class MainActivity extends AppCompatActivity implements View.OnClickListener, + FastBleScanCallback, DeviceAdapter.OnItemClickListener { + + private RecyclerView idRcvDevice; + private List mDeviceList = new ArrayList<>(); + private DeviceAdapter adapterDevice; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Logger.isDebug = true; + + if (!FastBleScanner.getInstance().isSupportBluetooth()) { + Toast.makeText(this, "本设备不支持蓝牙", Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1001); + } + + findViewById(R.id.id_bt_start_scan).setOnClickListener(this); + findViewById(R.id.id_bt_stop_scan).setOnClickListener(this); + findViewById(R.id.id_bt_disconnect).setOnClickListener(this); + + idRcvDevice = findViewById(R.id.id_rcv_device); + idRcvDevice.setLayoutManager(new LinearLayoutManager(this)); + idRcvDevice.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + adapterDevice = new DeviceAdapter(); + idRcvDevice.setAdapter(adapterDevice); + adapterDevice.setOnItemClickListener(this); + + FastBleScanner.getInstance().setScanCallback(this); + FastBleManager.getInstance().init(this); + } + + @Override + protected void onResume() { + super.onResume(); + if (!FastBleScanner.getInstance().isBluetoothEnabled()) { + FastBleScanner.getInstance().enableBluetooth(); + } + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.id_bt_start_scan) { + if (FastBleScanner.getInstance().isBluetoothEnabled()) { + mDeviceList.clear(); + adapterDevice.replaceData(mDeviceList); + FastBleScanner.getInstance().setIgnoreSame(true).startScan(); + } + } else if (v.getId() == R.id.id_bt_stop_scan) { + if (FastBleScanner.getInstance().isBluetoothEnabled()) { + FastBleScanner.getInstance().stopScan(); + } + } else if (v.getId() == R.id.id_bt_disconnect) { + + } + } + + @Override + public void onStartScan() { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MainActivity.this, "开始扫描", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onLeScan(final ScanDevice scanDevice) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Log.d("Sheng", "扫描 -> " + scanDevice.deviceName); + mDeviceList.add(scanDevice); + adapterDevice.replaceData(mDeviceList); + } + }); + } + + @Override + public void onStopScan() { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MainActivity.this, "停止扫描", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onScanFailure(int errorCode) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MainActivity.this, "扫描出错", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onItemClick(ScanDevice scanDevice) { + if (FastBleScanner.getInstance().isBluetoothEnabled()) { + FastBleScanner.getInstance().stopScan(); + } + Intent intent = new Intent(this, DiscoverServicesActivity.class); + intent.putExtra("CurrentDevice", scanDevice); + startActivity(intent); + } + + /* + public void onDeviceReady() { + Request requestEnableNotify = Request.newWriteRequest(mServiceUuid, mCharactiesticUuid, new byte[]{0x01}, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + mLogger.d("开启通知 发送成功"); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + mLogger.d("开启通知 发送失败"); + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + mLogger.d("开启通知 发送超时"); + return false; + } + }); + + mFastGatt.sendRequest(requestEnableNotify); + + Request request2EnableNotify = Request.newWriteRequest(mServiceUuid, mCharactiesticUuid, new byte[]{0x01}, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + mLogger.d("开启通知 发送成功"); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + mLogger.d("开启通知 发送失败"); + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + mLogger.d("开启通知 发送超时"); + return false; + } + }, 4000); + + mFastGatt.sendRequest(request2EnableNotify); + + for (int i = 513; i < 530; i ++) { + Request requestMtu = Request.newMtuRequest( + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1910"), + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1911"), i, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + Logger.d("Mtu 发送成功" + (int) request.getTag()); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + Logger.d("Mtu 发送失败"); + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + Logger.d("Mtu 发送超时" + (int) request.getTag()); + return false; + } + }); + requestMtu.setTag(i); + FastBleGatt.getInstance().sendRequest(requestMtu); + } + + byte[] data = new byte[]{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 + }; + List requests = FastBleGatt.getInstance().calculData( + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1910"), + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1911"), + data, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + Logger.d("发送成功" + ArraysUtils.bytesToHexString(request.data, ",")); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + return false; + } + }); + Logger.d("request length = " + requests.size()); + for (int i = 0; i < requests.size(); i++) { + Logger.d(String.format("request data[%d] = %s", i, ArraysUtils.bytesToHexString(requests.get(i).data, ","))); + } + + FastBleGatt.getInstance().sendCalcuData(requests); + + } + */ + +} diff --git a/app/src/main/java/com/cys/adapter/DeviceAdapter.java b/app/src/main/java/com/cys/adapter/DeviceAdapter.java new file mode 100644 index 0000000..a62bc61 --- /dev/null +++ b/app/src/main/java/com/cys/adapter/DeviceAdapter.java @@ -0,0 +1,66 @@ +package com.cys.adapter; + +import android.bluetooth.BluetoothDevice; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.cys.R; +import com.cys.fastblescan.bean.ScanDevice; + +import java.util.ArrayList; +import java.util.List; + +public class DeviceAdapter extends RecyclerView.Adapter { + + public List mDatas = new ArrayList<>(); + + public interface OnItemClickListener{ + void onItemClick(ScanDevice scanDevice); + } + + public OnItemClickListener mItemClickListener; + + public void setOnItemClickListener(OnItemClickListener clickListener){ + this.mItemClickListener = clickListener; + } + + public void replaceData(List datas) { + this.mDatas.clear(); + this.mDatas.addAll(datas); + notifyDataSetChanged(); + } + + @NonNull + @Override + public DeviceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_device, parent, false); + return new DeviceViewHolder(inflate); + } + + @Override + public void onBindViewHolder(@NonNull DeviceViewHolder holder, int position) { + final ScanDevice scanDevice = mDatas.get(position); + BluetoothDevice device = scanDevice.device; + holder.textViewByName.setText((TextUtils.isEmpty(scanDevice.deviceName) ? device.getAddress() : scanDevice.deviceName) + + "( rssi " + scanDevice.rssi + " ) "); + holder.textViewByMac.setText("Mac : " + device.getAddress()); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(mItemClickListener != null){ + mItemClickListener.onItemClick(scanDevice); + } + } + }); + } + + @Override + public int getItemCount() { + return mDatas.size(); + } +} diff --git a/app/src/main/java/com/cys/adapter/DeviceViewHolder.java b/app/src/main/java/com/cys/adapter/DeviceViewHolder.java new file mode 100644 index 0000000..785d7ad --- /dev/null +++ b/app/src/main/java/com/cys/adapter/DeviceViewHolder.java @@ -0,0 +1,20 @@ +package com.cys.adapter; + +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import com.cys.R; + + +public class DeviceViewHolder extends RecyclerView.ViewHolder { + + public TextView textViewByName; + public TextView textViewByMac; + + public DeviceViewHolder(View itemView) { + super(itemView); + textViewByName = itemView.findViewById(R.id.id_tv_name); + textViewByMac = itemView.findViewById(R.id.id_tv_mac); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..08e107f8a663229b0be6e5993d0b25597f9046f2 GIT binary patch literal 380 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=DjSL74G){)!Z!20>33$B+!?w^t5&GCN8f{m3fl;_9END0-tI zL0;~Q#y0^Ee#_-8A`8r=1$7Qi7O0hfcu>iBc1GH>-)#5z<#Z0~y_w%C>jDHzR%XA+ zDVMy&V|_RFnQe0X4fV2o_QpBM(r=`zk3WmJ#F@rewm7%jHc2qzi1xAa&1*NT;m@hr zeAFgkUXb86)@h~Pz5Bb)8mozaY>Q2p{B8q>VX)vgXRD5KpNSlwx-_L6*CwxW30OU2 zm0*bVGEbJPU71pjOOs=iR)q9qv&c#dD>eO-P8QU^@xyYqH_KmBwGRNE$FxKq?@3v#pZY-7U009fhHu=~4Uixd+ S-i!f;7=x#)pUXO@geCy@x{ZVY literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png new file mode 100644 index 0000000000000000000000000000000000000000..3911c266302b21581b834dfd9431cc836702f0f8 GIT binary patch literal 375 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}Ea{HEjtmSN z`?>!lvI6;>1s;*b3=BdgAk26#O}+xCfydLuF(kwJ?d5}+OpX#qKej4(h}bS|4M|{b zs(rG+eIxrpxgtMKO^19lArH=ZOr7tY<#je~UVU@!%>0Lc9~?9=XtzHtrUU{z&u-w) zY43fzs9)>`=kML`I(#?8?d*M#>Z8&0+SMnR<+;@|tw-X~$-ghjJ~vVm*?8oP@iV(+ znoX~gmPLepcbjl}hP(2K&@=VQCzALge(u%H^tsJ(O6qbbOKRs!tENfEj&v#_Bqf2PARg+ckNm0kSN1pU82s11` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_discover_services.xml b/app/src/main/res/layout/activity_discover_services.xml new file mode 100644 index 0000000..c445564 --- /dev/null +++ b/app/src/main/res/layout/activity_discover_services.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0bb9f33 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,50 @@ + + + +