From 4d01e1afe600eaf565fbb37718b95bf519706e38 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Fri, 30 Jan 2015 18:49:01 -0500 Subject: [PATCH] Stub icon scaling and allow background updating of the applist --- app/libs/limelight-common.jar | Bin 956639 -> 956815 bytes app/src/main/java/com/limelight/AppView.java | 326 +++++++++++++----- app/src/main/java/com/limelight/PcView.java | 13 +- .../computers/ComputerManagerService.java | 113 ++++++ .../com/limelight/grid/AppGridAdapter.java | 76 ++-- .../limelight/grid/GenericGridAdapter.java | 19 +- .../com/limelight/grid/PcGridAdapter.java | 4 +- .../preferences/PreferenceConfiguration.java | 5 +- .../java/com/limelight/utils/CacheHelper.java | 55 +++ app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/preferences.xml | 5 + 11 files changed, 467 insertions(+), 154 deletions(-) create mode 100644 app/src/main/java/com/limelight/utils/CacheHelper.java diff --git a/app/libs/limelight-common.jar b/app/libs/limelight-common.jar index c248abce216a9999b9e5eaabb3ca30b7b4857d19..63660c373a81e7edda5f5c7bd1ef4f2930331972 100644 GIT binary patch delta 11288 zcmZv?1yogA_dd*_a~~w6B&E9%5b5sj?v{{-gLFv8p+gWvQt6Ns5l~5`ySq{Pe>mQI z-+O=G#~6$8>}STBbM3w7T4(IJ@LnjPRtQH;5eb<90UaG3;ZgLn_c&yTNCP3yYFQA2 z|9~oFDkL~Fg7%5<9;CQxG1hjH&;DWasl!3{EL2Pt#8|t}{2hLk;`W5GR`qc(Tn9Su z0b)#1eT-j=!_e@DQRf6PLX`sn@>*oJa-;_^dl`g({fR@lWI?3R>Og=w1)1v{?%!Mi zW&&d#)(UgmBL6cb6cPjw)vof#z~`-Xdm{HIfDtst6izYJltnev+CxEnxIr!Iv%5c1 z)_%rXPO*zW8L~=xV89bwT498K4F>RQ=afd_ai*|Jvco(K!Dq{4f(7tA6ab(JF9Cwu zYxU=U%w4sH(f$`9Oo5;_$SD0!p75aIl%`oc%Ky=#ve|`e)q))~(cuAr9k`&Ap}>RM zQulIrpwPb^0MMsl08pFhNee>ihus5MdeDS00I&A)1u4=!1HBKgR@C<${0erDSfFa* zu=V8)j03~v_nuNjeF11^ILr}x94^}bheoT_OI`e94|agtW8G_`)_P~W#Dr_B=)s-f z*76j6{u9VQit>>FcI~q_Bq(sj`=_~eZYw_xqx_+$ET81;`c%CQbyB$DW7wM#1@i1#36 zX@$2oYuyFup9|6La}fOE$*v?MS%8GX5dkARnA)nRra$=$UN?UhiaY`mGtjTd&6!DJ zi%~$eZ}7`_WZJ<{k!a>MALvy~rH5eR#RDDf7XW(_Uav@iOHsU zDx$Hsa1>4}gJ4cZA)Jn#(foVr!QuIfqo^r`9yRSsAoR3kK zbsnm#61QnhO#G;`U`+#W>;hDLfhU~GF=DSiieT$B-J-@HG^Nw0qM z;6vmx73b53(Xy7>FU9H!qs6>tBu76|||(mwG0V#mIhdIEQ%t4pBw2*0W&)e#05kJEa;|oy0jmIEXS~Py_w0_vSK+Y_Im!(5 zKRTR|f(bQ}#B2RBCmRaod$qcGSs%W0`6Zo$VcSng3+!ktnwTBh{a@mjo`eg6Gi z4DV$F8tYMnS}jLSKhY6nu$Cu-0sA5lbl011S3$iVYwqbbQgExDLR=lci_s^s^SDq2 zf+fX1Z#De1BpPXFr~pL?g~5A2q?sa16-(JbDmK&oyXQE+&)=Vd+1?O#s}PR)N-i&3I;iPY^V@SPbJ?8@XJj8h3bpIh9#-*e5o9eE z*1r8*P@7qj%gV3x?v0R-_;^eumb9g~X=HtW%x}XRoiKZCb;^T%;Wrb|vrmZcyMB4Ru2v&R5p8DCjJzt7F;yHFHp%Juk&kaa;)fhrdCmHGmN=1i z%-4;qaVf(L8A;OO0tnTpIro4ni_i`&L5u)K+=-GY;5l2Xfy2j<8!N2y!c(&N+m>5zQYj4EykVxn$X_%3Z zf@zh`=Nr)ON{OPHYJ;Dhz+TEnc9gW&w>0Pi&=EpZ4a1U6jA(S%D%e9{gk_2YbTv{js@;Opnc(>@MX2`LoJf02@u+gWD zJrbCsr$h6L*GYvyTepCUvP3@b{(WyAW%gsBEz<6nNy=xptxY-59&SN+R&O9LjU$_BKd--owL@byY@4|b!R`=G4#xPthd%k5 zlg)E{8=R)tCSFnamW1L!|6}VEX5V!u8*1Q7dmF}4opwm;W88Q&#I;J{O0E;!(m0 zY;)%~PG=eNrloy*Z4BG-5rN-yez32ybfFl2%BL)JNdfSOe?E6gp$QFmo*c&oLGBlk;woPT$Egp^5W9+JEK zn*AOFgFtiAzc#PXxv|QGaQE$G68R!Gn^A`3(x7~u=t)n_u6;$V!C1|H>3 zc^_v&ZhBtmzY)Kax!hxWgIX!cs7A89P;f`jE%Ie5lwTcu6L}upcRf@*G}5DnI*O-h zJy2U;EwE|CH~#Gwd?OdJ7EQiT3NC&Uh(4L+OSbhWm2$4H=~Xq3%w3aNjb}@Pw&hLO z48vG@_J}%7=!RVT&-XQzBsiF}ujc4CMh!XXAx72AsPYYcmgz1BPoKVuBy(CeR?uRP~N2=XI49I%0yz?9}Wfb)9C26|@@kNvx)l->1ge z;~&m|AT>1Q6&XVuWi_+n?~#0^@==B{NQXufYPx&E2?*>JlqiNuo0^qsqLA3EuRSpe z8l@6El=4{gzoNtoyEi9pWVZ#o(}v5dY(T>MA7eveBZ)l1yA<(8s!qB;$IWZg>vEF{ zt2)w-Ck?B2b)KtKm8arBGuv`>&3b%h7m~k1%LH9aqU8<_`39|=>J6Gs7NE!zYI!foFPu^wn;r^2XwYl!lM$ms@|Ym99c(zGuVw%@U~D>fb3+=s zR#8bjWGmv;z8*)+wLS31dQ_{Z%kH{PA37u=fBoyTD@ycPdcUC$3+Cn}^FhRqa)i=n z-PGza3U8jfwf7UDv#A;fuJC8)$vrRZe@5~hhrwoW)h5O8^t0o;HokJ*W|H@OUzw<# z)$%Hw1g2EwxO*NSnc>b<^)@xqLA~7}kzJsYX38#otVi~tuUL~bO?6i)B}+x_ilpSk zCB`M%zetUs6gu@wr+0mEFD+qp&hM-cmpST9-a@uJ z48xxu!miFX@xap&b2FNZksBz-Y><+;P5&(BNsh_k;5O1Pys)h7G7p|2b0iglZ20|l zZy#M`xg>Am+Q_%*FE}C#yCKwSS{%8)76+AfVN;y3F+mPLJEvJ6^_}GwM6eDplHuN;QO5?HTL=}_Y^q= zJnUI;zL;6jvP9cF)GuF88 zpLK$gY8y0S79P_dtOZ)ERcBrE$<*2wV3nVvIJux2=4Cdqq6j1$t)N24m-vStrL;DY zwd%JneQ>W4b{2DSMhV?~5Y)X`w0Vx2ZR-vejehu@(m6^%pvbQNGg3?V3=MJ~{{^vV zXy**zj}s8_bMW1V{cL*C@@xpn%Uo0li<=gDSnFrXnr(^qe1$$WbSQ(r1*bRGU3-pJ zE1V2I=}gz@tSXehvB-*Sm%Bnxsx0s%`WU~6ZOh(3taaBiJ?*4`&-->C`<8mFSp01? z$Mxi@>QO1qtvn*<%tw`4@JmoaU^v9(a0vBDv$AeRe}@@wWZs^?MS2|MI#WfyE;-Nu z5!|6fxkz+p(9&BzdrkVThnK{sXLTWlI?l_q4&Y+7>=3vBCu6Ll^EzNCc0l>b{K2R7 zZ=lrTq}FWJCHc?ORQ$%z98(CRx6@ZB6T652@9ClQGBG?wIZc5l?&P@PuzmDyW>tX1 zcu*fn=318Ck@8A=W)WglDdSbeAK0JtQ_f&b$=CUCZd>TRnr)1?a!|rTq=J(LU(nGo zhV&L^!)MA_`}!|rlV?vLc86%u`}ITqDXNU8RW71tFTv6hq-CB8Cbn(v85#rDZ&_)# zZ(Vz>gX|f_7PlDEx{5xe&1f};9FRXiz6p8O@$z&L|BSdj%xDF|qln@Y%;XrnyJugD zBksM~&x0rIf$j+HpHKu9KPO4EeK+Dzar>F4xF;-OOG@9|ezC8(qRm}(nYiff-TNsq zzX9jNMikKXT0yl})L=x(1D}T|a}L~3>c26EpEe2o&>9=}yz+k@$1&YlsCwF&ZMAP- z!koCmB3PN+?4pehF-5MIfv}oIml9;Y(*u@3_V{_rI&V5j=CzOg8p>3l6E0WvjlGrY zVm7Xxp>v2|a(qj&I6RaDy_DK&5)dEtx*YDgvz(?{4lHSbu!~zAi_gN?Zy~Vq#=<3o*1m!UCFT>osWJV1rf@uJ^djDYa~#!YORbShyJ(sbb@YI1a@&<`^#DvdTGE>YK^FBJxA|f9 z)&uR9HOV{;Qr3W;`n(69|H{x}&*cRLZ-b$ovDrK4W0mQX7wKxr>iK02gKt-V;pnhz z8(*O!mm7js*ND28ZX|vZ4O2fd7HZ@q#gv5(4CrXqZ_&K#f;`#Q2~s!x)G;<)q}?#4 z1q#2+AwMcw6O-`fth+H+y^M|`>>;CFPHg$2#<}|YD}+ahP+eWPu?oOcaCk3zQ%zv{ zD%+vVU|pw`D(j>0SB5Lgsov42WPT8g;*X3Z;Vr%uF^#_^caYje>MhJ3Y-nii5xK2a zsG5w^mNx%b8*S}pKoxt|n&jN@4SNQA?xX-CADQ>sq4CylMa@}G?OXWy3VFKM))Zye zo7l#tA7_x|k@HK>1R2T<%Y=}v*yW&pu*-{BC6{!5-o$4Y{9AsfjGlA2F|t`~paQe8 zTW0?jJv5a)LL*J`L+K*CqA9?pU%7`PVoZ40Q_JE)F@33c)scQbHGq9=!svK0OSR|A zr-~&%C*(ZY5|M=Nlg+vfuHPI9pXu}amR#B-e-S{k3aT%@zo1bbo#^L9nTS3{qq*EJ zvCpa|@nQa~eY+MfSeb1Tl&e7bL(yi%JHBl>*RzEpDc?P7h4l)Eiqe}=Fj5_vEvcZ$ zTF`RO_-?oPnvPqGNm4OeK>;6S`0zCtdYP^aXb#fMWNV+;P+T|7|um}t3@7m!g*PI zD`QUlWogrOIWuo_uW>Dauqkn2`NUBg%t?zQi9Q+sE=O5;9ingrzs-u09#3_mN;amQ z*K+dqM7;_f;<;#c2H-sxQja6^X~SvdtJW>ZHdlUUwM$DO%@x(!fS$CMeESaPoY8CB z`IyX$DeeQW3%=Lc`=3_XThOY(yjz*Syhj+M*IC(4B%Cc{jp`8EKM?DC_f=W3idnf+=w!hlHuLx$Bx1Aa6=65YGg)xx`bPZ>qfiu}bV}0war5N+ z* zpxg-0osNRIZ72&VS8Zo3=T48;LwO}rAy+)JtApRF^9s8)M1F+IcbE%>4U&DBdjj3* z!vA$dt!#Q~gyu>!9aTO@41eLdH)kDR)3bwAA;Xs)DYtKt2BaHD^EZctMym@uhD{5Pm|(yE zZc*>N%p0k0a&JME9npMvXXjr;$%S(iK4a9RSPpm8p-^ybV(6{vOPy^#*msNI|73WB zBd$FuLm<=^dHeX~Z?CNfej92BYNbe7Cr9T}e!7ZO-uxR9mmj<2J2;9HY-w)9I7S(E zb5dyck`6uq_C(Xv!hO4Lvz}h^6t<8xw{vy=51Z)z-*b4g+HdT$hI?jt`-21&I)uMk z#=m)O`|3O|GLh|B=-1!+%%PDb85xx?RDFcQ`|OoPt$6%%1`vo>9c2`B@)p0eJm9rH z^CTaU)Bg2nDH?s}XIZ+ZAcs4es%2k(PXdCLcA@dKX zdohd)><)VaFutAJ!Tp-!1Tbov_o_^u|-&&TJ2 zO_%lkee*RnC=rzVdxsa_$o~E?&8=rm7nFW_$j5o}V(vP6^n*)#S+=WWyvM}yZIx`j zN3KkGKx0u)^t5ph3X^H?jVt7EpKrWxqAWG2e2rs7T|M2ii@P0c2MP|SKe@S4Y9c%nV*=Gt1;6i=rqEQg-YeETMI++A6eO)XoLexEzDBXscTGI3fD% z&!eh6Pl^buLV3lB{QU!*-HqDz4nYHHJDQQJzXW7Xa>2<{JwtqH6p;0egtK9yY})PP z-zZGQ+&xtVkr%>!$?C2vVI2sPH{|*^lwxA88V`P;GR?GM`Ym zUmVp1d=xE%-dxwO9^n^sb1R-Q($32mO3OB13FW@QxME)%fR*@x~d_>*W5;d%;&d;eyGYi zu{>@Z^25eieGu8#n<?#C=`d9%FEUC(2l#LB*pF6at-O8SNoQWtW` zb+TWMc;%jo;B){G)nv;3MDNGo8(n;xpzMYIni0*G6)~w)&yzb^!|Qyhh2nq-b(B6I z8S9B}`#KRgNgr$}lik2$feiC7gcBeKg>WlDQAv9e`QxeLbg~!poK(#+pF>S)*TN2~>Y}m#! zUg(1b&4$~4pbj(GMlkR8#651;s-UPopo1PleVVQy)O@hmeOxcT%8z;Fq5s8 zdX|@=G35l;L!6^L`H&*YzsMuZKIPNoAj!?wED?8U%zBZS{>B7)8ffg5sG|IleWF^n z*kp)pjLLV4Ky3BmqAHaE`3-axhFXD`txx{1Oyr#C`^=fPYlMjIOe|EjyM}h{_3-4acm_Get1i|3{pga#wV=@#pM2v@ zCq%i-Aa2##((d9ho%nZbZ(rFWfi0)IiX@MRmxU8`ea$t;glLirs)pm7zfI!LMUK$? zf4*I?fME(#qLE}N+W5l8kkSt1sO*)ySXL#s6r(DI;8!JTJFoR zN}f@%#l#_q>YxHjf5j<9$AM3om;_T38o8)(v3DDmu!4)8`v-D$GY8~ssKU#_71{A+ zgW22ntlDh6S~is3{=(ljYz#iJ+4tK<_h^8y*|me!CkrD3vvb$VWH5OD?UQ$^Tl_Z{U|0qewqQ zsm#D`tRp1Ydc(z>Yq4uwqrPuUG%)ry3Ccd{leXMXrz+~XY%8LLo>Q1!TF0&3YKrDy zm#9?t!BS9Z>^BW_--$)=Pc6g>4Hj)a483=1D{YOEjhZ=}>2}jmT7P`|t_c-X4%ZxW zlFn1AieV%|64jE(QKH;AEIzNOi+DFb0EN&{?!K06%a1>{Hh7QaL`Ylvi|a*r zi-zG{MvDT=Ic}Vmu47IR!FTVt&s(pTW!!TM$W!SErE`14<}?hv@^QWJEcMTc^9y<< zv(YV$%c};}tn>S-mJyY2UY$jITIMR%$74DU9*^U+=!PPyryD0&y&1P7S}8B+E>Ci( zE=+?onf6R*kefo+W0mF8_ym~^LkHaJZCc_Z%E;;mq||$&L<_#DxOQ*T2XTi62ng%j zsEb~>>=QP12P)g=weW%LFWp5Yz8uD(uVoK=&gP_>KBPN|{l-uvLQ>)~=0tn(^zn#f z*~|-|O>(X*NftO*m3T1`6HrWbwV<)lGSJR;Xd_gxZ;~_wJ_e7Aq+Z>rDWah_oyE^s zlOiCv3nC!=??deU_dNKgHnUw6sPHR*qVY2-s16bS!3_Q}^)DW1Y{CG|;=%d9^&3Au z0)-c)D8fX6yvc446LsSBJD`3;Qj-HdO~7pgxeG$^myC&jdE#tJ@jRZuq&w^UdX zTi{_EyHPP;m!KlUssH5g{tPc#%7cMcz__pO*P3ai&BOkMJ6#WDmimvm#>@GM+U%P; z4X=my?&@#^($;L>>v_I%NMkbZV@xxabf0Ev*@8G-GUPph?vmm+|B7+hxr!L;I4_C! zNlG?BA0}iM=9{ru#!ntfem%h5HPT^Rq2+FqhU1dW=RB6HYZnjRKcvlcABid;`AFL0 zRzx1yMBdF7K7#ejw5Qe160PB6=(xP~!i#ZsWH_e5t1a@E;6ZVlD^7_Nj~ zArHiMTBcoRnkR=XaOjv_L8zlhxTGS~EY{!b14|Fl14KhV(V)VRu3Yr8I6FD>DDo=p znEAMfm|MuwjhLh`&9cb&bBrt>d{Xuz$u=gO+;)K?>g|nh4oxBD%ikQEBG``}@*2s8 z;kggCKO}5f4z(pD?N08F8(Zq*8)GI_K_-EmMjVA9fAr6emZvRWl6?Bu|Dy(%6SM7; zpohP9bwD--R658skawe$yB^FvdPPc)&7BxMdFL;bSYNT>4QLK5YgRgQTQ5VTwEcgP z4_5VGT81l<&Fi7OR29TziOtmGB*weUISBo#%=+X7u_?3B+~EctJrX|tbHV<+oZ^CE z2+>d;#40A}L4u%CYNW&vWIcJ`rg@#;ha@^Z|Zxe#^<~^ zR9}Kx+iL%vhkgORzXK!7S+h1l6L{6bDUjbN0y|M7vz3;Mwh`I58v+9~1<-|OE`G5) zGsIR>CyGg8qca6$lAv8~mh7;0wCtzdhZqKt-ZjJ{`x1G~;@ju7NVG@$V?`>~pOC)h zBYr)TIu`g8#;}JF9YbDeskN&|ZfViCLrf&B-&Z`jB~;Q|vLhFpTW+bcTO>r0s|F!k zItm7zUr@9XZ#0C`W)(nb3#paNiLt)=DHu4u%26{FEol!@%2hZkE3XQ^+!0Omy~U(l zO&(c${8ahyk?a>~-K_)qqMabS0_wW0NE!S2u!fsqJ;|R{l%%fcBEKY4a0*qghdq&g z+6FPsC7y*-W(kK-gvF}`_`Z__PZgTD&3;0jNO`(J(7+iN_;u00_z217=<)g?%Q~6R zsnDhxF~wHk(C!H=k<`o(X+sH;1=iDRr29Gs(LgZcK}JCMjsAb? zSR<@2!2{kZ00pyv1R=>YE@NW25qIDE->*<4CyOz?33LAfaxee{q|+qmisjUq)l*|< z1^Wlw)+W2$jn4MsX3e&UZfF z1$jG_Qxfl65&WKgM0`U3hL&;J`m37Vwb{~jX*WZ zKA(4s{e=0F_3?NEtHgJ_{JA3oNK1_IgU&}{+L4%X=8hLHz4JDnkI_{tIGfkour)s~ z*NV?Fn$D_c-;i7xtE7AVw3x(UV}A1B_*X+l7`^c`Tuy>ANcxi-bGJ`nX{Utvxr1^j z8cLd|-!gylO_LCi^jfW<{p=X1Q#Q~%=8R{p^vei-)WE2*^kwK1Ln=A`RN6K{c(_4I zDYHGVJY$$XPsneD0V3l4uln^Qij`Ur3;&pp+OwqE?d@Tj8&A@Nu-$qE_qBu&teAhy z_8j=|N?Q|VyfM!(M6_A&#HN;9*g%F26xcw8 z4K&z5hYbwaz=RDf*uZYgPQuyRK!UY{K_h*E8hG21kRKoe2cdp|4jjz-0a|dt;}2NC zL54qI1P84F02vey_Ah0hnFxv=2w+p)w|Geia6FbqLO@`|KtOo%uXZm41bn)bC3;q< zSO7o@cVG;&MQmq;k=)RV000|K5(NULaNrvVIKjbYAYcLq@g^g z5Wo)3e1ib?a3B~A7{Eb#FrWZ1#Il69#j54~W8H4(~xg_+L76e}iWcFhBRA+z7xL4p1WhVyq%zdEJXT zBZ22|z!L?yz(H{oY&GshsL_7`yJ%Q~_sn=S;0Om2F@W)X`xEqa3@p=oaQQdThy}C( z7$ft)6DYJk7LdEQOMeAh{dH8Rc?v+1^4C$5nF=P5F);EyZo@-B_{@NS!1Kg}8z(ra2qI#NtM66UW1DSvCKV1KrB$DnQRRXAdJn-kVY!VOiLc;vN`7`4ICd&I1 z|6N#MS^n!U0lE|q@WU6DGy!H#$NtANtaLEJ*?@cYl=B}J6$*hBALvj5EUWwbGJh`z zc>X0%Pyk^~_aZcYlBalxihyv6gMh&QpQwe+?)9bMUUyI$d7@w*gR%av zR=v%=78mp!)-(eT5IYqXV4>4J%L&y_{p(S2xo4$4?pc=m(hESZgZ^4KtrF_J0Zar0 zfaw3e4lh0L^+})@X#gGEH&+_WOBv)33oFz_(4aJc8IdvO9|jNF5e-6z&ZYhJwCvtn z$iMm{ht+ZtsBrpUB87B-4Cz@i2wI*5dJK(ChdI(ngEpoE{D}JL(1Ub93=#i1lnV-o zBXU?kJ)wXoXck%r1)hPHpeIn6^pPx-F9Q%jH1USIWB|&b1!!9aK#t1j2f)_x{{e9{ BTd4p5 delta 11158 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 CONSECUTIVE_FAILURE_LIMIT) { + // The PC is unreachable now + AppView.this.runOnUiThread(new Runnable() { + @Override + public void run() { + // Display a toast to the user and quit the activity + Toast.makeText(AppView.this, getResources().getText(R.string.lost_connection), Toast.LENGTH_SHORT).show(); + finish(); + } + }); + } + + return; + } + + // App list is the same or empty; nothing to do + if (details.rawAppList == null || details.rawAppList.equals(lastRawApplist)) { + return; + } + + try { + lastRawApplist = details.rawAppList; + updateUiWithAppList(NvHTTP.getAppListByReader(new StringReader(details.rawAppList))); + + if (blockingLoadSpinner != null) { + blockingLoadSpinner.dismiss(); + blockingLoadSpinner = null; + } + } catch (Exception e) {} + } + }); + + if (poller == null) { + poller = managerBinder.createAppListPoller(computer); + } + poller.start(); + } + + private void stopComputerUpdates() { + if (poller != null) { + poller.stop(); + } + + if (managerBinder != null) { + managerBinder.stopPolling(); + } + } @Override protected void onCreate(Bundle savedInstanceState) { @@ -70,41 +202,35 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { setContentView(R.layout.activity_app_view); UiHelper.notifyNewRootView(this); - - byte[] address = getIntent().getByteArrayExtra(ADDRESS_EXTRA); - uniqueId = getIntent().getStringExtra(UNIQUEID_EXTRA); - remote = getIntent().getBooleanExtra(REMOTE_EXTRA, false); - if (address == null || uniqueId == null) { - finish(); - return; - } + + uuidString = getIntent().getStringExtra(UUID_EXTRA); String labelText = getResources().getString(R.string.title_applist)+" "+getIntent().getStringExtra(NAME_EXTRA); TextView label = (TextView) findViewById(R.id.appListText); setTitle(labelText); label.setText(labelText); - - try { - ipAddress = InetAddress.getByAddress(address); - } catch (UnknownHostException e) { - e.printStackTrace(); - finish(); - return; - } - try { - appGridAdapter = new AppGridAdapter(this, - PreferenceConfiguration.readPreferences(this).listMode, - ipAddress, uniqueId); - } catch (Exception e) { - e.printStackTrace(); - finish(); - return; - } - - getFragmentManager().beginTransaction() - .add(R.id.appFragmentContainer, new AdapterFragment()).commitAllowingStateLoss(); + // Bind to the computer manager service + bindService(new Intent(this, ComputerManagerService.class), serviceConnection, + Service.BIND_AUTO_CREATE); } + + private void populateAppGridWithCache() { + try { + // Try to load from cache + updateUiWithAppList(NvHTTP.getAppListByReader(new InputStreamReader(CacheHelper.openCacheFileForInput(getCacheDir(), "applist", uuidString)))); + LimeLog.info("Loaded applist from cache"); + } catch (Exception e) { + LimeLog.info("Loading applist from the network"); + // We'll need to load from the network + loadAppsBlocking(); + } + } + + private void loadAppsBlocking() { + blockingLoadSpinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title), + getResources().getString(R.string.applist_refresh_msg), true); + } @Override protected void onDestroy() { @@ -112,18 +238,25 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { SpinnerDialog.closeDialogs(this); Dialog.closeDialogs(); + + if (managerBinder != null) { + unbindService(serviceConnection); + } } @Override protected void onResume() { super.onResume(); - // Display the error message if it was the - // first load, but just kill the activity - // on subsequent errors - updateAppList(firstLoad); - firstLoad = false; + startComputerUpdates(); } + + @Override + protected void onPause() { + super.onPause(); + + stopComputerUpdates(); + } private int getRunningAppId() { int runningAppId = -1; @@ -232,62 +365,62 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { return super.onContextItemSelected(item); } } - - private void updateAppList(final boolean displayError) { - final SpinnerDialog spinner = SpinnerDialog.displayDialog(this, getResources().getString(R.string.applist_refresh_title), - getResources().getString(R.string.applist_refresh_msg), true); - new Thread() { - @Override - public void run() { - NvHTTP httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this)); - - try { - final List appList = httpConn.getAppList(); - - AppView.this.runOnUiThread(new Runnable() { - @Override - public void run() { - appGridAdapter.clear(); - for (NvApp app : appList) { - appGridAdapter.addApp(new AppObject(app)); + + private void updateUiWithAppList(final List appList) { + AppView.this.runOnUiThread(new Runnable() { + @Override + public void run() { + boolean updated = false; + + for (NvApp app : appList) { + boolean foundExistingApp = false; + + // Try to update an existing app in the list first + for (int i = 0; i < appGridAdapter.getCount(); i++) { + AppObject existingApp = (AppObject) appGridAdapter.getItem(i); + if (existingApp.app == null) { + continue; + } + + if (existingApp.app.getAppId() == app.getAppId()) { + // Found the app; update its properties + if (existingApp.app.getIsRunning() != app.getIsRunning()) { + existingApp.app.setIsRunningBoolean(app.getIsRunning()); + updated = true; + } + if (!existingApp.app.getAppName().equals(app.getAppName())) { + existingApp.app.setAppName(app.getAppName()); + updated = true; } - appGridAdapter.notifyDataSetChanged(); - } - }); - - // Success case - return; - } catch (GfeHttpResponseException ignored) { - } catch (IOException ignored) { - } catch (XmlPullParserException ignored) { - } finally { - spinner.dismiss(); - } - - if (displayError) { - Dialog.displayDialog(AppView.this, getResources().getString(R.string.applist_refresh_error_title), - getResources().getString(R.string.applist_refresh_error_msg), true); - } - else { - // Just finish the activity immediately - AppView.this.runOnUiThread(new Runnable() { - @Override - public void run() { - finish(); + foundExistingApp = true; + break; } - }); + } + + if (!foundExistingApp) { + // This app must be new + appGridAdapter.addApp(new AppObject(app)); + updated = true; + } } - } - }.start(); + + if (updated) { + appGridAdapter.notifyDataSetChanged(); + } + } + }); } - + private void doStart(NvApp app) { Intent intent = new Intent(this, Game.class); - intent.putExtra(Game.EXTRA_HOST, ipAddress.getHostAddress()); + intent.putExtra(Game.EXTRA_HOST, + computer.reachability == ComputerDetails.Reachability.LOCAL ? + computer.localIp.getHostAddress() : computer.remoteIp.getHostAddress()); intent.putExtra(Game.EXTRA_APP, app.getAppName()); - intent.putExtra(Game.EXTRA_UNIQUEID, uniqueId); - intent.putExtra(Game.EXTRA_STREAMING_REMOTE, remote); + intent.putExtra(Game.EXTRA_UNIQUEID, managerBinder.getUniqueId()); + intent.putExtra(Game.EXTRA_STREAMING_REMOTE, + computer.reachability != ComputerDetails.Reachability.LOCAL); startActivity(intent); } @@ -299,21 +432,26 @@ public class AppView extends Activity implements AdapterFragmentCallbacks { NvHTTP httpConn; String message; try { - httpConn = new NvHTTP(ipAddress, uniqueId, null, PlatformBinding.getCryptoProvider(AppView.this)); + httpConn = new NvHTTP(getAddress(), + managerBinder.getUniqueId(), null, PlatformBinding.getCryptoProvider(AppView.this)); if (httpConn.quitApp()) { message = getResources().getString(R.string.applist_quit_success)+" "+app.getAppName(); } else { message = getResources().getString(R.string.applist_quit_fail)+" "+app.getAppName(); } - updateAppList(true); } catch (UnknownHostException e) { message = getResources().getString(R.string.error_unknown_host); } catch (FileNotFoundException e) { message = getResources().getString(R.string.error_404); } catch (Exception e) { message = e.getMessage(); - } + } finally { + // Trigger a poll immediately + if (poller != null) { + poller.pollNow(); + } + } final String toastMessage = message; runOnUiThread(new Runnable() { diff --git a/app/src/main/java/com/limelight/PcView.java b/app/src/main/java/com/limelight/PcView.java index e9dd5a6e..ebf64628 100644 --- a/app/src/main/java/com/limelight/PcView.java +++ b/app/src/main/java/com/limelight/PcView.java @@ -156,7 +156,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { bindService(new Intent(PcView.this, ComputerManagerService.class), serviceConnection, Service.BIND_AUTO_CREATE); - pcGridAdapter = new PcGridAdapter(this, + pcGridAdapter = new PcGridAdapter(this, 1.0, PreferenceConfiguration.readPreferences(this).listMode); initializeViews(); @@ -474,16 +474,7 @@ public class PcView extends Activity implements AdapterFragmentCallbacks { Intent i = new Intent(this, AppView.class); i.putExtra(AppView.NAME_EXTRA, computer.name); - i.putExtra(AppView.UNIQUEID_EXTRA, managerBinder.getUniqueId()); - - if (computer.reachability == ComputerDetails.Reachability.LOCAL) { - i.putExtra(AppView.ADDRESS_EXTRA, computer.localIp.getAddress()); - i.putExtra(AppView.REMOTE_EXTRA, false); - } - else { - i.putExtra(AppView.ADDRESS_EXTRA, computer.remoteIp.getAddress()); - i.putExtra(AppView.REMOTE_EXTRA, true); - } + i.putExtra(AppView.UUID_EXTRA, computer.uuid.toString()); startActivity(i); } diff --git a/app/src/main/java/com/limelight/computers/ComputerManagerService.java b/app/src/main/java/com/limelight/computers/ComputerManagerService.java index afafbe58..9af8808b 100644 --- a/app/src/main/java/com/limelight/computers/ComputerManagerService.java +++ b/app/src/main/java/com/limelight/computers/ComputerManagerService.java @@ -1,7 +1,11 @@ package com.limelight.computers; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.net.InetAddress; import java.util.LinkedList; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import com.limelight.LimeLog; @@ -11,6 +15,7 @@ import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.NvHTTP; import com.limelight.nvstream.mdns.MdnsComputer; import com.limelight.nvstream.mdns.MdnsDiscoveryListener; +import com.limelight.utils.CacheHelper; import android.app.Service; import android.content.ComponentName; @@ -179,10 +184,26 @@ public class ComputerManagerService extends Service { // Just call the unbind handler to cleanup ComputerManagerService.this.onUnbind(null); } + + public ApplistPoller createAppListPoller(ComputerDetails computer) { + return new ApplistPoller(computer); + } public String getUniqueId() { return idManager.getUniqueId(); } + + public ComputerDetails getComputer(UUID uuid) { + synchronized (pollingTuples) { + for (PollingTuple tuple : pollingTuples) { + if (uuid.equals(tuple.computer.uuid)) { + return tuple.computer; + } + } + } + + return null; + } } @Override @@ -462,6 +483,98 @@ public class ComputerManagerService extends Service { public IBinder onBind(Intent intent) { return binder; } + + public class ApplistPoller { + private Thread thread; + private ComputerDetails computer; + private Object pollEvent = new Object(); + + public ApplistPoller(ComputerDetails computer) { + this.computer = computer; + } + + public void pollNow() { + synchronized (pollEvent) { + pollEvent.notify(); + } + } + + private boolean waitPollingDelay() { + try { + synchronized (pollEvent) { + pollEvent.wait(POLLING_PERIOD_MS); + } + } catch (InterruptedException e) { + return false; + } + + return !thread.isInterrupted(); + } + + public void start() { + thread = new Thread() { + @Override + public void run() { + do { + InetAddress selectedAddr; + + // Can't poll if it's not online + if (computer.state != ComputerDetails.State.ONLINE) { + listener.notifyComputerUpdated(computer); + continue; + } + + // Can't poll if there's no UUID yet + if (computer.uuid == null) { + continue; + } + + if (computer.reachability == ComputerDetails.Reachability.LOCAL) { + selectedAddr = computer.localIp; + } + else { + selectedAddr = computer.remoteIp; + } + + NvHTTP http = new NvHTTP(selectedAddr, idManager.getUniqueId(), + null, PlatformBinding.getCryptoProvider(ComputerManagerService.this)); + + try { + // Query the app list from the server + String appList = http.getAppListRaw(); + + // Open the cache file + LimeLog.info("Updating app list from "+computer.uuid.toString()); + FileOutputStream cacheOut = CacheHelper.openCacheFileForOutput(getCacheDir(), "applist", computer.uuid.toString()); + CacheHelper.writeStringToOutputStream(cacheOut, appList); + cacheOut.close(); + + // Update the computer + computer.rawAppList = appList; + + // Notify that the app list has been updated + listener.notifyComputerUpdated(computer); + } catch (IOException e) { + e.printStackTrace(); + } + } while (waitPollingDelay()); + } + }; + thread.start(); + } + + public void stop() { + if (thread != null) { + thread.interrupt(); + + try { + thread.join(); + } catch (InterruptedException e) {} + + thread = null; + } + } + } } class PollingTuple { diff --git a/app/src/main/java/com/limelight/grid/AppGridAdapter.java b/app/src/main/java/com/limelight/grid/AppGridAdapter.java index fb8476a6..481ae2be 100644 --- a/app/src/main/java/com/limelight/grid/AppGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/AppGridAdapter.java @@ -13,7 +13,9 @@ import com.limelight.AppView; import com.limelight.LimeLog; import com.limelight.R; import com.limelight.binding.PlatformBinding; +import com.limelight.nvstream.http.ComputerDetails; import com.limelight.nvstream.http.LimelightCryptoProvider; +import com.limelight.utils.CacheHelper; import java.io.BufferedInputStream; import java.io.File; @@ -32,6 +34,7 @@ import java.security.SecureRandom; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.UUID; import java.util.concurrent.Future; import javax.net.ssl.HostnameVerifier; @@ -45,17 +48,16 @@ import java.security.cert.X509Certificate; public class AppGridAdapter extends GenericGridAdapter { - private boolean listMode; - private InetAddress address; + private ComputerDetails computer; private String uniqueId; private LimelightCryptoProvider cryptoProvider; private SSLContext sslContext; private final HashMap pendingRequests = new HashMap(); - public AppGridAdapter(Context context, boolean listMode, InetAddress address, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException { - super(context, listMode ? R.layout.simple_row : R.layout.app_grid_item, R.drawable.image_loading); + public AppGridAdapter(Context context, double gridScaleFactor, boolean listMode, ComputerDetails computer, String uniqueId) throws NoSuchAlgorithmException, KeyManagementException { + super(context, listMode ? R.layout.simple_row : R.layout.app_grid_item, R.drawable.image_loading, gridScaleFactor); - this.address = address; + this.computer = computer; this.uniqueId = uniqueId; cryptoProvider = PlatformBinding.getCryptoProvider(context); @@ -117,6 +119,15 @@ public class AppGridAdapter extends GenericGridAdapter { }); } + private InetAddress getCurrentAddress() { + if (computer.reachability == ComputerDetails.Reachability.LOCAL) { + return computer.localIp; + } + else { + return computer.remoteIp; + } + } + public void addApp(AppView.AppObject app) { itemList.add(app); sortList(); @@ -144,47 +155,24 @@ public class AppGridAdapter extends GenericGridAdapter { } } - private Bitmap checkBitmapCache(String addrStr, int appId) { - File addrFolder = new File(context.getCacheDir(), addrStr); - if (addrFolder.isDirectory()) { - File bitmapFile = new File(addrFolder, appId+".png"); - if (bitmapFile.exists()) { - InputStream fileIn = null; - try { - fileIn = new BufferedInputStream(new FileInputStream(bitmapFile)); - Bitmap bm = BitmapFactory.decodeStream(fileIn); - if (bm == null) { - // The image seems corrupt - bitmapFile.delete(); - } - - return bm; - } catch (IOException e) { - e.printStackTrace(); - bitmapFile.delete(); - } finally { - if (fileIn != null) { - try { - fileIn.close(); - } catch (IOException ignored) {} - } - } - } - } - + private Bitmap checkBitmapCache(int appId) { + try { + InputStream in = CacheHelper.openCacheFileForInput(context.getCacheDir(), "boxart", computer.uuid.toString(), appId+".png"); + Bitmap bm = BitmapFactory.decodeStream(in); + in.close(); + return bm; + } catch (IOException e) {} return null; } // TODO: Handle pruning of bitmap cache - private void populateBitmapCache(String addrStr, int appId, Bitmap bitmap) { - File addrFolder = new File(context.getCacheDir(), addrStr); - addrFolder.mkdirs(); - - File bitmapFile = new File(addrFolder, appId+".png"); + private void populateBitmapCache(UUID uuid, int appId, Bitmap bitmap) { try { // PNG ignores quality setting - bitmap.compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(bitmapFile)); - } catch (FileNotFoundException e) { + FileOutputStream out = CacheHelper.openCacheFileForOutput(context.getCacheDir(), "boxart", uuid.toString(), appId+".png"); + bitmap.compress(Bitmap.CompressFormat.PNG, 0, out); + out.close(); + } catch (IOException e) { e.printStackTrace(); } } @@ -197,10 +185,10 @@ public class AppGridAdapter extends GenericGridAdapter { Ion.getDefault(imgView.getContext()).getHttpClient().getSSLSocketMiddleware().setSSLContext(sslContext); // Check the on-disk cache - Bitmap cachedBitmap = checkBitmapCache(address.getHostAddress(), obj.app.getAppId()); + Bitmap cachedBitmap = checkBitmapCache(obj.app.getAppId()); if (cachedBitmap != null) { // Cache hit; we're done - LimeLog.info("Image cache hit for ("+address.getHostAddress()+", "+obj.app.getAppId()+")"); + LimeLog.info("Image cache hit for ("+computer.uuid+", "+obj.app.getAppId()+")"); imgView.setImageBitmap(cachedBitmap); return true; } @@ -210,7 +198,7 @@ public class AppGridAdapter extends GenericGridAdapter { Future f = Ion.with(imgView) .placeholder(defaultImageRes) .error(defaultImageRes) - .load("https://" + address.getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" + + .load("https://" + getCurrentAddress().getHostAddress() + ":47984/appasset?uniqueid=" + uniqueId + "&appid=" + obj.app.getAppId() + "&AssetType=2&AssetIdx=0") .withBitmapInfo() .setCallback( @@ -225,7 +213,7 @@ public class AppGridAdapter extends GenericGridAdapter { if (result != null && result.getBitmapInfo() != null && result.getBitmapInfo().bitmap != null) { - populateBitmapCache(address.getHostAddress(), obj.app.getAppId(), + populateBitmapCache(computer.uuid, obj.app.getAppId(), result.getBitmapInfo().bitmap); } } diff --git a/app/src/main/java/com/limelight/grid/GenericGridAdapter.java b/app/src/main/java/com/limelight/grid/GenericGridAdapter.java index f0a17618..b6a416b6 100644 --- a/app/src/main/java/com/limelight/grid/GenericGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/GenericGridAdapter.java @@ -18,11 +18,13 @@ public abstract class GenericGridAdapter extends BaseAdapter { protected int layoutId; protected ArrayList itemList = new ArrayList(); protected LayoutInflater inflater; + protected double gridSizeFactor; - public GenericGridAdapter(Context context, int layoutId, int defaultImageRes) { + public GenericGridAdapter(Context context, int layoutId, int defaultImageRes, double gridSizeFactor) { this.context = context; this.layoutId = layoutId; this.defaultImageRes = defaultImageRes; + this.gridSizeFactor = gridSizeFactor; this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @@ -64,11 +66,26 @@ public abstract class GenericGridAdapter extends BaseAdapter { if (!populateImageView(imgView, itemList.get(i))) { imgView.setImageResource(defaultImageRes); } + + ViewGroup.LayoutParams params = imgView.getLayoutParams(); + params.width *= gridSizeFactor; + params.height *= gridSizeFactor; + imgView.setLayoutParams(params); } if (!populateTextView(txtView, itemList.get(i))) { txtView.setText(itemList.get(i).toString()); + + ViewGroup.LayoutParams params = txtView.getLayoutParams(); + params.width *= gridSizeFactor; + params.height *= gridSizeFactor; + txtView.setLayoutParams(params); } if (overlayView != null) { + ViewGroup.LayoutParams params = overlayView.getLayoutParams(); + params.width *= gridSizeFactor; + params.height *= gridSizeFactor; + overlayView.setLayoutParams(params); + if (!populateOverlayView(overlayView, itemList.get(i))) { overlayView.setVisibility(View.INVISIBLE); } diff --git a/app/src/main/java/com/limelight/grid/PcGridAdapter.java b/app/src/main/java/com/limelight/grid/PcGridAdapter.java index 8e370de3..b2a63cf1 100644 --- a/app/src/main/java/com/limelight/grid/PcGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/PcGridAdapter.java @@ -13,8 +13,8 @@ import java.util.Comparator; public class PcGridAdapter extends GenericGridAdapter { - public PcGridAdapter(Context context, boolean listMode) { - super(context, listMode ? R.layout.simple_row : R.layout.pc_grid_item, R.drawable.computer); + public PcGridAdapter(Context context, double gridScaleFactor, boolean listMode) { + super(context, listMode ? R.layout.simple_row : R.layout.pc_grid_item, R.drawable.computer, gridScaleFactor); } public void addComputer(PcView.ComputerObject computer) { diff --git a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java index 0d850a0e..bcc8c00c 100644 --- a/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java +++ b/app/src/main/java/com/limelight/preferences/PreferenceConfiguration.java @@ -15,6 +15,7 @@ public class PreferenceConfiguration { private static final String DEADZONE_PREF_STRING = "seekbar_deadzone"; private static final String LANGUAGE_PREF_STRING = "list_languages"; private static final String LIST_MODE_PREF_STRING = "checkbox_list_mode"; + private static final String SMALL_ICONS_PREF_STRING = "checkbox_small_icon_mode"; private static final int BITRATE_DEFAULT_720_30 = 5; private static final int BITRATE_DEFAULT_720_60 = 10; @@ -31,6 +32,7 @@ public class PreferenceConfiguration { private static final int DEFAULT_DEADZONE = 15; public static final String DEFAULT_LANGUAGE = "default"; private static final boolean DEFAULT_LIST_MODE = false; + private static final boolean DEFAULT_SMALL_ICON = false; public static final int FORCE_HARDWARE_DECODER = -1; public static final int AUTOSELECT_DECODER = 0; @@ -42,7 +44,7 @@ public class PreferenceConfiguration { public int deadzonePercentage; public boolean stretchVideo, enableSops, playHostAudio, disableWarnings; public String language; - public boolean listMode; + public boolean listMode, smallIconMode; public static int getDefaultBitrate(String resFpsString) { if (resFpsString.equals("720p30")) { @@ -149,6 +151,7 @@ public class PreferenceConfiguration { config.stretchVideo = prefs.getBoolean(STRETCH_PREF_STRING, DEFAULT_STRETCH); config.playHostAudio = prefs.getBoolean(HOST_AUDIO_PREF_STRING, DEFAULT_HOST_AUDIO); config.listMode = prefs.getBoolean(LIST_MODE_PREF_STRING, DEFAULT_LIST_MODE); + config.smallIconMode = prefs.getBoolean(SMALL_ICONS_PREF_STRING, DEFAULT_SMALL_ICON); return config; } diff --git a/app/src/main/java/com/limelight/utils/CacheHelper.java b/app/src/main/java/com/limelight/utils/CacheHelper.java new file mode 100644 index 00000000..b22bee93 --- /dev/null +++ b/app/src/main/java/com/limelight/utils/CacheHelper.java @@ -0,0 +1,55 @@ +package com.limelight.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.Scanner; + +public class CacheHelper { + private static File openPath(boolean createPath, File root, String... path) { + File f = root; + for (int i = 0; i < path.length; i++) { + String component = path[i]; + + if (i == path.length - 1) { + // This is the file component so now we create parent directories + if (createPath) { + f.mkdirs(); + } + } + + f = new File(f, component); + } + return f; + } + + public static FileInputStream openCacheFileForInput(File root, String... path) throws FileNotFoundException { + return new FileInputStream(openPath(false, root, path)); + } + + public static FileOutputStream openCacheFileForOutput(File root, String... path) throws FileNotFoundException { + return new FileOutputStream(openPath(true, root, path)); + } + + public static String readInputStreamToString(InputStream in) { + Scanner s = new Scanner(in); + + StringBuilder sb = new StringBuilder(); + while (s.hasNext()) { + sb.append(s.next()); + } + + return sb.toString(); + } + + public static void writeStringToOutputStream(OutputStream out, String str) throws IOException { + out.write(str.getBytes("UTF-8")); + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2b9e1a68..d5258ef4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -58,7 +58,8 @@ Searching for PCs… Yes No - + Lost connection to PC + Apps on Resume Session @@ -102,6 +103,8 @@ Language to use for Limelight Use lists instead of grids Display apps and PCs in lists instead of grids + Use small icons + Use small icons in grid items to allow more items on screen Host Settings Optimize game settings diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 44f93e74..4e505719 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -54,6 +54,11 @@ android:entryValues="@array/language_values" android:summary="@string/summary_language_list" android:defaultValue="default" /> +