From 7ce96b9e610de2782ec5f2af806e7bc0f90c8578 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Mon, 2 Feb 2015 11:27:58 -0500 Subject: [PATCH] Move non-ui bt settings code to SettingsLib Mostly this is moving classes from Settings to SettingsLib but there were a few changes to support this separation. - A bunch of things became public rather than package - Moved some settings only code out of these classes - Added error callback to handle errors To see the changes from original classes view the diff against patch-set 1. Bug: 19180466 Change-Id: I69fd888362c6dbb325f6113b32c4b15cc6a23a41 --- .../res/drawable-hdpi/ic_bt_cellphone.png | Bin 0 -> 653 bytes .../drawable-hdpi/ic_bt_headphones_a2dp.png | Bin 0 -> 631 bytes .../res/drawable-hdpi/ic_bt_headset_hfp.png | Bin 0 -> 649 bytes .../res/drawable-hdpi/ic_bt_misc_hid.png | Bin 0 -> 342 bytes .../res/drawable-hdpi/ic_bt_network_pan.png | Bin 0 -> 528 bytes .../res/drawable-hdpi/ic_bt_pointing_hid.png | Bin 0 -> 600 bytes .../res/drawable-hdpi/ic_lockscreen_ime.png | Bin 0 -> 285 bytes .../drawable-ldrtl-hdpi/ic_bt_cellphone.png | Bin 0 -> 4349 bytes .../drawable-ldrtl-mdpi/ic_bt_cellphone.png | Bin 0 -> 3872 bytes .../drawable-ldrtl-xhdpi/ic_bt_cellphone.png | Bin 0 -> 4781 bytes .../res/drawable-mdpi/ic_bt_cellphone.png | Bin 0 -> 450 bytes .../drawable-mdpi/ic_bt_headphones_a2dp.png | Bin 0 -> 444 bytes .../res/drawable-mdpi/ic_bt_headset_hfp.png | Bin 0 -> 448 bytes .../res/drawable-mdpi/ic_bt_misc_hid.png | Bin 0 -> 246 bytes .../res/drawable-mdpi/ic_bt_network_pan.png | Bin 0 -> 418 bytes .../res/drawable-mdpi/ic_bt_pointing_hid.png | Bin 0 -> 370 bytes .../res/drawable-mdpi/ic_lockscreen_ime.png | Bin 0 -> 175 bytes .../res/drawable-xhdpi/ic_bt_cellphone.png | Bin 0 -> 792 bytes .../drawable-xhdpi/ic_bt_headphones_a2dp.png | Bin 0 -> 764 bytes .../res/drawable-xhdpi/ic_bt_headset_hfp.png | Bin 0 -> 771 bytes .../res/drawable-xhdpi/ic_bt_misc_hid.png | Bin 0 -> 220 bytes .../res/drawable-xhdpi/ic_bt_network_pan.png | Bin 0 -> 717 bytes .../res/drawable-xhdpi/ic_bt_pointing_hid.png | Bin 0 -> 713 bytes .../res/drawable-xhdpi/ic_lockscreen_ime.png | Bin 0 -> 254 bytes .../res/drawable-xxhdpi/ic_bt_cellphone.png | Bin 0 -> 1091 bytes .../drawable-xxhdpi/ic_bt_headphones_a2dp.png | Bin 0 -> 1031 bytes .../res/drawable-xxhdpi/ic_bt_headset_hfp.png | Bin 0 -> 1045 bytes .../res/drawable-xxhdpi/ic_bt_misc_hid.png | Bin 0 -> 367 bytes .../res/drawable-xxhdpi/ic_bt_network_pan.png | Bin 0 -> 993 bytes .../drawable-xxhdpi/ic_bt_pointing_hid.png | Bin 0 -> 947 bytes .../res/drawable-xxhdpi/ic_lockscreen_ime.png | Bin 0 -> 327 bytes .../res/drawable-xxxhdpi/ic_bt_cellphone.png | Bin 0 -> 1337 bytes .../ic_bt_headphones_a2dp.png | Bin 0 -> 1353 bytes .../drawable-xxxhdpi/ic_bt_headset_hfp.png | Bin 0 -> 1362 bytes .../res/drawable-xxxhdpi/ic_bt_misc_hid.png | Bin 0 -> 305 bytes .../drawable-xxxhdpi/ic_bt_network_pan.png | Bin 0 -> 1284 bytes .../drawable-xxxhdpi/ic_bt_pointing_hid.png | Bin 0 -> 1178 bytes .../drawable-xxxhdpi/ic_lockscreen_ime.png | Bin 0 -> 412 bytes packages/SettingsLib/res/values/strings.xml | 83 ++ .../settingslib/bluetooth/A2dpProfile.java | 214 +++++ .../bluetooth/BluetoothCallback.java | 30 + .../bluetooth/BluetoothDeviceFilter.java | 169 ++++ .../BluetoothDiscoverableTimeoutReceiver.java | 86 ++ .../bluetooth/BluetoothEventManager.java | 362 ++++++++ .../bluetooth/CachedBluetoothDevice.java | 787 ++++++++++++++++++ .../CachedBluetoothDeviceManager.java | 172 ++++ .../settingslib/bluetooth/HeadsetProfile.java | 227 +++++ .../settingslib/bluetooth/HidProfile.java | 201 +++++ .../bluetooth/LocalBluetoothAdapter.java | 216 +++++ .../bluetooth/LocalBluetoothManager.java | 122 +++ .../bluetooth/LocalBluetoothProfile.java | 71 ++ .../LocalBluetoothProfileManager.java | 383 +++++++++ .../settingslib/bluetooth/MapProfile.java | 213 +++++ .../settingslib/bluetooth/OppProfile.java | 89 ++ .../settingslib/bluetooth/PanProfile.java | 183 ++++ .../bluetooth/PbapServerProfile.java | 152 ++++ .../android/settingslib/bluetooth/Utils.java | 43 + 57 files changed, 3803 insertions(+) create mode 100644 packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-hdpi/ic_bt_headphones_a2dp.png create mode 100644 packages/SettingsLib/res/drawable-hdpi/ic_bt_headset_hfp.png create mode 100644 packages/SettingsLib/res/drawable-hdpi/ic_bt_misc_hid.png create mode 100644 packages/SettingsLib/res/drawable-hdpi/ic_bt_network_pan.png create mode 100644 packages/SettingsLib/res/drawable-hdpi/ic_bt_pointing_hid.png create mode 100644 packages/SettingsLib/res/drawable-hdpi/ic_lockscreen_ime.png create mode 100644 packages/SettingsLib/res/drawable-ldrtl-hdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-ldrtl-mdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-mdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-mdpi/ic_bt_headphones_a2dp.png create mode 100644 packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png create mode 100644 packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png create mode 100644 packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png create mode 100644 packages/SettingsLib/res/drawable-mdpi/ic_bt_pointing_hid.png create mode 100644 packages/SettingsLib/res/drawable-mdpi/ic_lockscreen_ime.png create mode 100644 packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-xhdpi/ic_bt_headphones_a2dp.png create mode 100644 packages/SettingsLib/res/drawable-xhdpi/ic_bt_headset_hfp.png create mode 100644 packages/SettingsLib/res/drawable-xhdpi/ic_bt_misc_hid.png create mode 100644 packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png create mode 100644 packages/SettingsLib/res/drawable-xhdpi/ic_bt_pointing_hid.png create mode 100644 packages/SettingsLib/res/drawable-xhdpi/ic_lockscreen_ime.png create mode 100644 packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png create mode 100644 packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png create mode 100644 packages/SettingsLib/res/drawable-xxhdpi/ic_bt_misc_hid.png create mode 100644 packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png create mode 100644 packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png create mode 100644 packages/SettingsLib/res/drawable-xxhdpi/ic_lockscreen_ime.png create mode 100644 packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png create mode 100644 packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headphones_a2dp.png create mode 100644 packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png create mode 100644 packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png create mode 100644 packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png create mode 100644 packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_pointing_hid.png create mode 100644 packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java create mode 100755 packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java create mode 100644 packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java diff --git a/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-hdpi/ic_bt_cellphone.png new file mode 100644 index 0000000000000000000000000000000000000000..6e29d233fb18efa91ec2590e12546622517e8df1 GIT binary patch literal 653 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 literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-ldrtl-xhdpi/ic_bt_cellphone.png new file mode 100644 index 0000000000000000000000000000000000000000..8b67b91ef6675e93d48731a7701db549534dfe17 GIT binary patch literal 4781 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 literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_headset_hfp.png new file mode 100644 index 0000000000000000000000000000000000000000..05b27e883056f2322a2e65fe0120a141d45fcc56 GIT binary patch literal 448 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAEe-Gqab=j=KKRjecfP4W2b+}y z`2{n0%)9mWjn0YV+KwMy)SHNzHoSCDf4`WEd#T!?MT|=hxIW>0<}#D9 z)#Uq}WbTjIv+|~#YFnqdE2$}LZDIWO1oK&am$S+fN`vngUzFc-QTmWZ|@4-)AgKXfjdN6f6RKq mUdmzIR(6{Iq-_}UedaA!lwvR44z$KdJe=d#Wzp$Pz+gR)xy literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_misc_hid.png new file mode 100644 index 0000000000000000000000000000000000000000..6e9f8ae0cfd7b901ff8f8f2d1513c85b248a5c5d GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAnFaWSxH3#_Cj%|gxceVyf?!FI zUogXcncLSSq!k`2Jyv;gS5EW!`o6o1fs%2aE{-7@=Z{|S=4(*kVY#q!MZ<~Z-}mcu zF#5h~+L1Wz^CZux2lYGBgf3jY%&yZ_dG|=$t%aQ>Pq&?jt@_VqCbpqXRfex%dCR5g uE7kaaG|b>)(?8ewa-Ot(!K>TulNpbSaWflUd-5A-6N9I#pUXO@geCyr`CqI6 literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-mdpi/ic_bt_network_pan.png new file mode 100644 index 0000000000000000000000000000000000000000..5f3371f86c1a11c0fa0c3d75208b027c37915ce6 GIT binary patch literal 418 zcmV;T0bTxyP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt0Ut?3K~y-)#na6ygmDzd@lS-J45k^fWN+gJxBywn)<$swc0{DO z6IrpavZW~RGbSY)#U*$dax6RzJs#>=$l3k>=X_7+cV75wYtHx!PxkX931KZS5Y~SW z8nUDN3-Bt;{|4*|M+p+bq!(eK;qOWVJx&`)2uurM28_7To<@q!5+p?Ox=;eE!qoS& zN-l&WAsh=8C3IrYo2@J*W!0odp{k5=o8aDUw)kE(AzbRkm}fPx=jkJ$DFxI$fJb$7 zXws5&?lmiaccS1!MGq!ygFDwhg@1}ZQA7a)-fX19`_zO3okA!$6t47x6=5PP`?+Fj zaI;|4skZN{QAx)1xf)9ie`~s+VSCD{+u#PmbTw)TQ*JT)&KWbZ&LrYD_=f;>eABaW8JZB@*nN1-+#V4wFao6%hSa% zMC1I{iwF6d3x`-Uzy}&Ps zS%6W^FJ=DQM#%4=$jt^aMNYT+Z03=kUWw@TDNo2nJ7AKbLh*2~7Zkbv9!F literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_cellphone.png new file mode 100644 index 0000000000000000000000000000000000000000..4f381ba7976cec7ce9e64e3b4f2a9a41774b6eb5 GIT binary patch literal 792 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBgK_VAK!r32|kZ+CGYi4}n)1OhUjA zOeqQS3uf@Xch~FDgI}C)<%}+Ptgdg_759-@?J?h?uMT0~UD@8aO#k6#n!97O^Zwi1 zTFuE^my|!e*N^(A&LhuM{?ho}mMTv@$-d~&-+!-JGWG@?t8nsv8u@8m>d&JbO&a@F z%}zL>+5Tj?@H7!%RGE0XIEHu}e|z%-L?92#234t7Kuu)DAtYR?coBJ=-y zJ)3w-8sn9>w&~`<-&e1DX=$r`smDrpt;pl7%Z%nvD@qPTGj3VkaGqg5R|OBp3Ws*a zxmp4y3%pr(@xBOboWQuZR_a3kTQlAnqHLiFD{S7kl&-JZVPbEzD(C)JIb|JOe>1*T8F zd&Ii1?R}pAzR4&5?7r#nfdA5EX%nZ11t&PZef_;%Inel6c@lH8;QP874)->9n^&Dn z4u~4gy6`nP`K?z~jZ$*t%#$l$eydVj{Vu(jA*t*g`&oUChCho|#cMF>=$2vnG|q9tV>Hz&u>PXwXuQ0 zt{of(1~U}aG~M7baXHGFrO?IV5x|%uH;d&++zK`kPW2Vc1}Y9w2~q*7EC=|M4{F3r zV|MPm$sm>FQYoOs_FeIxgNn!-tpfA@WwA~|TbLOhpNNz%k9IW%x`Dyd)z4*}Q$iB} DzX?Em literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xhdpi/ic_bt_network_pan.png new file mode 100644 index 0000000000000000000000000000000000000000..fb76575fec6e1848b3a19af662c3dd30904589b7 GIT binary patch literal 717 zcmV;;0y6!HP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt0!m3lK~z}7?Uy}k6+sk+9}VhXP{bhQ77?@&L_|=q@H^3%7;i!# ze?YJi)JjFcsD)yqrGjXug=itzT4^O>V*)B_8c8Gx?q=`hSlsdK%t)mw$>7)7J(t zudS<{0UjIon0_x{zlFHg31EbVB2Ib$IEd>x{~mga5T{r)?hVE~0NBHCwePUT^6h1s z=T&C0$e;%R=hcp^5pQ7%p~;SFh@l+dAPc&T$1SfzruS~Oy+{~#0DVL%pN+Nm2*OXg zsv^!P;I!^g%!!=OIrD5^)rd+IkS^VTzPFto)7omDmOj|ih;r*64(mHRncrE@kc*Ir4KbqMEJAS&EL=Zk{&jSSEb9woG;@l=j)_qaq8^SBK zUm^%^%gT>=V#qg(vugc75bl+eAJF*8CtAc&YZl#^L>sLZo(XjYjCc|5QPE^`vHYMT zZy$C`A>JVfuZ!h}tV^e!JFeRy4wPM>VO{fo^4SaLqW8k7@daT@v!hKJr|Aht8?c)h zwcT@bBOk3y_m;Y6^e$i*ztp;s^WRi-GCc9hr+~f8E+Jo{tmBzSJ_XDzPrmhS_sGiA zcAt*_{bwYwJfYi84|G!A;(lfe=4`1{=zNymv zJyPYfs>}Wj`y&23JeTr6GDq&ylLRh@=kFd*Dr#3MIxov+$2dVwa_g@%5*K(IK637z z|K%X#vSnNCq#c+z(w@|n@fjW1^GC)qgMm9}K7Xk|8f!oUU%->UNhg_7`x{jw7*6wu z-q!SA7w0@A%t1pK;;c=@K_KI392~%Ql-Q=Vrryvu(K>4mX5v z{q;-FeqFU#LF?ze@#b+^ale%KN-j*Ova-4K>)A$*6P6QyUp5Y!W;6ZQDb|(6dX@F1 zzQ^*Up1#{tTJ8SL=zF}!>JJVVt81$Ie!txoUtL-ITJit4z09SpUh4nN-z)+~1%s!n KpUXO@geCxsX)4KAfS!+ZV8CI@^~wb|EGoCPZ<3 z5L({IxvsH)zQor*Oeb7b4=(Ct-?>7DRT<=&wN|DoleV1!G8sHw{an^LB{Ts5Ya3Qj literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_cellphone.png new file mode 100644 index 0000000000000000000000000000000000000000..7805b7ac7a66508b70d9fe2340499ee661056fcd GIT binary patch literal 1091 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`ol6-Q7<&SILR=Z9wvVDg5(1}L9(Mwx zr@17^FPMRM+QY9Woo6Y&%KX*xw*SH9ZO5-~{>IX+pJG2>jOnHE%EWZl8}}urUzIXH zDzJ;YdL8@U^&dI^Z`Zc_x2f{?yRA1C~fL;@r(&)?mVBu!x6N8Ixv|$_H=O! zi8%cBMtCrjp#b{_4y_d$U0t%(`4!*RMQliV&s%-;-tK4JtHt{^S#<6G{($-a@!4nc zC&i?hm-n;^o!aj+!|k*!XMcx_v(>xl1-4>cpV})-#JU(KFmfMd(%|EkWL$FC=_7;J z0Vg#E)ue?g463sy25>c0XbE{W2S^EdwFj^XooWdP5ewSnpefDy)Imd>^Qj|8wCAVB z;09ZdH5blz>N`!}mK48yiAa3T>6$s8xV&!qeX*Dty5v{WNHU zzHZ)~+dr8qqJh^`2vO7;xs@T?4O)FlU z_Kx56{DWyapX>T|Cwp>Bc6*=JzPWn8^W@EWE0fRe7I|)!6S1uKe*E2KMitr7h1)6| z%9>^b&o-(k52@P_F8$)e!h~;`@j-3!HS<>Nb^aIIbnD|{tJEE@f2edmRx~SqwU?ua z>&TgOq5qbS@+*qhdI=Pp?Yhz^v3o}d=dYadaK2K7V{ficvJyx(-`@PnA^!Vv>mRH~ zbzf||UcUb|w`lOI=ZF71zWns_>DArl`}eljX|;T^|IEnn;^6F%-^X?UQy+t;tDnm{ Hr-UW|_)g2` literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headphones_a2dp.png new file mode 100644 index 0000000000000000000000000000000000000000..812777439e80908dc187ab7f03c4e49bdf07230a GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`ol6-Q7&8KVLR=Z9wvQq@gn;D~)t|ti zFE0u53ue&HnzHlelXscSE^}Wzcjhj9T=4pp;n_0=Obx#meA-qUpeCpuxa0FYwzY?2 zIJshvu3lqfm&}+pEBn0arHmiDc=`5x-+uZO&&!WWsrkn}E$rPF9(y14*7)nb*){bu z5Bc~XxUX?nNaW2=_Aj?@%%9_D_ANNbFuCA9Zo zhS6+ax8{3`f;BfzyY=Q#|CF1j)^?eS@=fkoaw&4*8#C2VCFQGPtlNvOb_f+-FUqpH zXBB8v8pnS<;%n=A6O&)FW<~FNod0#}R&l%c49ZT&29BLS7kv4%@lB5$?^H=}C^vtPpo7LVcrp(a%ha&Q$XKlJB}b%jIpy3%}kM za)P!i1Z|ZTg_$l=cM&Pm7C*{4E!}aY#$*@QC$0f%r}hZB-Mcrn}4AfvJqa)78&qol`;+0GTekz5oCK literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_headset_hfp.png new file mode 100644 index 0000000000000000000000000000000000000000..84b8085c0bf62d6c39e2e1fa62b2835f0829fcdc GIT binary patch literal 1045 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`ol6-Q7&8KVLR=Z9wvQq@gn;D~)t|ti zFE0u53ue&HnzHlelXscSE^}Wzcjhj9T=4pp;n_0=Obx#meA-qUpeCpuxa0FYwzY?2 zIJshvu3lqfm&}+pEBn0arHmiDc=`5x-+uZO&&!WWsrkn}E$rPF9(y14*7)nb*){bu z5Bc~XxUX?nNaW2=_Aj^Rn+2Pk^Sv?0?_1D4GwBbDfYJWc)5S3);_%xU;lYO-MBKz3 zyH;?BxrBrWY;1@~a&+nG;N=#3bK~ee)B5=5c@8q>C!4JIP56DHyr}H`GqstF3x#xj zXNHBZGne@C)M{;D@RB1TiR(n_LZ4uV=4>1uW(|@ z-ibkvGm5x2JC+$PI)s$e?sozr0nVFsuJfE*}`@xDm={`q}IR~{=JGmU%^u1Qdd7@^fnpLl4 zTpE|pqd#X($Su`xS~=s3K#QT_>*??9_12jyvHp0jFh4$E*H*=vM^l5iZ+&p;K6N!)=a}dMedh7S$f$=QydTk{NYT@WJtxCoGeKU-`>D zNN-8uo>Uxlk@dm!78T~s{;(3(ina+4BA5P8^Zc~x#eLVGp)!lRLMCcyMP4-3zR0V6 zQCE9n`_zuj#zG6N=S}3e`?<&E#lwjcISTzxPUhhbRAiOY*S=`1ebHO+y1?tH6Ezxd ybleNdamjz*(RpH?Q@yxvoyqc@K|ihE3D0pjxJZ0zG`ocE$GB;d@ErxzVMT`Z(H zJf|#fYH+c#dSRNQ-B!+P;#YXmWSaAZg{qQ=_%u5MUZ}^MsPPvP`Bd}FAkYpj Rc?$F*gQu&X%Q~loCIIMlnL+>n literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_network_pan.png new file mode 100644 index 0000000000000000000000000000000000000000..72bc8040a4caa05612406385ae79d3701f47ca3d GIT binary patch literal 993 zcmV<710MW|P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt171l)K~!ko?V3$UR8bVi|2iMqqD-CA*|%vOEvkLAkzfcyXb}{( z2{)Q9in3_Y7eNq#6qISs=r}r~MnO>R`XV$bvNmPQmPsVT&}}i!op;Z@_q};vco*;7 z)w>_({oc7B=iD;`L=s6PW(orX!;on}3|CD9G6sp6hQx$&%`_kbkeF#m%rqpXEZ4() zWe*-8<`RhETv#~APys^$370@2S?LJM8|o)CRka`L~Qve}a#A2 zuP>{-x?8DWO{)8i3W452e3%%z)a0*!`^1n{PFa?`vc<0`w_-;>8I_zCN4U} z>leeZPThrwR@#YA8!df8{V6`;Wmwj!l_$hX+wl$z4Eu!I5#E0!JnPiTB(c&CEMtu< z@p_bJO!wEvC%$mSXp!x-uP6s^(+u4?>SL$|W5%{P0yC^q9EZ#&=@ z$N4Iq!bdV@SmDg8NYDIE)|g|mA&nI>reCSM33pJ&ZhzL1FU8L3m|>el#$kU6tD<7R zI3=tu1(~qQ@C>8sVj}}tqvvWF2s+Ey%d?Cn&GBxR^t>EK9=2+w32mlY?_38El4LOI zA#r}zte+M(4F{&-z%(2gXb><7Xwz|E8V*dusRJ4F-v+y1Ga`v35~JlmdUS3G0atgx P00000NkvXXu0mjf7+k)A literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png b/packages/SettingsLib/res/drawable-xxhdpi/ic_bt_pointing_hid.png new file mode 100644 index 0000000000000000000000000000000000000000..e31ce2b28b41bb04f090bb077f47df21c1a4f242 GIT binary patch literal 947 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`ol6-Q7`*~~LR=Z9wvQsJgg}jq{1IT_ z=9UEc1v9Xi=gi7BV`dLr`|0DF05b7-JqsO7Gh%_0%Nn`?sLKf1(_I+Nr#K zt;l-zc)rAYk)C$puL5}|kNp!5pVYr=`<R!7w`RcVS^Hp0c zZ<#V|6=#s08+(^+#=LuW4EBl)=l(IOJ(enAs#iQ<`{a?uVP%I}4hh-C)@L6JI2ZT_ z%=P3?5m>gzB>e)rB};w7BcIm+ju+CeoXZi&;#g=o)p$PxSBvXa%XlW_k!Zf@UFrIz8j6jo;&Po5#VZIPW0x;{k%X{am(rEZ!H(> z_^kw-+HGEVaHv*J$TNIy&pRX8NiD_cn8MuWGTaLl_`NC zEcuH%xM%l2{-HWGcQSLT%Ji~n%&{H|R=AZHP2VIFxzsuQlEW@Fea-39-Dmf5iHb;O zs^`TlTk&TuZ{VESEmqsTw`ZwWx@R(cFPth|zo4{pll!|0nJZyIT_@%|P~Lmr#E5p=ws-u0&zHJ4XFI^Jk z7tFwxe)`?1G*;tliVaF(l&f+iR9w4Gsb> zf!}NIZ+&0yf6yT#@Inz&>#vDw`ZM`XZIVbnpgYLgybDOGUq(n>} wU0AZ<<`oH3v;OS|_t}I_n|$z`L~?@mpJx|lbe+D=26PgGr>mdKI;Vst0J}PaV*mgE literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_cellphone.png new file mode 100644 index 0000000000000000000000000000000000000000..1e12f96a5e52c839e28b5b4c28a6fe82676bc5d1 GIT binary patch literal 1337 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wF4y>PY3vfxH3#_A4S6>1fGY|anKjG?qw8qe6-pAJDe_=a6YhUJ0*vz_^ zX@UAgvtYe!)pfZ&n$M3<@S$Ir8i4$4%I%I zQn~3qk5zQV%XfPNx%C+sm|8tu978hhy}kJ}UMNt2;e)22k?K|-o*! z?(XLEU23w}+Iq{TPafJnZ|$C+e>?r#TW&z)mY;Xq?ohGGmS7YYns}UJ&C-S%w*?JM7f!RUQEqgyQ<%@v^`jw4DB;eE z*PaQ?@&Qgv(mxx5gg$&^OjX&?$^T)pLj(T}AEu|GISvz83{J3xgiUOipWo-C5^$FN z$^?hEVrB{2dpU03Xn1R~p_!NC`XmS0`$|`Wf0j$t9MDtx|IW5&`m=jWe||T-m%(sh z!kqg5jQ1|BHq@-Y?D*`~y2x)oC;N9h=l%P%#r5?43t9pH<+k11YS#GtI{zN!w??Xa ztop5%%xXDj@3172!y=1sVMrE7?6M2GEHc5z=Y)G$ zGS9Gevy-z$I@86^Mh%4rMfBn(2^NKV-@dG^923@_6~Dh{!t$2J85|dGFKN+k>nI3) z8=50xHCf3^!pG42*oCbR>t`HzeeJ7_!@3fihK_mC=FK??eY-r!te9` z|MU3Mc-QYxMFyws$16YAcGy(eWVuUR-WOb||C`e9@qAM5yFF`Z z-QN7_e@e}Neq6d|)#LlZ_h*VlZrz+syQkL#?vQ~XiIODYl8-vjM7RG|}?x~Co3xyIH5)uO?8X^{Xi!n6p znpG6Zu;9gC_DBW=vrmRs8w1aWmL6$(eDz8^-+T-2*&mwTf1bYNy>$Fd?iGjf@7GT` rtJhZduPbTeUhn&y>T?Q`TpyWRonO|?m#Jj}%rp$1u6{1-oD!Mj|TXJxH3#_A4Nkw1kz1jRsrL6 zT1k*!FawKKM&GvUcVEBTYAKev{=+&^_J}RNe{Bw9<8OGy^!!Ke zZ1eVbZ@QDTpsMo;-*1}&cz6T9dPPgMzmN2*;;Z$x+bFM)&sg+Emi0*219i=ZZKn5S zj4mm?U{1;@;M~5OCG*6yLX8E43{p-dPDr+OI4|<^$LCKsC-;AJ z&Fv7D*s0 z?cGsn)9#)=^(LuI-$eK9xA$*P&q;ikxNdiR&AiI}pXPm@eb=)1nU3D{OKyR#OP{U# z^dfHl`PJ2CJGZ{#Qs12a?K;aDA=a#|y5~~(^VS-l*|ds3_jaCjt@GmROETP(o7aBv z{g*a*;k1IuHUF;7RcpUhc2exzyN;7fGj@k~mcB9Exz${4Uj6-lk7V5T7#A*3zIk=3 zO@+ooyO^Meds!F%xCgo3++AJOUH{%%sw+zNWpdKb{^;-*+4h0wCZCzJMXsxE;veQ| ze_jUHs@#6?a!YGLoBjU@Pyf!bbuUhHo;I~$diJ&Oh-(IiXIsZt+?W`+?~nMS)<+K_ zu3X^Q7XQSWe|o`z8>w>tr>?emAl3ih%5J~;v7d64GQx|@Ev1g=-Z`dLVXj*yE&tMU znPubm?LiCPeDJ%U7+GETd%lpRMY-atpK^ySS}hrH7i@>+yU>8+)qU3v_^KlG@Y{I$O&Wi zrN7#en(jqt7auQM{6K0`s!-m9?M4lC8LOUHK9k*Z?7LAz<&6XUE~lA_jy!!fr(ffb z@aDksQ;c?tKkyXK-gkoW(3#jazrUe;A9!2q)vbV literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_headset_hfp.png new file mode 100644 index 0000000000000000000000000000000000000000..03c503388bcd70c03e90c834b590d326694207f0 GIT binary patch literal 1362 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wF4y>j|TXJxH3#_A4Nkw1kz1jRsrL6 zT1k*!FawKKM&GvUcVEBTYAKev{=+&^_J}RNe{Bw9<8OGy^!!Ke zZ1eVbZ@QDTpsMo;-*1}&cz6T9dPPgMzmN2*;;Z$x+bFM)&sg+Emi0*219i=ZZKn5S zj4mm?U{1;@;M~5OCG*6yLX8E43{p-dPDr+OI4|<^$LE`qKe~2E zeCp2?-m&_p=_v*Vre&Tkjv*QM-cJ7+e%V3bxOmX4FrA}bE1DcWW;m{1D%M{Vv9(Df zN^3>Mq7IFFT~Qw$_H3+Rko#HvH&xyDx5uX4CHE$OFRMFSzHh#7Sz4KMN6%Iv(Wzal zPpz%m6W9ML?s?EE?NcxJ-3xzw-TP4d>Ku*!>V)LE%hpSLJ?J|-Joo;E7Z-Dryps>F zdNaG`>@MdmEwhgNjo$u>XJhW}jE8SHUi%1U3Tv(WHrJ~7P1mR2@9zb_S$NdyQT1)% zxpO;fe?MNwtlIi?<+HE$GjxjHzj~)xcduGQ!caS?{pX+l8R0K-t#?gQn`oDo*JUU7 zzix82XXd4p4N1f%`^7{*4CBoCbdB z2^S_XKCw1a_|7cpP?vRr!AQi7ZNfX&6o#4cK98673x(;zJ!(9X*- z_3#n3SH&k9;#tHW%s{JMUVOUj@AkNoegRi!rm Sl-B{~DF#nhKbLh*2~7a+V^4Je literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_misc_hid.png new file mode 100644 index 0000000000000000000000000000000000000000..b9a99231dd207eef974396ec2e6235e2ed83af1f GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeE3?v1%WpM*3{s5m4SB9zW$S8Tg*bJZ)OG%Jl zFhfT}`O5HsG9dq~r;B4q#NoG7FY-1U2sj7sa$obU&&!n}$_@<-jMt0H|6XHNVSlzSDQeR_tJyje+1gu6r>E{R(kP0tT@miG zUE|54nLCfx_%Sf(ZMI5gklA%jfo+q40mFfHJUk2m%PP$r8d$wQi4}C8Vqjw9QJA2c z#mp0t)6lrqvZ^`0^IwKp*stRoS8tTxV#<+qrs^_#hwY*gPLWmD9(`8&y-n+-QQGTy q`vRsF@Rx4n7Enl#Sn%_p4P*4Rly}EYyu1$d7=x#)pUXO@geCxiT60_g literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_bt_network_pan.png new file mode 100644 index 0000000000000000000000000000000000000000..989e1ab7e38617f2122b15ec5192c0db3a905356 GIT binary patch literal 1284 zcmV+f1^fDmP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00002 zVoOIv0RM-N%)bBt1c6CJK~#9!?V8_sARMdk%Ha@AyEk3NGJ$xh*T(QR`i1|s*OP1-FJTpk-95R7w_BdJ7=Dm zGv_@&-UiP1+?{#m%=i18nKRGx%z+XmN|Y#3A`7V6bue@RildU(6kt7CBLAUVq0nfO?eAd`U=Gq+fxfDTtWvo z>INTWLm~m+HhUs1c>lj^+W8iuRNJ`P+_&(BF!&f7y#>HBzH8=N*v3>M38nz(7TOkQiDVo0zHfEC6Ut$Mv0Ltin+Q~62Q;w_+)o5B-p`}=@qbt=Cv(gfXJ0#@>4 z0{U=8vzuW_782y;JVS4+fVKQ7^t|I(p1UHp`|{iuCfMo}u$tc!c^{x9vSPl>cWaxh zNst7rr|7ZIs8LcfKNP>cv3*1ujaW*uzH zYr%fWbH9Z%wRST||4i$Dtf+h;iT4+Adfws{qxCKA37-^~^7wx^Pt@6bXtX}BJ>znb zS(_jI_cW_ZMtiT;{#&t$XFUCPB6cH&#rjC>n;Trq*|u%HFJyU9tkBy3E9tBAoF62?# zf}PA*LpHOlvR*8!)K~Lt=wLRFy3iJ@_icr(Zn1}6Q;jcd4HrS*R$7!-%tApDG1dG@ zv)-Ep{a(l9aTj-=;g&d&`mAHU5E0v&o_;qIv9YGTye>FICvpzl85 z=n^H^g6$zK(OH$A{-oF7E&-1-oal&Y$Wg6Gh#pV+E&;7v6zLB*#;rI?a|yg}6Yzxgzl*>3Gr@4e(-l)d z8{dfZ4^s%18o$f9PgkNeJ1zgJ^ivp?@qL1`tte6X4~aJ^6bt{F#Gh^Xq%{2$j_SWA zK1Xx$^!L*gba2sMhv(|-*@77ze^LZ@#R@H&JnAQ<|DPG1sm074cJ82zY# uHy~sI0n#UQ`U?V%P@+VM5+zC$hJOLr>+ta>oIaEQ0000djfnyTp6aekD@^m0;gFXcLJlQ zxg^Lhm_a?Ue{Wxc8jIJ0$B*WFGD~zmf7T`@KJnXE_6Ch}r&B(2ET7B9yJrXY@nbx! z+qTR|)%*EF_d-d*r}x4=?IJZ5Ob_of+FJGAx-RuEG^nL<<+3v;AH4bX_VwK*3u~)_ z1OEOIIIpjj%k}apf5@&Uq7HVI-HS`jq9~=~Pr_8LB)aOs~_MWqyR&I0U(;s2hR)!5<zYLi+U zYI=aNVOErH{aw`p#w*7~{_;Lx-f>6c?fy$FoDI)v6ayF}8^j|2hk7ukc>jvN{luY~ zegFP#!bJ>I|JY~CNHN5|`F-0|cEXD^Cff^)JI=k{AGnUS=AQeDRxtLy4;a{GcoS}2yYI*JJ@)_)=aSq5%t9Tv7=HcucY)1D zsqwhk15Qqxt&HbA7F1umG{4eMz9%lCVM=QB5l=zpicH41LcadF?7w*{=&EY#M_-$W3H>8@WmuD)vnq|eyj7(Q%uY26MXHzt-o%( zZoS~;w1=0U|9#!QKcZZG<9g%j>*tR@cZ%pYIkx|8-2cDsXSg-(+3S1XMQr~6?j}e1 u+2CL4i>#*JV-b4!BE>-LV+^0~Kc*ue-&w6HE1C{W)C``kelF{r5}E)|w)z18 literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png b/packages/SettingsLib/res/drawable-xxxhdpi/ic_lockscreen_ime.png new file mode 100644 index 0000000000000000000000000000000000000000..2eb8a927644adecc88738143684d00b10a12efce GIT binary patch literal 412 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#*1AIbU8K$<=8htf2JPx!@p(MyJ zm_fVm%InL$TC9`*Okmr&efkv5N&VM8fBMq(TVuyImaE^_-?do=RC~hH#W5t~-rE}| zxeghKv_8C1(os5ZZSecw_De-#Im}(3%v;p2KMmdo^L{A}_ertSKhdgj=_sF}BXOV9zSo%)*s4~TJ#aNS63+{?7# zw-QUvf!F2p>(m?+L>UAbr9b@77i45A12UO38Q=M`a4cbwVVU($nQx!h2QH0`2Lc&G zoA2cN{mM)|{Ey-JA<6CaJCDWfSg6k$TfVk3d+qDB@AqCy=`lQZLwq;$vya=SzfPX( Q3G@?#r>mdKI;Vst04c4k8UO$Q literal 0 HcmV?d00001 diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f055a2c0b0edb..870afeb96aafc 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -67,4 +67,87 @@ Connected via Wi\u2011Fi assistant + + + Disconnected + + Disconnecting\u2026 + + Connecting\u2026 + + Connected + + + Media audio + + Phone audio + + File transfer + + Input device + + Internet access + + Contact sharing + + Use for contact sharing + + Internet connection sharing + + Message Access + + + Connected to media audio + + Connected to phone audio + + Connected to file transfer server + + Connected to map + + Not connected to file transfer server + + Connected to input device + + Connected to device for Internet access + + Sharing local Internet connection with device + + + Use for Internet access + + Use for map + + Use for media audio + + Use for phone audio + + Use for file transfer + + Use for input + + + Pair + + PAIR + + Cancel + + + Pairing grants access to your contacts and call history when connected. + + Couldn\'t pair with %1$s. + + + Couldn\'t pair with %1$s because of an incorrect PIN or passkey. + + Can\'t communicate with %1$s. + + Pairing rejected by %1$s. + diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java new file mode 100755 index 0000000000000..9608daad70e93 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +public final class A2dpProfile implements LocalBluetoothProfile { + private static final String TAG = "A2dpProfile"; + private static boolean V = false; + + private BluetoothA2dp mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + + static final ParcelUuid[] SINK_UUIDS = { + BluetoothUuid.AudioSink, + BluetoothUuid.AdvAudioDist, + }; + + static final String NAME = "A2DP"; + private final LocalBluetoothProfileManager mProfileManager; + + // Order of this profile in device profiles list + private static final int ORDINAL = 1; + + // These callbacks run on the main thread. + private final class A2dpServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothA2dp) proxy; + // We just bound to the service, so refresh the UI for any connected A2DP devices. + List deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "A2dpProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(A2dpProfile.this, BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + A2dpProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(), + BluetoothProfile.A2DP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public List getConnectedDevices() { + if (mService == null) return new ArrayList(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + List sinks = getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + // Downgrade priority as user is disconnecting the headset. + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON){ + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + boolean isA2dpPlaying() { + if (mService == null) return false; + List sinks = mService.getConnectedDevices(); + if (!sinks.isEmpty()) { + if (mService.isA2dpPlaying(sinks.get(0))) { + return true; + } + } + return false; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_a2dp; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_a2dp_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_a2dp_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headphones_a2dp; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.A2DP, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up A2DP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java new file mode 100644 index 0000000000000..b802f58f085f7 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + + +/** + * BluetoothCallback provides a callback interface for the settings + * UI to receive events from {@link BluetoothEventManager}. + */ +public interface BluetoothCallback { + void onBluetoothStateChanged(int bluetoothState); + void onScanningStateChanged(boolean started); + void onDeviceAdded(CachedBluetoothDevice cachedDevice); + void onDeviceDeleted(CachedBluetoothDevice cachedDevice); + void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java new file mode 100644 index 0000000000000..8dec86ac36141 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDeviceFilter.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothUuid; +import android.os.ParcelUuid; +import android.util.Log; + +/** + * BluetoothDeviceFilter contains a static method that returns a + * Filter object that returns whether or not the BluetoothDevice + * passed to it matches the specified filter type constant from + * {@link android.bluetooth.BluetoothDevicePicker}. + */ +public final class BluetoothDeviceFilter { + private static final String TAG = "BluetoothDeviceFilter"; + + /** The filter interface to external classes. */ + public interface Filter { + boolean matches(BluetoothDevice device); + } + + /** All filter singleton (referenced directly). */ + public static final Filter ALL_FILTER = new AllFilter(); + + /** Bonded devices only filter (referenced directly). */ + public static final Filter BONDED_DEVICE_FILTER = new BondedDeviceFilter(); + + /** Unbonded devices only filter (referenced directly). */ + public static final Filter UNBONDED_DEVICE_FILTER = new UnbondedDeviceFilter(); + + /** Table of singleton filter objects. */ + private static final Filter[] FILTERS = { + ALL_FILTER, // FILTER_TYPE_ALL + new AudioFilter(), // FILTER_TYPE_AUDIO + new TransferFilter(), // FILTER_TYPE_TRANSFER + new PanuFilter(), // FILTER_TYPE_PANU + new NapFilter() // FILTER_TYPE_NAP + }; + + /** Private constructor. */ + private BluetoothDeviceFilter() { + } + + /** + * Returns the singleton {@link Filter} object for the specified type, + * or {@link #ALL_FILTER} if the type value is out of range. + * + * @param filterType a constant from BluetoothDevicePicker + * @return a singleton object implementing the {@link Filter} interface. + */ + public static Filter getFilter(int filterType) { + if (filterType >= 0 && filterType < FILTERS.length) { + return FILTERS[filterType]; + } else { + Log.w(TAG, "Invalid filter type " + filterType + " for device picker"); + return ALL_FILTER; + } + } + + /** Filter that matches all devices. */ + private static final class AllFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return true; + } + } + + /** Filter that matches only bonded devices. */ + private static final class BondedDeviceFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return device.getBondState() == BluetoothDevice.BOND_BONDED; + } + } + + /** Filter that matches only unbonded devices. */ + private static final class UnbondedDeviceFilter implements Filter { + public boolean matches(BluetoothDevice device) { + return device.getBondState() != BluetoothDevice.BOND_BONDED; + } + } + + /** Parent class of filters based on UUID and/or Bluetooth class. */ + private abstract static class ClassUuidFilter implements Filter { + abstract boolean matches(ParcelUuid[] uuids, BluetoothClass btClass); + + public boolean matches(BluetoothDevice device) { + return matches(device.getUuids(), device.getBluetoothClass()); + } + } + + /** Filter that matches devices that support AUDIO profiles. */ + private static final class AudioFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS)) { + return true; + } + if (BluetoothUuid.containsAnyUuid(uuids, HeadsetProfile.UUIDS)) { + return true; + } + } else if (btClass != null) { + if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP) || + btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return true; + } + } + return false; + } + } + + /** Filter that matches devices that support Object Transfer. */ + private static final class TransferFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_OPP); + } + } + + /** Filter that matches devices that support PAN User (PANU) profile. */ + private static final class PanuFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.PANU)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_PANU); + } + } + + /** Filter that matches devices that support NAP profile. */ + private static final class NapFilter extends ClassUuidFilter { + @Override + boolean matches(ParcelUuid[] uuids, BluetoothClass btClass) { + if (uuids != null) { + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP)) { + return true; + } + } + return btClass != null + && btClass.doesClassMatch(BluetoothClass.PROFILE_NAP); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java new file mode 100644 index 0000000000000..acb7e7a3c718f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothDiscoverableTimeoutReceiver.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +/* Required to handle timeout notification when phone is suspended */ +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + + +public class BluetoothDiscoverableTimeoutReceiver extends BroadcastReceiver { + private static final String TAG = "BluetoothDiscoverableTimeoutReceiver"; + + private static final String INTENT_DISCOVERABLE_TIMEOUT = + "android.bluetooth.intent.DISCOVERABLE_TIMEOUT"; + + public static void setDiscoverableAlarm(Context context, long alarmTime) { + Log.d(TAG, "setDiscoverableAlarm(): alarmTime = " + alarmTime); + + Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT); + intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast( + context, 0, intent, 0); + AlarmManager alarmManager = + (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); + + if (pending != null) { + // Cancel any previous alarms that do the same thing. + alarmManager.cancel(pending); + Log.d(TAG, "setDiscoverableAlarm(): cancel prev alarm"); + } + pending = PendingIntent.getBroadcast( + context, 0, intent, 0); + + alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, pending); + } + + public static void cancelDiscoverableAlarm(Context context) { + Log.d(TAG, "cancelDiscoverableAlarm(): Enter"); + + Intent intent = new Intent(INTENT_DISCOVERABLE_TIMEOUT); + intent.setClass(context, BluetoothDiscoverableTimeoutReceiver.class); + PendingIntent pending = PendingIntent.getBroadcast( + context, 0, intent, PendingIntent.FLAG_NO_CREATE); + if (pending != null) { + // Cancel any previous alarms that do the same thing. + AlarmManager alarmManager = + (AlarmManager) context.getSystemService (Context.ALARM_SERVICE); + + alarmManager.cancel(pending); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + LocalBluetoothAdapter localBluetoothAdapter = LocalBluetoothAdapter.getInstance(); + + if(localBluetoothAdapter != null && + localBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { + Log.d(TAG, "Disable discoverable..."); + + localBluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE); + } else { + Log.e(TAG, "localBluetoothAdapter is NULL!!"); + } + } +}; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java new file mode 100755 index 0000000000000..7c92368e85f53 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * BluetoothEventManager receives broadcasts and callbacks from the Bluetooth + * API and dispatches the event on the UI thread to the right class in the + * Settings. + */ +public final class BluetoothEventManager { + private static final String TAG = "BluetoothEventManager"; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private LocalBluetoothProfileManager mProfileManager; + private final IntentFilter mAdapterIntentFilter, mProfileIntentFilter; + private final Map mHandlerMap; + private Context mContext; + + private final Collection mCallbacks = + new ArrayList(); + + interface Handler { + void onReceive(Context context, Intent intent, BluetoothDevice device); + } + + void addHandler(String action, Handler handler) { + mHandlerMap.put(action, handler); + mAdapterIntentFilter.addAction(action); + } + + void addProfileHandler(String action, Handler handler) { + mHandlerMap.put(action, handler); + mProfileIntentFilter.addAction(action); + } + + // Set profile manager after construction due to circular dependency + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + BluetoothEventManager(LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, Context context) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mAdapterIntentFilter = new IntentFilter(); + mProfileIntentFilter = new IntentFilter(); + mHandlerMap = new HashMap(); + mContext = context; + + // Bluetooth on/off broadcasts + addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler()); + + // Discovery broadcasts + addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true)); + addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false)); + addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler()); + addHandler(BluetoothDevice.ACTION_DISAPPEARED, new DeviceDisappearedHandler()); + addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler()); + + // Pairing broadcasts + addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler()); + addHandler(BluetoothDevice.ACTION_PAIRING_CANCEL, new PairingCancelHandler()); + + // Fine-grained state broadcasts + addHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler()); + addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler()); + + // Dock event broadcasts + addHandler(Intent.ACTION_DOCK_EVENT, new DockEventHandler()); + + mContext.registerReceiver(mBroadcastReceiver, mAdapterIntentFilter); + } + + void registerProfileIntentReceiver() { + mContext.registerReceiver(mBroadcastReceiver, mProfileIntentFilter); + } + + /** Register to start receiving callbacks for Bluetooth events. */ + public void registerCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + /** Unregister to stop receiving callbacks for Bluetooth events. */ + public void unregisterCallback(BluetoothCallback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + BluetoothDevice device = intent + .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + + Handler handler = mHandlerMap.get(action); + if (handler != null) { + handler.onReceive(context, intent, device); + } + } + }; + + private class AdapterStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + // update local profiles and get paired devices + mLocalAdapter.setBluetoothStateInt(state); + // send callback to update UI and possibly start scanning + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onBluetoothStateChanged(state); + } + } + // Inform CachedDeviceManager that the adapter state has changed + mDeviceManager.onBluetoothStateChanged(state); + } + } + + private class ScanningStateChangedHandler implements Handler { + private final boolean mStarted; + + ScanningStateChangedHandler(boolean started) { + mStarted = started; + } + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onScanningStateChanged(mStarted); + } + } + mDeviceManager.onScanningStateChanged(mStarted); + } + } + + private class DeviceFoundHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); + BluetoothClass btClass = intent.getParcelableExtra(BluetoothDevice.EXTRA_CLASS); + String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME); + // TODO Pick up UUID. They should be available for 2.1 devices. + // Skip for now, there's a bluez problem and we are not getting uuids even for 2.1. + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " + + cachedDevice); + // callback to UI to create Preference for new device + dispatchDeviceAdded(cachedDevice); + } + cachedDevice.setRssi(rssi); + cachedDevice.setBtClass(btClass); + cachedDevice.setNewName(name); + cachedDevice.setVisible(true); + } + } + + private void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceAdded(cachedDevice); + } + } + } + + private class DeviceDisappearedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "received ACTION_DISAPPEARED for an unknown device: " + device); + return; + } + if (CachedBluetoothDeviceManager.onDeviceDisappeared(cachedDevice)) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceDeleted(cachedDevice); + } + } + } + } + } + + private class NameChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onDeviceNameUpdated(device); + } + } + + private class BondStateChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); + return; + } + int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothDevice.ERROR); + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "CachedBluetoothDevice for device " + device + + " not found, calling readPairedDevices()."); + if (!readPairedDevices()) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but we have no record of that device."); + return; + } + cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.e(TAG, "Got bonding state changed for " + device + + ", but device not added in cache."); + return; + } + } + + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceBondStateChanged(cachedDevice, bondState); + } + } + cachedDevice.onBondingStateChanged(bondState); + + if (bondState == BluetoothDevice.BOND_NONE) { + int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, + BluetoothDevice.ERROR); + + showUnbondMessage(context, cachedDevice.getName(), reason); + } + } + + /** + * Called when we have reached the unbonded state. + * + * @param reason one of the error reasons from + * BluetoothDevice.UNBOND_REASON_* + */ + private void showUnbondMessage(Context context, String name, int reason) { + int errorMsg; + + switch(reason) { + case BluetoothDevice.UNBOND_REASON_AUTH_FAILED: + errorMsg = R.string.bluetooth_pairing_pin_error_message; + break; + case BluetoothDevice.UNBOND_REASON_AUTH_REJECTED: + errorMsg = R.string.bluetooth_pairing_rejected_error_message; + break; + case BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN: + errorMsg = R.string.bluetooth_pairing_device_down_error_message; + break; + case BluetoothDevice.UNBOND_REASON_DISCOVERY_IN_PROGRESS: + case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT: + case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS: + case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED: + errorMsg = R.string.bluetooth_pairing_error_message; + break; + default: + Log.w(TAG, "showUnbondMessage: Not displaying any message for reason: " + reason); + return; + } + Utils.showError(context, name, errorMsg); + } + } + + private class ClassChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onBtClassChanged(device); + } + } + + private class UuidChangedHandler implements Handler { + public void onReceive(Context context, Intent intent, + BluetoothDevice device) { + mDeviceManager.onUuidChanged(device); + } + } + + private class PairingCancelHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + if (device == null) { + Log.e(TAG, "ACTION_PAIRING_CANCEL with no EXTRA_DEVICE"); + return; + } + int errorMsg = R.string.bluetooth_pairing_error_message; + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + Utils.showError(context, cachedDevice.getName(), errorMsg); + } + } + + private class DockEventHandler implements Handler { + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + // Remove if unpair device upon undocking + int anythingButUnDocked = Intent.EXTRA_DOCK_STATE_UNDOCKED + 1; + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, anythingButUnDocked); + if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + if (device != null && device.getBondState() == BluetoothDevice.BOND_NONE) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice != null) { + cachedDevice.setVisible(false); + } + } + } + } + } + boolean readPairedDevices() { + Set bondedDevices = mLocalAdapter.getBondedDevices(); + if (bondedDevices == null) { + return false; + } + + boolean deviceAdded = false; + for (BluetoothDevice device : bondedDevices) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, device); + dispatchDeviceAdded(cachedDevice); + deviceAdded = true; + } + } + + return deviceAdded; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java new file mode 100755 index 0000000000000..ddcc49f7d3eed --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -0,0 +1,787 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.ParcelUuid; +import android.os.SystemClock; +import android.text.TextUtils; +import android.util.Log; +import android.bluetooth.BluetoothAdapter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +/** + * CachedBluetoothDevice represents a remote Bluetooth device. It contains + * attributes of the device (such as the address, name, RSSI, etc.) and + * functionality that can be performed on the device (connect, pair, disconnect, + * etc.). + */ +public final class CachedBluetoothDevice implements Comparable { + private static final String TAG = "CachedBluetoothDevice"; + private static final boolean DEBUG = Utils.V; + + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final LocalBluetoothProfileManager mProfileManager; + private final BluetoothDevice mDevice; + private String mName; + private short mRssi; + private BluetoothClass mBtClass; + private HashMap mProfileConnectionState; + + private final List mProfiles = + new ArrayList(); + + // List of profiles that were previously in mProfiles, but have been removed + private final List mRemovedProfiles = + new ArrayList(); + + // Device supports PANU but not NAP: remove PanProfile after device disconnects from NAP + private boolean mLocalNapRoleConnected; + + private boolean mVisible; + + private int mPhonebookPermissionChoice; + + private int mMessagePermissionChoice; + + private int mMessageRejectionCount; + + private final Collection mCallbacks = new ArrayList(); + + // Following constants indicate the user's choices of Phone book/message access settings + // User hasn't made any choice or settings app has wiped out the memory + public final static int ACCESS_UNKNOWN = 0; + // User has accepted the connection and let Settings app remember the decision + public final static int ACCESS_ALLOWED = 1; + // User has rejected the connection and let Settings app remember the decision + public final static int ACCESS_REJECTED = 2; + + // How many times user should reject the connection to make the choice persist. + private final static int MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST = 2; + + private final static String MESSAGE_REJECTION_COUNT_PREFS_NAME = "bluetooth_message_reject"; + + /** + * When we connect to multiple profiles, we only want to display a single + * error even if they all fail. This tracks that state. + */ + private boolean mIsConnectingErrorPossible; + + /** + * Last time a bt profile auto-connect was attempted. + * If an ACTION_UUID intent comes in within + * MAX_UUID_DELAY_FOR_AUTO_CONNECT milliseconds, we will try auto-connect + * again with the new UUIDs + */ + private long mConnectAttempted; + + // See mConnectAttempted + private static final long MAX_UUID_DELAY_FOR_AUTO_CONNECT = 5000; + + /** Auto-connect after pairing only if locally initiated. */ + private boolean mConnectAfterPairing; + + /** + * Describes the current device and profile for logging. + * + * @param profile Profile to describe + * @return Description of the device and profile + */ + private String describe(LocalBluetoothProfile profile) { + StringBuilder sb = new StringBuilder(); + sb.append("Address:").append(mDevice); + if (profile != null) { + sb.append(" Profile:").append(profile); + } + + return sb.toString(); + } + + void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) { + if (Utils.D) { + Log.d(TAG, "onProfileStateChanged: profile " + profile + + " newProfileState " + newProfileState); + } + if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) + { + if (Utils.D) Log.d(TAG, " BT Turninig Off...Profile conn state change ignored..."); + return; + } + mProfileConnectionState.put(profile, newProfileState); + if (newProfileState == BluetoothProfile.STATE_CONNECTED) { + if (profile instanceof MapProfile) { + profile.setPreferred(mDevice, true); + } else if (!mProfiles.contains(profile)) { + mRemovedProfiles.remove(profile); + mProfiles.add(profile); + if (profile instanceof PanProfile && + ((PanProfile) profile).isLocalRoleNap(mDevice)) { + // Device doesn't support NAP, so remove PanProfile on disconnect + mLocalNapRoleConnected = true; + } + } + } else if (profile instanceof MapProfile && + newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + profile.setPreferred(mDevice, false); + } else if (mLocalNapRoleConnected && profile instanceof PanProfile && + ((PanProfile) profile).isLocalRoleNap(mDevice) && + newProfileState == BluetoothProfile.STATE_DISCONNECTED) { + Log.d(TAG, "Removing PanProfile from device after NAP disconnect"); + mProfiles.remove(profile); + mRemovedProfiles.add(profile); + mLocalNapRoleConnected = false; + } + } + + CachedBluetoothDevice(Context context, + LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + mContext = context; + mLocalAdapter = adapter; + mProfileManager = profileManager; + mDevice = device; + mProfileConnectionState = new HashMap(); + fillData(); + } + + public void disconnect() { + for (LocalBluetoothProfile profile : mProfiles) { + disconnect(profile); + } + // Disconnect PBAP server in case its connected + // This is to ensure all the profiles are disconnected as some CK/Hs do not + // disconnect PBAP connection when HF connection is brought down + PbapServerProfile PbapProfile = mProfileManager.getPbapProfile(); + if (PbapProfile.getConnectionStatus(mDevice) == BluetoothProfile.STATE_CONNECTED) + { + PbapProfile.disconnect(mDevice); + } + } + + public void disconnect(LocalBluetoothProfile profile) { + if (profile.disconnect(mDevice)) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile)); + } + } + } + + public void connect(boolean connectAllProfiles) { + if (!ensurePaired()) { + return; + } + + mConnectAttempted = SystemClock.elapsedRealtime(); + connectWithoutResettingTimer(connectAllProfiles); + } + + void onBondingDockConnect() { + // Attempt to connect if UUIDs are available. Otherwise, + // we will connect when the ACTION_UUID intent arrives. + connect(false); + } + + private void connectWithoutResettingTimer(boolean connectAllProfiles) { + // Try to initialize the profiles if they were not. + if (mProfiles.isEmpty()) { + // if mProfiles is empty, then do not invoke updateProfiles. This causes a race + // condition with carkits during pairing, wherein RemoteDevice.UUIDs have been updated + // from bluetooth stack but ACTION.uuid is not sent yet. + // Eventually ACTION.uuid will be received which shall trigger the connection of the + // various profiles + // If UUIDs are not available yet, connect will be happen + // upon arrival of the ACTION_UUID intent. + Log.d(TAG, "No profiles. Maybe we will connect later"); + return; + } + + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + + int preferredProfiles = 0; + for (LocalBluetoothProfile profile : mProfiles) { + if (connectAllProfiles ? profile.isConnectable() : profile.isAutoConnectable()) { + if (profile.isPreferred(mDevice)) { + ++preferredProfiles; + connectInt(profile); + } + } + } + if (DEBUG) Log.d(TAG, "Preferred profiles = " + preferredProfiles); + + if (preferredProfiles == 0) { + connectAutoConnectableProfiles(); + } + } + + private void connectAutoConnectableProfiles() { + if (!ensurePaired()) { + return; + } + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + + for (LocalBluetoothProfile profile : mProfiles) { + if (profile.isAutoConnectable()) { + profile.setPreferred(mDevice, true); + connectInt(profile); + } + } + } + + /** + * Connect this device to the specified profile. + * + * @param profile the profile to use with the remote device + */ + public void connectProfile(LocalBluetoothProfile profile) { + mConnectAttempted = SystemClock.elapsedRealtime(); + // Reset the only-show-one-error-dialog tracking variable + mIsConnectingErrorPossible = true; + connectInt(profile); + // Refresh the UI based on profile.connect() call + refresh(); + } + + synchronized void connectInt(LocalBluetoothProfile profile) { + if (!ensurePaired()) { + return; + } + if (profile.connect(mDevice)) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile)); + } + return; + } + Log.i(TAG, "Failed to connect " + profile.toString() + " to " + mName); + } + + private boolean ensurePaired() { + if (getBondState() == BluetoothDevice.BOND_NONE) { + startPairing(); + return false; + } else { + return true; + } + } + + public boolean startPairing() { + // Pairing is unreliable while scanning, so cancel discovery + if (mLocalAdapter.isDiscovering()) { + mLocalAdapter.cancelDiscovery(); + } + + if (!mDevice.createBond()) { + return false; + } + + mConnectAfterPairing = true; // auto-connect after pairing + return true; + } + + /** + * Return true if user initiated pairing on this device. The message text is + * slightly different for local vs. remote initiated pairing dialogs. + */ + boolean isUserInitiatedPairing() { + return mConnectAfterPairing; + } + + public void unpair() { + int state = getBondState(); + + if (state == BluetoothDevice.BOND_BONDING) { + mDevice.cancelBondProcess(); + } + + if (state != BluetoothDevice.BOND_NONE) { + final BluetoothDevice dev = mDevice; + if (dev != null) { + final boolean successful = dev.removeBond(); + if (successful) { + if (Utils.D) { + Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); + } + } else if (Utils.V) { + Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + + describe(null)); + } + } + } + } + + public int getProfileConnectionState(LocalBluetoothProfile profile) { + if (mProfileConnectionState == null || + mProfileConnectionState.get(profile) == null) { + // If cache is empty make the binder call to get the state + int state = profile.getConnectionStatus(mDevice); + mProfileConnectionState.put(profile, state); + } + return mProfileConnectionState.get(profile); + } + + public void clearProfileConnectionState () + { + if (Utils.D) { + Log.d(TAG," Clearing all connection state for dev:" + mDevice.getName()); + } + for (LocalBluetoothProfile profile :getProfiles()) { + mProfileConnectionState.put(profile, BluetoothProfile.STATE_DISCONNECTED); + } + } + + // TODO: do any of these need to run async on a background thread? + private void fillData() { + fetchName(); + fetchBtClass(); + updateProfiles(); + migratePhonebookPermissionChoice(); + migrateMessagePermissionChoice(); + fetchMessageRejectionCount(); + + mVisible = false; + dispatchAttributesChanged(); + } + + public BluetoothDevice getDevice() { + return mDevice; + } + + public String getName() { + return mName; + } + + /** + * Populate name from BluetoothDevice.ACTION_FOUND intent + */ + void setNewName(String name) { + if (mName == null) { + mName = name; + if (mName == null || TextUtils.isEmpty(mName)) { + mName = mDevice.getAddress(); + } + dispatchAttributesChanged(); + } + } + + /** + * user changes the device name + */ + public void setName(String name) { + if (!mName.equals(name)) { + mName = name; + mDevice.setAlias(name); + dispatchAttributesChanged(); + } + } + + void refreshName() { + fetchName(); + dispatchAttributesChanged(); + } + + private void fetchName() { + mName = mDevice.getAliasName(); + + if (TextUtils.isEmpty(mName)) { + mName = mDevice.getAddress(); + if (DEBUG) Log.d(TAG, "Device has no name (yet), use address: " + mName); + } + } + + void refresh() { + dispatchAttributesChanged(); + } + + public boolean isVisible() { + return mVisible; + } + + public void setVisible(boolean visible) { + if (mVisible != visible) { + mVisible = visible; + dispatchAttributesChanged(); + } + } + + public int getBondState() { + return mDevice.getBondState(); + } + + void setRssi(short rssi) { + if (mRssi != rssi) { + mRssi = rssi; + dispatchAttributesChanged(); + } + } + + /** + * Checks whether we are connected to this device (any profile counts). + * + * @return Whether it is connected. + */ + public boolean isConnected() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTED) { + return true; + } + } + + return false; + } + + public boolean isConnectedProfile(LocalBluetoothProfile profile) { + int status = getProfileConnectionState(profile); + return status == BluetoothProfile.STATE_CONNECTED; + + } + + public boolean isBusy() { + for (LocalBluetoothProfile profile : mProfiles) { + int status = getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTING + || status == BluetoothProfile.STATE_DISCONNECTING) { + return true; + } + } + return getBondState() == BluetoothDevice.BOND_BONDING; + } + + /** + * Fetches a new value for the cached BT class. + */ + private void fetchBtClass() { + mBtClass = mDevice.getBluetoothClass(); + } + + private boolean updateProfiles() { + ParcelUuid[] uuids = mDevice.getUuids(); + if (uuids == null) return false; + + ParcelUuid[] localUuids = mLocalAdapter.getUuids(); + if (localUuids == null) return false; + + /** + * Now we know if the device supports PBAP, update permissions... + */ + processPhonebookAccess(); + + mProfileManager.updateProfiles(uuids, localUuids, mProfiles, mRemovedProfiles, + mLocalNapRoleConnected, mDevice); + + if (DEBUG) { + Log.e(TAG, "updating profiles for " + mDevice.getAliasName()); + BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); + + if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString()); + Log.v(TAG, "UUID:"); + for (ParcelUuid uuid : uuids) { + Log.v(TAG, " " + uuid); + } + } + return true; + } + + /** + * Refreshes the UI for the BT class, including fetching the latest value + * for the class. + */ + void refreshBtClass() { + fetchBtClass(); + dispatchAttributesChanged(); + } + + /** + * Refreshes the UI when framework alerts us of a UUID change. + */ + void onUuidChanged() { + updateProfiles(); + + if (DEBUG) { + Log.e(TAG, "onUuidChanged: Time since last connect" + + (SystemClock.elapsedRealtime() - mConnectAttempted)); + } + + /* + * If a connect was attempted earlier without any UUID, we will do the + * connect now. + */ + if (!mProfiles.isEmpty() + && (mConnectAttempted + MAX_UUID_DELAY_FOR_AUTO_CONNECT) > SystemClock + .elapsedRealtime()) { + connectWithoutResettingTimer(false); + } + dispatchAttributesChanged(); + } + + void onBondingStateChanged(int bondState) { + if (bondState == BluetoothDevice.BOND_NONE) { + mProfiles.clear(); + mConnectAfterPairing = false; // cancel auto-connect + setPhonebookPermissionChoice(ACCESS_UNKNOWN); + setMessagePermissionChoice(ACCESS_UNKNOWN); + mMessageRejectionCount = 0; + saveMessageRejectionCount(); + } + + refresh(); + + if (bondState == BluetoothDevice.BOND_BONDED) { + if (mDevice.isBluetoothDock()) { + onBondingDockConnect(); + } else if (mConnectAfterPairing) { + connect(false); + } + mConnectAfterPairing = false; + } + } + + void setBtClass(BluetoothClass btClass) { + if (btClass != null && mBtClass != btClass) { + mBtClass = btClass; + dispatchAttributesChanged(); + } + } + + public BluetoothClass getBtClass() { + return mBtClass; + } + + public List getProfiles() { + return Collections.unmodifiableList(mProfiles); + } + + public List getConnectableProfiles() { + List connectableProfiles = + new ArrayList(); + for (LocalBluetoothProfile profile : mProfiles) { + if (profile.isConnectable()) { + connectableProfiles.add(profile); + } + } + return connectableProfiles; + } + + public List getRemovedProfiles() { + return mRemovedProfiles; + } + + public void registerCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.add(callback); + } + } + + public void unregisterCallback(Callback callback) { + synchronized (mCallbacks) { + mCallbacks.remove(callback); + } + } + + private void dispatchAttributesChanged() { + synchronized (mCallbacks) { + for (Callback callback : mCallbacks) { + callback.onDeviceAttributesChanged(); + } + } + } + + @Override + public String toString() { + return mDevice.toString(); + } + + @Override + public boolean equals(Object o) { + if ((o == null) || !(o instanceof CachedBluetoothDevice)) { + return false; + } + return mDevice.equals(((CachedBluetoothDevice) o).mDevice); + } + + @Override + public int hashCode() { + return mDevice.getAddress().hashCode(); + } + + // This comparison uses non-final fields so the sort order may change + // when device attributes change (such as bonding state). Settings + // will completely refresh the device list when this happens. + public int compareTo(CachedBluetoothDevice another) { + // Connected above not connected + int comparison = (another.isConnected() ? 1 : 0) - (isConnected() ? 1 : 0); + if (comparison != 0) return comparison; + + // Paired above not paired + comparison = (another.getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0) - + (getBondState() == BluetoothDevice.BOND_BONDED ? 1 : 0); + if (comparison != 0) return comparison; + + // Visible above not visible + comparison = (another.mVisible ? 1 : 0) - (mVisible ? 1 : 0); + if (comparison != 0) return comparison; + + // Stronger signal above weaker signal + comparison = another.mRssi - mRssi; + if (comparison != 0) return comparison; + + // Fallback on name + return mName.compareTo(another.mName); + } + + public interface Callback { + void onDeviceAttributesChanged(); + } + + public int getPhonebookPermissionChoice() { + int permission = mDevice.getPhonebookAccessPermission(); + if (permission == BluetoothDevice.ACCESS_ALLOWED) { + return ACCESS_ALLOWED; + } else if (permission == BluetoothDevice.ACCESS_REJECTED) { + return ACCESS_REJECTED; + } + return ACCESS_UNKNOWN; + } + + public void setPhonebookPermissionChoice(int permissionChoice) { + int permission = BluetoothDevice.ACCESS_UNKNOWN; + if (permissionChoice == ACCESS_ALLOWED) { + permission = BluetoothDevice.ACCESS_ALLOWED; + } else if (permissionChoice == ACCESS_REJECTED) { + permission = BluetoothDevice.ACCESS_REJECTED; + } + mDevice.setPhonebookAccessPermission(permission); + } + + // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth + // app's shared preferences). + private void migratePhonebookPermissionChoice() { + SharedPreferences preferences = mContext.getSharedPreferences( + "bluetooth_phonebook_permission", Context.MODE_PRIVATE); + if (!preferences.contains(mDevice.getAddress())) { + return; + } + + if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { + int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); + if (oldPermission == ACCESS_ALLOWED) { + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + } else if (oldPermission == ACCESS_REJECTED) { + mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); + } + } + + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(mDevice.getAddress()); + editor.commit(); + } + + public int getMessagePermissionChoice() { + int permission = mDevice.getMessageAccessPermission(); + if (permission == BluetoothDevice.ACCESS_ALLOWED) { + return ACCESS_ALLOWED; + } else if (permission == BluetoothDevice.ACCESS_REJECTED) { + return ACCESS_REJECTED; + } + return ACCESS_UNKNOWN; + } + + public void setMessagePermissionChoice(int permissionChoice) { + int permission = BluetoothDevice.ACCESS_UNKNOWN; + if (permissionChoice == ACCESS_ALLOWED) { + permission = BluetoothDevice.ACCESS_ALLOWED; + } else if (permissionChoice == ACCESS_REJECTED) { + permission = BluetoothDevice.ACCESS_REJECTED; + } + mDevice.setMessageAccessPermission(permission); + } + + // Migrates data from old data store (in Settings app's shared preferences) to new (in Bluetooth + // app's shared preferences). + private void migrateMessagePermissionChoice() { + SharedPreferences preferences = mContext.getSharedPreferences( + "bluetooth_message_permission", Context.MODE_PRIVATE); + if (!preferences.contains(mDevice.getAddress())) { + return; + } + + if (mDevice.getMessageAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { + int oldPermission = preferences.getInt(mDevice.getAddress(), ACCESS_UNKNOWN); + if (oldPermission == ACCESS_ALLOWED) { + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED); + } else if (oldPermission == ACCESS_REJECTED) { + mDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_REJECTED); + } + } + + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(mDevice.getAddress()); + editor.commit(); + } + + /** + * @return Whether this rejection should persist. + */ + public boolean checkAndIncreaseMessageRejectionCount() { + if (mMessageRejectionCount < MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST) { + mMessageRejectionCount++; + saveMessageRejectionCount(); + } + return mMessageRejectionCount >= MESSAGE_REJECTION_COUNT_LIMIT_TO_PERSIST; + } + + private void fetchMessageRejectionCount() { + SharedPreferences preference = mContext.getSharedPreferences( + MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE); + mMessageRejectionCount = preference.getInt(mDevice.getAddress(), 0); + } + + private void saveMessageRejectionCount() { + SharedPreferences.Editor editor = mContext.getSharedPreferences( + MESSAGE_REJECTION_COUNT_PREFS_NAME, Context.MODE_PRIVATE).edit(); + if (mMessageRejectionCount == 0) { + editor.remove(mDevice.getAddress()); + } else { + editor.putInt(mDevice.getAddress(), mMessageRejectionCount); + } + editor.commit(); + } + + private void processPhonebookAccess() { + if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) return; + + ParcelUuid[] uuids = mDevice.getUuids(); + if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) { + // The pairing dialog now warns of phone-book access for paired devices. + // No separate prompt is displayed after pairing. + setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java new file mode 100755 index 0000000000000..65db95f893dc0 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices. + */ +public final class CachedBluetoothDeviceManager { + private static final String TAG = "CachedBluetoothDeviceManager"; + private static final boolean DEBUG = Utils.D; + + private Context mContext; + private final List mCachedDevices = + new ArrayList(); + + CachedBluetoothDeviceManager(Context context) { + mContext = context; + } + + public synchronized Collection getCachedDevicesCopy() { + return new ArrayList(mCachedDevices); + } + + public static boolean onDeviceDisappeared(CachedBluetoothDevice cachedDevice) { + cachedDevice.setVisible(false); + return cachedDevice.getBondState() == BluetoothDevice.BOND_NONE; + } + + public void onDeviceNameUpdated(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.refreshName(); + } + } + + /** + * Search for existing {@link CachedBluetoothDevice} or return null + * if this device isn't in the cache. Use {@link #addDevice} + * to create and return a new {@link CachedBluetoothDevice} for + * a newly discovered {@link BluetoothDevice}. + * + * @param device the address of the Bluetooth device + * @return the cached device object for this device, or null if it has + * not been previously seen + */ + public CachedBluetoothDevice findDevice(BluetoothDevice device) { + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { + if (cachedDevice.getDevice().equals(device)) { + return cachedDevice; + } + } + return null; + } + + /** + * Create and return a new {@link CachedBluetoothDevice}. This assumes + * that {@link #findDevice} has already been called and returned null. + * @param device the address of the new Bluetooth device + * @return the newly created CachedBluetoothDevice object + */ + public CachedBluetoothDevice addDevice(LocalBluetoothAdapter adapter, + LocalBluetoothProfileManager profileManager, + BluetoothDevice device) { + CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter, + profileManager, device); + synchronized (mCachedDevices) { + mCachedDevices.add(newDevice); + } + return newDevice; + } + + /** + * Attempts to get the name of a remote device, otherwise returns the address. + * + * @param device The remote device. + * @return The name, or if unavailable, the address. + */ + public String getName(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + return cachedDevice.getName(); + } + + String name = device.getAliasName(); + if (name != null) { + return name; + } + + return device.getAddress(); + } + + public synchronized void clearNonBondedDevices() { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + mCachedDevices.remove(i); + } + } + } + + public synchronized void onScanningStateChanged(boolean started) { + if (!started) return; + + // If starting a new scan, clear old visibility + // Iterate in reverse order since devices may be removed. + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + cachedDevice.setVisible(false); + } + } + + public synchronized void onBtClassChanged(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.refreshBtClass(); + } + } + + public synchronized void onUuidChanged(BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = findDevice(device); + if (cachedDevice != null) { + cachedDevice.onUuidChanged(); + } + } + + public synchronized void onBluetoothStateChanged(int bluetoothState) { + // When Bluetooth is turning off, we need to clear the non-bonded devices + // Otherwise, they end up showing up on the next BT enable + if (bluetoothState == BluetoothAdapter.STATE_TURNING_OFF) { + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + cachedDevice.setVisible(false); + mCachedDevices.remove(i); + } else { + // For bonded devices, we need to clear the connection status so that + // when BT is enabled next time, device connection status shall be retrieved + // by making a binder call. + cachedDevice.clearProfileConnectionState(); + } + } + } + } + private void log(String msg) { + if (DEBUG) { + Log.d(TAG, msg); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java new file mode 100755 index 0000000000000..5529866c83a8f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * HeadsetProfile handles Bluetooth HFP and Headset profiles. + */ +public final class HeadsetProfile implements LocalBluetoothProfile { + private static final String TAG = "HeadsetProfile"; + private static boolean V = true; + + private BluetoothHeadset mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final ParcelUuid[] UUIDS = { + BluetoothUuid.HSP, + BluetoothUuid.Handsfree, + }; + + static final String NAME = "HEADSET"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 0; + + // These callbacks run on the main thread. + private final class HeadsetServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothHeadset) proxy; + // We just bound to the service, so refresh the UI for any connected HFP devices. + List deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HeadsetProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(HeadsetProfile.this, + BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mProfileManager.callServiceDisconnectedListeners(); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + HeadsetProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new HeadsetServiceListener(), + BluetoothProfile.HEADSET); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + List sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + Log.d(TAG,"Not disconnecting device = " + sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + List deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty()) { + for (BluetoothDevice dev : deviceList) { + if (dev.equals(device)) { + if (V) Log.d(TAG,"Downgrade priority as user" + + "is disconnecting the headset"); + // Downgrade priority as user is disconnecting the headset. + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } + } + } + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; + List deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty()){ + for (BluetoothDevice dev : deviceList) { + if (dev.equals(device)) { + return mService.getConnectionState(device); + } + } + } + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public List getConnectedDevices() { + if (mService == null) return new ArrayList(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_headset; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_headset_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_headset_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_headset_hfp; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.HEADSET, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up HID proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java new file mode 100755 index 0000000000000..a9e8db599177a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.List; + +/** + * HidProfile handles Bluetooth HID profile. + */ +public final class HidProfile implements LocalBluetoothProfile { + private static final String TAG = "HidProfile"; + private static boolean V = true; + + private BluetoothInputDevice mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final String NAME = "HID"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 3; + + // These callbacks run on the main thread. + private final class InputDeviceServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothInputDevice) proxy; + // We just bound to the service, so refresh the UI for any connected HID devices. + List deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "HidProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(HidProfile.this, BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + HidProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + adapter.getProfileProxy(context, new InputDeviceServiceListener(), + BluetoothProfile.INPUT_DEVICE); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + List deviceList = mService.getConnectedDevices(); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + // TODO: distinguish between keyboard and mouse? + return R.string.bluetooth_profile_hid; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_hid_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_hid_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + if (btClass == null) { + return R.drawable.ic_lockscreen_ime; + } + return getHidClassDrawable(btClass); + } + + public static int getHidClassDrawable(BluetoothClass btClass) { + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.PERIPHERAL_KEYBOARD: + case BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING: + return R.drawable.ic_lockscreen_ime; + case BluetoothClass.Device.PERIPHERAL_POINTING: + return R.drawable.ic_bt_pointing_hid; + default: + return R.drawable.ic_bt_misc_hid; + } + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.INPUT_DEVICE, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up HID proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java new file mode 100644 index 0000000000000..0c1adec33d545 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import java.util.Set; + +/** + * LocalBluetoothAdapter provides an interface between the Settings app + * and the functionality of the local {@link BluetoothAdapter}, specifically + * those related to state transitions of the adapter itself. + * + *

Connection and bonding state changes affecting specific devices + * are handled by {@link CachedBluetoothDeviceManager}, + * {@link BluetoothEventManager}, and {@link LocalBluetoothProfileManager}. + */ +public final class LocalBluetoothAdapter { + private static final String TAG = "LocalBluetoothAdapter"; + + /** This class does not allow direct access to the BluetoothAdapter. */ + private final BluetoothAdapter mAdapter; + + private LocalBluetoothProfileManager mProfileManager; + + private static LocalBluetoothAdapter sInstance; + + private int mState = BluetoothAdapter.ERROR; + + private static final int SCAN_EXPIRATION_MS = 5 * 60 * 1000; // 5 mins + + private long mLastScan; + + private LocalBluetoothAdapter(BluetoothAdapter adapter) { + mAdapter = adapter; + } + + void setProfileManager(LocalBluetoothProfileManager manager) { + mProfileManager = manager; + } + + /** + * Get the singleton instance of the LocalBluetoothAdapter. If this device + * doesn't support Bluetooth, then null will be returned. Callers must be + * prepared to handle a null return value. + * @return the LocalBluetoothAdapter object, or null if not supported + */ + static synchronized LocalBluetoothAdapter getInstance() { + if (sInstance == null) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + sInstance = new LocalBluetoothAdapter(adapter); + } + } + + return sInstance; + } + + // Pass-through BluetoothAdapter methods that we can intercept if necessary + + public void cancelDiscovery() { + mAdapter.cancelDiscovery(); + } + + public boolean enable() { + return mAdapter.enable(); + } + + public boolean disable() { + return mAdapter.disable(); + } + + void getProfileProxy(Context context, + BluetoothProfile.ServiceListener listener, int profile) { + mAdapter.getProfileProxy(context, listener, profile); + } + + public Set getBondedDevices() { + return mAdapter.getBondedDevices(); + } + + public String getName() { + return mAdapter.getName(); + } + + public int getScanMode() { + return mAdapter.getScanMode(); + } + + public int getState() { + return mAdapter.getState(); + } + + public ParcelUuid[] getUuids() { + return mAdapter.getUuids(); + } + + public boolean isDiscovering() { + return mAdapter.isDiscovering(); + } + + public boolean isEnabled() { + return mAdapter.isEnabled(); + } + + public void setDiscoverableTimeout(int timeout) { + mAdapter.setDiscoverableTimeout(timeout); + } + + public void setName(String name) { + mAdapter.setName(name); + } + + public void setScanMode(int mode) { + mAdapter.setScanMode(mode); + } + + public boolean setScanMode(int mode, int duration) { + return mAdapter.setScanMode(mode, duration); + } + + public void startScanning(boolean force) { + // Only start if we're not already scanning + if (!mAdapter.isDiscovering()) { + if (!force) { + // Don't scan more than frequently than SCAN_EXPIRATION_MS, + // unless forced + if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) { + return; + } + + // If we are playing music, don't scan unless forced. + A2dpProfile a2dp = mProfileManager.getA2dpProfile(); + if (a2dp != null && a2dp.isA2dpPlaying()) { + return; + } + } + + if (mAdapter.startDiscovery()) { + mLastScan = System.currentTimeMillis(); + } + } + } + + public void stopScanning() { + if (mAdapter.isDiscovering()) { + mAdapter.cancelDiscovery(); + } + } + + public synchronized int getBluetoothState() { + // Always sync state, in case it changed while paused + syncBluetoothState(); + return mState; + } + + synchronized void setBluetoothStateInt(int state) { + mState = state; + + if (state == BluetoothAdapter.STATE_ON) { + // if mProfileManager hasn't been constructed yet, it will + // get the adapter UUIDs in its constructor when it is. + if (mProfileManager != null) { + mProfileManager.setBluetoothStateOn(); + } + } + } + + // Returns true if the state changed; false otherwise. + boolean syncBluetoothState() { + int currentState = mAdapter.getState(); + if (currentState != mState) { + setBluetoothStateInt(mAdapter.getState()); + return true; + } + return false; + } + + public void setBluetoothEnabled(boolean enabled) { + boolean success = enabled + ? mAdapter.enable() + : mAdapter.disable(); + + if (success) { + setBluetoothStateInt(enabled + ? BluetoothAdapter.STATE_TURNING_ON + : BluetoothAdapter.STATE_TURNING_OFF); + } else { + if (Utils.V) { + Log.v(TAG, "setBluetoothEnabled call, manager didn't return " + + "success for enabled: " + enabled); + } + + syncBluetoothState(); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java new file mode 100644 index 0000000000000..4adc62e77ee65 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothManager.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.content.Context; +import android.util.Log; + +/** + * LocalBluetoothManager provides a simplified interface on top of a subset of + * the Bluetooth API. Note that {@link #getInstance} will return null + * if there is no Bluetooth adapter on this device, and callers must be + * prepared to handle this case. + */ +public final class LocalBluetoothManager { + private static final String TAG = "LocalBluetoothManager"; + + /** Singleton instance. */ + private static LocalBluetoothManager sInstance; + + private final Context mContext; + + /** If a BT-related activity is in the foreground, this will be it. */ + private Context mForegroundActivity; + + private final LocalBluetoothAdapter mLocalAdapter; + + private final CachedBluetoothDeviceManager mCachedDeviceManager; + + /** The Bluetooth profile manager. */ + private final LocalBluetoothProfileManager mProfileManager; + + /** The broadcast receiver event manager. */ + private final BluetoothEventManager mEventManager; + + public static synchronized LocalBluetoothManager getInstance(Context context, + BluetoothManagerCallback onInitCallback) { + if (sInstance == null) { + LocalBluetoothAdapter adapter = LocalBluetoothAdapter.getInstance(); + if (adapter == null) { + return null; + } + // This will be around as long as this process is + Context appContext = context.getApplicationContext(); + sInstance = new LocalBluetoothManager(adapter, appContext); + if (onInitCallback != null) { + onInitCallback.onBluetoothManagerInitialized(appContext, sInstance); + } + } + + return sInstance; + } + + private LocalBluetoothManager(LocalBluetoothAdapter adapter, Context context) { + mContext = context; + mLocalAdapter = adapter; + + mCachedDeviceManager = new CachedBluetoothDeviceManager(context); + mEventManager = new BluetoothEventManager(mLocalAdapter, + mCachedDeviceManager, context); + mProfileManager = new LocalBluetoothProfileManager(context, + mLocalAdapter, mCachedDeviceManager, mEventManager); + } + + public LocalBluetoothAdapter getBluetoothAdapter() { + return mLocalAdapter; + } + + public Context getContext() { + return mContext; + } + + public Context getForegroundActivity() { + return mForegroundActivity; + } + + public boolean isForegroundActivity() { + return mForegroundActivity != null; + } + + public synchronized void setForegroundActivity(Context context) { + if (context != null) { + Log.d(TAG, "setting foreground activity to non-null context"); + mForegroundActivity = context; + } else { + if (mForegroundActivity != null) { + Log.d(TAG, "setting foreground activity to null"); + mForegroundActivity = null; + } + } + } + + public CachedBluetoothDeviceManager getCachedDeviceManager() { + return mCachedDeviceManager; + } + + public BluetoothEventManager getEventManager() { + return mEventManager; + } + + public LocalBluetoothProfileManager getProfileManager() { + return mProfileManager; + } + + public interface BluetoothManagerCallback { + void onBluetoothManagerInitialized(Context appContext, + LocalBluetoothManager bluetoothManager); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java new file mode 100755 index 0000000000000..abcb989802e9c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; + +/** + * LocalBluetoothProfile is an interface defining the basic + * functionality related to a Bluetooth profile. + */ +public interface LocalBluetoothProfile { + + /** + * Returns true if the user can initiate a connection, false otherwise. + */ + boolean isConnectable(); + + /** + * Returns true if the user can enable auto connection for this profile. + */ + boolean isAutoConnectable(); + + boolean connect(BluetoothDevice device); + + boolean disconnect(BluetoothDevice device); + + int getConnectionStatus(BluetoothDevice device); + + boolean isPreferred(BluetoothDevice device); + + int getPreferred(BluetoothDevice device); + + void setPreferred(BluetoothDevice device, boolean preferred); + + boolean isProfileReady(); + + /** Display order for device profile settings. */ + int getOrdinal(); + + /** + * Returns the string resource ID for the localized name for this profile. + * @param device the Bluetooth device (to distinguish between PAN roles) + */ + int getNameResource(BluetoothDevice device); + + /** + * Returns the string resource ID for the summary text for this profile + * for the specified device, e.g. "Use for media audio" or + * "Connected to media audio". + * @param device the device to query for profile connection status + * @return a string resource ID for the profile summary text + */ + int getSummaryResourceForDevice(BluetoothDevice device); + + int getDrawableResource(BluetoothClass btClass); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java new file mode 100644 index 0000000000000..b0a7b27bfae23 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothMap; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothPbap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.content.Intent; +import android.os.ParcelUuid; +import android.util.Log; +import android.os.Handler; +import android.os.Message; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.List; + +/** + * LocalBluetoothProfileManager provides access to the LocalBluetoothProfile + * objects for the available Bluetooth profiles. + */ +public final class LocalBluetoothProfileManager { + private static final String TAG = "LocalBluetoothProfileManager"; + private static final boolean DEBUG = Utils.D; + /** Singleton instance. */ + private static LocalBluetoothProfileManager sInstance; + + /** + * An interface for notifying BluetoothHeadset IPC clients when they have + * been connected to the BluetoothHeadset service. + * Only used by com.android.settings.bluetooth.DockService. + */ + public interface ServiceListener { + /** + * Called to notify the client when this proxy object has been + * connected to the BluetoothHeadset service. Clients must wait for + * this callback before making IPC calls on the BluetoothHeadset + * service. + */ + void onServiceConnected(); + + /** + * Called to notify the client that this proxy object has been + * disconnected from the BluetoothHeadset service. Clients must not + * make IPC calls on the BluetoothHeadset service after this callback. + * This callback will currently only occur if the application hosting + * the BluetoothHeadset service, but may be called more often in future. + */ + void onServiceDisconnected(); + } + + private final Context mContext; + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final BluetoothEventManager mEventManager; + + private A2dpProfile mA2dpProfile; + private HeadsetProfile mHeadsetProfile; + private MapProfile mMapProfile; + private final HidProfile mHidProfile; + private OppProfile mOppProfile; + private final PanProfile mPanProfile; + private final PbapServerProfile mPbapProfile; + + /** + * Mapping from profile name, e.g. "HEADSET" to profile object. + */ + private final Map + mProfileNameMap = new HashMap(); + + LocalBluetoothProfileManager(Context context, + LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + BluetoothEventManager eventManager) { + mContext = context; + + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mEventManager = eventManager; + // pass this reference to adapter and event manager (circular dependency) + mLocalAdapter.setProfileManager(this); + mEventManager.setProfileManager(this); + + ParcelUuid[] uuids = adapter.getUuids(); + + // uuids may be null if Bluetooth is turned off + if (uuids != null) { + updateLocalProfiles(uuids); + } + + // Always add HID and PAN profiles + mHidProfile = new HidProfile(context, mLocalAdapter, mDeviceManager, this); + addProfile(mHidProfile, HidProfile.NAME, + BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); + + mPanProfile = new PanProfile(context); + addPanProfile(mPanProfile, PanProfile.NAME, + BluetoothPan.ACTION_CONNECTION_STATE_CHANGED); + + if(DEBUG) Log.d(TAG, "Adding local MAP profile"); + mMapProfile = new MapProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mMapProfile, MapProfile.NAME, + BluetoothMap.ACTION_CONNECTION_STATE_CHANGED); + + //Create PBAP server profile, but do not add it to list of profiles + // as we do not need to monitor the profile as part of profile list + mPbapProfile = new PbapServerProfile(context); + + if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete"); + } + + /** + * Initialize or update the local profile objects. If a UUID was previously + * present but has been removed, we print a warning but don't remove the + * profile object as it might be referenced elsewhere, or the UUID might + * come back and we don't want multiple copies of the profile objects. + * @param uuids + */ + void updateLocalProfiles(ParcelUuid[] uuids) { + // A2DP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.AudioSource)) { + if (mA2dpProfile == null) { + if(DEBUG) Log.d(TAG, "Adding local A2DP profile"); + mA2dpProfile = new A2dpProfile(mContext, mLocalAdapter, mDeviceManager, this); + addProfile(mA2dpProfile, A2dpProfile.NAME, + BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + } + } else if (mA2dpProfile != null) { + Log.w(TAG, "Warning: A2DP profile was previously added but the UUID is now missing."); + } + + // Headset / Handsfree + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree_AG) || + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP_AG)) { + if (mHeadsetProfile == null) { + if (DEBUG) Log.d(TAG, "Adding local HEADSET profile"); + mHeadsetProfile = new HeadsetProfile(mContext, mLocalAdapter, + mDeviceManager, this); + addProfile(mHeadsetProfile, HeadsetProfile.NAME, + BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + } + } else if (mHeadsetProfile != null) { + Log.w(TAG, "Warning: HEADSET profile was previously added but the UUID is now missing."); + } + + // OPP + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush)) { + if (mOppProfile == null) { + if(DEBUG) Log.d(TAG, "Adding local OPP profile"); + mOppProfile = new OppProfile(); + // Note: no event handler for OPP, only name map. + mProfileNameMap.put(OppProfile.NAME, mOppProfile); + } + } else if (mOppProfile != null) { + Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing."); + } + mEventManager.registerProfileIntentReceiver(); + + // There is no local SDP record for HID and Settings app doesn't control PBAP + } + + private final Collection mServiceListeners = + new ArrayList(); + + private void addProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addProfileHandler(stateChangedAction, new StateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } + + private void addPanProfile(LocalBluetoothProfile profile, + String profileName, String stateChangedAction) { + mEventManager.addProfileHandler(stateChangedAction, + new PanStateChangedHandler(profile)); + mProfileNameMap.put(profileName, profile); + } + + public LocalBluetoothProfile getProfileByName(String name) { + return mProfileNameMap.get(name); + } + + // Called from LocalBluetoothAdapter when state changes to ON + void setBluetoothStateOn() { + ParcelUuid[] uuids = mLocalAdapter.getUuids(); + if (uuids != null) { + updateLocalProfiles(uuids); + } + mEventManager.readPairedDevices(); + } + + /** + * Generic handler for connection state change events for the specified profile. + */ + private class StateChangedHandler implements BluetoothEventManager.Handler { + final LocalBluetoothProfile mProfile; + + StateChangedHandler(LocalBluetoothProfile profile) { + mProfile = profile; + } + + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device); + if (cachedDevice == null) { + Log.w(TAG, "StateChangedHandler found new device: " + device); + cachedDevice = mDeviceManager.addDevice(mLocalAdapter, + LocalBluetoothProfileManager.this, device); + } + int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, 0); + if (newState == BluetoothProfile.STATE_DISCONNECTED && + oldState == BluetoothProfile.STATE_CONNECTING) { + Log.i(TAG, "Failed to connect " + mProfile + " device"); + } + + cachedDevice.onProfileStateChanged(mProfile, newState); + cachedDevice.refresh(); + } + } + + /** State change handler for NAP and PANU profiles. */ + private class PanStateChangedHandler extends StateChangedHandler { + + PanStateChangedHandler(LocalBluetoothProfile profile) { + super(profile); + } + + @Override + public void onReceive(Context context, Intent intent, BluetoothDevice device) { + PanProfile panProfile = (PanProfile) mProfile; + int role = intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, 0); + panProfile.setLocalRole(device, role); + super.onReceive(context, intent, device); + } + } + + // called from DockService + public void addServiceListener(ServiceListener l) { + mServiceListeners.add(l); + } + + // called from DockService + public void removeServiceListener(ServiceListener l) { + mServiceListeners.remove(l); + } + + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceConnectedListeners() { + for (ServiceListener l : mServiceListeners) { + l.onServiceConnected(); + } + } + + // not synchronized: use only from UI thread! (TODO: verify) + void callServiceDisconnectedListeners() { + for (ServiceListener listener : mServiceListeners) { + listener.onServiceDisconnected(); + } + } + + // This is called by DockService, so check Headset and A2DP. + public synchronized boolean isManagerReady() { + // Getting just the headset profile is fine for now. Will need to deal with A2DP + // and others if they aren't always in a ready state. + LocalBluetoothProfile profile = mHeadsetProfile; + if (profile != null) { + return profile.isProfileReady(); + } + profile = mA2dpProfile; + if (profile != null) { + return profile.isProfileReady(); + } + return false; + } + + public A2dpProfile getA2dpProfile() { + return mA2dpProfile; + } + + public HeadsetProfile getHeadsetProfile() { + return mHeadsetProfile; + } + + public PbapServerProfile getPbapProfile(){ + return mPbapProfile; + } + + public MapProfile getMapProfile(){ + return mMapProfile; + } + + /** + * Fill in a list of LocalBluetoothProfile objects that are supported by + * the local device and the remote device. + * + * @param uuids of the remote device + * @param localUuids UUIDs of the local device + * @param profiles The list of profiles to fill + * @param removedProfiles list of profiles that were removed + */ + synchronized void updateProfiles(ParcelUuid[] uuids, ParcelUuid[] localUuids, + Collection profiles, + Collection removedProfiles, + boolean isPanNapConnected, BluetoothDevice device) { + // Copy previous profile list into removedProfiles + removedProfiles.clear(); + removedProfiles.addAll(profiles); + profiles.clear(); + + if (uuids == null) { + return; + } + + if (mHeadsetProfile != null) { + if ((BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.HSP_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.HSP)) || + (BluetoothUuid.isUuidPresent(localUuids, BluetoothUuid.Handsfree_AG) && + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Handsfree))) { + profiles.add(mHeadsetProfile); + removedProfiles.remove(mHeadsetProfile); + } + } + + if (BluetoothUuid.containsAnyUuid(uuids, A2dpProfile.SINK_UUIDS) && + mA2dpProfile != null) { + profiles.add(mA2dpProfile); + removedProfiles.remove(mA2dpProfile); + } + + if (BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.ObexObjectPush) && + mOppProfile != null) { + profiles.add(mOppProfile); + removedProfiles.remove(mOppProfile); + } + + if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hid) || + BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.Hogp)) && + mHidProfile != null) { + profiles.add(mHidProfile); + removedProfiles.remove(mHidProfile); + } + + if(isPanNapConnected) + if(DEBUG) Log.d(TAG, "Valid PAN-NAP connection exists."); + if ((BluetoothUuid.isUuidPresent(uuids, BluetoothUuid.NAP) && + mPanProfile != null) || isPanNapConnected) { + profiles.add(mPanProfile); + removedProfiles.remove(mPanProfile); + } + + if ((mMapProfile != null) && + (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) { + profiles.add(mMapProfile); + removedProfiles.remove(mMapProfile); + mMapProfile.setPreferred(device, true); + } + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java new file mode 100644 index 0000000000000..e6a152f7205b1 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothMap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * MapProfile handles Bluetooth MAP profile. + */ +public final class MapProfile implements LocalBluetoothProfile { + private static final String TAG = "MapProfile"; + private static boolean V = true; + + private BluetoothMap mService; + private boolean mIsProfileReady; + + private final LocalBluetoothAdapter mLocalAdapter; + private final CachedBluetoothDeviceManager mDeviceManager; + private final LocalBluetoothProfileManager mProfileManager; + + static final ParcelUuid[] UUIDS = { + BluetoothUuid.MAP, + BluetoothUuid.MNS, + BluetoothUuid.MAS, + }; + + static final String NAME = "MAP"; + + // Order of this profile in device profiles list + + // These callbacks run on the main thread. + private final class MapServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothMap) proxy; + // We just bound to the service, so refresh the UI for any connected MAP devices. + List deviceList = mService.getConnectedDevices(); + while (!deviceList.isEmpty()) { + BluetoothDevice nextDevice = deviceList.remove(0); + CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); + // we may add a new device here, but generally this should not happen + if (device == null) { + Log.w(TAG, "MapProfile found new device: " + nextDevice); + device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); + } + device.onProfileStateChanged(MapProfile.this, + BluetoothProfile.STATE_CONNECTED); + device.refresh(); + } + + mProfileManager.callServiceConnectedListeners(); + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mProfileManager.callServiceDisconnectedListeners(); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + if(V) Log.d(TAG,"isProfileReady(): "+ mIsProfileReady); + return mIsProfileReady; + } + + MapProfile(Context context, LocalBluetoothAdapter adapter, + CachedBluetoothDeviceManager deviceManager, + LocalBluetoothProfileManager profileManager) { + mLocalAdapter = adapter; + mDeviceManager = deviceManager; + mProfileManager = profileManager; + mLocalAdapter.getProfileProxy(context, new MapServiceListener(), + BluetoothProfile.MAP); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return true; + } + + public boolean connect(BluetoothDevice device) { + if(V)Log.d(TAG,"connect() - should not get called"); + return false; // MAP never connects out + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + List deviceList = mService.getConnectedDevices(); + if (!deviceList.isEmpty() && deviceList.get(0).equals(device)) { + if (mService.getPriority(device) > BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + return mService.disconnect(device); + } else { + return false; + } + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.STATE_DISCONNECTED; + List deviceList = mService.getConnectedDevices(); + if(V) Log.d(TAG,"getConnectionStatus: status is: "+ mService.getConnectionState(device)); + + return !deviceList.isEmpty() && deviceList.get(0).equals(device) + ? mService.getConnectionState(device) + : BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + if (mService == null) return false; + return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; + } + + public int getPreferred(BluetoothDevice device) { + if (mService == null) return BluetoothProfile.PRIORITY_OFF; + return mService.getPriority(device); + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + if (mService == null) return; + if (preferred) { + if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) { + mService.setPriority(device, BluetoothProfile.PRIORITY_ON); + } + } else { + mService.setPriority(device, BluetoothProfile.PRIORITY_OFF); + } + } + + public List getConnectedDevices() { + if (mService == null) return new ArrayList(0); + return mService.getDevicesMatchingConnectionStates( + new int[] {BluetoothProfile.STATE_CONNECTED, + BluetoothProfile.STATE_CONNECTING, + BluetoothProfile.STATE_DISCONNECTING}); + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return BluetoothProfile.MAP; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_map; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_map_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_map_profile_summary_connected; + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_cellphone; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.MAP, + mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up MAP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java new file mode 100755 index 0000000000000..31e675c2355bd --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import com.android.settingslib.R; + +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; + +/** + * OppProfile handles Bluetooth OPP. + */ +final class OppProfile implements LocalBluetoothProfile { + + static final String NAME = "OPP"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 2; + + public boolean isConnectable() { + return false; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + return false; + } + + public boolean disconnect(BluetoothDevice device) { + return false; + } + + public int getConnectionStatus(BluetoothDevice device) { + return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return BluetoothProfile.PRIORITY_OFF; // Settings app doesn't handle OPP + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + } + + public boolean isProfileReady() { + return true; + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_opp; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return 0; // OPP profile not displayed in UI + } + + public int getDrawableResource(BluetoothClass btClass) { + return 0; // no icon for OPP + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java new file mode 100755 index 0000000000000..3af89e6d25031 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; +import android.content.Context; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.HashMap; +import java.util.List; + +/** + * PanProfile handles Bluetooth PAN profile (NAP and PANU). + */ +final class PanProfile implements LocalBluetoothProfile { + private static final String TAG = "PanProfile"; + private static boolean V = true; + + private BluetoothPan mService; + private boolean mIsProfileReady; + + // Tethering direction for each device + private final HashMap mDeviceRoleMap = + new HashMap(); + + static final String NAME = "PAN"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 4; + + // These callbacks run on the main thread. + private final class PanServiceListener + implements BluetoothProfile.ServiceListener { + + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothPan) proxy; + mIsProfileReady=true; + } + + public void onServiceDisconnected(int profile) { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + PanProfile(Context context) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + adapter.getProfileProxy(context, new PanServiceListener(), + BluetoothProfile.PAN); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + if (mService == null) return false; + List sinks = mService.getConnectedDevices(); + if (sinks != null) { + for (BluetoothDevice sink : sinks) { + mService.disconnect(sink); + } + } + return mService.connect(device); + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(device); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + return mService.getConnectionState(device); + } + + public boolean isPreferred(BluetoothDevice device) { + // return current connection status so profile checkbox is set correctly + return getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PAN + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + if (isLocalRoleNap(device)) { + return R.string.bluetooth_profile_pan_nap; + } else { + return R.string.bluetooth_profile_pan; + } + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + int state = getConnectionStatus(device); + switch (state) { + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_pan_profile_summary_use_for; + + case BluetoothProfile.STATE_CONNECTED: + if (isLocalRoleNap(device)) { + return R.string.bluetooth_pan_nap_profile_summary_connected; + } else { + return R.string.bluetooth_pan_user_profile_summary_connected; + } + + default: + return Utils.getConnectionStateSummary(state); + } + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_network_pan; + } + + // Tethering direction determines UI strings. + void setLocalRole(BluetoothDevice device, int role) { + mDeviceRoleMap.put(device, role); + } + + boolean isLocalRoleNap(BluetoothDevice device) { + if (mDeviceRoleMap.containsKey(device)) { + return mDeviceRoleMap.get(device) == BluetoothPan.LOCAL_NAP_ROLE; + } else { + return false; + } + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + BluetoothAdapter.getDefaultAdapter().closeProfileProxy(BluetoothProfile.PAN, mService); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up PAN proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java new file mode 100755 index 0000000000000..a552b24a713cd --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothPbap; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.BluetoothUuid; +import android.content.Context; +import android.os.ParcelUuid; +import android.util.Log; + +import com.android.settingslib.R; + +import java.util.HashMap; +import java.util.List; + +/** + * PBAPServer Profile + */ +public final class PbapServerProfile implements LocalBluetoothProfile { + private static final String TAG = "PbapServerProfile"; + private static boolean V = true; + + private BluetoothPbap mService; + private boolean mIsProfileReady; + + static final String NAME = "PBAP Server"; + + // Order of this profile in device profiles list + private static final int ORDINAL = 6; + + // The UUIDs indicate that remote device might access pbap server + static final ParcelUuid[] PBAB_CLIENT_UUIDS = { + BluetoothUuid.HSP, + BluetoothUuid.Handsfree, + BluetoothUuid.PBAP_PCE + }; + + // These callbacks run on the main thread. + private final class PbapServiceListener + implements BluetoothPbap.ServiceListener { + + public void onServiceConnected(BluetoothPbap proxy) { + if (V) Log.d(TAG,"Bluetooth service connected"); + mService = (BluetoothPbap) proxy; + mIsProfileReady=true; + } + + public void onServiceDisconnected() { + if (V) Log.d(TAG,"Bluetooth service disconnected"); + mIsProfileReady=false; + } + } + + public boolean isProfileReady() { + return mIsProfileReady; + } + + PbapServerProfile(Context context) { + BluetoothPbap pbap = new BluetoothPbap(context, new PbapServiceListener()); + } + + public boolean isConnectable() { + return true; + } + + public boolean isAutoConnectable() { + return false; + } + + public boolean connect(BluetoothDevice device) { + /*Can't connect from server */ + return false; + + } + + public boolean disconnect(BluetoothDevice device) { + if (mService == null) return false; + return mService.disconnect(); + } + + public int getConnectionStatus(BluetoothDevice device) { + if (mService == null) { + return BluetoothProfile.STATE_DISCONNECTED; + } + if (mService.isConnected(device)) + return BluetoothProfile.STATE_CONNECTED; + else + return BluetoothProfile.STATE_DISCONNECTED; + } + + public boolean isPreferred(BluetoothDevice device) { + return false; + } + + public int getPreferred(BluetoothDevice device) { + return -1; + } + + public void setPreferred(BluetoothDevice device, boolean preferred) { + // ignore: isPreferred is always true for PBAP + } + + public String toString() { + return NAME; + } + + public int getOrdinal() { + return ORDINAL; + } + + public int getNameResource(BluetoothDevice device) { + return R.string.bluetooth_profile_pbap; + } + + public int getSummaryResourceForDevice(BluetoothDevice device) { + return R.string.bluetooth_profile_pbap_summary; + } + + public int getDrawableResource(BluetoothClass btClass) { + return R.drawable.ic_bt_cellphone; + } + + protected void finalize() { + if (V) Log.d(TAG, "finalize()"); + if (mService != null) { + try { + mService.close(); + mService = null; + }catch (Throwable t) { + Log.w(TAG, "Error cleaning up PBAP proxy", t); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java new file mode 100644 index 0000000000000..c9194268543a3 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java @@ -0,0 +1,43 @@ +package com.android.settingslib.bluetooth; + +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settingslib.R; + +public class Utils { + public static final boolean V = false; // verbose logging + public static final boolean D = true; // regular logging + + private static ErrorListener sErrorListener; + + public static int getConnectionStateSummary(int connectionState) { + switch (connectionState) { + case BluetoothProfile.STATE_CONNECTED: + return R.string.bluetooth_connected; + case BluetoothProfile.STATE_CONNECTING: + return R.string.bluetooth_connecting; + case BluetoothProfile.STATE_DISCONNECTED: + return R.string.bluetooth_disconnected; + case BluetoothProfile.STATE_DISCONNECTING: + return R.string.bluetooth_disconnecting; + default: + return 0; + } + } + + static void showError(Context context, String name, int messageResId) { + if (sErrorListener != null) { + sErrorListener.onShowError(context, name, messageResId); + } + } + + public static void setErrorListener(ErrorListener listener) { + sErrorListener = listener; + } + + public interface ErrorListener { + void onShowError(Context context, String name, int messageResId); + } + +}