From 243d18eb22363fcfe5fd76d93c8d2e30f1246ffd Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 11 Mar 2009 12:11:54 -0700 Subject: [PATCH] auto import from //branches/cupcake/...@137873 --- emulator/keymaps/AVRCP.kl | 7 + emulator/keymaps/Android.mk | 5 + host/windows/prebuilt/adb.exe | Bin 118784 -> 0 bytes samples/ApiDemos/AndroidManifest.xml | 12 +- ..._configure.xml => appwidget_configure.xml} | 4 +- ...et_provider.xml => appwidget_provider.xml} | 2 +- samples/ApiDemos/res/values/strings.xml | 8 +- ...et_provider.xml => appwidget_provider.xml} | 8 +- .../ExampleAppWidgetConfigure.java} | 58 ++-- .../ExampleAppWidgetProvider.java} | 76 +++--- .../ExampleBroadcastReceiver.java | 22 +- .../com.android.ide.eclipse.adt/plugin.xml | 18 +- .../eclipse/adt/build/PreCompilerBuilder.java | 7 +- .../adt/build/ResourceManagerBuilder.java | 1 + .../adt/launch/AndroidLaunchController.java | 5 +- .../adt/launch/DeviceChooserDialog.java | 78 ++++-- .../eclipse/adt/launch/EmulatorConfigTab.java | 3 +- .../eclipse/adt/project/ProjectHelper.java | 10 +- .../eclipse/adt/sdk/AndroidTargetData.java | 6 +- .../eclipse/adt/sdk/AndroidTargetParser.java | 16 +- .../newproject/NewProjectCreationPage.java | 62 +++-- .../common/project/AndroidManifestHelper.java | 241 ----------------- .../common/project/AndroidManifestParser.java | 254 ++++++++++++++++-- .../common/project/XmlErrorHandler.java | 28 +- .../eclipse/editors/AndroidContentAssist.java | 12 +- .../editors/layout/ProjectCallback.java | 19 +- .../layout/descriptors/LayoutDescriptors.java | 22 +- .../manifest/model/UiClassAttributeNode.java | 6 +- .../manager/CompiledResourcesMonitor.java | 39 ++- .../wizards/NewXmlFileCreationPage.java | 8 +- .../xml/descriptors/XmlDescriptors.java | 46 ++-- .../com.android.ide.eclipse.tests/.classpath | 2 +- .../project/AndroidManifestHelperTest.java | 97 ------- .../project/AndroidManifestParserTest.java | 94 +++++++ .../android/ide/eclipse/mock/FileMock.java | 58 ++-- .../hierarchyviewer/scene/ProfilesLoader.java | 77 ++++++ .../android/hierarchyviewer/ui/Workspace.java | 52 +++- .../ui/model/ProfilesTableModel.java | 68 +++++ tools/runtest | 2 +- .../com/android/sdklib/avd/AvdManager.java | 5 +- .../com/android/sdkuilib/ApkConfigWidget.java | 2 +- .../src/com/android/sdkuilib/AvdSelector.java | 85 +++--- 42 files changed, 954 insertions(+), 671 deletions(-) create mode 100644 emulator/keymaps/AVRCP.kl delete mode 100755 host/windows/prebuilt/adb.exe rename samples/ApiDemos/res/layout/{gadget_configure.xml => appwidget_configure.xml} (92%) rename samples/ApiDemos/res/layout/{gadget_provider.xml => appwidget_provider.xml} (95%) rename samples/ApiDemos/res/xml/{gadget_provider.xml => appwidget_provider.xml} (78%) rename samples/ApiDemos/src/com/example/android/apis/{gadget/ExampleGadgetConfigure.java => appwidget/ExampleAppWidgetConfigure.java} (61%) rename samples/ApiDemos/src/com/example/android/apis/{gadget/ExampleGadgetProvider.java => appwidget/ExampleAppWidgetProvider.java} (58%) rename samples/ApiDemos/src/com/example/android/apis/{gadget => appwidget}/ExampleBroadcastReceiver.java (71%) delete mode 100644 tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java delete mode 100644 tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestHelperTest.java create mode 100644 tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java create mode 100644 tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java create mode 100644 tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/model/ProfilesTableModel.java diff --git a/emulator/keymaps/AVRCP.kl b/emulator/keymaps/AVRCP.kl new file mode 100644 index 000000000..175824ef7 --- /dev/null +++ b/emulator/keymaps/AVRCP.kl @@ -0,0 +1,7 @@ +key 164 PLAYPAUSE WAKE +key 128 STOP WAKE +key 163 NEXTSONG WAKE +key 165 PREVIOUSSONG WAKE +key 168 REWIND WAKE +key 159 FORWARD WAKE + diff --git a/emulator/keymaps/Android.mk b/emulator/keymaps/Android.mk index 90db52ed4..81ac53000 100644 --- a/emulator/keymaps/Android.mk +++ b/emulator/keymaps/Android.mk @@ -11,3 +11,8 @@ file := $(TARGET_OUT_KEYLAYOUT)/qwerty.kl ALL_PREBUILT += $(file) $(file): $(LOCAL_PATH)/qwerty.kl | $(ACP) $(transform-prebuilt-to-target) + +file := $(TARGET_OUT_KEYLAYOUT)/AVRCP.kl +ALL_PREBUILT += $(file) +$(file) : $(LOCAL_PATH)/AVRCP.kl | $(ACP) + $(transform-prebuilt-to-target) diff --git a/host/windows/prebuilt/adb.exe b/host/windows/prebuilt/adb.exe deleted file mode 100755 index 4cba503e153780cecae69680f268675052cf617a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 118784 zcmeFadvsk@wa0&wCJ<;!0t5*1DiEMRf$~r)6gYhxC{Uo%R*{DS76j!{;e@tG`tT%3 zJOogA3L+H*6x6B}Dz@GfN>dcEV3i_OiWc366Ezp3L~b;{&v&l1A7`gU?)~F8#&3*Y z2juL%)?D+w=9=rV_des?+cG0EnM@7;4-I89%lXy6to?oWKZ~f|VV|dV$Sm9L=ld?N zn)dU3XI^&swWH@;bM+^$x#-H#pSb9%tFF$EzW9>S*R)## zKZ=T>@956&;H!q#{dkCf zog0dvxnQ8P_kyQuGAG;)iu}`aPdnkux>#0Od)o=MDn0Lnz4&Q;7DVa;NB*NT4~=o2 z#;2h1EnhXXuKbk~?gY5}qNloC%ymuyJ_CHmKN{MvgNfkU4_0MLS8xT`Na5@@n2nPi z-{U&E8`{r~tB2N=PI7l*;75jY8aN*ZzH~qg7gPI^YDQAi^#m<4T`LaK5Bi)@=xu>$ zrZJld+D?XCZ%y#Vu~nI1)A*`PPj0}Va|5u`xMb-?6Q$UiO%o+TeOIoZpStB4cV81+ z0FfngGLti?VD^LEKo@rz*ooVh8akGMsPN9F{5}RC>6WOfm>t(y(-Q1VO9xwcr#n9; z4KrOZf1Om7DUZ4>`7ihxWyM`Ysk1To_Xl8qb6@aleuFQnMr_tC3He)MjDzXX_hEx) zf{y{xxo1moXRa#a)RJJUQ6z6L^y0IWMR&KE|J9)aX?$px4O9fn|4l{LvIC{4s9tf; z*WaD2FOF*5d2*)njSGfe@5&#TnN=K>3GXGB^Y5-)eP?d42Ni8QXJW?GROrr%p9xY^ zZctLu10rsH?GDtcRYR)PHY%@aL4cv)H%OGE5^N`w86p3!)B$Fp*fE9T87X3`gzuns zON6^r?g?MSwGkde$x_ehYvACgJJ)sQ`g?MHw5MTTVZyT)f+CZ@ftzBD-3ZwohOD=k z>pwo%+d4~?v-*qmu70L;D;3vt*0%)b0@~5tdZa@z8*H7qpwJd%TI+jim*VQuYI%I| zjKKQZFE?#xrRD9ra(zRY(p+kb^d9*0vNcoEl^djP2dhv@m7`uV!PN)KmCh zQq&*+MO?rzEnY27I#bKNz=PSNF{ePjR!YO7C1SF$sfsSyES>%qj7gU&y$kG{DcF@p ztxhUDfpj2bK22gE)$B%^1x-ln)X zgL%rQxJuWhs`9(iv8ooA%11#xrb0sX6M~PVbIOOo+T}1`zDzx=y@}tnz8pf$y|Bw;up97W{zF+D% z(ct?EZGD;iHgr(ni@Ey3yy=WkUw&cm$c@DtRx^YCgF zz)qkfZh4DqDXP+f=W<|zMTDah(ptyLr^LKXxN`M9SuB#tlO-IXX9}VC z_Yk?>#cjPGIeTWVp}h+x{mtAxxR~+LGbiV#aiK2R(0(-4r7EbZgdWt;PVyGs%-xC7 zv!>@LT}Nr@6^BdVqM`j3^jPYl1e%3;jhXxrNZ`zAA!)DXsP8qjcN<*29AJ;Zp`p@J zcaJr|D>bNQoBvUCPU3iFH)ymw*uuPQCclf*Qlim|?<5=f4*U)@f+97(^EY%H6EpA? z$dp=Qqz&!6*-f1q=lV^Y=1hL);w*Y`fAQ?*s;4_=^$nfTSH`RRI~kq4Z=2U1)k^Z> zM?wr_)-fws@FYBl*I+fgl$N3~3oD_B#jK?@cgzLFWID(|ak~C?PU5deG*Haa8!Wd_ zJy$5INixq;eB!P+X*~I-W@|K(k{a4iu^XaIlc1kxHs?8PXdi1;1BH3@nbw20#7y0m zm>GatXH!ctD^ewAF-K~n-GrnrN|IV-RZ-IkH2qGp>5_`32PT`|7*;jPAhH7#nmZ@e znAD**O!#Al){zi=BHd|6T~it5zDPzNDOF*gaV=EmvjgM|`x*_ij7EQ&2VJDxluCzV z0g1Y0p?9iOZG-rydUESE;B@8It6z5IHYB>hZ7)dk>k4h_GV`m;Cn-=cG=X!Sa2{?# z+U4M91Q>@}RS&RuQ6A}G?^_*@z7oUo(MAOhk{2k}gS)O8MnpqPF`nFwTI3vU z%gv#w;3t2$>|gBL7OSTVJuzw9^sMSQyu@vU>21 z5$Csp$lSBPbJ7@}^p@`TAnJgnYe6dejDrs}Ao+rSYEV}Ap#t$kNTIHDH&T6EIwzwt26y;9 zo^ntE@UgwN4EV%7Zdw23IchT84E$ zBs>6m-S!xi2;jq%g6o^2e>Au{J2r!&uu^|B`QwVCZ2oFQco zvo5~4cR((mtADV0)5J`ntv=Jx@f!y6p4R)Le5-4fYD1z8It08YTn6|W%&o>}6xxEdfXuhtF zdB6#4ld#ITKy`Io2AAGb#b5BSA*%P_Q(9WY%aoSUv|x^B_(y(BM8th9ikCc!15 zRI6FB-L^kU+1>lYds}^V>tvw*E`PfkCz2mNy>*RBjCoKXH4Gjw@TTAua?@bM??Cx5 zcw-5Q)?0kf9kadT@&+3FOl2G+EMvz^;cQQ-W<7O22H5CxJ3}9EQSkbvp|OGb^{1db zR+N3_J&(46g`$3Iz7v!DDri{Ay}i5PiEi==L5=cIKZ>E|RzQ*Y^k-T}YLd%vy^Aca z`R*v9j+9x*)r~5TaTcRvZ|9^b5>|mo93|BE^&c@TA5B`=g91SC=T=r0*1zm-`t0U)Q;o7+=$X#n z_&9hv`r%J1D7^??q5X5L!x_kgraLrJin2DV1i~%daxD`wF?az`Tc~~8)t*FcI?Mk% zB$9)}W59sVw^)rH;=3cL>wMbZqdImXY=9Y=*JvH`@FONjX)3z{k))DyR6N%&(?T}z zIAXP|dS9p(iCKEejS)46fX#cw1e!`MH&HLaL+?AvSeoa>I1ng;wZFSexDa=Nh@-gT*gY0^q!Hs|hMp~*QK<*A((%#Hk;!c+v~9?=?x(tsT6^np*PdTN>EniGgDuc)7!m^3?ejYk z4)jY5;`fkzbvcb@Y$cjzhX&c`*=EA;lP42%3T+!Rt%oFKnG^H!8+utn>R%!O@Iy?K zI(i8S^MO<#)Ag6t%B~y@ljV`~D%!KrXU2WlC|H=>g-I+!!!>az`_Y@?D|ED3)Njqv z6f|+P-;a?62mD=&8aPK&No>I#Dr6DcifZ~-v@1W4Zf09+Iecac-3|P=DyUhtiG)p? z3c=L9sxrYJ_TbM~|Hw%A_-|#D<& zpExkuF1gHf!xmDnhKd9tNNc8Qq<@~m_b~VhZM~U>TfSt1^$O6jieH;YUGsg7khY4k zV5j1&zOG#F!EAi++h5gKemX(u$Y3t$Z0C}ZQ?mv_T}1kKDbj};>AuYTh9IlU@iim~ zb8w%Y+zNS7={4i1zQ~N3MVtWfblI%bAh()27+*bN;iO`2byxkfx-aH}Uj_%TpaZ|% zg|_Z}7VN^`1q&Oxa%(%E9x-cmG1q-lb$L5(N90zo$sFF5TaD?LugN{1)YmkZA?)c1 zHX#S$+lXtOvPsycv>>6+Dk2F7QxcwvaU3p<{zYtNs|rVHPb{~>alFEJlS=D(a{u2D zdu(fEr#kz9EsA|S>&)IcoyXm3CfkC?ox1eBdt9xEsef<5<35sN>_?>Z%0ThB7j&8S zxbjg6XHNE-))b;Sv>s+b`G{NRobKad(MOP5TKEL&EH?qQrw$AXZI9;C*sY1!W0P6xgZKdzeLRq zCeZE&KjPWT-*jE2=QKkg0lmLLPsoPg$ELtb`!RCNeO<$w76sYY)P{{+*(BUn7zc;4 z6N=dhos*iZV)Ww=e{Ti)a6%X}2r=K;W;`I0=~Lj0`Dkc=++`;Z$ zPIX5L@Fa2ib2KAPwRvot^o|tUr(f{49VT_h}WloybjdD{VOq@ZdMEZP7R`^kJ~3=xWP$ zd7TL|GO^}uc6kYOX*Sa~SwFhMhf%qA3d+q`(D^P za@Mc@mRw>!t4ZFnC!U>@BhACq6rv6xay_=vL8fj~=^)JsxejC~a7u|n{ijoS4+F0s zCOAb@vyh}?kYhf_u+T(|&|wDFzK(Txh_>ebA0%BSw`WU-BSUqaP0x-4S|IrA3DlK8 z=3@MJz*yUQY~ci9W4&dGVFjoeAk9ohV>)@sc2W9 zFsvseO^%Z7DG^TtqfTz*rzHhFX_%nO)BT`gVxGOHiP_))#f3Jqc5$-(c**JuCuW9f z^_OB&RnHZ%+YP^Dk@MZ8*JhJZ=d)z?=@)~kWp<`e-QmwtF%dQE{e@qes_HD}Q2z25E=%()UTp5kjt3WJ z4FvT0A4jtHN%o~Iyd_L|m=?RHB=Xuw-lqALo$E8AHfysI;7NPh1*>GuA^Ycf+L;S6 z5w%NZ%T$w05J{dCI~KZuRtaXdtoUP6j0Ep z`&pRu{NE(5dVXzA{@@B3BschAH^1m3+SfYogW1dy6-sTG@tjRGx`!!MsI;(bI*JU% zYq0^b&yNlX(cbwMwTdKSuNgkIii14`XcHo4${*CN<> zL`Y72cfP)|`y3?(d`M^pO_D%E`*-kxu(r_?nfjlJ9G)Wb^WD@nIBYaN(|Yz+G&3#L z<{q`N10?9)B}4IDqf#Sty2Wr6a(E6gDdZpjWwRx!nnBeP#Q>%Ni`xcu)zJQu^@2g2 zhapZs$sKAP$`cnzB(lfn>hiUvC01IWS>Q@%9-phHv>3KLC$oT59`JLH&o$=NeRKk$ zd{v>XF|z=#1o=6c);;WEe02FtZK^IV zdH|ygZKB21T$B&xVy-Us-yq~S1pE+Z6 zvxmUnesvdU%<*X0dJt{^QO)UO5h6U3)JG4aOZVU6Kaa6R&N|uQMyE5Ox@8kx6a^L|sgtt!${(meL2cw7m( zN**s~moLrZM=E&S*?9ElD+|3$fuK)5sB=+XLybgd5(kZPFp*ArM}M|bQR87k-L#?o zCi74m9C2G(GhCY&)ZDMNA4`C>+BndaYt(8Zzk`X@Mv*-v*iJHO&`KKT$P~@hJBj9i z7JXjPr7il*m1DV+OXVpEQIccVNhw61rFFD2YUB9jCBuR!RP=f0^jbggFwCN09eX#Ou%+YmApL26=(+*LZ@gVhViqvqOD$_!zh3;mkI{GPThqgk0^bM6U;Ftvc;dCzJ zypE(z1Yld3$fmITWe21{ex>wy#SeDjB*U@R5tbF&mSyH2P2m-^2|57m%q=anEzRWj zmbI(}E!Cu$TPjU|@d5Q9YdCLif->q^CUHe5pJL!j@T!{TSDMZ2^2l#k(p)FxYV{Rb zb>)@|>gG|JUoUq?GvN9ERQPzD)|v8-@N3UT=;%dhKQvIxJsu8s=w29}LUZ91z@NUG zn&8&-^mvNRHBhAsN~Ky5Zap`xv{T5oY*6XX%ugBOVU?JQVN*dG9QB;fXWn(H_+jY%Z>EY0 zJsP6xda_f|1{yQwI`3kvchC0ZIgc}#nO7UUM5j-9m|w5>IFMFqIGQdvEsLCBYrHGG zs#}-yW%WNgnJa~J9)X%_U=!56>?+Ge=7MLUsEJ`H_kJty+EU!Kvp1*RXqp@rH|L~m z@#gmOZ3~Vi?Sz9~j&dj6Dj7FFOL>8@B+q{BRsyJz{bh%d9Tq_EPLaJfCR@gLjb?j_ z=J3Gg11QFMH_^{)-!c!&Q#|~2JBjYr4KDZc*-Cu6qQYSe;dNLG4tlavM0BgjaQSr~9({ zcfzZbyM?|uB|fp|N!KiHt9y}w+J;k8(shg5>PM}kgy(kZ|I8V@xavGIuI*ddJ8sIf zHWb>L-Lq%%>~dDg*p{mfSlrgAy=SYPWDTqAp*3q*;SuT-7N~S!dXtt0PorVsgxfYv zwDns3%9StM*7Wh9?&)L?KEQ9-LJNDLUeT1mKZ<9D14O(=G$T>Exc5<-$pdrz4x56l z`LRc+AJ9Gq19k(@E?pw5;hk8Ws$P?A~R!3653)?c}63Q#6Ua`!&apXHr!@ zJM_@&SO5xrdY>ioT`{r5oI7UY??CA>80W#LvL%Dpe{4-68V>AWL+Htf35J=>9aT5G`OEqB!Nmxt zNn}g;Nlu2jrgbrrP~qyy=`kvWJZvK5tWxetnZKLg7~D>Cq-p4&<7diG z^mJ?zn0T=`Oa@w8BbBU)4IX%hbRW_E=X`-D=ML&0=NH{&AH#w^$8s)pex3F5{5L z`ag*Mac0n}rzVwSS^W2IPARA1)*p~WYPB&Oib!+(qK=;)n%ido83E`O3QxgVP>WlK z6OTiJR}bdSca2pk#;e6_uGcg$%cmk>sgod=X4D~g2ST4_BGvpBo@Ur#R+iyN}$kUk4+jHxqBO>V#9i#2Ca@> z5zs32SV13W@|&6FOTv!8=JM?qus7(E( zL~$-(`L_PP`qhngt;*sZx?VmTJO{5MQmDxOnviB^P4WIF7oYzx_B0cRHMirww2U5O z+Sel{^-vGardR<9!-dXJp{9i#;3eZYO4ize2Fdy8A88w_@9UjO?UdV`YlXPg{t((r z^(WE}oRimd3?e~H)_nAc6p_UhL{6wA(j1fDKdOF3#U@hczY{iA+pYxr(@6lSNVD}ha(Bxok@TZ)o2|;UGE`@&Z)3FhA`4emeCLr1>foRg^I@*wKRhPeMwd6gv z8?caK53&AlLNOqt?I9*Gn*G+aw2Hz3K;k%ybiKQ8r9AY*pq8=Y4v&O@4n2p#$^W?5BhB{GQK2`C2R1(rTvs zE9A*a&KDqJN);M0q&EQ=79NgHAwUVd|Kj{Ey>&)W`)ME;E^n%~5{H;Fy(7cJgl9fr zlVi2&c~aBkNSo{%KZS?oii|Fs8Ly!HYg`M(te0XwB_5MK*c_ITUOhtlTJqmFq)@w( zSg+A}U1VUirxwz7@*n_8EV%SY@kr43yro`XaNOO?AUJde3uE<#jC_ylj_w7YbRyL7 zV4=!`Hlv^zBM%sAa30XqrBNfqy9&m`+G2-bW$(Ct3H81ly8A=7d}~~Lv%;%;etuqc z`8ct?E9jG&yNxDj$rdDktzlB4$r)=}4hJ+Cw<9RO?Z?rVIQ8!@_hSjgFZ0p##d1x9*%=Kt4^VFOJe*A=X{~(*C|1~&ZQ7FA=WDM@22=0XnIL)<7-;CidkKi5-;cAy| za*qg>7CX3NX3_Y=8Z`A47sX6=(fHY|*V!c#hlZaRJ1--upG@j_c(qvEAC&t2@o1EM zEV5KBYz)q03D-GZ=`nl)G%m3P?r;Ba@I~Mw|B5b3f3@+ZE@Pf1Wqc-LzC=ruab}d2 zD`w22I(Y6GkxOJEZZF{m>XLqP8Lay7Z*E>{fH!G>!H2x&?~aJw;E0X)0)63ckwCxM zp;Dmzyg*Ow1F|dY2m|yC@(@Ye!2uSQd|Ngsq zGrAcH&W280v#q*Eh3H43mB(lue3M3ATxe3dMx|dCDia@5XG-;OfJorQYG*ij+JO z>G1CBXgWY)aQl8~=?;zM8C41rOX|#{;diH%6VY1+jMeAA^GH1eE0j1!I zJ!4^-Ad@t~mm!o8W`g5Ph8PnIZ{>YK8a2uGa$wJy%~X&mCpjK(H#CTYxZ zG$K6j-vXWq9?#SSp1V_c#-w=lu*S#WHt-k^lW%bAZ95%|E;~Di_OdP6A+WN-M?@_e zXUyKUrN^k2Q)?@eND!V4^eIuo@^yw!me=58mOmrqA!hka5G0&=IJ5i#BH(*~`mJ$O z;Sc*Gf#|og3Aoj6^6y$Xji{^V$3eLkUHdNjsz6+xdU2$QGaW~HS^PRmlL7iI>acE+ z;$T~bT{_VberHXu$~-E#Wmk~kQzL))nX8iJ{}^F9 z(P7HT;lMO4#&jRxVSF%*eqc*t-|s^a_A6uTF2IWm8^2N%v|v76gTrmhR^59a8@iZw?RE zyMAV>b?X6zm2L&qplfdvbU1yV7D2}t7L?FP1Tn|%v<~6vKS}KJ`W-1;HbFcDDD`(= zu}f63qpR56SL_>A3(e@WH@Pr6>k^|lgEEYsrH$dkH$BEWGajVHut9sYU_Gxh@ ztv1l=Sx%@sot7{4T%8g%dF4;0o+jelObUaG71C_TDu2)eUm5{F?tpg;9^af4q1+IB zH!9GG;}(GYGU#PY!8nb^AvbDJ%Q7u?u?%v@@-EK3Wr}AkE6!Tl#c?%Oh^jlMbS=le z+germ57D;nG6_K+Q$uRAx71!pFn90^d&jKf+^W3KxQY;VoTAt$T4RD20%!G(c>;Bkv6U~EF>~pdL#_eBp`+d9ZD?LspSKN7rLhmj;Z^;yby=p>6$BgxSZ0o`Hx-51q zOb=@eBvfIUaqm7PR9w+fBpxu>zBr5S!PckFP}nZ*(SMM?^NwMv=wE%V%67M#sd5;c zXu{=IQEjXLM=v{7Zi73=W5CWRJ-!QUzN3}aaIar4e&KdE^#Qw*{{QlOM8i7XXXA=l zRLHTJj+@z-BWJvS#zNW8N#^mMhGxU}c0+Y&}9X=c&eN zw)b`ORXbrZJB8>rkI29ynVxbj43Uv=h#pKJT9HC@iV)ez|1XM0b_Unz7P6B!uF z>+ME$B9(&K4VVJ;)n)c+qV@Tq6u~(VWRpgQK7s}z?a9#g0sMtcqSdE6^8!2i>YMC) zh3fR^t`6HL>Fn|4N`uzLbb4qM(s`9G?{O`q*b!mqFdtvf^uS)|)|;tN+JqGD$KfzU z_yrK62p;1RHi_NXmD|J*-w6pMfV6Ze2=s_7LVttZ2i_}|Td`f^ux1-wv&~_%tK()@ z(G1`B@6g*Ytm#_U)Xqhb5x>#H585OE zO+jcrnP95AJVU&EF@_?W5MLHQ#P~xixj`83$fN#qDjd6r5+b zo24#!wLA~{2d=l_&2{mP3xO%D)L&j8FUaOQD_!p(1nB8F2LOC3Q@V>dq*OJ3VvN=T z&M$r`w0XlZA%)`)qzDNdGh8h5Oj$@=bPU2H`B`GC5oj_ zr6>l!L1|gA6s?K+h=|RG7_aiBxj3I}C}(r$p(xvjvSNqI1o1xvB4g<97H@b(>BX`1 zHmIESk~!Mx8e=?>pQm^^A6{tXqfrDV$3k!|-v|F>>C5PJLC4R0L@14F#|l(7K@yl; zHbnKE5S0#OnajXi@78$hekI9U1ST&J5nd1?WOu_l(aX7pXpbo;q`O; zXac7K9B&>8cX>~rXbr19msn9lskmA1@y*TrjZ{e z0Mep$^p612<=*3a*sl?kcdMmAE{Q?52gF04#U+a5)qx?ZIR>{@R{8y}%%R+QJbTw) zDDxhx<%nAFw{}7nE9BptnV_qdC$>o}q1ym? zH_eiX)HZQbGF^=U<2|M)E*jqYiB#*glEhIfADSlhixlv^4j3vh8@`rMiI-P|dU>db z-c{BkM@!NYctT14WH@*`7~)OAs-({$0)f`t?M@$d+bAgq*d%hL-G_s2=Rxl<(0sPx ziCh^5n)rQ_-$s5*{MPdug`Y;+gq-X}JJ1^^PeCJO!zJ{g94$_cy~Ny!iPrnXa+DXY zVO2@Z8yy-O%Y+73`6O_>J7{s+W=-m@!)@&%F=WXByv3gEHl-P{a}&hAAG4~L|81^S z8|J*8ecpBL)1mM!AXT^lK3IqR)(z%@Ea@~aGFE$$94ZPNaua3bN8+iU`u-S0CL_tQ%aBuj=xSM30 zFS=<2UgqSw#bcBKBR4R}$z~b#YfCkpYC|*!y2|SE7>SyJ5Dilta5wcFYR2+w0|;(f)uY}k(|kBT>TY!NchTVKdZSq=Jk>v^KNfzqUX|NBK# zjX5HXs_voDZug5=L;K}U%QbZrCflr3n+|=ZQ~yfe6PP`SB@Jrfso|y-N&O_=&S5#^ zJ1b3?+S0|eTw@;+F!NM&is*v_%9q?jVFi2B$m|8RrGshc-tQq(C7(Sr0{4UjIPH&C zt?Z@qv6OKcy1F@IL}mj*>D#D>mY#RK*R&vZ7y`CmX91IEVEQ{v^3snb>q0Ffc0*C511GM zSfrDl0m_u8`Kk#HnLUGTfqoIqlJ?mPW+O(+e7egN@Zxz%s}&8e4+c29KEkO@e@c2j-HO!%9Fu|6)@=6Uc{1mic6)`0co2FXM-s(~n=ci5w-$8nPYG~7 z!h7O}81apI6ZZVE=%`KsO}hGas?8XwK1ZH~K3DQbuob;?qg21}$KLgB0_0tS@o&K9 zg0Z69b=Bqb=+9YU$i5K8D8}b0Q8(9IpKfkcb~h@mWMi90DjbTxyu9a?{l7Ji(-FZAl_&2kXWh*Z;!YW$#bF~HJ{d#F}_ zJ1410Pe#0`rb*oCopE*ce!Mz8&r_!t=ke7|Er6IDp9M{#Hx$_E$!7u4T7l9e*OZN= zsIgWF3~LWUNDQ7ZwlHc}HdRo2|Hgiv7Pm*7FIWbn^H23P-7tU#>*DoHafJ9ts=&VO!?DZpjL)h>t^5YBqqCkr1F`RMOHQ? zpnY6ooM3qgc=)V1x&?QA=k#&vhRQl9D0DZ9+ui1=cN;MRav|JP^q;O04>4?cZuE?s zp6WIS?IY>~Ayhj692P%Aao9D5K#r2-0!QH;#sHeU+4{xvCj7C;i_+xw)Coqmh6jD$6i*A~l$>AmVJl_OH|IiWEZ9q@j8HY;RWZ6_Q& zl0dni@fSJe-YG(|Jw~Ht9RF6$kni$Im)?wCA=TTJ>(eWw_|*&R*Qk7cbMQR$ov-d3 zblY_ee)}lsW!J^LCLCh6YB706+XA< z`Iui@nhaWFX;=A~^PNF&v0t^SE}&=f%2ZSOtijOPOFX_$5vm~)G_((zcR&K;1}gY3 zD+nz3@v4g;>eO7xB;lyge|D@DdUQ~n6|iFDI`s$6S_qj>ULr==higcdQxEyC=!r)cacXz)(+i99WPhkT2KVNFq88=(yvB z3R#_ZYLJsh>g0Qx<>*XvBv92$%E>puy;{daNB$y4MMKniNtE}`%m z$7L;m%)BO=r29B-UqH0J$5qs2a;YxUVEIS#J)p2`J?OUt5-+9_yMF1je5&|B#@87? z=tzmK$y`K4oEjr?A4`#Y0NtTzm+kt<6cRjqr*|PZ=l@9Xh%FGTOR#Mz^1Kw6?=WmB zsAuc-wO$*56c(k%N%U`IAB%>zr=N}@x!En*D^I2lz1j1LQLlV) zp}L|Nv$PMD9<%2c)qA)a|K;#%Le-ZjbMK#?L{Bs8edb$fmI)7$U%#GWc`ekzx;5Ru zl>)y9@Zg}uUiU`tK#H>TH@=IiXYm$DhLQIYSwE8^?|~b{ zgK-=P7{Za;`aUqXMNjP4+%W~*Lz{Sjv@m8|d3XE$NO?#5J+`dyrkJaIXONEujYL6Y z9`_H6RDODsFvnQb=mx@+MMmFOX_1G!+2MS|q_N)qvmJ-Usc=xihw^aO zDSo`b`l96o$g3oO#9WrW`ZD}0?E^Xaf&9S}!w>B9j;>?oSr{DDlzDD-IU!a<`#LrQ zrR-sTeR4{|3rRvsUk5s&6nexKT3(Q9c_1z62lmp2`s1h9TsJL&;uzSqIo#mE>v&pY zI3IIT*f#t``Joegj4@w70vWqo)-cOUAeJ!;-D8F${BjE6=RxRT)uS}?l_r+}rM}e3 znguCX$De$L)_s_{x`iVdOJkFWU?&@%54%MR8LbsMzM@(bEQ_!vQ7n5j#mXSWN+$v> zPPX%${1PCKd~k&=K80^H-9kYBZ3^SJz=&{u1{~_G&xA34`#H2hPGHB?FM>7@e zcYwN9Ftp4qVeVA_91lausQh&_R~ga_qZZ(b`d)mf8Mb1?k*yAh5QDtE$6BwItT{O? zmbe>LG{UHR!n_B~7>o`Z2F5D7lxR6RK} zxGlx_-(eEk(W|4w7b+$YnZOUF+O4D=xy#J^vY9~pF-m4N-V9m?a=92iNm&5OG{3Lh z(Y5tVrPJ1u20rn^0lBJCqw47EIp1BLeCFSlrS0_}(V^(kT8CV08nqPp@NY5C)Q6ni z1Wr#)SZH9*?gFSye(rskY+#?T4mIE=wuGc!ARo9M0HieRqYS$he_=}Gs$Rh0F#K@% zdgVbCrM!8>PJf(vj%_693m5B9Yp~==beZGH1$wxam?)R42Cq<_Om_b!;LUj)$R)eH zo(R41u1vZh7sM_p5BV%v?8Y=?J`#haByII_G}4rF5qP!IK~48$UE1Ej54nW$_V+`i zA?B!@T(IY0zw}46iVmcV0X`Z19`eX@y(|Yr{b#G+u6P#k_x2Vfv%0q9S-uK$<51SA zDJJ#->vHKc3@9X%x(!XOu6iSFyqO!Ha0BnX&cwTH72aMQ=A@H+qG84R~)av!?WdNwu0PS_UwP|6C&hlnEJ2qA9= zvb3|BdM6gwfKy+TvCf81r2&pnA=vb7AqdwlT-I9(Jd>{FfI9A_wy`o;LZ0w-uB}&z z$~F{8;@e^!^)53iXBm}k?ENk7GPJ>6z{1j3#L^iqB9t!_GH@BPuO|S;dVrJsTVz{5 zAjmKwdMGB_>d5XUZVXXP%Wt29^~bQM*YmuQUdJN=&}IDcoiLioV|;S@F%NtLh(jG& zg!@)Z^fsQVW?Msdg{deKsi-_eg^!G;vUp&yt@Y(SLaec!0j zuh-AK`=ns=q5K%n)kF7gm8*2{U)H-Vnbpl##cnbD83}lj-U$8^9{yCR zff>1Z>Opb4zkko@==ll0HY@Z0k`mwj4q5`meLV&?;Vj0WKaF&ZZ`ySc8(<|8TfF_i zQdyeU7X|lp&g%V9c2WbkeQ%dKS9Sif>WpB=3y7LpI4vai25P%spC}xe;1TTe;5zO) zcUjNhFuYC)m1aS1bWBJ`92B~jCq^&*w@)$sVb?N`0Ceb`P~&tk-6 zCmV^~3ah7tufiP9HtUCGojTKh(_P{#QdOS8y8eDYge>m8%rt0;@kJQ1vtu5Ej~ZvpoH$d*-M6O0*Niuvv03jZC%3Ghc9m?)_2kxxSb3^+*oZ&9 z6k(4{Na0HMu{CU@FljdGw@JoAs`XB)^(m=tupHvOSYnpxa4VJ=?<)VZB}(^%GB|^6 z%@XiFOf6%=0|`FZdS`lzVaN~r^G^&8MIg6n{Nm%3TNW#yu@U*wRIvCRAVf)N(>`X3 zFZX1oOq@AU2Cx@m>TSxFhWYXG-eX0~EpF77ZbYeC=a`=jegG=HuC}`TOz;TTj)yab z^Ke6ohjRfbXU$+PweNQn>19fT!%q6^NOA5|j?O1l*^p5>3-&#GFTGzADvt@Y9`aJ@zj~BE6jx$FvJK8-SmI5tfp}Qj>V3Bme6$ z=*h>@tSfMYtnU%AJ}6C>cLls_)<9KtEW#Wof;s2n-VMf4^QE1+e{c?U6XQrvj;Wz+ zwM;+K48*&c{c#+3G3T0HXkN_^hnD{#tIL94Le>1%WN8zh?Em0{$-B=Ql5RcF+|XWd z3uT}Y5erqf9|;jV^Iupw8!j{RCk8#VarV7Zt>4|pt_P&2W2PkMU+D~xi#N+oLPw6C zDR|-i5=qaR4&@JUPH5ncqX_H~z})k86WnlO-hF4vvoc~cYO{hijt@qJOzBw$R{tfE zbG|8}%kA;3B)(-Sd`p3g@m*1guhrv2w4#4fIU;@OG1B2Vi0{tBAD>&6KPAEVh7_Sw zDhQ2$kWgarTgXHbGE|9XMCDbeNFsAQ10T9TbT5Q%_&ALo$OpjGHISnEBFH_R11ss= zIrSAve&+xmGy8mOdkkx=jD!|53cLR{tVxEFY#m7e6S_&@llt4goEb1NQq#q7hCh zj85loO|my{w?U$|^f!dwvhYj3Y#`)8QO^?a;HB+kVGG#lAaiP3{2N7K)+#mh?U7|ylqa(13rP~V&=;qbk}f=J`uU*iz)b{f%o~^TZE*zY0)&`hjl)LWaDa2?)iYQ zDf)QjUEfd#U%$9*y#-44cvj<~L!~{P`xykIgZ9NA&&lIwik^e+jSbOCweLzGu){=t+cOMryDZnIThvngIT8g22a6P{A0G0LRtFV5AB|Ju^qcDLRJ z&?98XAE(HF3i5g+6T2KdF6QJ0!|#*a=Mf!wUeK;QO#$X2UC`8pFTmsbw89uxc$(8Bmpt@^}A%_390MT^HGs7$MNCdAAm zuFRGQ!Ub?8M8w);SP(SGSku^qC4$9EqQw!d8UIaXyV9G(-1d;5_op^*v(f9HDdX*I zq71-TrmXa}UP4J%tA3D0xMU8yvwOx4?K*It9Z+%e)^&H)R2RBOOB?@uKrQVnUaj^NOU;tX!SEcK2cLj{a@HPE zw~O#dV_#2J6FOJc6kRpjm0sZASLohTYvQ^d9FglAL3-Z3x1AC3+LrJfAmH8g$lSX# zKN)q`gY6waoEr zQTV?ZMZ&4W#GoKL)-w<03RKC#B0W#}03babx6$=1ZeLw3QnsAG(Ok`aBCUN8nx>Ds z&W_vFPwnrb@rSn_;x2icu)ebKOmufRr$h~iw+OqsN4$kUfjIhf3EDOBW^m8fOR^`7 zfXH>Vr5oVH4R1AF9d|=arrujZxhCi)F2D4C_1EQ6SX?;quJEljeR{+&%QukpTIqW5 zlVyp2na$Y(J(~_Mx25c*yzWSu9utVAWXq*KU_+PRTFWC`%kb?kxuxFTKfY5de|Z+X zBYg&cf?a2uF0Pzy3?6=x-aWIIyZ}Domb-1=RHJVr=r@Xz_AanKF zHjv@uQ4IDQ&1rvOPHR@&77|P)?ae@^d&~aqKH_V)J)ggpOO^q3v^8#in{VzlINkbg zAdOuPxE8%Q_2K4)k5Vuq*BGL26wvK4dchAvf25&Tw>JwX&~G22Z*;DRU`pzEEwTm8 z9R|ak#)+wOz={|R?b>cpWc91-yp_uD(1uF?DtrC7o-`z%FMXT80)o2%JLexZy*hfW z$YwEE`ELfv1FT$&uyaPYj=6`{*xNx1ih8*eC>fAp9^%pmL2J4(T_+MW3C(8 zSxyaW6yWtqyzuqpUL4fURpi?egahedWi8o&nxdiCc20hzbeT)rQLm2!9+%g6Q+wGFdjMzMawCFU<1pvGcFugG(t z%wX1sFzX$RQZP8`ra$MCgxltat8)_Oos zkDxcS6LDd3wRcFEY3L|ekqQ2LF8$k1T0zOdffcg#3fX#v1ba^7C?k`Fk`>(ek7`kM ziw&`EC4+9|2;XG5{d89TW@f#`9Kt5CV`!!MY_2}|47+=-n>R^c0OO|Wba;d{t<=z& zuJ4gm7^(8;;A}%+>bpVubs2zM(5C{+3aj&B0JYsNvSp~7=ttZm(FbMN^W)Opt&}ag z_xhbKHl${U?)a-`<&IiVk~==uy>)kdiQ%{>_b67%l=M~|cZR|&VB^Fx>Asscr@HRw z05wUogI-7QJy`w@OCsKrPdOC?Kp00OMi!&OFw)nhv?@vQ*x1DfEl9X{g=6-b-cVm+i{Nh9}a@s zaq96q=EFm!Sr|Mjc!^RMhv#wSoFG#8xRmZX1e}8sqrOfmXqioSoYdiiX|@uY3qMC= zT@3H}44Mn4pyDqP7)}<&VS!AN@I0SDJ@WD_HdC1_%wfmRF{O!fu?T|!Rc|i>i za%)4K>Zho7cO!DQXLlaQ?kzaX8o-eIYC?mk;Wd_W0#?jb%!s+PG& zrG+Ng>B`;hv}w*Xc9C4Vm5*Q;n8|ExnZgZZI|ou*IvT&_xo^v$0r&He`>`0YW%`dVggL{>liy(WJ~h>A^9x^;($@QvfAZ~poJ}ltPfj`V z3ZU^Bt)rUoTiNO!4^3nSlV8CyBP$|d;XPbsVsnt;P?IF=81Ayx1=m2y^{-}e7%S_< zp-6_y7@OU$&}ucuHrnek#;an7n?)`kreH@0?*m(5e08fmYaxGQ^?EA}_e?Zo<=mM6 z7}U`bF@&=^ZcISn03OG{zC+nheUEW_p=`5HL`_ua(cI$=9 zK{E!2fz)%p3uN8+rM0|mNg%egmiO9?AymAU_o^!+jC+(}1oN2lyF^iYj0`#!HoO8a zgp40F^XzvFGVTO7t7l6PliUB+=of&jddq3F!pGn7gBGs(&H+Psh02rz=Iet~K*H1N ztmKU1B-%RYKPsq$9f4rp;-vm zX<2{a@%qd9I)OM7%p}yi?R7%9bzv4qjse4ZwX3Wxoe%FG_vgTkvr3vWtJp^GDl4ut7>`90>wCA?J<>I;-NQpHtK6 zG<6L)Swu7CU%8EPql8=sM<{s^Od7PP{&Gst!%&cRj5<~r%lfO+T@CHMU|YO0yq~+5 zyVfph{%U`%^FI5PmPUX&!IeMSRcZ??e|3pOz;l`q?HPN|Uqky`1>W{8L2PeHY(x7j zAj+3;;>qb|xR!oJg#xI~#g-?$3*ar*iHNiU+*!RYh?RQSK;$3!9Xt}cz-L$+Nhs7z zK9JJipMmlH=kYjj3AC=-yg?bQy(t4ZgV)e@p_i#|U9D_O0E%5%cD&skfZG%-O2F(A<}667P)hD}a@IFC_D7cAp0(XW=YqZgLwa=HbQ z>JWLeYmPDCJmN1{zYXd^Z6h!m;UZK1bA>XV#Ojr2Y!xWXnESktW#qM~*iIB#;O0q) z2ibe-nd0*xT`Yc}(jWzeFUdXvagwfa? z)}gQRG`wI3Xax_U0!$w4DIGr(yy2~6s@f435j-?cfMzhjAD4MwcnbZR&|1gb_G>oWVy@kB?7ai0lJH_q(6 z9ISXW9-r%P&C8ouXUnKcugL`0!vLBBT%OC~a6;x|-bH4eFmYeE{=P}y$h26m%4>9++jgO~Lp>pr zWZNx2S7=7L)RM;|&0NMHgJzDD=|-4lIM>@0P)*+FpJlXnbyVrr6%R$gZI;< zFn)eRyIwaO9AtN~z$;)d7Pu3&t8 z^K9%PW-gQZO6&L&A9(gr?QSy*-6jJ2rh%<>BP}@17WWMUgM-YXw&$yrv>N{MwY)C# z7wX*nYE4|P&-}T%H`9jS3mw0kWQiV^1x{Gh_KT|4!`zyR<#C{tl|9XyJxzUU0wpe= z+Bu1rjkzWC#7eo@YWZc1OTnzxD7toa1f*N;t4sbHK)4CPz&5e?ZK8YYzEQtR8X3ye zy_)n3t4o^QMctA;-9`P9n~a@K{~c$2zFB$q%{1?_&-h5v`1?i0@UzZK9`Ir`v@64P z5QXlhPWzHQY=WrMzO(GUZGS0KwtS2G61*$n`8z&WZQz9r7y2eMJ6r4ASD4p`e)#pL z_q!Cc<7Fw*zNI8esQgg|T)#Nh(9YAEsM$snJ$R;6^EN4?V>KHU8Nxnlabt>=NHEfF z>ZBPOdi&fS+UG=%Y@|`o?^XdU>eqdm?sl?=`VrZ!zkFi>5r?$SSXmO6|6Jud|EtEG zarqA^kBwPsSHFq$?81dhEG@4YFKZ*0#IXP(Ym@t!c)q;OdqEBNi+msa+G2IGJ?TpJ zaSS?!rVW{rJO&g3MSYeGwHqj#PrN|NG8v854ec)&M88ec_l4&$effYT-YtvTZ8fc5 ziWR~vE|#39T_rL7Vl*L1L>VsBdHZO>plL1Lc!52PlP@e1(@hNW?A7*Ze5@{?7Lz+r zvc+vh^@G3H}G$_w4IL&@(=@sNjbaI2_iwnJdg1Cu4*&$f=V;$Z!$9ryc5njj## zUQE^c-qG(O^y7x1*PH|sAx%Zx&x%(F&3 z7aqeAsu;4cT)brTl95EI*V91x5ciq;HL)3N*0(&R{I@>~H)PrQgZ;DSvM)0abrHLA zd6Z(YyhKlAGcuYWwGJdyAKFpRJrF-;v`UqZ9#(2fCT;Um8cl(vyQ9~ksv7XgBHCCXW zr!RCjC)%k?ZBA+Q8`tA+-TdjvNJF@@_$)VD20M5iJyn7^{Z@M5du8qiH@s)&8Xu~R zGIe5Z|Nj$nO51Hir7FLV4oEB{P2s}^+iTgiuR7mU-Xly)4&*}emG|O;ktI`Z3cfpE z3fa{ZVmoqWws6b@=+K)vo)4G}#+sNA)iiLZwJG?t;c!ZIIFvX>UP4&vCrp}yFey1E zoN{sO53-sH*uf!VKQQ(K&wk+9?+0!XA1tvjoGqW`F1nMkY#%qJun>NJO1Y2CL%D1C z6AEh6hSCqw1$|};(7~IGYvI7KTksWwtv97O*iK;G?6R5Ctqx-T42l~R$pLIuoSy8VrXbEF&3r7*gO|8eu1u;weXtn>O6+tJR(1#{F%7D z#geb38a}3mYxvWXk=6aQsq{mxQ!T%nYWW$pyxCf6(Dp6M3sTMgE7j~cHJgLpEgV_Z1@F@N$lJW#mIDG9#8uW8nkaW-^Y&9)qB zD+G}Z7fC1v^lja*pmnc9_WD)8%X%yBisH9!#g)*GrF%Ck?hRkTb~u>pv}VP=_hQ92 z$ftvhN#(5QqF-6Eo;|64?LTS4^6Eg?E7b{euVUSOqB%zA&-H!4SnKVB;ZTH-ey1^R3Z zmHH9AIoLc~PBq9YzM2l=7|sX$g~kK6phlICqTp!NG?Whto&=p6y~`g8zGXKf@%HkR z;H!2c*DoI%bGL8MA@rWP#uRg>(k7T~4R@ts80r&)J&TdS3}Kk;#s-@M6js-Sr&>&A zxf%BJ3Bxgfv%V1*9Bldk+h>JTCPDqR2f~|x_sKT zLJnT~gs?Kw`N>D{B_=tHox*`F-Yc4W(`ZiL5}LZ80G#3E+f|G=g%h$Qp_$gxcoK{T z=w7S*)hH>r+%R%vd^Qz4wrA$B#mU}awTz|{qLjUMu^EEB%ill* zObdgJuE=lhfd+6(DI^Vd!OskUG(fwOL#@ZfUT4EuKILp1A*&GK z(*f*|<7kOxy}++=ggUzCpBK~6ilu^=zO81Pe&2we<_ph%lM8L^d#7KT;$vpa$4}6s z=c7D3Mt!L%Jc6H5Wx)o>mGpaYE{rTg+W?Y>zWAnq6A$)&y>?!Rh zRC+FLtq&qmm%Pww`t5{f)Fp33U12z=4Sr*bFgDC>`k0i%##*0}e&(D`35S~?u3wx+ z+faE@16l|>aP^g9sITG4Y#kkT1Kra1;@wT>HR{Ex|F5jQzk^FcGusYep=0sjmO9n9 zK_dLZ>jEheT5XKMzZ!#)KZFc+A!XQcwZ<8{#um-|BZmbuho+hqBPf45?r}UfYF_ZP z7V=nWV$pr8otlA2VJO-0rll!l`t@OdMj&h<8eiw~|M8696 zTkM0i)@)cGyf$vTr9N14E1&PV58jlr=+{0KS@eFPiu<73FEgiI!KBa6B=Rht4J;n7 z(UL{H$~D6%*UDvQL4-?@F@Nqk#^tKf=QXKGn}3Pd4S1tk?TFVoS?f;nOwLj)tZ+2p?{wOu3LNvs{zh z7N(=gW5})j>XZ=Ow+_WClbpO~3Rtx?u1NyFiu)LF3;gNrwP@>QE31jssJyr*Ya1tm znm0Prx+53GY>U8`dvMh>hc$bu=6+Y>haHz)Ta#xon@gdKS%X{_6?h)5t!Ns)+C}@C z3mK{UBuZ&H8C}fvsu+bmgtQTmxc1A5TDC2B3-z!qO3qkZ&+Rd@}ly;L3onE8Yve@*q;g%vuaa-QpvVhfXO((b$BR&sy+mXC* zy3lRA6z4&a#bdvF%ogn$+};aax0qFNNOE`?mN{WKnqM9dlO!gx zX>5aMav7zz5nWsQrU1Z(TKIfS6`y-?{4Ug+(dyTYFvZvX6+OuS+dXRU5NzTn_@8}k z`tu*`#+-DUdDe4VYwaxtgQ!gfsRSCB$DaX%2_Ba$9k1V@r5F%Xa##$`+rJIt?GHgESr-PM zr9Pauw}(7sHp(kCve9BAZ!?Ia8KS{?`w$TM9e6tnC<&dy`aQfweSCfx#@jE_lQ7=? zYZz~vL@xwy3tox$TIth3p}cY##5H(iP+ocS>I?A-aj`iPJI=GvC76sHTV4);zMb_W zG?Qc;B{Mr}L$v>Qg328@QF&%7*q`*)Rj`zq@6zyxq0D3Qf#M5{M`qa|I0sh@#<_;( zBQR^qO-fbL0lG5Nb!4f5mYKG5K|ioekeM?Lm@ZZ}ff}`#81gFA6c(ri2%PG0i2LI2@!q#_WHq9G%wVPh3vA66nGq@{=WwToB?qXmGm%4P7gLp zp%<_RIqBjgrcusep86(GmpB9K7_P@9@-zUWzACt4x?4XsV9Q~dDdhpDK4%#0uOh$y zA3&fq!Hy&Bh|2&0-=wsBDKUTP3zlLwl;am28;OF%yX=ge(;nQ2H1!!MbDRL+yFmsG z8kHmW%k?|t2GSxYq7gm=Bwx~wyOfd-MC!qrV4^_I2j?Ia=n%cW2!oFbNnK4B91ZC< z^spPwpcH5Hp+1IYYH$bIO}LciLr|cbpP7RYm}DRqx{I$}JxjNG#{kh|(Pk=9T8$! z0-KCz-IanAqpjkZxpB0z3_>dKxc4|R%94oSvnsglIFA(?92gP})NQt!NTw4AcpH}N zTA(vz`!KtjDPD?x5m(aYe608a4!)qJy10qeiE(2(VZOL-A@~4`T*CoG#GUTEmd7-j zp{l&u37GwsJ&hlh9j962^ieGVedIj`G#t9q3;`iN0G(@1Nb7pU=H}oCSSZXvFel3t z8r&*)FjSMj4^OXmybex3U}b<%FG+N~B!Iig9TpDYi-`C!xE;}@N)GXk(hx)$S1G!e7N zXXp}&0O77L6c?bAlruoL6t(j#ICAt43jdFF#E{#M$rM6>6e?jf%R>|Ts3R<6i7QrQ zfUU?I_GTy2(g@tf@=CmthdpE`v#FH2x%?z0tGBKzcfcfu*gc4@G zGcd9w>-8dTC791&0o-wPIND!}aX~l_NPo*3 ztymsDOq8KZR%P&b&B_KW*)~*A9#X*~RN$FPMHIKicaFlZTo*;-xTeh9b3YitSEkly zDPOOTQ&5utDC!x~l%!>T1?)ac3C~petGKTcfJjE#7}Dm+ive`O8GcS{pFm_5Ab_s+<+uMY>cq;)|B?of8f0u9|z| zjf$YP(fldJLm^qy&(GQ+WD{B^Wk5j{en%RK0f2qO`vI|!<@e{B#6B2OePU5He;|b2 zE`1ve+nQuFalfcjygvrqMaBy$U$$6;t46pWG)UV=Dkub(L`!6~32O%AV>f{Elluir zLg;y+&+)Aj=m z5pcC20?_O^Lep?1N;i+fmyRX!s&532QCgT*$OvIh4xP>(X9Ph$BHBG2PqbrHW6D~e zucZL7nE)l3Pq4`>+4K0ajEqqe`=H{JBk)w>+4jaof|iX~7}w~6W|EhYa8ZQQD!|9E zAnQ)@9{JA#TF7!Swz+~#79a8vir^6z2J@rOuy8|OPK9d4B|}A!;$npC3WaGA)~Z#> z#=s+Pgsx!|!pIYWvatH0RSEmL(f0;5Qy7Gg)DS-D+opBK>?`8{co^VQ^>7nR2r^N} zu!%}AdDf0lVBiYiO9*0i;RQfQ5;i=05(3K;B(TGB zPbw<5>cve+SSSH3&W-oHZqnwUCVZa`yGUSziFTt5nW97{s_drF%IJ$`p_N7Gm0_>S zcd3lp`Nn)wuFMeo*F$79i6ROFV1RFeu*akd#_Gl4+uTElNFq=UNB*9%YX}zidd$2Y zl|JGt(|D7d+0#Q%iRUCkB*&yMZYV4zkVGv%1BN0VAcllI#YiT8Cv(o75i}95!NX9i z;P>Gs%J$ii7IN9Thz8I;O&HXcx=w%`tOt^bSV*V49w?6rse6W@?j@*Olp?^`KxDZn zF}KtzG3QzyY-XGO~S4qH*{Sa-&Nq{4}{u_aJPL8*oDo%md|9O6a}TY}?V}I|+14;mDT6OGg9{maB7+Y^Vl+U8k#s==!cq;D z3_{EZ=|bZTnflpB3(Z90Lqa)8M=XY0D3oD95R^e61e7O@P@WV}o)l2R`!)$P318xL-^3& zC8`kPLEjCcE6~|Og&UZFC46GPpezDakH<*5r!>V%sgIR-7jA!Y^BVj14-U#X9*;EuYw|yVLQ;FycWVxh)4ZNK!dO1PDM#SRNtz#yOPhP zx9*6+P3`#Szt=`30V}kT8B^a;zUL#!AkmJwV?2A}waz6)h3&}U8^NV}dmGm%jk~TV zyrTXERKM44_U5T@lolltGk3f91G&Aa z(RCfcgAlZ~;hcAaSu6ZPbD#0v)|h7^nC>H(+P%}GG#5U*Gb#!6kMd1HkM>603}C&; zie&*C&S`zXsX;a`eAX=#Hu0O;veb-!q84wAu%bL8?>ElCW4##pJNDCWq<=IhWP@gMCWBl$TL`?j@+ z!giK6DqV^9x50iav~0WEWZ7nFJ7JFL#C1g5iFm$gYL$v%(%a8aq>xG@3-l&YvwZeYw zxMP&8PK@V`nR8s$yYK{$k2^m_zdOkR3Vj)$J6`e@c4D`6YP;tv02dAVAVt9^VEz(h2KaCkU9jhk$G{8;4FFmFQlcxm$nkx{e4jl;bJDopQ5 zq#Em6(R`xoGRyYP#_UTox-36BMlj}^8>e5YS;2`kw_ktA9c$S>C$21W&IlLnpYGGq z^V-#fm-R{JSDEuLc*JaMoUbJh0kLkh0MkWtclv4 z>Ok&E@cD>LoE{3!0lEEcKHBNMjCD zXA-qm&`rc`%whF#itMWVZiN3Ea~PmYkVWet7$!C5j3y8xafjf`Q7QFr6rkmlxm}Gp zH+b5|(Y+UO56hhEu>~lWIi{U zJ?G9OdID*r=j4(kmY+mU>NlF&nvJHK(JPePqnCV@)GTQ%mc0N@Z>;wh5GSNhL1UIS zb4GzVhZeSgQ3l#OvWZZNoI8X4E>bjhadQGS-}vB!XjGkDp=Hm}vS+5Zin;cVY(`>7 zHqVoFWG5&aQ6yVM37k^IX{({ncd2e*11QyQm?@C>LmH){7!Vc~MPXNLCanD)aTq_>(DPx8Vb3yXjg!*hdU(E^WO;0NOy?GprT!51y~bKQOj2?~Lcn(3**wBdH^MA|X*L)QzT5pk@mq zt^>k|YWMuZ()(sdHk0%8d%b(WQ+hk|k~(sj=nQ}F-$Lx6@ZejEg_Cn7doXk)*` zC-8+;g=9G$hM@zahivldQQo|XP%LEa{5Q&a7o>n}E^mIn*BYPNo^i~w=|^;X)&%XH z)HgGZuZhr()8`1XF@N1hL{IH9y-p1xBJO)VOHoq4veds$CGsYqF4LKePD|Yhq&1}T z#%6Rn&89P2-dM;>gKg5(k;gpeot3ncc(@~vIq~obZ9z&1I1k+ijCrkbG~%$nhP&EF zLo6SS>s92Xpo!k6c2GS+mw|4m_;3#wAo!Vnb5(R*%bSS4aJKqGmU@a?gjrtHII`Rw zN8$r5r5+>7`2G&hQd@>jrS$%U=^-2UlfsksD%->B!JYN*TI$vV7#bp3qhNmHGf{Su z$l2gpmrXn9y%=bp?=5Wh4)dHH=334UpPAC`W{PbTU>XXGoqD`;qqKsP-X)W?!fx*a zQ?vpoi6hd$^qd`4b4Ad;7)WYd+5-RNj;Q+muII~|n0GvT*0UP#h$I=XgZ{96ALqTa z*XtRu)ZkrrCy5yz0qzF!PiklBTb>D7@8 z%MOy*0O$Y!@)k6AL{QrQ5vc1KxEkFoJV`|PFWkIfH?KxF3%b)0%z!RzZu{6=dqNpd zlr~?YsS^*{Y~6|C4ip~jJyWM?pO%vde1h5aSLa2>5{h%1y>sHgU3Gmx6m$UYDe0|X zMl=77@_hmc11Mpoz$($K6FMS>Aqy6SM_cNjh1BNp;-hEx#ChF4+I_Hm`mnsK;G}jp zR=YNn%KiQn5=(bYg5*l@WL{=@s0(CZ>f+r)M_iY~SOATDUtn7VC|sWdNrMV8&RKk# zy-T7}3%gQtk*Uj+2b1%NX+eTFzu9Xwo9+hBLZG4tkL|W>J5=8ZpJ81*^mAPswNZG4 z#)da9F13pX7%U8o$V`aT&^{5EPKdNA^`~5s(3T*l=k{Tix_hV!aCi1t7&gOTyFA*8 zjz?J@WzmfnKoxevQN+hqI|2DXz#qUC1D%_xW|HGHyVl+2Lln`3X%lzzsl1C|uP9&$ zNe*-;eW1fcPJq{avi_8%?l)iv!2pGV&m(iRLY+5>0Z?_0ia|PrY%k2o{bbSSvK}rT^;9KUu%uQkNTO;6ejQ!afq=@NTw}yU`i+LW7XS=wA?jYA@P* z45D3cPxOM6`Ki58mb&+OxCO`VAs92FPj1ntA$gix?-Pw()QaJor)tZxsApsy%2pl? zwnBX0EM5Y@t#}w|&E92MnT02{t~HT7gj~f`+bk6_W~%qT`1(_t`+)0a?|lj0yA!pe zse2ovMrU-ciP8?HeoA}9NDdeD!BmzX=l$7aPy#%5x9H?+Xg;;@B&sywL&AAk;HNx* zXsI87lI2l^m9fno)-1rHbeM+V@2S0JOWkM)GAV1`LeBJeX}rLmE@-xFn-~eBHwXP3 zCv1}Isl7Y0wEWGPGgRm0T7N^-MaVdioh2PTNk4GMW3B0j&{@k*cGq{hUkH%?z;K2V zAR z;*1LcuE_|`YXNxT!{9lBQ4)k_2-y?FKy~XNiAchy>Fo>I{5%b+{F8&qW(#C zPGV+mx20|zYJfcH$V=>pQ0<5$iOOQu-$ct6X`~=}Z>c+q$O5GN`$Ih+ojF|Dy@L#` zj!63JUr8AYn!Sr>AU>3n@zt7SIZQ@h5R8nkYTm`zYd9(mM_@Vi_>3`~ro1dv&O8dC zN|J^%5EygkP+aD&MTdJD=M3~-FYQUu-IvO`T};*IK`2+X2hAHuNtQfV_#z)D9#p+uFBeS?UfG zOBI+qvJ<1WL%HQQ`|U9IiTj3-zvIpAAu1f;rKCL&&Ol9SyEl6p9N$fcKtqfwBqt#0 zsXG%|_DLScR0*G2|lrrQF~HG?oQpXNz}ksi{*d zc+Pvrs*E-*|7nZo6UtO5X$lduWTsZQ)jMlGc@Ef(Q~=8F1Fi76pivL%$XPir+PWncGY zxRZ-DD3-bw8p!$P4r}5S#6X43{_#S-rVnUyvUJl$0U2pnc^7BFRI1-iGPa<32RKF% z=86C3*jEfFIuvqFT2RG+|4F(xRG@VWV60w&OBg&vMSmnQLvAN2b^mFBUhlG{gutv-8L zfuA&R{)!T!r-RrZ5o`>2KEdQbw|CYmPY>Ro{b)=-A{1rXv<20TR+E;u3O06RMwjDB zwB*_F^u;w}yai8ZTejC-{4eCg2rlcGW~~Nqb!L0cT)gHVnspUm+X17CI4gvC!O6tB z;A>Kj;K_uv&BV%JU@_$vMO}WA-lUc9gS#)#CR0iz!7Nj(m(L;uI@_U279?ba4dBeep-1WUNE$9vycQ-5k# z#%{}|Cn4-%DkE?(8|`|my^UI(1Q*Rc<(WvYB#xVvTV!mex*L1 zLag0w@0(#fn%cB|r$5m4rFI_lnL68iQQGdd6Va)=GY(qnc2SM2KPsjE7*BPfjl1Lh z@!&QRMDybPztWkF{nt`3ATu4}JC?cyNRcBo!6+XKu(A9Cm`8ry_{>-~%2F4@HImm@ zM+hAfL}(T!R6rc>GH&@{Sh~V#6E^#2KYI>5eP2w`x(S_J|EcNv$$wc`+Z+2FL z$vTkVqviWpgLXu)9=!pXL+mPn5~1*p(+t$${LWa%Yw@8l0df2-^v(dVbKUR&J8vbp z+|B;`DfB^FJl=05(hDwl5-cn4HsdZtjpd;NU{I=BTcG9h^kMH|sY)Q!n1Mlqk!@eb zcurtKdMq)t2v-Ii;`CD-OQbOC*O#M(LZNMfPIcrk@W1~W?0p7#u!zTRB}S2TTk3`Z zzK*OU8YINZ=K#6Kx6%KruuKbZSKnlSOw*}*`A66@K^QS7PVxh|FOOMbK5Q!gL{tIm z0QUUzs2o=pS?cI@lS1!!T8CzwSuG?t9qG6D+WFO=|XF6uu2Xa013k-t0JYNmCWBu(=iDgX?$KKqh?*ez1X|C*QT$5cv zIX?71g&cZY5daPEkz(Ve1(*Y9gNUDy+%<;6r|g$QrK$yhc`YxjI$(U#5V7y{*|_^*PZGc-cmhZQtKQcS#IlL8a6 zqwQc=F?KO<{NvD+Wm};HQGnV|*#8RH3ET@I2GARFKSeF1L>dY{Ysmkc5W;{%0*GlH zJL;s`VnI*xLR3rLAymMapfSl-pJ$lb_t8Kml_vFL1UTC8KmYV2!nAcRRRrm@f(s=%Ouyba9xBmqaS;BG1s_4jGME*Zbalpo1gmx1E zb39wY*`sPqcFYa`|^4bjl%nz3;W;$!_D=YQx|3Vf5KIg;le#8gPl~mKa=DN znBi0D&S*Iap;2Dlri}nog8>*Z6KZYU3JBxb&~3WB)zcHa#%?zvM>iRlBOf>qOh|DU82uL z0M+;7yO05U5c5q~=8=GU5bC+fn?n}Zjox__;!5()m{`Bxox*%4r_wsa3>Ov?V?j`1 zds!2%c*+6^ri#pmicm@K9{K$ZUbAE7B|H>-#M+&@-=NptWuu6)Pts)$ujP3Tm!8bn zofM9>)IUU+86*zB5_K>>%DiSWSn&_ zpmiZR6Ul@yeVO_?O%GB0`;}yBJVnbH+mXYsrcOU3CYH2Lxu}PM!9CmvEc5}4?qB$q z6Jebb#k6Xi>C@D+gexr$?}tUL+V{uy{=OrUyzZUkb@#r4u#A{5#tba*e60v|jyjcX zj$kCZ;xOlrd0^`Vl%bj2HHw^04yD(^1=`!Rf{SJbH<;7k^_nxjvit=8yXS1A<#G7# zuFTN*Pr_)=S*&&0T{96)-d1fWNug;6wd>v>?x&=!gvS~l&$UC-@T zH7;A_y0>EpZI1TlP4O<6q_t5fvIo)s0ggMzYX^n;bVe4ZW5-#`VO|nwp zeYci331vOF#9vI{ejsC4sJpSCA7s$Fd!5lTVys3Z6R(k z9n~OFW}7_^tct=%+KLir)l@RJn$p}2Qq!SCI);$?^z4aatjIY!kudo4g1*4?P$sT> zC}%EgPjB^19_~KvojV2i=r3f^L-dvNy5DlyZ_^Wb2Zf_{zwp`O?E-E=u%3`_k}=pZfH zeI*LjnoKD3LyVP7KEd) z^5Y5+ii?m^*Ne-EV$r(=DrSkPBex+dF)`Vc@==nAdc6hW1K&K~n??Ah*LTFx3u1s6 z69Sf4+7o5!^7x`@Vs~8}Cf#PI?ryA`G=PE-sj!dH&4UCQBx+U)=A3B(&fKxoTtS!j zjtS&~&Y9_5G|5|yS>I0Yj5%8V0q+bEIMa4xnw(1Cq2=%M-if$3n%v{Pb0X3$p07vL zT!Z;m?NiTQK!qt-eJZydmwXq~y09Adjp^!5I+fcT?_P$#S(f^(q;a?9ix_O%$ML9k zOjl!61QsWG6J+$Op)D05?%jubc~aZmIvx2MF7xL5GBDXoq9b!d56_>#aR&PQ>rf}M zcx=rUt)SI4KGWUoyf_>>f+o-`w|ThNl-mkDm%8^mcV-VdGg@?G*9E(A;QVfE!OqBL z*IpPrfHGsRR9*oEm%_$vj?h!Q+>Eo=aaI2E)IrOP7lv}T}~XFuJgTZm+4Pe+pt zLkIGk-Ctn&ruJp(ZvSub99>o|^{875$~KoIMQ1|EGl~oW#6ZP$l+x1)B8rh1x)z`%Ohcw zc78BegZ@iUoiKJIc7Uk4{UVU$zYg~i^=0{krM0zvn+frS@do{YFs*x^qU3qLwygQQ zBbV$Ug>J2^E~D@{#|PfrL@&b7o5}Lja-RsFAN3Y& zF7vD}*r&LM+^H38c7Ni{-<*;E#F|Jg|A~&=r$Jo6xEcB+ul6He?^%%Gc{58p(`>?W zz=&Fyil}swH$TC9Q6pZ`WYX@B@|a+WCU%gGucYs<-9sJG_DOpI5fRy5Ot$AsNROx2 z^m+52Mw3ozawiqy1)35}^vAu=bSQPVrJ)!hIPQ0E{h)RVv$?ZjMAKCWXvozkI+3zZp&qy+5j@2jtZ1*8-##bthfA=#oIhcYg7= zBewy4!&rI(3*2P;m|pT;tmQunK?!U>Or6&rTaV0#Z4a}a?#ON8Lb(rj+ z;o#w(`rHOlLvMW3!9P+}-bVUcpWguf5|MH-Hb804dTj_+W;&x(P!|2Ur^tD zMW%bRrS7Mcok%sxvOVfY+ zc^Ro17B4*QEo^E`crf*-z%zFrY8QQmyl67*%go*EObJ>40Z?-HrD8e%1JG}enhxPB z(Rx0|2ZSrOI%q}J!$cXFWa+H|vb0jXwcI)o-;3?Tbq#S%G-_aZ=ReUf zv&n?btKR%NY!R+!)ONO=i2%R;FoGxwa)PMmQ}p3+=;#BJyp6YBRlI z*_LOL=LmU@l;^->9wyIm@;qFgN653qvp1eD@3hp{ppz~4 zZ5+Ds^!t2j``A)`=u`A5_2B6bf{DYj4-ZX2esb>FPx}aq_DZdZ(_1^^uoqb55 zm3cIA`Uu&_?s)Amv^U~BZKpAhNkb$Uk2uvHX{q~|2SQ}dgncya?>BoB>Z#WBgR|Vn zJzrmK*)#_A$n`*xp7yA8Fxp=&+YWl8>X+}fDmOwZaq>| zQ=9kymHYn@FP?v?7bXQ!^r4B+N1m@E*UUE9yP(s*qRuBVXe+7wP4!0BwAP>Mz0|U8 zP*(C`ZoLy5AN36puR(Ar1?l@?(R}E-MB1OHcAH!gH^Xg;I{2~svqEiOd$|2f_PW+V z5jQMX#IjLu!c&MWZiCd(qJEX*`ReKi{>hfFzFtto7i#+`lnCpdOi4sIDUi8S^|btx zHHG$=57=PSq)B_Uo)TH6L!L8NKk#YeUsj>&M$Vv}@jXE8eA&oZeD@*Id&R-8T(<>o zEyFD=EpXq4yJG^SW{A74Bid)EgZAbe{7T#F-jBU}>3eJU5NKLIL#=%#>;4chXnT8G znhILX?}m-wy9|#nZB^w#P$NwYap4vjvJb$Pv3^7=^M16Y5KxzXqvuj8k(lB`GuzG zJC&hn`XFU!nm!g8nx?NchNkH&jG<}zcwuO|!jPVCNKY}OCm7PzhV)EBy3~+fWk`=T zq_Yg^rH1q(L)v9X&oQKD7}CoP>1l@aL_>N~SX%JMGo5%B4}GrkvVnN-C6s{k>75(( zEL|di>c@7Ozt(IStRo#qh78aIYpW2I^QU8Us)|sqq#JUEGE_;SI0fdOtyRnT*(UVb>R7`r( z%*hBn8INrciV~$K)l+SOoLQ&-s=8upxlXjeAHmyb`Sny>Zd@~#;6UmbYEf&e-B0#1 z*OleEdbk4L_*r@)`VYhk&`iAAV2Q?chV}p54}!_2yS4X$t*J`9qB!Df{dD%kC~0)= z^{2MsTIpBCop(5bF!@e8UGG#(a`~ z2p!l9)2-mh=~Kvd`jmexa!CmMQB(r@3rNA$!0Dr&!^-J*{UrC{g6(TjQNOovA4;co z!C%HEfPU>OJUMXK7Xu?Ve;*Ks&_izL;eYVmkLljw547Vl4;FSJ&zaNiBicKMZJnq8 zk@2N=P;0NTa3CYIu#|>tFt|n`w5^U6gT{T}h@}pQM}nx9R(Pp9e*T3ySOqO+E!p zuv`$dK`(OqaBl0>P8am+bXf*s;4OEdG|52%D}i>X03K1@{2rj>q}I>jnjY;P?HJI5 zt-6S2=bvP1^c*H?oIFhNiXJ?qxIFfLoIb4`K5XlQ75U50dXSQ@V+Xq=Wb7PC8X z-f2lRH*Ll_iD!GJCGlS6qgc0#w*t^^|E4BP_SE*N7|!0!Nwn+M=N(G3?ertVY6@|W8u|pP1Uq(cZwnTay+qG2AJ!6ui$)h#ub@>dKJdMOmb6d9^aTn3 zsK%y&ufP5pFU{IrdtxJgF8}G>8#iJebqGzWv_&X~JVzr_KW(hN8O_rq^a8ArA;pD_ zDAX2K_tFn(o+OllgXu|;=R_iahsar`y)+spn$6c|4o#@Cb`L$|mz1>o zp2iLFl|aPo_yLUGcAv(#Y4vYe4 z5%96pjYKoBAYLWN@KVPn`ddeTiFn>k1UM14k_l_6r#Sw=Cd%x6TyzEz$7PMn611b? z{>FEtU*jg~CvnlyGVKw@1Q?WByb@qGZX!g{8{d|-Gc#>sX2R2d;;F{aWnteGyzj$nw{fgeiIeNUVk%alRU&T2#`GtZWO9P%P(|nM9kx zqkIp8zqIL5wY|#5+uz!`@$n~=+V|dj@s}o4Al6~(?<(-!M3oFhBPw2w126zmif7N* zaB!ioy2A9sKyr^E-Dya#8X2zNju@Ut8J-_7JWnz_FEylHhIFDKonS~e8PZD)>Gg(m zg&~b?iDCMBz9Bu$ke*>kHyF}S7}8S=={iGtlp+0`Aw9v6UTa97G~jov;rSdxy4#R{ zrs272NT(Rm8x8444e2aHdXXXhupzzKkghhQpEji94C!-*{wy~hPWys%bc;077D~9}g49`ayo}V#1Pc%I5G(0~UmS+EW6!}M(hYa|~ zhsZxJj3d9d-#r$N(BhyY)CW6hLPEyBISKb-Vx=ugHI5JmvLum&1im zyW#2|aL>p%W7)J7PiXq~^yhF^W#pg&hL_BZcmS=#E$Bfo09{AufI8d>e^e|i)n12L z;LSgfk>9=MY7MW8KsL(HnQ28fzf}&1!pn9LF6d_0v(sIS6)+jO2gvg*?9w9!7oi@+ zC{mAIi6{kLH;9*7rwPOH#SteZM;0N|efoY(FE)tw?B zfF*410|@g{SmTI>zyKZ{0nRBRfLI9DhEp*n#Stcp2oF#etT#gzH{uAV-2mypVO!Vf z4>Ai5Xm4vBdQ@OTmmU>3ex_H8q7l8S&51)+CaX%U zewFM*DqShAs8EVaS13+<$!eR+3DCvaQL|QYJ8^$qRSEt$OKsK8L7Tlw^4Hb!e62iR zkFyCS#^VfcMY#=UIBiNM&TtHr@8JwrLYam$!WDs^by{}O{JGYgTtyu_Ia4VsF0Zha zsxG?b4N}GCpvh(^_r347DyVMosRi*OI z4bIA9hfB0^gZ?}~!isXI%LbGY&Bj&OS1JydGOJ*wQtVRF($ZAiRe>-EBQd4AycEEv zs(1oub3jX%ZBBW$4Sgc+a64>cRc3EBxdMHp!ZiTIUgHGu9YC61#$hk9Ih}(RQj`+A zyP{O3UJ$dW_SI^c!(OR^9o2bUi{8jo(F{Eh)vKlDrB&CvRBO&%0#~<8xcN3^HWI7L zF}m>M{%|EK-2y+aSchgR?O+Lq4J2{8Vo|_)kK%F^S2+o}lY^IOD=LEMmpxXh?5Z5u z;-s!{JJ%`V+Nn&J3@D!wy1~)ZLh^)(Ym=SILI<@S{ur2Yf>jmDqO7#J(yATyQg?}q zuhG@=;)*J}Qf7D56gx^ME0x6+xUNKJs{%^>9URKG0qUH0R+E>#+3koylF)=g+p9Kgn$0ekiS|%!+uk!?r$tpKk z>byH3u0*#@j&NBwYBAOfI09qK?siqXT}7@{1kYI2m3DhssX(lFGFhE>&#W9NVO2sC z%Zp$?xT7MIl*uIH&TCSyt+d!xtX8`#t78@Y76hBEd=0orjt2qwSd6WzQj(#lPU0aH zf1=&*(qv$mVNeUWgKdk>=^8_kFiCU}wry+6Sq6n5CV;#0stcel6i%kI4kil}BW_|9 zu1LqU4ouihso?aSlbGnMC|93$26@3*{}wUl>LOQka&qOrTC#ux1tX zt}4|XW#oL{jGE$7(zGSEDp!$|EP#!4VzJYS`sK|c66Zou21EpC6U2+dwi5EnQS5>+ zlA{6b2cCmJtC?BW5DK8=Fo0~f>nTAq?Tpw}V zR(TbkR8)l4UIMy8elkv=Oo_^R!}5_}Z~YnC92~Ae`pQ6tLm+b59JXNp!~h6n147P2 znz0{5bPP1(?YTFqDGpoNR36-c3c2TV(?TAdhqu+`C98E3kS&F43@vFOh1g7j7lb_( zFk^HBN*R}xrQC)i3&$Ne=HgJ2OJkCq{GZtRJbIM7nB34ift%F$>5na z#T6I=mBlN|OVByva{zL?nr~fHG{<@m8Hqedfdl;~Iw!rw6!&^L-?ID+x0mvu234Z%#>V)PSj549m)73Qfe9DBk zbLRzL+6@FuVRpY=og$c$36};8SFhvR#)EW3E`i@b zK@cHOK(B$Z8eWjCqOhVAb5NJaR#EruYN0@{Y?DTA+U=5>@c!4S~Wvi&BumaS8=&TcWDh8_{O?9}dbO|PMl~-ZZ7MBp8SF8&wKSeTF2!9V! zUS^Ud;R7SKL7fJ9P;nahQq=D>bwN2KnCvXF$pX61r6V?g>9n9b#;%n<5Fq@VDElox zy;^GCsTMn60#=b3I9dJnvZ_jff+^&^-u_*33l%oST~)qTErpaR*R4I3A`9QC3ZP~3 z6i}WBVRPtM(u)&~b>nSHadmYFL1=I@YhwhWpVigGzZ4Y_9twpX$o!PZUg2Kpuu;E^ z*aKZ{F1G^#Dez5s6|h$+%vNFQQ+*tM`*w7=DCXi^#X*P_WLcI}xJx0tfRtkR_AAj4 z0WdCBg2cRQ1c%aTR7(x|f_`x(X%WQ6>H-dOF9qQPdo}eib&xtBi|rK9aM|4@Li(MD zm9&~c%IUYDtPq|V9G%bt7yf~F0|M93WNgr&)HOsvfO8?DfZjvB1tL#KjZ6yZZi6%{ z+~tV)=!QCc2#H!zV#yR{qe69w&<7Af0VNEVl;D*4Nl~gIcTjf_2qF0$#g#I5sg0;P z$WQ1l@>+yamg<)C2gm&i2-WlD3pd7aIErgRm9;KqVx1+9auB>~rLOgAipyPTVA!+} z#4=QA>I#g^)v6m-LPcm@U?>@C71A*20Q~loHR=q|p=`TV-?A z*d640>9nkd{F>r&t-D=af`BxHCxjsaT!hQH&|7on%uy+LtQO46hF+hA(BEWro!za= z8wz?ByIl~$u5vO?)(MjyD4#QJPKf?MS6?WGubv&j*T((LMJB$w(QBFubFdP~X))Y*rkjlWp*NC#$sKC`VRQ8&&G@#Zo zkCxaS4jUrBBIpGw1{9FWCK=nP=T(Vd8cb{=M6pDbu%hjbb%^Fc#&L`uZQIrs!$lO1 z7}-DNWq=JeBT!@~9v4*v3Uj$q^_p?x#>pNFs41;O?$9e?waLaink9B|L6sx+?p~qS zS8gNZ5TG#K1W-;`(Ll4)K9+%im1z^^3DpLD?670-(V*1pST~+((X}*-@*si(UA32S zw}pEaC<=XsvP4g?I8>UUUyKUAC;KME1hF#l2$|OWLBhz1{V~e z)5$y)qb5%*O-?3a4@Us~1{oozxB^r6v&73{GXufs&Jc3PN%eI!yG-HOT*Cacy zQ8O_{$jnw$1*#ePk1#-zOR6YLFBxTm@#8Sl{V74A@R9RYbE+j*L;2GijOz94zmv#V%Jd!b7Eq?h}Hzhq9l@ z9ebfZu=r${`3m!w6`?&qQG!c2*&-rN1RxFwQKkYYx`EtMff7Na?$p^Vv|_S9s2eiI zr!cC-UnmtNtwssh(FzH7R=P@T?9~78hl0d7|6%E=vC4Q?W%YOg1#t!2gX2i@C{pN+ z54pt0(0x96n=)gS%~r~adQ~w( zfTWqr${i4F;Fpzfx`H&MAYZ`HQC-C=fMa*{N(Un4FmywC| zk%7Hj(Jo2|l+nzOjxhZmOdg)9fHxh$yZ%g9g^b&fr#g)SwgapZTQLW^VB;056fEQH zB%7&Q>&`#OI;}5 zEEb7CZ35Rcl_3|6gk}n%fXqgB!s-!lmyyn49x8K&J=XgY3neFd1=JVQ{14gaP^ai; zkg|klL@NVXVdu;%$jQl_H;&Dn0MGe1>BMpFr&i@S&Y$5pi(|-RR%HYZ6~~P@rr@{} z$L)Bw4Cg|e?KnS(V-t=h98crWe==@QmNKmDLE6U%49_Ea$viJFCI1Ybk1(e*A{q^? zizZ@p6rER}um&9~l%Vazlesj^Liw1|tk=e4VA2c&T7VSgi3pZs5EC*#2@~;D-VoAs zg{`C*V88*nURayO3=E4N9ZkVGq2Lu32LPvZj(&>A1jLr10Nsgqg}~z(Rc{^ck^SSspS) zDnpgX$i^OT^70QSdGGK>?o)O zC#n?d>}Z~x#1F;8oaOi1N`rk9#ZssTm>L&r*vS1WRjzz2>pLAcBj1IN8}V$g<3joW zzrKGh+8^wgHwatl_XtU474{m49hqwdmKzY-5ws4Lbbw@>Dgk z4$Ce*5S+3$HPE)?Sv|kbmN~?ZWD@bpM}1Xa5I+JOT6GfOSjr6nXegiy0(q7rRQ5&} zs&XX)tsNR<5Ce`fyFRuJCB@1Gv0j42vq%d;N?}ik5tcMkG>Oth2<}0V@N(JD&Ip5`ZDDG}^`hylV;j{x!{V+M1wA3-t*>OGK4*j2$yVmU9b z=@kom$!V!735%PCaTW#Buq1{5>U1L`C)iu)iiYhy}}I1(->j7M6HZE`V-_s zS*KtEDF6bU!^M%F@u%N-V?$O$!c%WMJO92qT%Lxl=;UJL3KkLmXgQm{MIbONvExA3 zYRElkUIsgmPmELna#7i;A{w|wn1Kl{43w*#=^-~W8N?Rqlh(NkR)i(P^SO2hTZ&{w zQZu0-ie`0*#qxn!mGV+|ig+M}mJlN=AmoBYlrzV&l%eltDdpW+%6Oa);An}mvh_+L zF9^I?Q~+5yVQun+wZxOup2!nKUr8IYV zFAk?>Sb)e`55p6(;{@mGmM8>%5h|^+PsU8Qur*3aLSlCe>$|8AvXlup9>(z;j$RzU zH*=pukw(h{F?&lU5hf={4U;(Fl{ZBdc6;>-?58RUu7}haNLRv~5zJd!O)8QY?mLu~ zTa+MG+2zDozCe3C7XpHE><*GCh?Mi&AxnbxFnxzIasoQgEjFo`9^X({!U+4jv=mDF zTU#T7$}+|9aI4QBA%eFId5CpvRd)BvRnm|kH<}6);+aD#xJ5G$UDmin5}-TE8ntrL4a+?RJoFAxxb- zyJH^2b%kxVP#OXb^duQJS_m|7tSg-3fjkQR)26*5#PV;xX+{luz2 zOfXZYa1WI*@sK2bqCHd?s#PwLHv~2zV*X>$u#fqREaewCZuxhXl8@t$IJU+LIU*=R z1}q7@Zz5tf78eNzImjcToIna&YGk;Q_@S;qMzb2!omx!zqj#(9ygr)~JXEoi5|RLS z;bT({n67KNU~u_8+h3BDsJjftBdv&*rZCFUE=x=oz#ZZs04dmFy`@3&11w$v3LYf| z$<>d+jywFQZ%jJu#|xPY0XqobD3c+Nq-GdDgyx}rA+($?1#5L}i1sjoLh7JR8W3e{ z6fh{sYhwd_)RBNt_ewVm5Zg+)Hwf?p8)9FOxWCGcAL9Ih+d=$eZWduiJSh^fev)74 zO_j?5ize8RyDHEWDv_oeTsu@M8VXb(pOGmL0F!)thkYI6n*0SKyb!je3emWrn7EY! z4k;2hq!?kUvBu@w0t?J+gXSY{3B#9~f?3a%;G7CV?x1BB&a>tc*UW=;Q{tjcj9?iE zXE785#KR~wl;@oSlv8JO)>C0^&@do}2>SWMS;oLjsV@s)_ z{?M}2FNTRg0~3V&(#PF2?q;NQ`xGQ(w;s#s$|y2`xDeBH9aiXK9$+d-xcf0K=>FoO zdGi59eo^kMX_R)^F?CS{GEPmdaATeTtgtSJL=PUuDypz`u2HTD1t52J&NQYf8wg|h za&b9}QN`*gSw%eQ1f%gB5K%v1CZk4VRH*`t)U{aIL$>qnw0BB$VJt{NVJvEVrmbov zED;WBVCN}7Wyidtf?%O4h!@oxVG~x;@&r!V)|T+x7ezw^tCp171Tq-Ried+tSZF8i z5((5I+AoFmM@0eKyom82`GNB-1U;&;%1LfCl;G&nFJ8HbzX@-~uId=< zu#UxU>tWb=9f$XP4#!UH5!jVY9~!yPZ#iwoHqXHkuOyhA%Ed#k#x+b6GZaTms}esH z?Uzu zEv~U`%zP7`??LL%I*;V{p9;$7rNCqx}+GKY-(Qq`T4nmAHnZZXSp9E}Tc;cm&5Rq|YgW z{&ypN5&G|u^uG`3bUfdU`U&1*9Al9FHO?j+wK%LupTKj1=N=rFBmEOee+XoA2GWNl z{mYR~Mf%S;6TBLZJCObi<%ynnXSMkfCEgri67+W?jTi35ye{dF@H{>Wt;GBeXTn!K zj$EWaMtP!tAr5?+F6I}K{u_{0S|9zx0k$zj!e>KwJ>zHjg6F+%z z+=cWhl&AhI#Bo^&{cDiE4bT53>Aw=`>yiEw&eYyRIHn{0FO(;I7U96Sjd@Da{|88a z59#+L{q0DPNBUKqiJyLiV>Z%XOZs1E{I{WgqW^LnD)K*rvw~wIjx40#$8)0pA{>cG z|5(z0Ez*;aJ}BwG3h5N2U&NW{)qrCL(x0L{(f>l@f4`)EDe@#C|8H@o{B<~{A&vJB zhmHTAOZw~M|EQ#Y1@ezY{v9~S;dmIwOr+1EJkj$)bq& zLzE|aEXHvK(vL~{-;eZUq~DVCzZdBnkbW6w!sich+==wRQJ(03q4EFUlKy4La~<;k z5of~pCLDQ4{}bhj{>yNTM*5eM{tqC1JJNrb^shpC9MXTmc?6C}aLhvbf4*h>x1)Z7 zw;0D5hLwbT7&iKNaG*3h5^${Wl;z73p^+{VS19L;4k*hvR6(k&pCO zC{Ogi(D?6=^e;i4YmxuIaHjko963mTgz`lHB{;4``e%~<-$yzV>31akS0jBR(%W$! zhQo{FE~HPRJoV>7cP`(!BFQ@W}7ex=H@Z6|vJ6Hh+m)S&pd`OtNFyfCzmDGMCs(ZGpQLcNiEpPV9_@c`Ei|NYH{4 z)i4}sHh_JNP@qYbz3f`ooc$83Z%l|3kLf=w@AAbWo}N?xvv!Hb;`RZ(h{HpOJBHJKtzk)|lq z5R=&yXNor^npD#SQz37}_n%$yKYjacKb*bPlyJ0SNJ+_5%a{KiZf@MCR%I@WFQ0kRy8JE2eJg)` z@MPqa=|`gO`k(sYhgQEF`}~(%UcRN`i9ct*z5kVWv(E1N!v_^zH@TM{&wX?F+uip} zJ^1@SOgMVoWaYiPH{SiuzDtWcUwiqB<}ugqd1=AVAKUq+<@vv~Jk=7BVs3~o`t#Aa zy-EIIFFv&1KK`Pas}}wKs^Ye(X=U4*tlevd-1n|&mwM=l8{*&kM5}-CzN=r}Hstw} zul?%gBQ3sP?;pB$;#tM=$d;JL6Q3CI{wu@p{XyE)O7qVuN`GuxTJn!C?q0omMRCQb zt_imKS;~sf{`uIDitj&fy0YyH^MecbSpL2D=vLbt|MrVNZh6(azG26?<5&HC#T{wK zF8O@@;fHNAk9@VL_|N%&yZhzIE@juVCnmgd#QOa4JKuS%Yii}5gU@~S#oLGP^_ScStG8Gk018xCN*w<^2@6h zzEp2lV~&*V_|?hczl}Ki-m)k5A5FdHiO#LfTi)roZ0bw*)DCYRm$~#W+y7Xxvz0!I zv+bqVei3(KTjVbez39rA{?^AY)ExTdCD*+h<+-%mdGWB459%^YAHDtF%HdlU-+TYC zH%FZOXhrP1n(uixuUl(2QYUa+rrVRVb zpMSEr`NpVwUt0QK1Mlokf3x#t?^8#|UiGK*Zh>i@p<)!HT3?Kt&*%~pHO^zDiNSZr>nzSlDFdghR_rNc}Y z4LoK0QTd-%oH;zOx;XdEic8m?h<*BBhlam1-xD=!_e+r(#n-L;uhW+<@64ZGe#g5t zRkz=H@4-KHFMj)I*|6hT{h3|2&HK}?-~RQfSKj-h*Wj`5h~AR&YZNrqI0GFIk!mJEr^98<@TL=i%U48OIH>i7A5@8kD) z-1~Ukd;hriyB?3{!+X8gu-Dmp?d{ms`?VGh%Ej1z)YnHg*Sr^tFV)L_QN}|o*;^A& zg8d+1h|_Ph8|4=9vn{Q#&3%#c%R8N;>z^pK%U^2-6qLsZhottf zC5P+MH(#OU9@QIgTlaaWxNK#BEf6_2S1yGuPi9LD4Iws8W({V}gPx4<2p6SvO6HlF zVdmJ-=z~dl#lci+lMl~%LK8kQq}}IKDao zrlQ2(<>czH=jL7DBz}I`M3D0|IVpv+G#mYzq{?Iosac!5zT1)*%4vObO|Z72S^W9U zIJSFd(@7uvs!L<4i4LXq#pRunUM{(C;JfD8pf>M#o3&N(f*{U_Slf3+DFC?;F09?$ z#-=|`5XtaJp_V#2WS%>_c~rWQsx@6&W4-odY);~l?!@S$-e{&BrS7t=etb|fQ1YDwc#eDv#z&!cj3uO^4c9?47Q>ewkKkLWC* z)iztUphmB&e2^b?zE9okb(!8|4Z*GW^^F_)O^_mHL=`KUL^3BF(^?K?g@&@$9gkFW zzfo&qpFEGbJUWUt;m{Ulun3T(d@9V!_mK^fqlc2K^~6KcA0P{&`t-^lGPbUNG+bY9 zd6d(_Kx_SZ zeJ<3R=I6B%L}qgyt6f~E7;k!-KvhBO>~$A)l0fOu zS4BK*r&k$B&z(^bH zYfto!rjsnL1(@eIqf}ku)X<{odN=1K>1Far$+;bY8oLlmo^IARCoxN{F4mScE|X6% zvVN+r^G8N>?>8iFuZeqkZnS>7?$~Z`4wZaJByT`ToP0G;uiF+wr-Q7@+$BHE-i>l6 z)Wo_;K&uqF-gj?lwawxJ@~V|I`V5IK`ACmFo_P5FmvU?-a2X6GG;UBqOyrW2erxU| zDe;dcy343dT%~)II##xiX83Lr)%3CfrOozna=rKwvLnK&M=q+GQnDcoz4?AS5wgE2o00FCew5QrGvO=#AGh%di$xAPV$%e z2%f;z2&q`^3b-GU<6mcU7k=wqdOGFZhDiQ%Dbc3r6t~SUM(zd19G;IQCOp_q#bboD zb-g`SnY@&r4BhmZxEG*ZaW#;UjOzADWRHKQSCDT!$0eWl=|p~e@7rz_iPWLPl>(e` zJJPNT4|ZJ8VdYLY7BP;9YvT6jq}LqC0t=Dy49?cJuY|00-z-=TVzW`7>TPUl2)S&d z9j5F!JF#j^84T)LC-koR-i*7lYmL+HBxlolSZSh>^)Xp9Ii*FVaD_zmr#x&8hD_#WSN>o}WxhFU&cO z?teLBSM`~g%5PTs3hy!v=j`|Esm~X!)-9K2XGG@*B9;~+C0~83H+A^BQgw8%y}xfa z6`i!7M`Cbrn)dh(tKZ14OD(C}Hw#R+)E1z%z>wx2yca`P=nPd>A1RWrzYyx&*hz@q z{BWTC)6&dm)KIEsg!SExTv(!$^MfYA3tA}ni!#U!Y8IT{Z{>URv?V}rtW8~kxxMSUVe_vG32&bV54=g3r)$bB z)a*=ajRjY#5@Q};sTF^6yKgN;?A*O*Qj4qi z&8Mki9HM$+7oP=1j&ELy3RWk&SI*rQ5n+=O(nxI-JXgdK)HgP9N53mR)H+-Dt_(9% zIF-jxSbFbFUQ!KbZa0zb%Wap;SN=ZV3a-)%6w<$T%|F$a|C;&EYL4hPiEM9!o0*F8 zm02wUTj^_;Winpw`M-FU)bPC5v!(nC3rSf+jzYz~55bj`;~z?7i)l)cH0s3$sHh@) zE^MuZ8bb}qPrWMfr*YL~k+`~$DYp7CbCbrUv&jwc{g1M%tBQI@{_=ZaIlW;LGWN_0 zPHMu!=~T5e_SrIjS!<*=*=?JtdRKyQ0&G>|@Rg4vjz1(YlT+7et&P$QS#);9vgVJa z(eAi!y}tCW>DFX^PP^S@GLM_QuH7Hp0`n6XnCXOnRc6Yk_A-a_&G#I?6zlo?HD_B< zhpHc$T;ZAY8}DM6%k5;3^g`h!4$?ZJlopFgGb_e-!;?N=>{7d zgwf7^{e&^PMm4geVBe^_m9ZtX(vp^>CMd>bqHUNu^V3y5FrI9q{dSJR5OdS6vsQ1~ zIiX7K=vL;$wDdqhJ{Hxz8h=hj#KYKi+Rb^j+>1uXo*2pJa#uWH7a!QnxaZj8eT*je z{p)Gcz=lxEwE@M8Y;(p{dFvM@88q&GY3`c}3UHtn?Y!lrhI#v(hN`pOzSvWs zoybZfy=rtL*;VPm!N6{Sj_r3=sj=&teAU$a3d9v{%SD+lA}HC87meQu#M+&Y^XV5_ zKj9KjxXCMP`yeGGLS8g($tYex-%_beoOFwFGFOwMre`&Kaq{8*y($qEJEkZ42sggP zTD28-oTx^JSkU&k!53xgr|I%e+wCC(O!ks*JZK_)JEhEK3v{MU6%O>ZP9@L(^o)oq zU|g1B+)@Z8dd4SL$8%!gq@s_(<3j9I)cYb+3E2wcCG)<|21NlnszWS`2k4WsNOG0% zu`df}-UY|RxDBQ*@fUCFKCrkx8!RJkBojg&n=|Jkaf0kk7?pFjOnK(CLj9YtU1CuI zb*3v?8-;7$TxSyLQX_pDgp%1ulQ^$-tow)*s5Lba?Dc2aDmt>6CvN(l7t~HlOf&nj z%Ke#`#YxjL0J{;>hy3!b0%vh8hP+v7q1^Ty0w_q_AnPws$)uh zc0K9H8-hkjm$H04ax#V#Ef4ksf$l82uv?X0)9o}LMrj}BHoO*8p<|Ke_Rb2rY1=kJ zOWdBrUoU0>1dvmEh? zW5v9Bjl(J57TUH;?`J4eAgNbaNF6jX8g!pr7?xGeiWU2-Nq@mX%D&W7m4avJVs!uZB2q6-J!W?9Zg2H zA?w2VPQ!uLe8^I9_PL$A#|6UKzNSX3XN2*#f=ZnH|Ht3`|Yf}OzC*5^6v=#C$N^Il@ z64FAGBrLHKWZeoTTYs}b(_)A!8f~MmT+oSXsQS8F9pR7s$Uez1Caivcz<=%6pax2_ z#L7sfT(|p09pp7wD>idwGE4E|NCGyquf=&_c>UL9hXu#8E~C5ob`dILmQ}qr|aF)YTH5bZmZcebKEwyz-UKu zQExI=J2tn>xbz~ENEy|g%6e1TrW$nqQdJ0NN@HmSVZ{ukS8;#J*CEyO&-yWiMB}^+ zo)e5ApR6h9AKA!;QaL-y`8k^8&QkYK#FHO9I|3E(-6l;f276B^g_FJL=OFlD??BSL zzlk(73qsj-vY;j1U7Se1><_Yf-y0B`q8;-yvHN(XYrA^VHKt*Co2jzlvSm@s(D3k! z`Ha5!0O^tD7VXI&*ZOMhRSULBzl8Y?`)sv${u<)=b;8F|B2;Fit)*lVFUextAjI_>_3)nofr#o zxzoV-;b%4P4c8*fG;^gYCW6dl?tsS8&6Y&=JskmsX)G%BU<+B`VC8f$z=-ZqPCqi^ zgQe>Ku0<50f1a?kImeFIs@IZnQ{6#TQ_cl5UeM=wWpvnNLSd4^R%%3cC9keP4_BMo zs#?+$C{}*3P+$Lp?R`yiq+V&f7*E-Y?3!Ll;tyDoczv9q0Qaa}W2vp5NQ>2#Dl>al zgrKX?qcVyHskf9d879P&tip(8@Q)J+Vqq0mJR^w=782HbYlChjx{X;^zHX8ermdLR z{%$mqtgpFNeP?+-^3(cg=j(E9%JPB$&D0R#nDAt_o-57tx_YDBv_9)@16Iq54@C;F z22$m7$Jmm~vBV*viNUPN#!sL;<|5&p@jS`SlsSx<*#R2OHmE2+`N4!bH6fJe`TaD8 zPdOzjoDF#xtJV4G?D@6cFK=-nJY%gTC^NHymr5#W-&~!#_`Tis9L|e76>tigEK`t@ zpQdM%cAiv`Tx&BUEm?BYcVBlxiPZ|OZ9W%ouDHh*ck=;h`dOy5x?j|x(KV;?aK0Bx zmZd${d=DJw)fyCAS#KNR1TU=kY7;LY1C*Mzg~P}7+1eg4L=r?(*D7Rl&xbThk2XuE zw^E&~UDr60m=k+6IimSQoAfqtTdhcs$&GcWSqZuNi<%i=EsgrQnWS#b| zJi5i{o&JrT&Ug?Z$!hu&B7z?Hg=I)@d;|rDaRkY05I$% zKc&?xZ9@=O@PauqZAK!e>!e`ao@GAIbYYo%@AowN5aBTL5_E?e&#PWNhOfT@R0L*F z7<*B9-=C5^YL)EdtX@R)g6FR4)&01p7G3I#!ns)nEQ^1cTif5?Yi=Csjq~s7OurZs zDY=spM_OB4r|*-SgE}u8l6;jbIqwL0`A!FIL1%=;vYEEZI-1&fRQ`ikGxhy76Z*?v z6}b^_&~9%=Fp5D*l33Lk;W?5lsJyj~H8j*+G4i;*N$rixnE7NA^ynyqur>#!WPk-9 ztMF4fi0xx_GDM#qBCZ!zfPC<E^T{wtxKk7e(zRLo>Sf95|h1>G;+CDq0)h;*?5Z1kut1u2&$YR6~}n0xlNbp zI`wY&sf0*co0VWXcd%Oh({nW2;03@GHZ;00k^V|WG&jYwbhme*wTkx$xy@9LOQT7} z8RT2|sj2VUX(wL_(9a_@@ZD+BKz81_Bi(u;nS0&5lsf0xONPXW&-xJSuC}-d7Kwb^ z?W;GUKCR89vlDRr7FIX`*~K;}936S|Y+~)T6SebkBLZ!i&lstEWq_g=OQM7*vu{Psf>Z7_WSwV4h~Cp0aj6p*Tq+4STvF7TL;c#MfZ#pbQ6E#$?t>Tz@EiE_Zb37^2L723C{ z$Qb>5kSBeEyfS?*am4!(rN6(`_I?jtCsO1bpcL*Zy@PYvd9dJA9)@2NqtW6a@2ix{Z)G% z0||tk;mta^>y^XgdRtBRugScu)$us$xjDPOkW9qf_X`W^}^NPwj&bqdsPqBQ;WnW4rgL$K^hq z$GLCRO_rubecU+NKOuGXGuDZ6WH`@ud2o58Vo12;PA@W(s4u-+VqnhAv7hI-S*!Uz zZOhX~d~IWb0qx8R>dl7NyWS>T`1NKW_<0lEd_t#YVeb3b*0gsWx7s===vKRwNWDIU zIbZH+8aUpKs1}YZOjLN_yo-4#bV($BfuAKYTkdI+jsB|yuCG5HO$D{4)wOS>2ITiV zl|GvIY{xevx!jfKF^2f)6Y<(BDQkVVqwk#)yMNV!G=^&0JhmswAu{OMV$`M0@q0w- z!4YlTVB2! zvR&}a$G=d3{%XGKYx>vuZKrZp?=WXed=t&QY2ckzDX*BmCD4)~b9wEB|K7{z4N1?+ zTReNqNLap9DC9I$27kC$@?o5^l%`m=Se*u06ooRV#d6u#FsNBn>HQ?Bj(aLzhl?z$ zXPXLXG%+7*NIts+-~Uy0cBq%+@1p3-3%|!AlMJPBD`!qQSxh{`N>{g*@h{&d(~fkl zpR&P%7XVj=8>@aej(nVG!f(_YtMF-)7!65H9KO*?k!^_KhOt)_Q&?K$~3J;*M9 z=yv5z$PaWArekK1&#e3v&fJ@Nyl0;8xo7O9qBhRgWPYk0>1PV%m}B1EI3jCTUJ?%V zit0!^BrRG{-ZfsCx%fFL92FMc_fcl5tS4661S7#O|8>@3b7>j^NvBEPT85gZyYD_uZMHWp{Rn71dJ6X6dv-iA%v|MKW=>Sy z%Rir|OlRKZt@)j#d-b06gc5JG&6eW)wy3YJVm!xLJ>KH6Zz;o#rQ2-_6v`R*Syrf# z85$0x7oO-ggz`y_Cq8_9PDEs1hgZM)aq2v7_r94s{n=?oOrE4jH(jLqD?<4><=T)t zoS&xRylwD*DrvvsChPtG>Im477pzZwAy!{{^hR2^J(Qf=c}sYq1E&<$gJ}UoOVN3*$fE>e|I+cNOKG@S^PY zv#qvzgd-+dh2?(0DjXn$>M0gnJ_fKosc051Ep zZvAh=|4$5mTChItcM4@Agr3q9La+bfL$eb?UBLH%?mz1-0$&l>ubC3~|0FdILg@4{ zLdfzTz6jt80>1w)Re`b=KrdhdFbh}&YypTE2q9Vk8{kjk2evdo4PXR71N;FIfJcB# zKpCJ3&;=L-OaT@FTL3af&?kTmzylBiNCT7rIshYpB>)ZZ0t5l>1CjxmfFeL0;2mHP zFa=lyYypUvK;Hm106*X?KmniuFap>B+yMT72*4viI^Z>+3h*bj0DBNH3s?sbF@yDV z05O0XzzE<52m(9+3||Y6`%>w1sDWO02TpTfIo?d6^sXP7N7>O0r&$R0g3>< zfLQ<;n0u!IN`OD<-}B?&^W(pt9~N-zrKcyDEJTonwY@XE0u+LHAq${m2lj*mwgQ9? zdxo?jyb1)Q3;*=6xe20Gplf*B!P*&QkPwLM1A%Q$z;6v~F9&D*`b8J$FbW#DAwI|g z?iiM#2Vpv%&;^ipvjDL;-VPq{ZydS9ye$|8zO4?(!!g7_e9Gd_dPpzG0txovaXHk` zfFNHmZg>kRxc_7j%>{=W!*(GALa<$I@KPagzUVNI*U5wPrH6T-9qR?P0UPK#!g0~i z0%YOfii99z0t=z#-}oq>8Y`$FaiouOdKoa28=(AK~C><6wtBAsz^LH;lt$Vh}*|1AnC;$Omt` z`NH*mVH@7G2-tqG4KH;C={5q0t-@awv%HH6s1gP@>aTwYN z*N;bd{%w$m@C2GjF8K44gaA_qmH`DR8E@O;`+tnL;aLPhPw@8P*q*`m?@^?|Hdvzz z*GY$Mm&1-S4sCqn=Z7}F@e9~?LwdmTF%z~y+!}nam#_^~_tC(vL3sZ?vAob_IDlOZ zgioU3bD{8iM?-uZAy{^=^gFKtf_Xs}(*LvLA&8U(S;*i|{!p?7t^zqhN+1By3w0Un zc?YY0>w$1M@D&aU0GmXD_0a$7hMq#oVAkjy_5j+SB~V3q;ScBq0yvRCx7!on@A*G| zJrE2C>M6r{4J%JC<-;qc4d*q%#)4=R+*%8$JmA}f!hSt3n?D~i9-qg{59)*4RnZf! z4O^hkRJcqZW1tQ*T5obxKjuU05+H{_|plx z2iIQ@<#idzAG@Mam%)`i9FqsQCcyu@RwHb~&2?Y_Z4`d4w86f^%Ig2Q83Y7%!2W-n zgHLeY;E(EnG6);Q@5KOmJM3r~p@&AH+#r6!-=W`{K+(+sC?OvPf5VSNNbpbDKf}U- zUzX4i-o+6A!Kfh+Y|%pg`2ObwCi-3WxB6i>U|slO8$GF1jqrm@PD*rBk|Bm>dz}eH=8th$S1LTRJW1#;3tNinG@`KO$ zf19hy*R+&1fZ7B8OheEGFoyqK80wv|06i;dfG{~7(C_~$@C - + - - + + - + - + diff --git a/samples/ApiDemos/res/layout/gadget_configure.xml b/samples/ApiDemos/res/layout/appwidget_configure.xml similarity index 92% rename from samples/ApiDemos/res/layout/gadget_configure.xml rename to samples/ApiDemos/res/layout/appwidget_configure.xml index bc9f40dc8..c041ad64b 100644 --- a/samples/ApiDemos/res/layout/gadget_configure.xml +++ b/samples/ApiDemos/res/layout/appwidget_configure.xml @@ -23,11 +23,11 @@ diff --git a/samples/ApiDemos/res/layout/gadget_provider.xml b/samples/ApiDemos/res/layout/appwidget_provider.xml similarity index 95% rename from samples/ApiDemos/res/layout/gadget_provider.xml rename to samples/ApiDemos/res/layout/appwidget_provider.xml index 49cf42f82..373db8f7d 100644 --- a/samples/ApiDemos/res/layout/gadget_provider.xml +++ b/samples/ApiDemos/res/layout/appwidget_provider.xml @@ -15,7 +15,7 @@ --> listSeparatorTextViewStyle - + - This text will be shown before the date in our example gadget. - Oh hai - %1$s: %2$s + This text will be shown before the date in our example widget. + Oh hai + %1$s: %2$s diff --git a/samples/ApiDemos/res/xml/gadget_provider.xml b/samples/ApiDemos/res/xml/appwidget_provider.xml similarity index 78% rename from samples/ApiDemos/res/xml/gadget_provider.xml rename to samples/ApiDemos/res/xml/appwidget_provider.xml index 9ad6845f4..5f5c7351e 100644 --- a/samples/ApiDemos/res/xml/gadget_provider.xml +++ b/samples/ApiDemos/res/xml/appwidget_provider.xml @@ -14,14 +14,14 @@ limitations under the License. --> - - + diff --git a/samples/ApiDemos/src/com/example/android/apis/gadget/ExampleGadgetConfigure.java b/samples/ApiDemos/src/com/example/android/apis/appwidget/ExampleAppWidgetConfigure.java similarity index 61% rename from samples/ApiDemos/src/com/example/android/apis/gadget/ExampleGadgetConfigure.java rename to samples/ApiDemos/src/com/example/android/apis/appwidget/ExampleAppWidgetConfigure.java index 03e7bb42f..e0a4c76b0 100644 --- a/samples/ApiDemos/src/com/example/android/apis/gadget/ExampleGadgetConfigure.java +++ b/samples/ApiDemos/src/com/example/android/apis/appwidget/ExampleAppWidgetConfigure.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.example.android.apis.gadget; +package com.example.android.apis.appwidget; import android.app.Activity; +import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.gadget.GadgetManager; import android.os.Bundle; import android.util.Log; import android.view.View; @@ -33,19 +33,19 @@ import java.util.ArrayList; import com.example.android.apis.R; /** - * The configuration screen for the ExampleGadgetProvider gadget sample. + * The configuration screen for the ExampleAppWidgetProvider widget sample. */ -public class ExampleGadgetConfigure extends Activity { - static final String TAG = "ExampleGadgetConfigure"; +public class ExampleAppWidgetConfigure extends Activity { + static final String TAG = "ExampleAppWidgetConfigure"; private static final String PREFS_NAME - = "com.example.android.apis.gadget.ExampleGadgetProvider"; + = "com.example.android.apis.appwidget.ExampleAppWidgetProvider"; private static final String PREF_PREFIX_KEY = "prefix_"; - int mGadgetId = GadgetManager.INVALID_GADGET_ID; - EditText mGadgetPrefix; + int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + EditText mAppWidgetPrefix; - public ExampleGadgetConfigure() { + public ExampleAppWidgetConfigure() { super(); } @@ -53,70 +53,70 @@ public class ExampleGadgetConfigure extends Activity { public void onCreate(Bundle icicle) { super.onCreate(icicle); - // Set the result to CANCELED. This will cause the gadget host to cancel - // out of the gadget placement if they press the back button. + // Set the result to CANCELED. This will cause the widget host to cancel + // out of the widget placement if they press the back button. setResult(RESULT_CANCELED); // Set the view layout resource to use. - setContentView(R.layout.gadget_configure); + setContentView(R.layout.appwidget_configure); // Find the EditText - mGadgetPrefix = (EditText)findViewById(R.id.gadget_prefix); + mAppWidgetPrefix = (EditText)findViewById(R.id.appwidget_prefix); // Bind the action for the save button. findViewById(R.id.save_button).setOnClickListener(mOnClickListener); - // Find the gadget id from the intent. + // Find the widget id from the intent. Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { - mGadgetId = extras.getInt( - GadgetManager.EXTRA_GADGET_ID, GadgetManager.INVALID_GADGET_ID); + mAppWidgetId = extras.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } - // If they gave us an intent without the gadget id, just bail. - if (mGadgetId == GadgetManager.INVALID_GADGET_ID) { + // If they gave us an intent without the widget id, just bail. + if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { finish(); } - mGadgetPrefix.setText(loadTitlePref(ExampleGadgetConfigure.this, mGadgetId)); + mAppWidgetPrefix.setText(loadTitlePref(ExampleAppWidgetConfigure.this, mAppWidgetId)); } View.OnClickListener mOnClickListener = new View.OnClickListener() { public void onClick(View v) { // When the button is clicked, save the string in our prefs and return that they // clicked OK. - saveTitlePref(ExampleGadgetConfigure.this, mGadgetId, - mGadgetPrefix.getText().toString()); + saveTitlePref(ExampleAppWidgetConfigure.this, mAppWidgetId, + mAppWidgetPrefix.getText().toString()); setResult(RESULT_OK); finish(); } }; - // Write the prefix to the SharedPreferences object for this gadget - static void saveTitlePref(Context context, int gadgetId, String text) { + // Write the prefix to the SharedPreferences object for this widget + static void saveTitlePref(Context context, int appWidgetId, String text) { SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit(); - prefs.putString(PREF_PREFIX_KEY + gadgetId, text); + prefs.putString(PREF_PREFIX_KEY + appWidgetId, text); prefs.commit(); } - // Read the prefix from the SharedPreferences object for this gadget. + // Read the prefix from the SharedPreferences object for this widget. // If there is no preference saved, get the default from a resource - static String loadTitlePref(Context context, int gadgetId) { + static String loadTitlePref(Context context, int appWidgetId) { SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0); String prefix = prefs.getString(PREF_PREFIX_KEY, null); if (prefix != null) { return prefix; } else { - return context.getString(R.string.gadget_prefix_default); + return context.getString(R.string.appwidget_prefix_default); } } - static void deleteTitlePref(Context context, int gadgetId) { + static void deleteTitlePref(Context context, int appWidgetId) { } - static void loadAllTitlePrefs(Context context, ArrayList gadgetIds, + static void loadAllTitlePrefs(Context context, ArrayList appWidgetIds, ArrayList texts) { } } diff --git a/samples/ApiDemos/src/com/example/android/apis/gadget/ExampleGadgetProvider.java b/samples/ApiDemos/src/com/example/android/apis/appwidget/ExampleAppWidgetProvider.java similarity index 58% rename from samples/ApiDemos/src/com/example/android/apis/gadget/ExampleGadgetProvider.java rename to samples/ApiDemos/src/com/example/android/apis/appwidget/ExampleAppWidgetProvider.java index eb1dab311..6977d3e6e 100644 --- a/samples/ApiDemos/src/com/example/android/apis/gadget/ExampleGadgetProvider.java +++ b/samples/ApiDemos/src/com/example/android/apis/appwidget/ExampleAppWidgetProvider.java @@ -14,14 +14,14 @@ * limitations under the License. */ -package com.example.android.apis.gadget; +package com.example.android.apis.appwidget; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.gadget.GadgetManager; -import android.gadget.GadgetProvider; import android.os.SystemClock; import android.util.Log; import android.widget.RemoteViews; @@ -33,89 +33,89 @@ import java.util.ArrayList; import com.example.android.apis.R; /** - * A gadget provider. We have a string that we pull from a preference in order to show - * the configuration settings and the current time when the gadget was updated. We also + * A widget provider. We have a string that we pull from a preference in order to show + * the configuration settings and the current time when the widget was updated. We also * register a BroadcastReceiver for time-changed and timezone-changed broadcasts, and * update then too. * *

See also the following files: *

    - *
  • ExampleGadgetConfigure.java
  • + *
  • ExampleAppWidgetConfigure.java
  • *
  • ExampleBroadcastReceiver.java
  • - *
  • res/layout/gadget_configure.xml
  • - *
  • res/layout/gadget_provider.xml
  • - *
  • res/xml/gadget_provider.xml
  • + *
  • res/layout/appwidget_configure.xml
  • + *
  • res/layout/appwidget_provider.xml
  • + *
  • res/xml/appwidget_provider.xml
  • *
*/ -public class ExampleGadgetProvider extends GadgetProvider { +public class ExampleAppWidgetProvider extends AppWidgetProvider { // log tag - private static final String TAG = "ExampleGadgetProvider"; + private static final String TAG = "ExampleAppWidgetProvider"; - public void onUpdate(Context context, GadgetManager gadgetManager, int[] gadgetIds) { + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.d(TAG, "onUpdate"); - // For each gadget that needs an update, get the text that we should display: + // For each widget that needs an update, get the text that we should display: // - Create a RemoteViews object for it // - Set the text in the RemoteViews object - // - Tell the GadgetManager to show that views object for the gadget. - final int N = gadgetIds.length; + // - Tell the AppWidgetManager to show that views object for the widget. + final int N = appWidgetIds.length; for (int i=0; i - - + + array = new ArrayList(2); array.add(project); - AndroidManifestHelper helper = new AndroidManifestHelper(project); - IFile manifest = helper.getManifestIFile(); + IFile manifest = AndroidManifestParser.getManifest(project); if (manifest != null) { array.add(manifest); } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java index 275addfbd..a960bda0d 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/DeviceChooserDialog.java @@ -36,12 +36,8 @@ import com.android.sdkuilib.AvdSelector; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.preference.IPreferenceStore; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; @@ -62,6 +58,12 @@ import org.eclipse.swt.widgets.Table; import java.util.ArrayList; +/** + * A dialog that lets the user choose a device to deploy an application. + * The user can either choose an exiting running device (including running emulators) + * or start a new emulator using an Android Virtual Device configuration that matches + * the current project. + */ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener { private final static int ICON_WIDTH = 16; @@ -373,15 +375,27 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener mViewer.setContentProvider(new ContentProvider()); mViewer.setLabelProvider(new LabelProvider()); mViewer.setInput(AndroidDebugBridge.getBridge()); - mViewer.addDoubleClickListener(new IDoubleClickListener() { - public void doubleClick(DoubleClickEvent event) { - ISelection selection = event.getSelection(); - if (selection instanceof IStructuredSelection) { - IStructuredSelection structuredSelection = (IStructuredSelection)selection; - Object object = structuredSelection.getFirstElement(); - if (object instanceof Device) { - mResponse.setDeviceToUse((Device)object); - } + + mDeviceTable.addSelectionListener(new SelectionAdapter() { + /** + * Handles single-click selection on the device selector. + * {@inheritDoc} + */ + @Override + public void widgetSelected(SelectionEvent e) { + handleDeviceSelection(); + } + + /** + * Handles double-click selection on the device selector. + * Note that the single-click handler will probably already have been called. + * {@inheritDoc} + */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + handleDeviceSelection(); + if (isOkButtonEnabled()) { + okPressed(); } } }); @@ -397,18 +411,14 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener layout.marginLeft = 30; offsetComp.setLayout(layout); - mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget, - false /*allowMultipleSelection*/); + mPreferredAvdSelector = new AvdSelector(offsetComp, getNonRunningAvds(), mProjectTarget); mPreferredAvdSelector.setTableHeightHint(100); mPreferredAvdSelector.setEnabled(false); - mDeviceTable.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - handleDeviceSelection(); - } - }); - mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { + /** + * Handles single-click selection on the AVD selector. + * {@inheritDoc} + */ @Override public void widgetSelected(SelectionEvent e) { if (mDisableAvdSelectionChange == false) { @@ -416,6 +426,22 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener enableOkButton(); } } + + /** + * Handles double-click selection on the AVD selector. + * + * Note that the single-click handler will probably already have been called + * but the selected item can have changed in between. + * + * {@inheritDoc} + */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + widgetSelected(e); + if (isOkButtonEnabled()) { + okPressed(); + } + } }); AndroidDebugBridge.addDeviceChangeListener(this); @@ -586,6 +612,14 @@ public class DeviceChooserDialog extends Dialog implements IDeviceChangeListener okButton.setEnabled(mResponse.getAvdToLaunch() != null); } } + + /** + * Returns true if the ok button is enabled. + */ + private boolean isOkButtonEnabled() { + Button okButton = getButton(IDialogConstants.OK_ID); + return okButton.isEnabled(); + } /** * Executes the {@link Runnable} in the UI thread. diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java index 5b4cdbb6f..b898f63c5 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/launch/EmulatorConfigTab.java @@ -178,8 +178,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { mPreferredAvdLabel = new Label(offsetComp, SWT.NONE); mPreferredAvdLabel.setText("Select a preferred Android Virtual Device:"); AvdInfo[] avds = new AvdInfo[0]; - mPreferredAvdSelector = new AvdSelector(offsetComp, avds, - false /*allowMultipleSelection*/); + mPreferredAvdSelector = new AvdSelector(offsetComp, avds); mPreferredAvdSelector.setTableHeightHint(100); mPreferredAvdSelector.setSelectionListener(new SelectionAdapter() { @Override diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java index b1f8ffc03..fd0c045d8 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java @@ -19,9 +19,9 @@ package com.android.ide.eclipse.adt.project; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer; import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; import com.android.ide.eclipse.common.project.AndroidManifestParser; +import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; @@ -389,18 +389,16 @@ public final class ProjectHelper { continue; } - AndroidManifestHelper androidManifest = new AndroidManifestHelper(p); - // check that there is indeed a manifest file. - if (androidManifest.getManifestIFile() == null) { + IFile manifestFile = AndroidManifestParser.getManifest(p); + if (manifestFile == null) { // no file? skip this project. continue; } AndroidManifestParser parser = null; try { - parser = AndroidManifestParser.parseForData( - androidManifest.getManifestIFile()); + parser = AndroidManifestParser.parseForData(manifestFile); } catch (CoreException e) { // skip this project. continue; diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java index a8852e7de..34391c236 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java @@ -44,7 +44,7 @@ public class AndroidTargetData { public final static int DESCRIPTOR_RESOURCES = 5; public final static int DESCRIPTOR_SEARCHABLE = 6; public final static int DESCRIPTOR_PREFERENCES = 7; - public final static int DESCRIPTOR_GADGET_PROVIDER = 8; + public final static int DESCRIPTOR_APPWIDGET_PROVIDER = 8; public final static class LayoutBridge { /** Link to the layout bridge */ @@ -158,8 +158,8 @@ public class AndroidTargetData { return ResourcesDescriptors.getInstance(); case DESCRIPTOR_PREFERENCES: return mXmlDescriptors.getPreferencesProvider(); - case DESCRIPTOR_GADGET_PROVIDER: - return mXmlDescriptors.getGadgetProvider(); + case DESCRIPTOR_APPWIDGET_PROVIDER: + return mXmlDescriptors.getAppWidgetProvider(); case DESCRIPTOR_SEARCHABLE: return mXmlDescriptors.getSearchableProvider(); default : diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java index 04baeba92..67eec78b9 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java @@ -203,9 +203,9 @@ public final class AndroidTargetParser { attrsManifestXmlParser); Map> enumValueMap = attrsXmlParser.getEnumFlagValues(); - Map xmlGadgetMap = null; + Map xmlAppWidgetMap = null; if (mAndroidTarget.getApiVersionNumber() >= 3) { - xmlGadgetMap = collectGadgetDefinitions(attrsXmlParser); + xmlAppWidgetMap = collectAppWidgetDefinitions(attrsXmlParser); } if (progress.isCanceled()) { @@ -241,7 +241,7 @@ public final class AndroidTargetParser { XmlDescriptors xmlDescriptors = new XmlDescriptors(); xmlDescriptors.updateDescriptors( xmlSearchableMap, - xmlGadgetMap, + xmlAppWidgetMap, preferencesInfo, preferenceGroupsInfo); progress.worked(1); @@ -611,23 +611,23 @@ public final class AndroidTargetParser { } /** - * Collects all gadgetProviderInfo definition information from the attrs.xml and returns it. + * Collects all appWidgetProviderInfo definition information from the attrs.xml and returns it. * * @param attrsXmlParser The parser of the attrs.xml file */ - private Map collectGadgetDefinitions( + private Map collectAppWidgetDefinitions( AttrsXmlParser attrsXmlParser) { Map map = attrsXmlParser.getDeclareStyleableList(); Map map2 = new HashMap(); - for (String key : new String[] { "GadgetProviderInfo" }) { //$NON-NLS-1$ + for (String key : new String[] { "AppWidgetProviderInfo" }) { //$NON-NLS-1$ if (map.containsKey(key)) { map2.put(key, map.get(key)); } else { AdtPlugin.log(IStatus.WARNING, - "Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + "AppWidget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ key, attrsXmlParser.getOsAttrsXmlPath()); AdtPlugin.printErrorToConsole("Android Framework Parser", - String.format("Gadget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + String.format("AppWidget declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ key, attrsXmlParser.getOsAttrsXmlPath())); } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java index 33ec2bcff..6c4f4ba57 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java @@ -24,7 +24,7 @@ package com.android.ide.eclipse.adt.wizards.newproject; import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; import com.android.sdklib.project.ProjectProperties; @@ -36,6 +36,7 @@ import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; @@ -821,26 +822,41 @@ public class NewProjectCreationPage extends WizardPage { Path path = new Path(f.getPath()); String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); - AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); - if (!manifest.exists()) { + + AndroidManifestParser manifestData = null; + try { + manifestData = AndroidManifestParser.parseForData(osPath); + } catch (CoreException e1) { + // ignore any parsing issue + } + if (manifestData == null) { return; } String packageName = null; String activityName = null; - String minSdkVersion = null; + int minSdkVersion = 0; // 0 means no minSdkVersion provided in the manifest try { - packageName = manifest.getPackageName(); - activityName = manifest.getActivityName(1); - minSdkVersion = manifest.getMinSdkVersion(); + packageName = manifestData.getPackage(); + minSdkVersion = manifestData.getApiLevelRequirement(); + + // try to get the first launcher activity. If none, just take the first activity. + activityName = manifestData.getLauncherActivity(); + if (activityName == null) { + String[] activities = manifestData.getActivities(); + if (activities != null && activities.length > 0) { + activityName = activities[0]; + } + } } catch (Exception e) { // ignore exceptions } - if (packageName != null && packageName.length() > 0) { mPackageNameField.setText(packageName); } + + activityName = AndroidManifestParser.extractActivityName(activityName, packageName); if (activityName != null && activityName.length() > 0) { mInternalActivityNameUpdate = true; @@ -917,12 +933,10 @@ public class NewProjectCreationPage extends WizardPage { } } - if (!foundTarget && minSdkVersion != null) { + if (!foundTarget && minSdkVersion > 0) { try { - int sdkVersion = Integer.parseInt(minSdkVersion); - for (IAndroidTarget target : mSdkTargetSelector.getTargets()) { - if (target.getApiVersionNumber() == sdkVersion) { + if (target.getApiVersionNumber() == minSdkVersion) { mSdkTargetSelector.setSelection(target); foundTarget = true; break; @@ -945,7 +959,8 @@ public class NewProjectCreationPage extends WizardPage { if (!foundTarget) { mInternalMinSdkVersionUpdate = true; - mMinSdkVersionField.setText(minSdkVersion == null ? "" : minSdkVersion); //$NON-NLS-1$ + mMinSdkVersionField.setText( + minSdkVersion <= 0 ? "" : Integer.toString(minSdkVersion)); //$NON-NLS-1$ mInternalMinSdkVersionUpdate = false; } } @@ -1072,8 +1087,8 @@ public class NewProjectCreationPage extends WizardPage { // Check there's an android manifest in the directory String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); - AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); - if (!manifest.exists()) { + File manifestFile = new File(osPath); + if (!manifestFile.isFile()) { return setStatus( String.format("File %1$s not found in %2$s.", AndroidConstants.FN_ANDROID_MANIFEST, f.getName()), @@ -1081,15 +1096,24 @@ public class NewProjectCreationPage extends WizardPage { } // Parse it and check the important fields. - String packageName = manifest.getPackageName(); + AndroidManifestParser manifestData; + try { + manifestData = AndroidManifestParser.parseForData(osPath); + } catch (CoreException e) { + return setStatus( + String.format("File %1$s could not be parsed.", osPath), + MSG_ERROR); + } + + String packageName = manifestData.getPackage(); if (packageName == null || packageName.length() == 0) { return setStatus( String.format("No package name defined in %1$s.", osPath), MSG_ERROR); } - String activityName = manifest.getActivityName(1); - if (activityName == null || activityName.length() == 0) { + String[] activities = manifestData.getActivities(); + if (activities == null || activities.length == 0) { // This is acceptable now as long as no activity needs to be created if (isCreateActivity()) { return setStatus( @@ -1097,7 +1121,7 @@ public class NewProjectCreationPage extends WizardPage { MSG_ERROR); } } - + // If there's already a .project, tell the user to use import instead. if (path.append(".project").toFile().exists()) { //$NON-NLS-1$ return setStatus("An Eclipse project already exists in this directory. Consider using File > Import > Existing Project instead.", diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java deleted file mode 100644 index cd238d2bf..000000000 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php - * - * 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.ide.eclipse.common.project; - -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.xml.sax.InputSource; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathExpressionException; - -/** - * Utility class that manages the AndroidManifest.xml file. - *

- * All the get method work by XPath. Repeated calls to those may warrant using - * {@link AndroidManifestParser} instead. - */ -public class AndroidManifestHelper { - private IFile mManifestIFile; - private File mManifestFile; - private XPath mXPath; - - /** - * Creates an AndroidManifest based on an existing Eclipse {@link IProject} object. - *

- * Use {@link #exists()} to check if the manifest file really exists in the project. - * - * @param project The project to search for the manifest. - */ - public AndroidManifestHelper(IProject project) { - mXPath = AndroidXPathFactory.newXPath(); - mManifestIFile = getManifest(project); - } - - /** - * Creates an AndroidManifest based on a file path. - *

- * Use {@link #exists()} to check if the manifest file really exists. - * - * @param osManifestFilePath the os path to the AndroidManifest.xml file. - */ - public AndroidManifestHelper(String osManifestFilePath) { - mXPath = AndroidXPathFactory.newXPath(); - mManifestFile = new File(osManifestFilePath); - } - - - /** - * Returns the underlying {@link IFile} for the android manifest XML file, if found in the - * given Eclipse project. - * - * Always return null if the constructor that takes an {@link IProject} was NOT called. - * - * @return The IFile for the androidManifest.xml or null if no such file could be found. - */ - public IFile getManifestIFile() { - return mManifestIFile; - } - - /** - * Returns the underlying {@link File} for the android manifest XML file. - */ - public File getManifestFile() { - if (mManifestIFile != null) { - return mManifestIFile.getLocation().toFile(); - } - - return mManifestFile; - } - - /** - * Returns the package name defined in the manifest file. - * - * @return A String object with the package or null if any error happened. - */ - public String getPackageName() { - try { - return mXPath.evaluate("/manifest/@package", getSource()); //$NON-NLS-1$ - } catch (XPathExpressionException e1) { - // If the XPath failed to evaluate, we'll return null. - } catch (Exception e) { - // if this happens this is due to the resource being out of sync. - // so we must refresh it and do it again - - // for any other kind of exception we must return null as well; - } - - return null; - } - - /** - * Returns the minSdkVersion defined in the manifest file. - * - * @return A String object with the package or null if any error happened. - */ - public String getMinSdkVersion() { - try { - return mXPath.evaluate("/manifest/uses-sdk/@" //$NON-NLS-1$ - + AndroidXPathFactory.DEFAULT_NS_PREFIX - + ":minSdkVersion", getSource()); //$NON-NLS-1$ - } catch (XPathExpressionException e1) { - // If the XPath failed to evaluate, we'll return null. - } catch (Exception e) { - // if this happens this is due to the resource being out of sync. - // so we must refresh it and do it again - - // for any other kind of exception we must return null as well; - } - - return null; - } - /** - * Returns the i-th activity defined in the manifest file. - * - * @param index The 1-based index of the activity to return. - * @return A String object with the activity or null if any error happened. - */ - public String getActivityName(int index) { - try { - return mXPath.evaluate("/manifest/application/activity[" //$NON-NLS-1$ - + index - + "]/@" //$NON-NLS-1$ - + AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$ - getSource()); - } catch (XPathExpressionException e1) { - // If the XPath failed to evaluate, we'll return null. - } catch (Exception e) { - // if this happens this is due to the resource being out of sync. - // so we must refresh it and do it again - - // for any other kind of exception we must return null as well; - } - return null; - } - - /** - * Returns an IFile object representing the manifest for the specified - * project. - * - * @param project The project containing the manifest file. - * @return An IFile object pointing to the manifest or null if the manifest - * is missing. - */ - public static IFile getManifest(IProject project) { - IResource r = project.findMember(AndroidConstants.WS_SEP - + AndroidConstants.FN_ANDROID_MANIFEST); - - if (r == null || r.exists() == false || (r instanceof IFile) == false) { - return null; - } - return (IFile) r; - } - - /** - * Combines a java package, with a class value from the manifest to make a fully qualified - * class name - * @param javaPackage the java package from the manifest. - * @param className the class name from the manifest. - * @return the fully qualified class name. - */ - public static String combinePackageAndClassName(String javaPackage, String className) { - if (className == null || className.length() == 0) { - return javaPackage; - } - if (javaPackage == null || javaPackage.length() == 0) { - return className; - } - - // the class name can be a subpackage (starts with a '.' - // char), a simple class name (no dot), or a full java package - boolean startWithDot = (className.charAt(0) == '.'); - boolean hasDot = (className.indexOf('.') != -1); - if (startWithDot || hasDot == false) { - - // add the concatenation of the package and class name - if (startWithDot) { - return javaPackage + className; - } else { - return javaPackage + '.' + className; - } - } else { - // just add the class as it should be a fully qualified java name. - return className; - } - } - - - - /** - * Returns true either if an androidManifest.xml file was found in the project - * or if the given file path exists. - */ - public boolean exists() { - if (mManifestIFile != null) { - return mManifestIFile.exists(); - } else if (mManifestFile != null) { - return mManifestFile.exists(); - } - - return false; - } - - /** - * Returns an InputSource for XPath. - * - * @throws FileNotFoundException if file does not exist. - * @throws CoreException if the {@link IFile} does not exist. - */ - private InputSource getSource() throws FileNotFoundException, CoreException { - if (mManifestIFile != null) { - return new InputSource(mManifestIFile.getContents()); - } else if (mManifestFile != null) { - return new InputSource(new FileReader(mManifestFile)); - } - - return null; - } - -} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java index 850c59d71..b2817ff0d 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java @@ -22,6 +22,8 @@ import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IJavaProject; import org.xml.sax.Attributes; @@ -30,6 +32,8 @@ import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; +import java.io.File; +import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.Set; @@ -56,6 +60,8 @@ public class AndroidManifestParser { private final static String NODE_ACTION = "action"; //$NON-NLS-1$ private final static String NODE_CATEGORY = "category"; //$NON-NLS-1$ private final static String NODE_USES_SDK = "uses-sdk"; //$NON-NLS-1$ + private final static String NODE_INSTRUMENTATION = "instrumentation"; //$NON-NLS-1$ + private final static String NODE_USES_LIBRARY = "uses-library"; //$NON-NLS-1$ private final static int LEVEL_MANIFEST = 0; private final static int LEVEL_APPLICATION = 1; @@ -66,6 +72,12 @@ public class AndroidManifestParser { private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ + /** + * XML error & data handler used when parsing the AndroidManifest.xml file. + *

+ * This serves both as an {@link XmlErrorHandler} to report errors and as a data repository + * to collect data from the manifest. + */ private static class ManifestHandler extends XmlErrorHandler { //--- data read from the parsing @@ -82,6 +94,10 @@ public class AndroidManifestParser { private Boolean mDebuggable = null; /** API level requirement. if 0 the attribute was not present. */ private int mApiLevelRequirement = 0; + /** List of all instrumentations declared by the manifest */ + private final ArrayList mInstrumentations = new ArrayList(); + /** List of all libraries in use declared by the manifest */ + private final ArrayList mLibraries = new ArrayList(); //--- temporary data/flags used during parsing private IJavaProject mJavaProject; @@ -95,12 +111,13 @@ public class AndroidManifestParser { private Locator mLocator; /** - * - * @param manifestFile - * @param errorListener - * @param gatherData - * @param javaProject - * @param markErrors + * Creates a new {@link ManifestHandler}, which is also an {@link XmlErrorHandler}. + * + * @param manifestFile The manifest file being parsed. Can be null. + * @param errorListener An optional error listener. + * @param gatherData True if data should be gathered. + * @param javaProject The java project holding the manifest file. Can be null. + * @param markErrors True if errors should be marked as Eclipse Markers on the resource. */ ManifestHandler(IFile manifestFile, XmlErrorListener errorListener, boolean gatherData, IJavaProject javaProject, boolean markErrors) { @@ -160,6 +177,23 @@ public class AndroidManifestParser { return mApiLevelRequirement; } + /** + * Returns the list of instrumentations found in the manifest. + * @return An array of instrumentation names, or empty if no instrumentations were + * found. + */ + String[] getInstrumentations() { + return mInstrumentations.toArray(new String[mInstrumentations.size()]); + } + + /** + * Returns the list of libraries in use found in the manifest. + * @return An array of library names, or empty if no libraries were found. + */ + String[] getUsesLibraries() { + return mLibraries.toArray(new String[mLibraries.size()]); + } + /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator) */ @@ -217,7 +251,13 @@ public class AndroidManifestParser { } catch (NumberFormatException e) { handleError(e, -1 /* lineNumber */); } - } + } else if (NODE_INSTRUMENTATION.equals(localName)) { + value = getAttributeValue(attributes, ATTRIBUTE_NAME, + true /* hasNamespace */); + if (value != null) { + mInstrumentations.add(value); + } + } break; case LEVEL_ACTIVITY: if (NODE_ACTIVITY.equals(localName)) { @@ -232,7 +272,13 @@ public class AndroidManifestParser { } else if (NODE_PROVIDER.equals(localName)) { processNode(attributes, AndroidConstants.CLASS_CONTENTPROVIDER); mValidLevel++; - } + } else if (NODE_USES_LIBRARY.equals(localName)) { + value = getAttributeValue(attributes, ATTRIBUTE_NAME, + true /* hasNamespace */); + if (value != null) { + mLibraries.add(value); + } + } break; case LEVEL_INTENT_FILTER: // only process this level if we are in an activity @@ -355,8 +401,7 @@ public class AndroidManifestParser { String activityName = getAttributeValue(attributes, ATTRIBUTE_NAME, true /* hasNamespace */); if (activityName != null) { - mCurrentActivity = AndroidManifestHelper.combinePackageAndClassName(mPackage, - activityName); + mCurrentActivity = combinePackageAndClassName(mPackage, activityName); mActivities.add(mCurrentActivity); if (mMarkErrors) { @@ -387,8 +432,7 @@ public class AndroidManifestParser { String serviceName = getAttributeValue(attributes, ATTRIBUTE_NAME, true /* hasNamespace */); if (serviceName != null) { - serviceName = AndroidManifestHelper.combinePackageAndClassName(mPackage, - serviceName); + serviceName = combinePackageAndClassName(mPackage, serviceName); if (mMarkErrors) { checkClass(serviceName, superClassName, false /* testVisibility */); @@ -412,6 +456,9 @@ public class AndroidManifestParser { * the class or of its constructors. */ private void checkClass(String className, String superClassName, boolean testVisibility) { + if (mJavaProject == null) { + return; + } // we need to check the validity of the activity. String result = BaseProjectHelper.testClassForManifest(mJavaProject, className, superClassName, testVisibility); @@ -477,6 +524,8 @@ public class AndroidManifestParser { private final String[] mProcesses; private final Boolean mDebuggable; private final int mApiLevelRequirement; + private final String[] mInstrumentations; + private final String[] mLibraries; static { sParserFactory = SAXParserFactory.newInstance(); @@ -484,8 +533,12 @@ public class AndroidManifestParser { } /** - * Parses the Android Manifest, and returns an object containing - * the result of the parsing. + * Parses the Android Manifest, and returns an object containing the result of the parsing. + *

+ * This method is useful to parse a specific {@link IFile} in a Java project. + *

+ * If you only want to gather data, consider {@link #parseForData(IFile)} instead. + * * @param javaProject The java project. * @param manifestFile the {@link IFile} representing the manifest file. * @param errorListener @@ -496,8 +549,12 @@ public class AndroidManifestParser { * @return an {@link AndroidManifestParser} or null if the parsing failed. * @throws CoreException */ - public static AndroidManifestParser parse(IJavaProject javaProject, IFile manifestFile, - XmlErrorListener errorListener, boolean gatherData, boolean markErrors) + public static AndroidManifestParser parse( + IJavaProject javaProject, + IFile manifestFile, + XmlErrorListener errorListener, + boolean gatherData, + boolean markErrors) throws CoreException { try { SAXParser parser = sParserFactory.newSAXParser(); @@ -512,7 +569,51 @@ public class AndroidManifestParser { return new AndroidManifestParser(manifestHandler.getPackage(), manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), manifestHandler.getProcesses(), manifestHandler.getDebuggable(), - manifestHandler.getApiLevelRequirement()); + manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(), + manifestHandler.getUsesLibraries()); + } catch (ParserConfigurationException e) { + } catch (SAXException e) { + } catch (IOException e) { + } finally { + } + + return null; + } + + /** + * Parses the Android Manifest, and returns an object containing the result of the parsing. + *

+ * This version parses a real {@link File} file given by an actual path, which is useful for + * parsing a file that is not part of an Eclipse Java project. + *

+ * It assumes errors cannot be marked on the file and that data gathering is enabled. + * + * @param manifestFile the manifest file to parse. + * @return an {@link AndroidManifestParser} or null if the parsing failed. + * @throws CoreException + */ + private static AndroidManifestParser parse(File manifestFile) + throws CoreException { + try { + SAXParser parser = sParserFactory.newSAXParser(); + + ManifestHandler manifestHandler = new ManifestHandler( + null, //manifestFile + null, //errorListener + true, //gatherData + null, //javaProject + false //markErrors + ); + + parser.parse(new InputSource(new FileReader(manifestFile)), manifestHandler); + + // get the result from the handler + + return new AndroidManifestParser(manifestHandler.getPackage(), + manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), + manifestHandler.getProcesses(), manifestHandler.getDebuggable(), + manifestHandler.getApiLevelRequirement(), manifestHandler.getInstrumentations(), + manifestHandler.getUsesLibraries()); } catch (ParserConfigurationException e) { } catch (SAXException e) { } catch (IOException e) { @@ -535,13 +636,16 @@ public class AndroidManifestParser { * @return an {@link AndroidManifestParser} or null if the parsing failed. * @throws CoreException */ - public static AndroidManifestParser parse(IJavaProject javaProject, - XmlErrorListener errorListener, boolean gatherData, boolean markErrors) + public static AndroidManifestParser parse( + IJavaProject javaProject, + XmlErrorListener errorListener, + boolean gatherData, + boolean markErrors) throws CoreException { try { SAXParser parser = sParserFactory.newSAXParser(); - IFile manifestFile = AndroidManifestHelper.getManifest(javaProject.getProject()); + IFile manifestFile = getManifest(javaProject.getProject()); if (manifestFile != null) { ManifestHandler manifestHandler = new ManifestHandler(manifestFile, errorListener, gatherData, javaProject, markErrors); @@ -552,7 +656,8 @@ public class AndroidManifestParser { return new AndroidManifestParser(manifestHandler.getPackage(), manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), manifestHandler.getProcesses(), manifestHandler.getDebuggable(), - manifestHandler.getApiLevelRequirement()); + manifestHandler.getApiLevelRequirement(), + manifestHandler.getInstrumentations(), manifestHandler.getUsesLibraries()); } } catch (ParserConfigurationException e) { } catch (SAXException e) { @@ -588,6 +693,18 @@ public class AndroidManifestParser { true /* gatherData */, false /* markErrors */); } + /** + * Parses the manifest file, and collects data. + * + * @param osManifestFilePath The OS path of the manifest file to parse. + * @return an {@link AndroidManifestParser} or null if the parsing failed. + * @throws CoreException + */ + public static AndroidManifestParser parseForData(String osManifestFilePath) + throws CoreException { + return parse(new File(osManifestFilePath)); + } + /** * Returns the package defined in the manifest, if found. * @return The package name or null if not found. @@ -633,6 +750,22 @@ public class AndroidManifestParser { public int getApiLevelRequirement() { return mApiLevelRequirement; } + + /** + * Returns the list of instrumentations found in the manifest. + * @return An array of fully qualified class names, or empty if no instrumentations were found. + */ + public String[] getInstrumentations() { + return mInstrumentations; + } + + /** + * Returns the list of libraries in use found in the manifest. + * @return An array of library names, or empty if no uses-library declarations were found. + */ + public String[] getUsesLibraries() { + return mLibraries; + } /** @@ -647,15 +780,92 @@ public class AndroidManifestParser { * @param processes the list of custom processes declared in the manifest. * @param debuggable the debuggable attribute, or null if not set. * @param apiLevelRequirement the minSdkVersion attribute value or 0 if not set. + * @param instrumentations the list of instrumentations parsed from the manifest. + * @param libraries the list of libraries in use parsed from the manifest. */ private AndroidManifestParser(String javaPackage, String[] activities, String launcherActivity, String[] processes, Boolean debuggable, - int apiLevelRequirement) { + int apiLevelRequirement, String[] instrumentations, String[] libraries) { mJavaPackage = javaPackage; mActivities = activities; mLauncherActivity = launcherActivity; mProcesses = processes; mDebuggable = debuggable; mApiLevelRequirement = apiLevelRequirement; + mInstrumentations = instrumentations; + mLibraries = libraries; + } + + /** + * Returns an IFile object representing the manifest for the specified + * project. + * + * @param project The project containing the manifest file. + * @return An IFile object pointing to the manifest or null if the manifest + * is missing. + */ + public static IFile getManifest(IProject project) { + IResource r = project.findMember(AndroidConstants.WS_SEP + + AndroidConstants.FN_ANDROID_MANIFEST); + + if (r == null || r.exists() == false || (r instanceof IFile) == false) { + return null; + } + return (IFile) r; + } + + /** + * Combines a java package, with a class value from the manifest to make a fully qualified + * class name + * @param javaPackage the java package from the manifest. + * @param className the class name from the manifest. + * @return the fully qualified class name. + */ + public static String combinePackageAndClassName(String javaPackage, String className) { + if (className == null || className.length() == 0) { + return javaPackage; + } + if (javaPackage == null || javaPackage.length() == 0) { + return className; + } + + // the class name can be a subpackage (starts with a '.' + // char), a simple class name (no dot), or a full java package + boolean startWithDot = (className.charAt(0) == '.'); + boolean hasDot = (className.indexOf('.') != -1); + if (startWithDot || hasDot == false) { + + // add the concatenation of the package and class name + if (startWithDot) { + return javaPackage + className; + } else { + return javaPackage + '.' + className; + } + } else { + // just add the class as it should be a fully qualified java name. + return className; + } + } + + /** + * Given a fully qualified activity name (e.g. com.foo.test.MyClass) and given a project + * package base name (e.g. com.foo), returns the relative activity name that would be used + * the "name" attribute of an "activity" element. + * + * @param fullActivityName a fully qualified activity class name, e.g. "com.foo.test.MyClass" + * @param packageName The project base package name, e.g. "com.foo" + * @return The relative activity name if it can be computed or the original fullActivityName. + */ + public static String extractActivityName(String fullActivityName, String packageName) { + if (packageName != null && fullActivityName != null) { + if (packageName.length() > 0 && fullActivityName.startsWith(packageName)) { + String name = fullActivityName.substring(packageName.length()); + if (name.length() > 0 && name.charAt(0) == '.') { + return name; + } + } + } + + return fullActivityName; } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java index fda55c450..1810ad295 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java @@ -86,8 +86,13 @@ public class XmlErrorHandler extends DefaultHandler { */ @Override public void warning(SAXParseException exception) throws SAXException { - BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), - exception.getLineNumber(), IMarker.SEVERITY_WARNING); + if (mFile != null) { + BaseProjectHelper.addMarker(mFile, + AndroidConstants.MARKER_XML, + exception.getMessage(), + exception.getLineNumber(), + IMarker.SEVERITY_WARNING); + } } protected final IFile getFile() { @@ -104,12 +109,19 @@ public class XmlErrorHandler extends DefaultHandler { mErrorListener.errorFound(); } - if (lineNumber != -1) { - BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), - lineNumber, IMarker.SEVERITY_ERROR); - } else { - BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), - IMarker.SEVERITY_ERROR); + if (mFile != null) { + if (lineNumber != -1) { + BaseProjectHelper.addMarker(mFile, + AndroidConstants.MARKER_XML, + exception.getMessage(), + lineNumber, + IMarker.SEVERITY_ERROR); + } else { + BaseProjectHelper.addMarker(mFile, + AndroidConstants.MARKER_XML, + exception.getMessage(), + IMarker.SEVERITY_ERROR); + } } } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java index 332ce6f83..0d0883ecb 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java @@ -442,11 +442,15 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor { tooltip = ((TextAttributeDescriptor) choice).getTooltip(); } + // Get the namespace URI for the attribute. Note that some attributes + // do not have a namespace and thus return null here. String nsUri = ((AttributeDescriptor)choice).getNamespaceUri(); - nsPrefix = nsUriMap.get(nsUri); - if (nsPrefix == null) { - nsPrefix = lookupNamespacePrefix(currentNode, nsUri); - nsUriMap.put(nsUri, nsPrefix); + if (nsUri != null) { + nsPrefix = nsUriMap.get(nsUri); + if (nsPrefix == null) { + nsPrefix = lookupNamespacePrefix(currentNode, nsUri); + nsUriMap.put(nsUri, nsPrefix); + } } if (nsPrefix != null) { nsPrefix += ":"; //$NON-NLS-1$ diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java index 81fd2ed0c..94ad87a11 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java @@ -18,12 +18,14 @@ package com.android.ide.eclipse.editors.layout; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.editors.resources.manager.ProjectClassLoader; import com.android.ide.eclipse.editors.resources.manager.ProjectResources; import com.android.layoutlib.api.IProjectCallback; +import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; import java.lang.reflect.Constructor; import java.util.HashMap; @@ -83,17 +85,20 @@ public final class ProjectCallback implements IProjectCallback { } /** - * {@inheritDoc} - * * Returns the namespace for the project. The namespace contains a standard part + the * application package. + * + * @return The package namespace of the project or null in case of error. */ public String getNamespace() { if (mNamespace == null) { - AndroidManifestHelper manifest = new AndroidManifestHelper(mProject); - String javaPackage = manifest.getPackageName(); - - mNamespace = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, javaPackage); + IFile manifestFile = AndroidManifestParser.getManifest(mProject); + try { + AndroidManifestParser data = AndroidManifestParser.parseForData(manifestFile); + String javaPackage = data.getPackage(); + mNamespace = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, javaPackage); + } catch (CoreException e) { + } } return mNamespace; diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java index 7caa50f12..c3f4dd807 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java @@ -17,6 +17,8 @@ package com.android.ide.eclipse.editors.layout.descriptors; import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; +import com.android.ide.eclipse.common.resources.ResourceType; import com.android.ide.eclipse.common.resources.ViewClassInfo; import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; @@ -25,6 +27,7 @@ import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.descriptors.ReferenceAttributeDescriptor; import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; import com.android.sdklib.SdkConstants; @@ -131,8 +134,23 @@ public final class LayoutDescriptors implements IDescriptorProvider { String xml_name = info.getShortClassName(); String tooltip = info.getJavaDoc(); - // Process all View attributes ArrayList attributes = new ArrayList(); + + // All views and groups have an implicit "style" attribute which is a reference. + AttributeInfo styleInfo = new DeclareStyleableInfo.AttributeInfo( + "style", //$NON-NLS-1$ xmlLocalName + new DeclareStyleableInfo.AttributeInfo.Format[] { + DeclareStyleableInfo.AttributeInfo.Format.REFERENCE + }); + styleInfo.setJavaDoc("A reference to a custom style"); //tooltip + DescriptorsUtils.appendAttribute(attributes, + "style", //$NON-NLS-1$ + null, //nsUri + styleInfo, + false, //required + null); // overrides + + // Process all View attributes DescriptorsUtils.appendAttributes(attributes, null, // elementName SdkConstants.NS_RESOURCES, @@ -155,7 +173,7 @@ public final class LayoutDescriptors implements IDescriptorProvider { null /* overrides */); } } - + // Process all LayoutParams attributes ArrayList layoutAttributes = new ArrayList(); LayoutParamsInfo layoutParams = info.getLayoutData(); diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java index f886080f0..e32be86b0 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java @@ -17,7 +17,7 @@ package com.android.ide.eclipse.editors.manifest.model; import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.editors.AndroidEditor; import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; @@ -251,8 +251,8 @@ public class UiClassAttributeNode extends UiTextAttributeNode { String javaPackage = getManifestPackage(); // build the fully qualified name of the class - String className = AndroidManifestHelper.combinePackageAndClassName(javaPackage, - textValue); + String className = AndroidManifestParser.combinePackageAndClassName( + javaPackage, textValue); // only test the vilibility for activities. boolean testVisibility = AndroidConstants.CLASS_ACTIVITY.equals( diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java index 455c825af..fa0930553 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java @@ -18,7 +18,7 @@ package com.android.ide.eclipse.editors.resources.manager; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.resources.ResourceType; import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener; @@ -28,6 +28,7 @@ import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -120,7 +121,14 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi if (projectResources != null) { // create the classname String className = getRClassName(project); - + if (className == null) { + // We need to abort. + AdtPlugin.log(IStatus.ERROR, + "loadAndParseRClass: failed to find manifest package for project %1$s", //$NON-NLS-1$ + project.getName()); + return; + } + // create a temporary class loader to load it. ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */, project); @@ -199,13 +207,28 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi } return false; } - + + /** + * Returns the class name of the R class, based on the project's manifest's package. + * + * @return A class name (e.g. "my.app.R") or null if there's no valid package in the manifest. + */ private String getRClassName(IProject project) { - // create the classname - AndroidManifestHelper manifest = new AndroidManifestHelper(project); - String javaPackage = manifest.getPackageName(); - - return javaPackage + ".R"; //$NON-NLS-1$ + try { + IFile manifestFile = AndroidManifestParser.getManifest(project); + AndroidManifestParser data = AndroidManifestParser.parseForData(manifestFile); + String javaPackage = data.getPackage(); + return javaPackage + ".R"; //$NON-NLS-1$ + } catch (CoreException e) { + // This will typically happen either because the manifest file is not present + // and/or the workspace needs to be refreshed. + AdtPlugin.logAndPrintError(e, + "Android Resources", + "Failed to find the package of the AndroidManifest of project %1$s. Reason: %2$s", + project.getName(), + e.getMessage()); + return null; + } } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java index 578193854..e84c05123 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java @@ -222,10 +222,10 @@ class NewXmlFileCreationPage extends WizardPage { null, // default attributes 1 // target API level ), - new TypeInfo("Gadget Provider", // UI name - "An XML file that describes a gadget provider.", // tooltip + new TypeInfo("AppWidget Provider", // UI name + "An XML file that describes a widget provider.", // tooltip ResourceFolderType.XML, // folder type - AndroidTargetData.DESCRIPTOR_GADGET_PROVIDER, // root seed + AndroidTargetData.DESCRIPTOR_APPWIDGET_PROVIDER, // root seed null, // default root SdkConstants.NS_RESOURCES, // xmlns null, // default attributes @@ -1109,7 +1109,7 @@ class NewXmlFileCreationPage extends WizardPage { TypeInfo type = getSelectedType(); if (type.getTargetApiLevel() > currentApiLevel) { - error = "The API level of the selected type (e.g. gadget, etc.) is not " + + error = "The API level of the selected type (e.g. AppWidget, etc.) is not " + "compatible with the API level of the project."; } } diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java index 7929b5aef..144b7ac26 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java @@ -53,8 +53,8 @@ public final class XmlDescriptors implements IDescriptorProvider { /** The root document descriptor for preferences. */ private DocumentDescriptor mPrefDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ - /** The root document descriptor for gadget provider. */ - private DocumentDescriptor mGadgetDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ + /** The root document descriptor for widget provider. */ + private DocumentDescriptor mAppWidgetDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ /** @return the root descriptor for both searchable and preferences. */ public DocumentDescriptor getDescriptor() { @@ -75,9 +75,9 @@ public final class XmlDescriptors implements IDescriptorProvider { return mPrefDescriptor; } - /** @return the root descriptor for gadget providers. */ - public DocumentDescriptor getGadgetDescriptor() { - return mGadgetDescriptor; + /** @return the root descriptor for widget providers. */ + public DocumentDescriptor getAppWidgetDescriptor() { + return mAppWidgetDescriptor; } public IDescriptorProvider getSearchableProvider() { @@ -104,14 +104,14 @@ public final class XmlDescriptors implements IDescriptorProvider { }; } - public IDescriptorProvider getGadgetProvider() { + public IDescriptorProvider getAppWidgetProvider() { return new IDescriptorProvider() { public ElementDescriptor getDescriptor() { - return mGadgetDescriptor; + return mAppWidgetDescriptor; } public ElementDescriptor[] getRootElementDescriptors() { - return mGadgetDescriptor.getChildren(); + return mAppWidgetDescriptor.getChildren(); } }; } @@ -123,13 +123,13 @@ public final class XmlDescriptors implements IDescriptorProvider { * all at once. * * @param searchableStyleMap The map style=>attributes for from the attrs.xml file - * @param gadgetStyleMap The map style=>attributes for from the attrs.xml file + * @param appWidgetStyleMap The map style=>attributes for from the attrs.xml file * @param prefs The list of non-group preference descriptions * @param prefGroups The list of preference group descriptions */ public synchronized void updateDescriptors( Map searchableStyleMap, - Map gadgetStyleMap, + Map appWidgetStyleMap, ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) { XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor( @@ -137,16 +137,16 @@ public final class XmlDescriptors implements IDescriptorProvider { SdkConstants.NS_RESOURCES); ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns); - ElementDescriptor gadget = createGadgetProviderInfo(gadgetStyleMap, xmlns); + ElementDescriptor appWidget = createAppWidgetProviderInfo(appWidgetStyleMap, xmlns); ElementDescriptor preferences = createPreference(prefs, prefGroups, xmlns); ArrayList list = new ArrayList(); if (searchable != null) { list.add(searchable); mSearchDescriptor.setChildren(new ElementDescriptor[]{ searchable }); } - if (gadget != null) { - list.add(gadget); - mGadgetDescriptor.setChildren(new ElementDescriptor[]{ gadget }); + if (appWidget != null) { + list.add(appWidget); + mAppWidgetDescriptor.setChildren(new ElementDescriptor[]{ appWidget }); } if (preferences != null) { list.add(preferences); @@ -190,25 +190,25 @@ public final class XmlDescriptors implements IDescriptorProvider { } /** - * Returns the new ElementDescriptor for + * Returns the new ElementDescriptor for */ - private ElementDescriptor createGadgetProviderInfo( - Map gadgetStyleMap, + private ElementDescriptor createAppWidgetProviderInfo( + Map appWidgetStyleMap, XmlnsAttributeDescriptor xmlns) { - if (gadgetStyleMap == null) { + if (appWidgetStyleMap == null) { return null; } - ElementDescriptor gadget = createElement(gadgetStyleMap, - "GadgetProviderInfo", //$NON-NLS-1$ styleName - "gadget-provider", //$NON-NLS-1$ xmlName - "Gadget Provider", // uiName + ElementDescriptor appWidget = createElement(appWidgetStyleMap, + "AppWidgetProviderInfo", //$NON-NLS-1$ styleName + "appwidget-provider", //$NON-NLS-1$ xmlName + "AppWidget Provider", // uiName null, // sdk url xmlns, // extraAttribute null, // childrenElements false /* mandatory */ ); - return gadget; + return appWidget; } /** diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/.classpath b/tools/eclipse/plugins/com.android.ide.eclipse.tests/.classpath index 40886832d..6f2a534bb 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/.classpath +++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/.classpath @@ -5,6 +5,6 @@ - + diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestHelperTest.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestHelperTest.java deleted file mode 100644 index 66042648b..000000000 --- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestHelperTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php - * - * 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.ide.eclipse.common.project; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -import junit.framework.TestCase; - -public class AndroidManifestHelperTest extends TestCase { - private File mFile; - private AndroidManifestHelper mManifest; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mFile = File.createTempFile("androidManifest", "xml"); //$NON-NLS-1$ //$NON-NLS-2$ - assertNotNull(mFile); - - FileWriter fw = new FileWriter(mFile); - fw.write("\n"); //$NON-NLS-1$ - fw.write("\n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \"\n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write(" \n"); //$NON-NLS-1$ - fw.write("\n"); //$NON-NLS-1$ - fw.flush(); - fw.close(); - - mManifest = new AndroidManifestHelper(mFile.getAbsolutePath()); - } - - @Override - protected void tearDown() throws Exception { - assertTrue(mFile.delete()); - super.tearDown(); - } - - public void testExists() { - assertTrue(mManifest.exists()); - } - - public void testNotExists() throws IOException { - File f = File.createTempFile("androidManifest2", "xml"); //$NON-NLS-1$ //$NON-NLS-2$ - assertTrue(f.delete()); - AndroidManifestHelper manifest = new AndroidManifestHelper(f.getAbsolutePath()); - assertFalse(manifest.exists()); - } - - public void testGetPackageName() { - assertEquals("com.android.testapp", mManifest.getPackageName()); - } - - public void testGetActivityName() { - assertEquals("", mManifest.getActivityName(0)); //$NON-NLS-1$ - assertEquals(".MainActivity", mManifest.getActivityName(1)); //$NON-NLS-1$ - assertEquals(".OptionsActivity", mManifest.getActivityName(2)); //$NON-NLS-1$ - assertEquals(".InfoActivity", mManifest.getActivityName(3)); //$NON-NLS-1$ - assertEquals("", mManifest.getActivityName(4)); //$NON-NLS-1$ - } - -} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java new file mode 100644 index 000000000..516e448e6 --- /dev/null +++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/common/project/AndroidManifestParserTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.ide.eclipse.common.project; + +import junit.framework.TestCase; + +import com.android.ide.eclipse.mock.FileMock; + +/** + * Tests for {@link AndroidManifestParser} + */ +public class AndroidManifestParserTest extends TestCase { + private AndroidManifestParser mManifest; + + private static final String PACKAGE_NAME = "com.android.testapp"; //$NON-NLS-1$ + private static final String ACTIVITY_NAME = "com.android.testapp.MainActivity"; //$NON-NLS-1$ + private static final String LIBRARY_NAME = "android.test.runner"; //$NON-NLS-1$ + private static final String INSTRUMENTATION_NAME = "android.test.InstrumentationTestRunner"; //$NON-NLS-1$ + + @Override + protected void setUp() throws Exception { + super.setUp(); + + // create the test data + StringBuilder sb = new StringBuilder(); + sb.append("\n"); //$NON-NLS-1$ + sb.append("\n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" \"\n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" \n"); //$NON-NLS-1$ + sb.append(" "); //$NON-NLS-1$ + sb.append(" \n"); + sb.append("\n"); //$NON-NLS-1$ + + FileMock mockFile = new FileMock("AndroidManifest.xml", sb.toString().getBytes()); + + mManifest = AndroidManifestParser.parseForData(mockFile); + assertNotNull(mManifest); + } + + public void testGetPackage() { + assertEquals("com.android.testapp", mManifest.getPackage()); + } + + public void testGetActivities() { + assertEquals(1, mManifest.getActivities().length); + assertEquals(ACTIVITY_NAME, mManifest.getActivities()[0]); + } + + public void testGetLauncherActivity() { + assertEquals(ACTIVITY_NAME, mManifest.getLauncherActivity()); + } + + public void testGetUsesLibraries() { + assertEquals(1, mManifest.getUsesLibraries().length); + assertEquals(LIBRARY_NAME, mManifest.getUsesLibraries()[0]); + } + + public void testGetInstrumentations() { + assertEquals(1, mManifest.getInstrumentations().length); + assertEquals(INSTRUMENTATION_NAME, mManifest.getInstrumentations()[0]); + } +} diff --git a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java index 2220ed1cd..987ea9247 100644 --- a/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java +++ b/tools/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/mock/FileMock.java @@ -37,6 +37,7 @@ import org.eclipse.core.runtime.jobs.ISchedulingRule; import sun.reflect.generics.reflectiveObjects.NotImplementedException; +import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.Reader; import java.net.URI; @@ -44,16 +45,28 @@ import java.util.Map; /** * Mock implementation of {@link IFile}. + * + * Optionally backed by an in-memory byte array + * *

Supported methods: *

    + *
  • getName()
  • + *
  • getContents()
  • + *
  • getContents(boolean force)
  • *
*/ public class FileMock implements IFile { private String mName; + private byte[] mContentData; public FileMock(String name) { + this(name, new byte[0]); + } + + public FileMock(String name, byte[] fileData) { mName = name; + mContentData = fileData; } // -------- MOCKED METHODS ---------------- @@ -62,6 +75,15 @@ public class FileMock implements IFile { return mName; } + public InputStream getContents() throws CoreException { + return new ByteArrayInputStream(mContentData); + } + + public InputStream getContents(boolean force) throws CoreException { + // ignore force + return getContents(); + } + // -------- UNIMPLEMENTED METHODS ---------------- public void appendContents(InputStream source, int updateFlags, IProgressMonitor monitor) @@ -115,14 +137,6 @@ public class FileMock implements IFile { throw new NotImplementedException(); } - public InputStream getContents() throws CoreException { - throw new NotImplementedException(); - } - - public InputStream getContents(boolean force) throws CoreException { - throw new NotImplementedException(); - } - public int getEncoding() throws CoreException { throw new NotImplementedException(); } @@ -139,7 +153,8 @@ public class FileMock implements IFile { throw new NotImplementedException(); } - public void move(IPath destination, boolean force, boolean keepHistory, IProgressMonitor monitor) + public void move(IPath destination, boolean force, boolean keepHistory, + IProgressMonitor monitor) throws CoreException { throw new NotImplementedException(); } @@ -229,7 +244,8 @@ public class FileMock implements IFile { throw new NotImplementedException(); } - public void deleteMarkers(String type, boolean includeSubtypes, int depth) throws CoreException { + public void deleteMarkers(String type, boolean includeSubtypes, int depth) + throws CoreException { throw new NotImplementedException(); } @@ -424,26 +440,26 @@ public class FileMock implements IFile { throw new NotImplementedException(); } - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") public Map getPersistentProperties() throws CoreException { throw new NotImplementedException(); - } + } - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") public Map getSessionProperties() throws CoreException { throw new NotImplementedException(); - } + } - public boolean isDerived(int options) { + public boolean isDerived(int options) { throw new NotImplementedException(); - } + } - public boolean isHidden() { + public boolean isHidden() { throw new NotImplementedException(); - } + } - public void setHidden(boolean isHidden) throws CoreException { + public void setHidden(boolean isHidden) throws CoreException { throw new NotImplementedException(); - } - + } } + diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java new file mode 100644 index 000000000..83b911342 --- /dev/null +++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/scene/ProfilesLoader.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2009 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.hierarchyviewer.scene; + +import com.android.ddmlib.Device; +import com.android.hierarchyviewer.device.Window; +import com.android.hierarchyviewer.device.DeviceBridge; + +import java.net.Socket; +import java.net.InetSocketAddress; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.io.BufferedReader; +import java.io.InputStreamReader; + +public class ProfilesLoader { + public static double[] loadProfiles(Device device, Window window, String params) { + Socket socket = null; + BufferedReader in = null; + BufferedWriter out = null; + + try { + socket = new Socket(); + socket.connect(new InetSocketAddress("127.0.0.1", + DeviceBridge.getDeviceLocalPort(device))); + + out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + + out.write("PROFILE " + window.encode() + " " + params); + out.newLine(); + out.flush(); + + String response = in.readLine(); + String[] data = response.split(" "); + + double[] profiles = new double[data.length]; + for (int i = 0; i < data.length; i++) { + profiles[i] = (Long.parseLong(data[i]) / 1000.0) / 1000.0; // convert to ms + } + return profiles; + } catch (IOException e) { + // Empty + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + if (socket != null) { + socket.close(); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + return null; + } +} diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java index 77ebb39e8..d530c3540 100644 --- a/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java +++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java @@ -27,6 +27,7 @@ import com.android.hierarchyviewer.scene.ViewHierarchyScene; import com.android.hierarchyviewer.scene.ViewManager; import com.android.hierarchyviewer.scene.ViewNode; import com.android.hierarchyviewer.scene.WindowsLoader; +import com.android.hierarchyviewer.scene.ProfilesLoader; import com.android.hierarchyviewer.util.OS; import com.android.hierarchyviewer.util.WorkerThread; import com.android.hierarchyviewer.ui.action.ShowDevicesAction; @@ -43,6 +44,7 @@ import com.android.hierarchyviewer.ui.util.PngFileFilter; import com.android.hierarchyviewer.ui.util.IconLoader; import com.android.hierarchyviewer.ui.model.PropertiesTableModel; import com.android.hierarchyviewer.ui.model.ViewsTreeModel; +import com.android.hierarchyviewer.ui.model.ProfilesTableModel; import org.jdesktop.swingworker.SwingWorker; import org.netbeans.api.visual.graph.layout.TreeGraphLayout; import org.netbeans.api.visual.model.ObjectSceneEvent; @@ -123,6 +125,7 @@ public class Workspace extends JFrame { private JSplitPane sideSplitter; private JSplitPane mainSplitter; private JTable propertiesTable; + private JTable profilingTable; private JComponent pixelPerfectPanel; private JTree pixelPerfectTree; private ScreenViewer screenViewer; @@ -274,11 +277,32 @@ public class Workspace extends JFrame { JScrollPane tableScroller = new JScrollPane(propertiesTable); tableScroller.setBorder(null); + profilingTable = new JTable(); + profilingTable.setModel(new DefaultTableModel(new Object[][] { + { " " , " " }, { " " , " " }, { " " , " " } }, + new String[] { "Operation", "Duration (ms)" })); + profilingTable.setBorder(null); + profilingTable.getTableHeader().setBorder(null); + + JScrollPane firstTableScroller = new JScrollPane(profilingTable); + firstTableScroller.setBorder(null); + + setVisibleRowCount(profilingTable, 5); + firstTableScroller.setMinimumSize(profilingTable.getPreferredScrollableViewportSize()); + + JSplitPane tablesSplitter = new JSplitPane(); + tablesSplitter.setBorder(null); + tablesSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT); + tablesSplitter.setResizeWeight(0); + tablesSplitter.setLeftComponent(firstTableScroller); + tablesSplitter.setBottomComponent(tableScroller); + tablesSplitter.setContinuousLayout(true); + sideSplitter = new JSplitPane(); sideSplitter.setBorder(null); sideSplitter.setOrientation(JSplitPane.VERTICAL_SPLIT); sideSplitter.setResizeWeight(0.5); - sideSplitter.setLeftComponent(tableScroller); + sideSplitter.setLeftComponent(tablesSplitter); sideSplitter.setBottomComponent(null); sideSplitter.setContinuousLayout(true); @@ -603,6 +627,22 @@ public class Workspace extends JFrame { propertiesTable.setModel(new PropertiesTableModel(node)); } + private void updateProfiles(double[] profiles) { + profilingTable.setModel(new ProfilesTableModel(profiles)); + setVisibleRowCount(profilingTable, profiles.length + 1); + } + + public static void setVisibleRowCount(JTable table, int rows) { + int height = 0; + for (int row = 0; row < rows; row++) { + height += table.getRowHeight(row); + } + + Dimension size = new Dimension(table.getPreferredScrollableViewportSize().width, height); + table.setPreferredScrollableViewportSize(size); + table.revalidate(); + } + private void showPixelPerfectTree() { if (pixelPerfectTree == null) { return; @@ -1134,22 +1174,24 @@ public class Workspace extends JFrame { } } - private class LoadGraphTask extends SwingWorker { + private class LoadGraphTask extends SwingWorker { public LoadGraphTask() { beginTask(); } @Override @WorkerThread - protected ViewHierarchyScene doInBackground() { + protected double[] doInBackground() { scene = ViewHierarchyLoader.loadScene(currentDevice, currentWindow); - return scene; + return ProfilesLoader.loadProfiles(currentDevice, currentWindow, + scene.getRoot().toString()); } @Override protected void done() { try { - createGraph(get()); + createGraph(scene); + updateProfiles(get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { diff --git a/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/model/ProfilesTableModel.java b/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/model/ProfilesTableModel.java new file mode 100644 index 000000000..fcbe6b57e --- /dev/null +++ b/tools/hierarchyviewer/src/com/android/hierarchyviewer/ui/model/ProfilesTableModel.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 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.hierarchyviewer.ui.model; + +import javax.swing.table.DefaultTableModel; +import java.text.NumberFormat; + +public class ProfilesTableModel extends DefaultTableModel { + private static final String[] NAMES = { "measure", "layout", "draw" }; + + private final double[] profiles; + private final NumberFormat formatter; + + public ProfilesTableModel(double[] profiles) { + this.profiles = profiles; + formatter = NumberFormat.getNumberInstance(); + } + + @Override + public int getRowCount() { + return profiles == null ? 0 : profiles.length; + } + + @Override + public Object getValueAt(int row, int column) { + if (profiles == null) return ""; + + if (column == 0) { + return NAMES[row]; + } + + + return formatter.format(profiles[row]) + ""; + } + + @Override + public int getColumnCount() { + return 2; + } + + @Override + public String getColumnName(int column) { + return column == 0 ? "Operation" : "Duration (ms)"; + } + + @Override + public boolean isCellEditable(int arg0, int arg1) { + return false; + } + + @Override + public void setValueAt(Object arg0, int arg1, int arg2) { + } +} diff --git a/tools/runtest b/tools/runtest index 349b5a795..62809100f 100755 --- a/tools/runtest +++ b/tools/runtest @@ -113,7 +113,7 @@ knownTests=( # system-wide tests "framework frameworks/base/tests/FrameworkTest # com.android.frameworktest.AllTests com.android.frameworktest.tests #" - "android frameworks/base/tests/AndroidTests # AndroidTests com.android.unit_tests #" + "android frameworks/base/tests/AndroidTests com.android.unit_tests AndroidTests # #" "smoke frameworks/base/tests/SmokeTest com.android.smoketest # com.android.smoketest.tests #" "core frameworks/base/tests/CoreTests # android.core.CoreTests android.core #" "libcore frameworks/base/tests/CoreTests # android.core.JavaTests android.core #" diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java index 7b8fdbecf..65cbbe356 100644 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java @@ -303,8 +303,11 @@ public final class AvdManager { } if (NUMERIC_SKIN_SIZE.matcher(skinName).matches()) { - // Skin name is an actual screen resolution, no skin.path + // Skin name is an actual screen resolution. + // Set skin.name for display purposes in the AVD manager and + // set skin.path for use by the emulator. values.put(AVD_INI_SKIN_NAME, skinName); + values.put(AVD_INI_SKIN_PATH, skinName); } else { // get the path of the skin (relative to the SDK) // assume skin name is valid diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java index 6bf1df308..825be93b1 100644 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ApkConfigWidget.java @@ -41,7 +41,7 @@ import java.util.Set; * The APK Configuration widget is a table that is added to the given parent composite. *

* To use, create it using {@link #ApkConfigWidget(Composite)} then - * call {@link #fillTable(Map) to set the initial list of configurations. + * call {@link #fillTable(Map)} to set the initial list of configurations. */ public class ApkConfigWidget { private final static int INDEX_NAME = 0; diff --git a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java index 9d0b9285a..67c70a676 100644 --- a/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java +++ b/tools/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/AvdSelector.java @@ -36,21 +36,17 @@ import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; -import java.util.ArrayList; - /** * The AVD selector is a table that is added to the given parent composite. *

- * To use, create it using {@link #AvdSelector(Composite, AvdInfo[], boolean)} then + * To use, create it using {@link #AvdSelector(Composite, AvdInfo[])} then * call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)} - * and finally use {@link #getFirstSelected()} or {@link #getAllSelected()} to retrieve the - * selection. + * and finally use {@link #getFirstSelected()} to retrieve the selection. */ public final class AvdSelector { private AvdInfo[] mAvds; - private final boolean mAllowMultipleSelection; private SelectionListener mSelectionListener; private Table mTable; private Label mDescription; @@ -63,11 +59,8 @@ public final class AvdSelector { * * @param parent The parent composite where the selector will be added. * @param avds The list of AVDs. This is not copied, the caller must not modify. - * @param allowMultipleSelection True if more than one SDK target can be selected at the same - * time. */ - public AvdSelector(Composite parent, AvdInfo[] avds, IAndroidTarget filter, - boolean allowMultipleSelection) { + public AvdSelector(Composite parent, AvdInfo[] avds, IAndroidTarget filter) { mAvds = avds; // Layout has 1 column @@ -76,7 +69,6 @@ public final class AvdSelector { group.setLayoutData(new GridData(GridData.FILL_BOTH)); group.setFont(parent.getFont()); - mAllowMultipleSelection = allowMultipleSelection; mTable = new Table(group, SWT.CHECK | SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER); mTable.setHeaderVisible(true); mTable.setLinesVisible(false); @@ -112,11 +104,9 @@ public final class AvdSelector { * * @param parent The parent composite where the selector will be added. * @param avds The list of AVDs. This is not copied, the caller must not modify. - * @param allowMultipleSelection True if more than one SDK target can be selected at the same - * time. */ - public AvdSelector(Composite parent, AvdInfo[] avds, boolean allowMultipleSelection) { - this(parent, avds, null /* filter */, allowMultipleSelection); + public AvdSelector(Composite parent, AvdInfo[] avds) { + this(parent, avds, null /* filter */); } @@ -160,8 +150,7 @@ public final class AvdSelector { * The event's item contains a {@link TableItem}. * The {@link TableItem#getData()} contains an {@link IAndroidTarget}. *

- * It is recommended that the caller uses the {@link #getFirstSelected()} and - * {@link #getAllSelected()} methods instead. + * It is recommended that the caller uses the {@link #getFirstSelected()} method instead. * * @param selectionListener The new listener or null to remove it. */ @@ -201,28 +190,10 @@ public final class AvdSelector { return found; } - /** - * Returns all selected items. - * This is useful when the table is in multiple-selection mode. - * - * @see #getFirstSelected() - * @return An array of selected items. The list can be empty but not null. - */ - public AvdInfo[] getAllSelected() { - ArrayList list = new ArrayList(); - for (TableItem i : mTable.getItems()) { - if (i.getChecked()) { - list.add((IAndroidTarget) i.getData()); - } - } - return list.toArray(new AvdInfo[list.size()]); - } - /** * Returns the first selected item. * This is useful when the table is in single-selection mode. * - * @see #getAllSelected() * @return The first selected item or null. */ public AvdInfo getFirstSelected() { @@ -278,20 +249,11 @@ public final class AvdSelector { private void setupSelectionListener(final Table table) { // Add a selection listener that will check/uncheck items when they are double-clicked table.addSelectionListener(new SelectionListener() { - /** Default selection means double-click on "most" platforms */ - public void widgetDefaultSelected(SelectionEvent e) { - if (e.item instanceof TableItem) { - TableItem i = (TableItem) e.item; - i.setChecked(!i.getChecked()); - enforceSingleSelection(i); - updateDescription(i); - } - - if (mSelectionListener != null) { - mSelectionListener.widgetDefaultSelected(e); - } - } + /** + * Handles single-click selection on the table. + * {@inheritDoc} + */ public void widgetSelected(SelectionEvent e) { if (e.item instanceof TableItem) { TableItem i = (TableItem) e.item; @@ -305,11 +267,32 @@ public final class AvdSelector { } /** - * If we're not in multiple selection mode, uncheck all other - * items when this one is selected. + * Handles double-click selection on the table. + * Note that the single-click handler will probably already have been called. + * + * On double-click, always check the table item. + * + * {@inheritDoc} + */ + public void widgetDefaultSelected(SelectionEvent e) { + if (e.item instanceof TableItem) { + TableItem i = (TableItem) e.item; + i.setChecked(true); + enforceSingleSelection(i); + updateDescription(i); + } + + if (mSelectionListener != null) { + mSelectionListener.widgetDefaultSelected(e); + } + } + + /** + * To ensure single selection, uncheck all other items when this one is selected. + * This makes the chekboxes act as radio buttons. */ private void enforceSingleSelection(TableItem item) { - if (!mAllowMultipleSelection && item.getChecked()) { + if (item.getChecked()) { Table parentTable = item.getParent(); for (TableItem i2 : parentTable.getItems()) { if (i2 != item && i2.getChecked()) {