From 73b979d8c141c7ceac82dad7c5b271a6a42afa67 Mon Sep 17 00:00:00 2001 From: Romain Guy Date: Tue, 9 Jun 2009 12:57:21 -0700 Subject: [PATCH] Add gestures to Home. Press the Home key while in Home to enable the gestures pad. --- AndroidManifest.xml | 4 + res/anim/fade_in_fast.xml | 23 ++ res/anim/fade_out_fast.xml | 23 ++ res/drawable/btn_circle.xml | 32 ++ res/drawable/btn_circle_disable.png | Bin 0 -> 938 bytes res/drawable/btn_circle_disable_focused.png | Bin 0 -> 1436 bytes res/drawable/btn_circle_normal.png | Bin 0 -> 1249 bytes res/drawable/btn_circle_pressed.png | Bin 0 -> 1613 bytes res/drawable/btn_circle_selected.png | Bin 0 -> 1645 bytes res/drawable/gestures_background.xml | 20 + res/drawable/ic_btn_round_plus.png | Bin 0 -> 526 bytes res/drawable/texture_paper.jpg | Bin 0 -> 989 bytes res/layout/gestures.xml | 96 +++++ res/layout/gestures_settings.xml | 37 ++ res/layout/gestures_settings_item.xml | 31 ++ res/layout/rename_folder.xml | 1 + res/values/colors.xml | 6 +- res/values/dimens.xml | 2 + res/values/strings.xml | 31 +- res/values/styles.xml | 6 + src/com/android/launcher/ApplicationInfo.java | 24 +- src/com/android/launcher/GesturesActivity.java | 310 ++++++++++++++++ src/com/android/launcher/GesturesConstants.java | 25 ++ src/com/android/launcher/GesturesPanel.java | 47 +++ src/com/android/launcher/ItemInfo.java | 21 +- src/com/android/launcher/Launcher.java | 409 ++++++++++++++++++--- src/com/android/launcher/LauncherModel.java | 120 +++++- src/com/android/launcher/LauncherProvider.java | 45 ++- src/com/android/launcher/LauncherSettings.java | 168 +++++---- .../launcher/UninstallShortcutReceiver.java | 2 +- 30 files changed, 1335 insertions(+), 148 deletions(-) create mode 100644 res/anim/fade_in_fast.xml create mode 100644 res/anim/fade_out_fast.xml create mode 100644 res/drawable/btn_circle.xml create mode 100644 res/drawable/btn_circle_disable.png create mode 100644 res/drawable/btn_circle_disable_focused.png create mode 100644 res/drawable/btn_circle_normal.png create mode 100644 res/drawable/btn_circle_pressed.png create mode 100644 res/drawable/btn_circle_selected.png create mode 100755 res/drawable/gestures_background.xml create mode 100644 res/drawable/ic_btn_round_plus.png create mode 100644 res/drawable/texture_paper.jpg create mode 100644 res/layout/gestures.xml create mode 100644 res/layout/gestures_settings.xml create mode 100644 res/layout/gestures_settings_item.xml create mode 100644 src/com/android/launcher/GesturesActivity.java create mode 100644 src/com/android/launcher/GesturesConstants.java create mode 100644 src/com/android/launcher/GesturesPanel.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 39fa9a2..a0d0e5c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -91,6 +91,10 @@ + + + + + diff --git a/res/anim/fade_out_fast.xml b/res/anim/fade_out_fast.xml new file mode 100644 index 0000000..a061a6c --- /dev/null +++ b/res/anim/fade_out_fast.xml @@ -0,0 +1,23 @@ + + + + diff --git a/res/drawable/btn_circle.xml b/res/drawable/btn_circle.xml new file mode 100644 index 0000000..9208010 --- /dev/null +++ b/res/drawable/btn_circle.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + diff --git a/res/drawable/btn_circle_disable.png b/res/drawable/btn_circle_disable.png new file mode 100644 index 0000000000000000000000000000000000000000..33b74a66c07f360cd4590aff32f83aaf3e1db888 GIT binary patch literal 938 zcmV;b16BNqP)Ch-*X{PkvQZF$g)4+z8j?#1k$eZtgP<&%n<7wHTycBK6`QjsyY$my%E1#l=M)S6~l^$KdB=mkUVI zlh(DkxcE_NDQeUw^-T=Ml3mAw)6>%*dU|@?YSk&~n;68BC5C@MGQoj?0k7Iclre&g z*>J?#XJ%&lEEY?v+G4~Y7BMBoU{2M%BODG3yrv$;>R{|AN6bAwJ|1v597bMZ#3Ck} zQJxfYpePuPs$(G%J7Q>>gfRs2qJgoB)W2^*YOO}WD>gsB`R4P5FEkz7s5wqd42j(fHqut$I z)klpnju=x`L5*!}Y#c=*5!D_9>YEtElD)KM*E_PjynGgoMinm~)F<^#jK}wu-0fR0 z(g=1mokK%Iip-%02M2f9d7a~M5A5TA+3X0url_tV4L+8R~%FH|AGyi(-d#iP*+ zBl^b1#$4Ef>HU7c!Rz(v+S=NFixvt6F`Z89lF8%)D!RD6y-lwIz9GIg8l|Si9_`Zo zw244?+3M5APG7TmkLuHRMDnTKw&G&k7iXH7CZ>sLVw#w8?Uw)p01hp0ZAtpRSO5S3 M07*qoM6N<$f@yNRPyhe` literal 0 HcmV?d00001 diff --git a/res/drawable/btn_circle_disable_focused.png b/res/drawable/btn_circle_disable_focused.png new file mode 100644 index 0000000000000000000000000000000000000000..005ad8dcaeaeb6f245fe6090282ac10fbdc1c78d GIT binary patch literal 1436 zcmV;N1!MY&P)c(T!WGpB|q7gCE#i?;IF(fW7#z*uI7o#yVGg+3HEt@WT#06u50k<&z zkd4P8ZgIK}feDnBcU$^`zFWVq^fH`VU$n+QdXg_U_uSs^_q(U}-t(PH`Tc(Ke{57z zEGd>0i{>PX62pdpkV2$Dh#&;P!)^#CgdJiL!UhaSvG0FT#0hIAj1pOI$}RI6~P+nsaI!D@&TQ zgo}>|iBk~?z(qu+S(o%$Ti@;TE$62%LDx(qBN$c;q|#n_R9x}VbEOAVV&itc-zAIX zp7BsZtHzOj`QQ)3hfWPRZUA>7W-*XrziObL-cyTaQUpIJY1y zFj)n;Htd(VrGvky>x+EH2l#){DH>bgs`v$+gG&EFZsfk^BgJW5i-0 zmGa^Pr25G7?BNXmosk+(t?BwD_pRPo{>sOuj#d?#uk7UZ4HT`-&1LP6Q{ z@Jg_mrA2dz62sgm6@c>XdAJEBCKGz>>HgpeW{Jv_Ffqu@agd4ZP+}6=>tuYvSKFdw zd74Q?!`$46(S9f1_>xd!Jp3{BAJvYWn4kLh{eJh&?{ZV3Meb3SE>1CtlET~^2bqBi zDTLUPoun9Xmcn4H8bzYSFgM3RCbC0`nGBwk=@l`{SZ2b+AUDTBCbC0`8Ah!9ac(DT zrnfp#VyklpnTAkevnP8s18!Opu;+W0~Ph^=!yQxZVxREyu$=;Yx<^}N2}r`g}E?iU>yJqWFa$b z*#qX2$JL~L*u(zH^<__A*;z7tX7gI5FbC#ZDL#RLEM!Jfk6}}Pa6xy zaV;Z<=VFePrwno<16k27MhadpCwo7vmhWSHmb{hKkw9h~D{ku0DI+U~l)BF1qS4lR z?(YjMMtJhcPkSuKPN>PZz@La^`Bw5;IeF>8c6K-G)tCNboyeD1X0xh#tg@mx8>2EU zNtfqU3B1u6n?@nCj%8-e_EvLGAJ_CWJACM}{@h75IScIDYgzEBw&sx9&l;3_%7pqY ze4itxY0ASB+lE~1_4nI#r>{}CX zZpEKpcTe?y^9N~zE?k9&EvKUT+JmD9o3b4$vh|H8@+&rFc$Mo!j!Y@Br}39d7viBr zwndV%Fz*!4-7?U+<3DR}bZf{}I5ZS)l;ZIj3U6d3L@va-VD%~GO0D3$HxDs|RrLK< q+sQAANwK6@QYR;sZopj1nGv5j7+lgpe3yMT#IF z#!?rgK-VqHZlTJD=a_rVFwV@*E<@wPPI59ccV_N6bMEKwmv(%7Z2LbRX(?eTVJTs} zNlP>PoeOvp$Of_iC*Tmz9s)5S0^A2eK-5g7C$uCeEEhNr6buXuTrVvxea7qc7G`B- z|T*w|Pr1U>I`I-fG* zLM#@$2Z2A-*Vp&s_7`A9&%s26YbaHxndZMwh@dMn> z0UM_g_B2p6H8s^yTwGjfMoc`BD=RC3nwpv(TuuYOB_pf=s0{=H?-UgkT~@xAs5oW4 zyk1>hov*B{d>^-yqW_r|#*y~Q3N5{x1 zHVV4J>~bVNDq#u^yOFSi-QC>>E|<&wD4J6C*=RKSfZRsH_Cldh1Vi{AVaj>T>dw!4 z*o-`rliWr#{~y6%FsyV+>6~m{HPmaP-aG16kk{lSw~?^l=jZ1`T4Y*_x89;|RIc?C zp`^`H6a{p)Mx#|a6%L2@5Swmwc6NS` z;#ilQtkg%{+sxu`lB`hT@CCs|xZvh4Xd{=#v_6N+a00000 LNkvXXu0mjfjPhEW literal 0 HcmV?d00001 diff --git a/res/drawable/btn_circle_pressed.png b/res/drawable/btn_circle_pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..8f40afdfcb30cf7e2f014bba7c03ff2372d539e2 GIT binary patch literal 1613 zcmV-T2D15yP)O9+s$JUDLZP5BfU(#pSbXq@2cJwxgAYVWFfk@Rm>3gDPIgERpe zO{!3+W=pBXluE%;yR==}?sjH(uIJ3&p|jJSooNdR4?D@1{kb#e`|i2to_o$v&N+D$ zFGe2GD|tMS;}1%y)93O5C4gdpACL?1im@nQ8ZZF}0mcAfCq|7K!oA3eJPxQp zw0+v#RQuG5veH$)HU9jp;+$+ZiY2p=Sa>2l7aF-Ub1OJ7KHU1jnNIk7NZ__pB5G8b z#$yG58bIy& zggpUhJpIk~{o5M~H~YLKkEn5y5Ty$v260q3rU}!)Tx2Fn?p^4aytwP_3tz$L?|^@n zBdiLrt+(U3cb~5E){=OP5H&V03dL|@;W7PF7Y9+}a+5y>qrv)?U%r6BF2LYYgkdZ- z_nqEyaDAD(io_M7c?zKka(I+!HmT+{4K9gX9gYpw?E38p44e_M=U5o-v{x^Ezwyw< zC*1)OQw*M3geappeQD8iZi!s}E7rg1wabTL^artnlZ092v1d;nt=_j`9Sv}KmZ(Yu zbj=CK;4}pwJA{&WMt>e`p-r@n9LQyx$HY|$*z!Vyr%nX}1dNk7#*>;Np6onFQ^Xwu z$0x!9Igkr*4e+;}u+8nqDqhKzqCPImgvVnRfzEBj(S@J-hKMslU2&2t#eK-N=fKca zJ7Fs^S2YG`r7A}V#unF712^R>o@Pg;*qKS<5};bl#z|v9!WF6&-2Yh#3w+dC{7j)& z&Qs+%!ZGO0N%a1A^MB@@XcR@0{}UU5!lVnmJP$cPZVUaKPT0D-O1eswql7Cc%(#qJ z;w;2Eo)yGcKp3{VXrZ zDnjmbF}7l59?4e~4j$te0=9yh8kuPUbz=J2w8%peRo zkvp9*H!eFDC(m+(5hubht0j1-S&LcN+~hQm`aLv{bkYN{|z|)0NiK zqZ54AxRdl0#zopn`ppg1j~P7uv&I8H!Kqcfmcqua-{$uSWAoeEDsZ@s2W`DACJ$U1 zXQBkR6n5jv^;np?T##n45`Znu=zN}JB=vVISlqAK)NJF?(f z=$ahW{CGs{2JG!TR?)PziWP807nev>+*pfoOGP5snU)q%6qP)rdr+NfIxvKd@NvK? z(Y-tFk_xdQ0km`+uGrC3@9}d*x$ikG=JcZ>Wj!VeLN@OyDoLcXSDt7&G;|(r=n##_ z(?KaR`1;8&40{KJp^}aQa5g>wQ%`h_x(G;<+GQf;eDTsV8hXkyAN>1 z=&7X&(_?NmcIDWt0P5a(qo}f>+LIk9ld?)nn5VdaX$3)ON{x+-sdE3YGS_oWj(mCI zE;bo|0xpUQbySb32O-S(as>;z_1GT($^dJ`lIM$i!eWVT0fqtn0PKRstzM=)2x0n_ zErj5EF`w|LB*qlskr`1jpwP%--;dGE>-3DUjIfNbjIf7u{V%`(GZqttrg%w600000 LNkvXXu0mjfZ21U! literal 0 HcmV?d00001 diff --git a/res/drawable/btn_circle_selected.png b/res/drawable/btn_circle_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..c74fac227a2381ea347e0ee4930b0f60cd00645e GIT binary patch literal 1645 zcmV-z29o)SP)r` z9p1gOv7)f7tkAXI<92$o9ikgASPDk`v;M&B==9=5&&c%X!Ojc6z}Lf|A<$T=j;Nl} zB4T-A~=MAO?_N*r}05+U(Wx@eIEQPaT@Q6zue3HCM}^?nK$Tms#^AFvA0bG;|( z-l;9JRgo}4NhCUaF@jhz?2*Vz$*%!#^6Fh;VjteLOO-?bzT+IQcD~!7*TF>)4ZL4wb#x{1~fdfn~yi z0m8yT!Xl^;D9i<)B@-F1!{4F}v;{WNHu^wcN!KxcRARF4suCJlU`34-4k6QWi5WB2 zBw{o^gp_|TLDZxO*;OSrpfAt>Us5c=wsjpW+Up|0Y!-~b-Ag>#bx=_l{fuO>0Q&Ka zLh^HxKq$buGNf$uweQqaUox;lPx;(5h`J+HIHfjKt?~vW-^TQvP&Rc<1JyC2c zmT?G!d?qqEZsg+LNfJ29Zzv{k0+?p-6(#7CZ*^D9z)H(=s7DTg5fGE$T~4rESuJ4+ zBNqFv{6qjK0Am1+KKVXunqvip4wAzVDh!HPL?_i&ftp1TULwE&82UutR$y+ojXLE3 zK!u1JvHJx|3WyOS3YgnTo#@*NOq3hSs z#w=w#Y1ta0mN4J$Ex70teOr~*{>de_j2AOSBwj@Kt{1zvImAf+n>eXUh~g7`TY*gt z`k0^Duo6%=T}wD>05JPMfep3V0Q(^H$;)t4u-o1VX@Q8a#@J9`M1vC^kSMo_wv`%9 zAc{}$Z3T9t^UCtTbVyjy;Iz9^jA_PgT>8&O8LtyGAT3fV6#7D+=-Vp!2eGQWJW6L? z*_>5I2yRR2CeDnTx3SXD&QTg)QRtR@WgbCAOYK1Hu#?L-r5Rqy4kl|xo(FXdzhBy( zOQj4sgcXJyU$ru`1&*4RKRnqfL`%pJ$vm@#DnA;-JretXv&q`G-$5;1|HxZjZ*rAU z0rpIY8pF+8PO%mtvg{ zp);A87mQYm7+vy)4I`bF|sfT@*&Lc2J8APPfxbu8f9(|H3MVeB=300malC1I1hAN11KhZ@_SsdDGbP@l1g z93581^n0lx$0)a_4Ij)v0Xn2#hx~J|e%6Dn+jqPX@!d~fxenA0+H>-?>fJ9l6?pO; zf}I&6#|#xLrLmF1WS<6@*!YDzsCjH^i#s0a4x4!rr*Q!5wPI(hOCRi<)mUCETK84K+KEvo}FRyHV zJSVeyLzbh|kxDYZmwr-Qn9zKM1>^nPiWn)&iy-nH literal 0 HcmV?d00001 diff --git a/res/drawable/gestures_background.xml b/res/drawable/gestures_background.xml new file mode 100755 index 0000000..34ec051 --- /dev/null +++ b/res/drawable/gestures_background.xml @@ -0,0 +1,20 @@ + + + + diff --git a/res/drawable/ic_btn_round_plus.png b/res/drawable/ic_btn_round_plus.png new file mode 100644 index 0000000000000000000000000000000000000000..1ec8a956abd8553fe4aa9ef89e484261c1cdbced GIT binary patch literal 526 zcmV+p0`dKcP)M0=r(H!Qin zQ4iA$!x^JaVxPqXA86AX7;p1QN&cbShOGi{5imY9v-3lM28rtn=V4U6#czHA>_WYRPbox%K?ig(m=7Wy|UF5 zxK~E|kb#;zi=}=M2bO~VhRRAwZb_I0nEpyqFa=XE1ye8uQ!oYlTlp4X0EjV>gx4b+ Q$^ZZW07*qoM6N<$f|9b`bpQYW literal 0 HcmV?d00001 diff --git a/res/drawable/texture_paper.jpg b/res/drawable/texture_paper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27f4fd66761587f56156ebda7bffdecb7666aee2 GIT binary patch literal 989 zcmex=>NCNTpS!+d;+|@e7w8@f;>Qyk4KPSNLWNzh+jfNN=iaP zSxHGrSse}-IXO5uxj4BwIl1|`dAa!n`G7!B7;J#Bgdjf<2ug^Fi-?Gbi-ELDNhvBS zC@RABtN%a1AjrY6fMEeMqY?v?AS1IN*4%x-t+c{Y?-=yg)?7uEG|F6*_olOPjx9(rbc#EsTY=YpjBRt~j zZ-SO{TLv&NEvmR^u<2d@tFASz?V3&chbER~6}?b9bSBi-vDmQvr^?)=y{YN<&-`<| z>Pi4A^Q3=Iq#=eI{i9u7J&F`b#MsgCAfzxAKp7;G4mVDR>z_4DAV zS5uCauTTnnwybjbl+Z_ul(w^2o0=s()nZ+)f9YGoLOrnp^$8_MzIAJxX&IiHtzCIt zDMz$r)tB-$9O`y5U7IJrzV2udR&eR7@9PwHlTVi%Ekhm%&oG{v$@nm0J?8?&N46iL z9hZrHn(Ml!`2x?bZ~JGgzjR4!QhsjhFNfV?qRTa;c2)b$kDekWIwyEaJXfE6_VkwD zA)mTtSL()RZk)&TRM+M~LxY~H_%HQ43-(8bl=%y6@HPtEDDYP$n(KniMXPC#mNGtg zqRO=JzNpdaOApt@i#+@`Yt9O-AHH^l=5>q{i$6p=oVugCpLOnaoea&*e`(Ibht*%+5|Hag@)^YCgRa{$xH`~0e4NcE{)$}~|wdAjP TQ?270ZpFqgGg=o=|NkZcMTcg} literal 0 HcmV?d00001 diff --git a/res/layout/gestures.xml b/res/layout/gestures.xml new file mode 100644 index 0000000..d2beaa8 --- /dev/null +++ b/res/layout/gestures.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/gestures_settings.xml b/res/layout/gestures_settings.xml new file mode 100644 index 0000000..4b1976f --- /dev/null +++ b/res/layout/gestures_settings.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/res/layout/gestures_settings_item.xml b/res/layout/gestures_settings_item.xml new file mode 100644 index 0000000..3c47cab --- /dev/null +++ b/res/layout/gestures_settings_item.xml @@ -0,0 +1,31 @@ + + + + diff --git a/res/layout/rename_folder.xml b/res/layout/rename_folder.xml index 2c578f3..ba78995 100644 --- a/res/layout/rename_folder.xml +++ b/res/layout/rename_folder.xml @@ -21,6 +21,7 @@ android:orientation="vertical"> #B2191919 #A5FF0000 - #fccc - #f444 + #FCCC + #F444 + + #FFFFFF00 diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 4ae6686..b802353 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -16,4 +16,6 @@ 19dip + 8dip + 64dip diff --git a/res/values/strings.xml b/res/values/strings.xml index 3e8cb7c..f083c98 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -94,7 +94,9 @@ Search Notifications - + + Gestures + Settings @@ -123,4 +125,31 @@ Problem loading widget + + + + + Draw a gesture to get started + + Unknown gesture + + Added gesture "%s" + + Gesture could not be created + + Loading gestures... + + No gestures defined + + Gestures + + Rename + + Delete + + Gesture deleted + + Rename gesture + + Gesture name diff --git a/res/values/styles.xml b/res/values/styles.xml index 9b06d26..5319bb0 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -60,4 +60,10 @@ 10dip 10dip + + + diff --git a/src/com/android/launcher/ApplicationInfo.java b/src/com/android/launcher/ApplicationInfo.java index 9bc0950..c9c8c5c 100644 --- a/src/com/android/launcher/ApplicationInfo.java +++ b/src/com/android/launcher/ApplicationInfo.java @@ -61,7 +61,7 @@ class ApplicationInfo extends ItemInfo { Intent.ShortcutIconResource iconResource; ApplicationInfo() { - itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT; } public ApplicationInfo(ApplicationInfo info) { @@ -80,7 +80,7 @@ class ApplicationInfo extends ItemInfo { /** * Creates the application intent based on a component name and various launch flags. - * Sets {@link #itemType} to {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}. + * Sets {@link #itemType} to {@link LauncherSettings.BaseLauncherColumns#ITEM_TYPE_APPLICATION}. * * @param className the class name of the component representing the intent * @param launchFlags the launch flags @@ -90,7 +90,7 @@ class ApplicationInfo extends ItemInfo { intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.setComponent(className); intent.setFlags(launchFlags); - itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION; } @Override @@ -98,22 +98,24 @@ class ApplicationInfo extends ItemInfo { super.onAddToDatabase(values); String titleStr = title != null ? title.toString() : null; - values.put(LauncherSettings.Favorites.TITLE, titleStr); + values.put(LauncherSettings.BaseLauncherColumns.TITLE, titleStr); String uri = intent != null ? intent.toURI() : null; - values.put(LauncherSettings.Favorites.INTENT, uri); + values.put(LauncherSettings.BaseLauncherColumns.INTENT, uri); if (customIcon) { - values.put(LauncherSettings.Favorites.ICON_TYPE, - LauncherSettings.Favorites.ICON_TYPE_BITMAP); + values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE, + LauncherSettings.BaseLauncherColumns.ICON_TYPE_BITMAP); Bitmap bitmap = ((FastBitmapDrawable) icon).getBitmap(); writeBitmap(values, bitmap); } else { - values.put(LauncherSettings.Favorites.ICON_TYPE, - LauncherSettings.Favorites.ICON_TYPE_RESOURCE); + values.put(LauncherSettings.BaseLauncherColumns.ICON_TYPE, + LauncherSettings.BaseLauncherColumns.ICON_TYPE_RESOURCE); if (iconResource != null) { - values.put(LauncherSettings.Favorites.ICON_PACKAGE, iconResource.packageName); - values.put(LauncherSettings.Favorites.ICON_RESOURCE, iconResource.resourceName); + values.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, + iconResource.packageName); + values.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE, + iconResource.resourceName); } } } diff --git a/src/com/android/launcher/GesturesActivity.java b/src/com/android/launcher/GesturesActivity.java new file mode 100644 index 0000000..a112e1b --- /dev/null +++ b/src/com/android/launcher/GesturesActivity.java @@ -0,0 +1,310 @@ +/* + * 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.launcher; + +import android.app.ListActivity; +import android.app.Dialog; +import android.app.AlertDialog; +import android.os.Bundle; +import android.os.AsyncTask; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import android.widget.AdapterView; +import android.widget.Toast; +import android.widget.EditText; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.view.View; +import android.view.ViewGroup; +import android.view.LayoutInflater; +import android.view.ContextMenu; +import android.view.MenuItem; +import android.gesture.GestureLibrary; +import android.gesture.Gesture; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.BitmapDrawable; +import android.text.TextUtils; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Collections; +import java.util.Map; + +public class GesturesActivity extends ListActivity { + private static final int MENU_ID_RENAME = 1; + private static final int MENU_ID_REMOVE = 2; + + private static final int DIALOG_RENAME_GESTURE = 1; + + private final Comparator mSorter = + new LauncherModel.ApplicationInfoComparator(); + + private GesturesAdapter mAdapter; + private GestureLibrary mStore; + private GesturesLoadTask mTask; + private TextView mEmpty; + + private Dialog mRenameDialog; + private EditText mInput; + private ApplicationInfo mCurrentRenameInfo; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.gestures_settings); + + mAdapter = new GesturesAdapter(this); + setListAdapter(mAdapter); + + mStore = Launcher.getGestureLibrary(); + mEmpty = (TextView) findViewById(android.R.id.empty); + mTask = (GesturesLoadTask) new GesturesLoadTask().execute(); + + registerForContextMenu(getListView()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if (mTask != null && mTask.getStatus() != GesturesLoadTask.Status.FINISHED) { + mTask.cancel(true); + mTask = null; + } + + cleanupRenameDialog(); + } + + private void checkForEmpty() { + if (mAdapter.getCount() == 0) { + mEmpty.setText(R.string.gestures_empty); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenu.ContextMenuInfo menuInfo) { + + super.onCreateContextMenu(menu, v, menuInfo); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + menu.setHeaderTitle(((TextView) info.targetView).getText()); + + menu.add(0, MENU_ID_RENAME, 0, R.string.gestures_rename); + menu.add(0, MENU_ID_REMOVE, 0, R.string.gestures_delete); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) + item.getMenuInfo(); + final ApplicationInfo info = (ApplicationInfo) menuInfo.targetView.getTag(); + + switch (item.getItemId()) { + case MENU_ID_RENAME: + renameGesture(info); + return true; + case MENU_ID_REMOVE: + deleteGesture(info); + return true; + } + + return super.onContextItemSelected(item); + } + + private void renameGesture(ApplicationInfo info) { + mCurrentRenameInfo = info; + showDialog(DIALOG_RENAME_GESTURE); + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id == DIALOG_RENAME_GESTURE) { + return createRenameDialog(); + } + return super.onCreateDialog(id); + } + + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + super.onPrepareDialog(id, dialog); + if (id == DIALOG_RENAME_GESTURE) { + mInput.setText(mCurrentRenameInfo.title); + } + } + + private Dialog createRenameDialog() { + final View layout = View.inflate(this, R.layout.rename_folder, null); + mInput = (EditText) layout.findViewById(R.id.folder_name); + ((TextView) layout.findViewById(R.id.label)).setText(R.string.gestures_rename_label); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setIcon(0); + builder.setTitle(getString(R.string.gestures_rename_title)); + builder.setCancelable(true); + builder.setOnCancelListener(new Dialog.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + cleanupRenameDialog(); + } + }); + builder.setNegativeButton(getString(R.string.cancel_action), + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + cleanupRenameDialog(); + } + } + ); + builder.setPositiveButton(getString(R.string.rename_action), + new Dialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + changeGestureName(); + } + } + ); + builder.setView(layout); + return builder.create(); + } + + private void changeGestureName() { + final String name = mInput.getText().toString(); + if (!TextUtils.isEmpty(name)) { + mCurrentRenameInfo.title = mInput.getText(); + LauncherModel.updateGestureInDatabase(this, mCurrentRenameInfo); + } + } + + private void cleanupRenameDialog() { + if (mRenameDialog != null) { + mRenameDialog.dismiss(); + mRenameDialog = null; + mInput = null; + } + } + + private void deleteGesture(ApplicationInfo info) { + mStore.removeEntry(String.valueOf(info.id)); + // TODO: On a thread? + mStore.save(); + + final GesturesActivity.GesturesAdapter adapter = mAdapter; + adapter.setNotifyOnChange(false); + adapter.remove(info); + adapter.sort(mSorter); + checkForEmpty(); + adapter.notifyDataSetChanged(); + + LauncherModel.deleteGestureFromDatabase(this, info); + + Toast.makeText(this, R.string.gestures_delete_success, Toast.LENGTH_SHORT).show(); + } + + private class GesturesLoadTask extends AsyncTask { + private int mThumbnailSize; + private int mThumbnailInset; + private int mPathColor; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + + final Resources resources = getResources(); + mPathColor = resources.getColor(R.color.gesture_color); + mThumbnailInset = (int) resources.getDimension(R.dimen.gesture_thumbnail_inset); + mThumbnailSize = (int) resources.getDimension(R.dimen.gesture_thumbnail_size); + } + + protected Boolean doInBackground(Void... params) { + if (isCancelled()) return Boolean.FALSE; + + final GestureLibrary store = mStore; + + if (store.load()) { + final LauncherModel model = Launcher.getModel(); + + for (String name : store.getGestureEntries()) { + final Gesture gesture = store.getGestures(name).get(0); + final Bitmap bitmap = gesture.toBitmap(mThumbnailSize, mThumbnailSize, + mThumbnailInset, mPathColor); + final ApplicationInfo info = model.queryGesture(GesturesActivity.this, name); + + mAdapter.addBitmap(info.id, bitmap); + publishProgress(info); + } + + return Boolean.TRUE; + } + + return Boolean.FALSE; + } + + @Override + protected void onProgressUpdate(ApplicationInfo... values) { + super.onProgressUpdate(values); + + final GesturesActivity.GesturesAdapter adapter = mAdapter; + adapter.setNotifyOnChange(false); + + for (ApplicationInfo info : values) { + adapter.add(info); + } + + adapter.sort(mSorter); + adapter.notifyDataSetChanged(); + } + + @Override + protected void onPostExecute(Boolean aBoolean) { + super.onPostExecute(aBoolean); + checkForEmpty(); + } + } + + private class GesturesAdapter extends ArrayAdapter { + private final LayoutInflater mInflater; + private final Map mThumbnails = Collections.synchronizedMap( + new HashMap()); + + public GesturesAdapter(Context context) { + super(context, 0); + mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + void addBitmap(Long id, Bitmap bitmap) { + mThumbnails.put(id, new BitmapDrawable(bitmap)); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.gestures_settings_item, parent, false); + } + + final ApplicationInfo info = getItem(position); + final TextView label = (TextView) convertView; + + label.setTag(info); + label.setText(info.title); + label.setCompoundDrawablesWithIntrinsicBounds(info.icon, null, + mThumbnails.get(info.id), null); + + return convertView; + } + } +} diff --git a/src/com/android/launcher/GesturesConstants.java b/src/com/android/launcher/GesturesConstants.java new file mode 100644 index 0000000..3151ea3 --- /dev/null +++ b/src/com/android/launcher/GesturesConstants.java @@ -0,0 +1,25 @@ +/* + * 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.launcher; + +interface GesturesConstants { + final double PREDICTION_THRESHOLD = 1.0; + final String STORE_NAME = "gestures"; + final long MATCH_DELAY = 370; + final float LENGTH_THRESHOLD = 120.0f; + int PATH_SAMPLE_COUNT = 10; +} diff --git a/src/com/android/launcher/GesturesPanel.java b/src/com/android/launcher/GesturesPanel.java new file mode 100644 index 0000000..ee39613 --- /dev/null +++ b/src/com/android/launcher/GesturesPanel.java @@ -0,0 +1,47 @@ +/* + * 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.launcher; + +import android.widget.RelativeLayout; +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; + +public class GesturesPanel extends RelativeLayout { + public GesturesPanel(Context context) { + super(context); + } + + public GesturesPanel(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean isOpaque() { + return true; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + ((Launcher) mContext).hideGesturesPanel(); + return true; + } + + return super.dispatchKeyEvent(event); + } +} diff --git a/src/com/android/launcher/ItemInfo.java b/src/com/android/launcher/ItemInfo.java index 51449a7..71cee18 100644 --- a/src/com/android/launcher/ItemInfo.java +++ b/src/com/android/launcher/ItemInfo.java @@ -76,6 +76,11 @@ class ItemInfo { */ int spanY = 1; + /** + * Indicates whether the item is a gesture. + */ + boolean isGesture = false; + ItemInfo() { } @@ -96,13 +101,15 @@ class ItemInfo { * @param values */ void onAddToDatabase(ContentValues values) { - values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); - values.put(LauncherSettings.Favorites.CONTAINER, container); - values.put(LauncherSettings.Favorites.SCREEN, screen); - values.put(LauncherSettings.Favorites.CELLX, cellX); - values.put(LauncherSettings.Favorites.CELLY, cellY); - values.put(LauncherSettings.Favorites.SPANX, spanX); - values.put(LauncherSettings.Favorites.SPANY, spanY); + values.put(LauncherSettings.BaseLauncherColumns.ITEM_TYPE, itemType); + if (!isGesture) { + values.put(LauncherSettings.Favorites.CONTAINER, container); + values.put(LauncherSettings.Favorites.SCREEN, screen); + values.put(LauncherSettings.Favorites.CELLX, cellX); + values.put(LauncherSettings.Favorites.CELLY, cellY); + values.put(LauncherSettings.Favorites.SPANX, spanX); + values.put(LauncherSettings.Favorites.SPANY, spanY); + } } static void writeBitmap(ContentValues values, Bitmap bitmap) { diff --git a/src/com/android/launcher/Launcher.java b/src/com/android/launcher/Launcher.java index b4437d4..0c54382 100644 --- a/src/com/android/launcher/Launcher.java +++ b/src/com/android/launcher/Launcher.java @@ -41,6 +41,8 @@ import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Rect; +import android.graphics.PorterDuffXfermode; +import android.graphics.PorterDuff; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; @@ -58,7 +60,6 @@ import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.method.TextKeyListener; -import android.util.Log; import static android.util.Log.*; import android.view.Display; import android.view.KeyEvent; @@ -67,6 +68,8 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.MotionEvent; +import android.view.Gravity; import android.view.View.OnLongClickListener; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -74,8 +77,16 @@ import android.widget.GridView; import android.widget.SlidingDrawer; import android.widget.TextView; import android.widget.Toast; +import android.widget.ImageView; +import android.widget.PopupWindow; +import android.widget.ViewSwitcher; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; +import android.gesture.GestureOverlayView; +import android.gesture.GestureLibraries; +import android.gesture.GestureLibrary; +import android.gesture.Gesture; +import android.gesture.Prediction; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -92,6 +103,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On private static final boolean PROFILE_DRAWER = false; private static final boolean PROFILE_ROTATE = false; private static final boolean DEBUG_USER_INTERFACE = false; + private static final boolean DEBUG_GESTURES = false; private static final int WALLPAPER_SCREENS_SPAN = 2; @@ -100,7 +112,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On private static final int MENU_WALLPAPER_SETTINGS = MENU_ADD + 1; private static final int MENU_SEARCH = MENU_WALLPAPER_SETTINGS + 1; private static final int MENU_NOTIFICATIONS = MENU_SEARCH + 1; - private static final int MENU_SETTINGS = MENU_NOTIFICATIONS + 1; + private static final int MENU_GESTURES = MENU_NOTIFICATIONS + 1; + private static final int MENU_SETTINGS = MENU_GESTURES + 1; private static final int REQUEST_CREATE_SHORTCUT = 1; private static final int REQUEST_CREATE_LIVE_FOLDER = 4; @@ -109,6 +122,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On private static final int REQUEST_PICK_SHORTCUT = 7; private static final int REQUEST_PICK_LIVE_FOLDER = 8; private static final int REQUEST_PICK_APPWIDGET = 9; + private static final int REQUEST_PICK_GESTURE_ACTION = 10; + private static final int REQUEST_CREATE_GESTURE_ACTION = 11; + private static final int REQUEST_CREATE_GESTURE_APPLICATION_ACTION = 12; static final String EXTRA_SHORTCUT_DUPLICATE = "duplicate"; @@ -154,6 +170,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME = "launcher.rename_folder"; // Type: long private static final String RUNTIME_STATE_PENDING_FOLDER_RENAME_ID = "launcher.rename_folder_id"; + // Type: Gesture (Parcelable) + private static final String RUNTIME_STATE_PENDING_GESTURE = "launcher.gesture"; private static final LauncherModel sModel = new LauncherModel(); @@ -164,6 +182,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On private static WallpaperIntentReceiver sWallpaperReceiver; + private static GestureLibrary sLibrary; + private final BroadcastReceiver mApplicationsReceiver = new ApplicationsIntentReceiver(); private final ContentObserver mObserver = new FavoritesChangeObserver(); @@ -202,11 +222,26 @@ public final class Launcher extends Activity implements View.OnClickListener, On private DesktopBinder mBinder; + private View mGesturesPanel; + private GestureOverlayView mGesturesOverlay; + private ViewSwitcher mGesturesPrompt; + private ImageView mGesturesAdd; + private PopupWindow mGesturesWindow; + private Launcher.GesturesProcessor mGesturesProcessor; + private Gesture mCurrentGesture; + private GesturesAction mGesturesAction; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInflater = getLayoutInflater(); + if (sLibrary == null) { + // The context is not kept by the library so it's safe to do this + sLibrary = GestureLibraries.fromPrivateFile(Launcher.this, + GesturesConstants.STORE_NAME); + } + mAppWidgetManager = AppWidgetManager.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); @@ -308,13 +343,17 @@ public final class Launcher extends Activity implements View.OnClickListener, On // For example, the user would PICK_SHORTCUT for "Music playlist", and we // launch over to the Music app to actually CREATE_SHORTCUT. - if (resultCode == RESULT_OK && mAddItemCellInfo != null) { + if (resultCode == RESULT_OK && (mAddItemCellInfo != null || + ((requestCode == REQUEST_PICK_GESTURE_ACTION || + requestCode == REQUEST_CREATE_GESTURE_ACTION || + requestCode == REQUEST_CREATE_GESTURE_APPLICATION_ACTION) && mCurrentGesture != null))) { + switch (requestCode) { case REQUEST_PICK_APPLICATION: completeAddApplication(this, data, mAddItemCellInfo, !mDesktopLocked); break; case REQUEST_PICK_SHORTCUT: - addShortcut(data); + processShortcut(data, REQUEST_PICK_APPLICATION, REQUEST_CREATE_SHORTCUT); break; case REQUEST_CREATE_SHORTCUT: completeAddShortcut(data, mAddItemCellInfo, !mDesktopLocked); @@ -331,6 +370,16 @@ public final class Launcher extends Activity implements View.OnClickListener, On case REQUEST_CREATE_APPWIDGET: completeAddAppWidget(data, mAddItemCellInfo, !mDesktopLocked); break; + case REQUEST_PICK_GESTURE_ACTION: + processShortcut(data, REQUEST_CREATE_GESTURE_APPLICATION_ACTION, + REQUEST_CREATE_GESTURE_ACTION); + break; + case REQUEST_CREATE_GESTURE_ACTION: + completeCreateGesture(data, true); + break; + case REQUEST_CREATE_GESTURE_APPLICATION_ACTION: + completeCreateGesture(data, false); + break; } } else if (requestCode == REQUEST_PICK_APPWIDGET && resultCode == RESULT_CANCELED && data != null) { @@ -358,10 +407,20 @@ public final class Launcher extends Activity implements View.OnClickListener, On @Override protected void onPause() { super.onPause(); + if (mGesturesWindow != null) { + mGesturesWindow.setAnimationStyle(0); + mGesturesWindow.update(); + } closeDrawer(false); } @Override + protected void onStop() { + super.onStop(); + hideGesturesPanel(); + } + + @Override public Object onRetainNonConfigurationInstance() { // Flag any binder to stop early before switching if (mBinder != null) { @@ -448,6 +507,8 @@ public final class Launcher extends Activity implements View.OnClickListener, On mFolderInfo = sModel.getFolderById(this, id); mRestoring = true; } + + mCurrentGesture = (Gesture) savedState.get(RUNTIME_STATE_PENDING_GESTURE); } /** @@ -495,6 +556,68 @@ public final class Launcher extends Activity implements View.OnClickListener, On dragLayer.setIgnoredDropTarget(grid); dragLayer.setDragScoller(workspace); dragLayer.setDragListener(deleteZone); + + mGesturesPanel = mInflater.inflate(R.layout.gestures, mDragLayer, false); + final View gesturesPanel = mGesturesPanel; + + mGesturesPrompt = (ViewSwitcher) gesturesPanel.findViewById(R.id.gestures_actions); + mGesturesAction = new GesturesAction(); + + mGesturesPrompt.getChildAt(0).setOnClickListener(mGesturesAction); + mGesturesPrompt.getChildAt(1).setOnClickListener(mGesturesAction); + + mGesturesAdd = (ImageView) gesturesPanel.findViewById(R.id.gestures_add); + final ImageView gesturesAdd = mGesturesAdd; + gesturesAdd.setAlpha(128); + gesturesAdd.setEnabled(false); + gesturesAdd.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + createGesture(); + } + }); + + mGesturesOverlay = (GestureOverlayView) gesturesPanel.findViewById(R.id.gestures_overlay); + mGesturesProcessor = new GesturesProcessor(); + + final GestureOverlayView overlay = mGesturesOverlay; + overlay.setFadeOffset(GesturesConstants.MATCH_DELAY); + overlay.addOnGestureListener(mGesturesProcessor); + overlay.getGesturePaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); + } + + private void createGesture() { + mCurrentGesture = mGesturesOverlay.getGesture(); + mWaitingForResult = true; + pickShortcut(REQUEST_PICK_GESTURE_ACTION, R.string.title_select_shortcut); + } + + private void completeCreateGesture(Intent data, boolean isShortcut) { + ApplicationInfo info; + + if (isShortcut) { + info = infoFromShortcutIntent(this, data); + } else { + info = infoFromApplicationIntent(this, data); + } + + boolean success = false; + if (info != null) { + info.isGesture = true; + + if (LauncherModel.addGestureToDatabase(this, info, false)) { + mGesturesProcessor.addGesture(String.valueOf(info.id), mCurrentGesture); + mGesturesProcessor.update(info, mCurrentGesture); + Toast.makeText(this, getString(R.string.gestures_created, info.title), + Toast.LENGTH_SHORT).show(); + success = true; + } + } + + if (!success) { + Toast.makeText(this, getString(R.string.gestures_failed), Toast.LENGTH_SHORT).show(); + } + + mCurrentGesture = null; } /** @@ -545,14 +668,20 @@ public final class Launcher extends Activity implements View.OnClickListener, On cellInfo.screen = mWorkspace.getCurrentScreen(); if (!findSingleSlot(cellInfo)) return; - // Find details for this application + final ApplicationInfo info = infoFromApplicationIntent(context, data); + if (info != null) { + mWorkspace.addApplicationShortcut(info, cellInfo, insertAtFirst); + } + } + + private static ApplicationInfo infoFromApplicationIntent(Context context, Intent data) { ComponentName component = data.getComponent(); PackageManager packageManager = context.getPackageManager(); ActivityInfo activityInfo = null; try { activityInfo = packageManager.getActivityInfo(component, 0 /* no flags */); } catch (NameNotFoundException e) { - Log.e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e); + e(LOG_TAG, "Couldn't find ActivityInfo for selected application", e); } if (activityInfo != null) { @@ -568,8 +697,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On itemInfo.icon = activityInfo.loadIcon(packageManager); itemInfo.container = ItemInfo.NO_ID; - mWorkspace.addApplicationShortcut(itemInfo, cellInfo, insertAtFirst); + return itemInfo; } + + return null; } /** @@ -653,6 +784,14 @@ public final class Launcher extends Activity implements View.OnClickListener, On static ApplicationInfo addShortcut(Context context, Intent data, CellLayout.CellInfo cellInfo, boolean notify) { + final ApplicationInfo info = infoFromShortcutIntent(context, data); + LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, + cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); + + return info; + } + + private static ApplicationInfo infoFromShortcutIntent(Context context, Intent data) { Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); @@ -660,7 +799,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On Drawable icon = null; boolean filtered = false; boolean customIcon = false; - Intent.ShortcutIconResource iconResource = null; + ShortcutIconResource iconResource = null; if (bitmap != null) { icon = new FastBitmapDrawable(Utilities.createBitmapThumbnail(bitmap, context)); @@ -668,9 +807,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On customIcon = true; } else { Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); - if (extra != null && extra instanceof Intent.ShortcutIconResource) { + if (extra != null && extra instanceof ShortcutIconResource) { try { - iconResource = (Intent.ShortcutIconResource) extra; + iconResource = (ShortcutIconResource) extra; final PackageManager packageManager = context.getPackageManager(); Resources resources = packageManager.getResourcesForApplication( iconResource.packageName); @@ -694,8 +833,6 @@ public final class Launcher extends Activity implements View.OnClickListener, On info.customIcon = customIcon; info.iconResource = iconResource; - LauncherModel.addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP, - cellInfo.screen, cellInfo.cellX, cellInfo.cellY, notify); return info; } @@ -723,15 +860,15 @@ public final class Launcher extends Activity implements View.OnClickListener, On // An exception is thrown if the dialog is not visible, which is fine } - // If we are already in front we go back to the default screen, - // otherwise we don't if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) { - if (!mWorkspace.isDefaultScreenShowing()) { - mWorkspace.moveToDefaultScreen(); + + if (mGesturesPanel != null && mDragLayer.getWindowVisibility() == View.VISIBLE) { + onHomeKeyPressed(); } closeDrawer(); - View v = getWindow().peekDecorView(); + + final View v = getWindow().peekDecorView(); if (v != null && v.getWindowToken() != null) { InputMethodManager imm = (InputMethodManager)getSystemService( INPUT_METHOD_SERVICE); @@ -743,6 +880,74 @@ public final class Launcher extends Activity implements View.OnClickListener, On } } + private void onHomeKeyPressed() { + if (mGesturesWindow == null || !mGesturesWindow.isShowing()) { + showGesturesPanel(); + } else { + hideGesturesPanel(); + } + } + + private void showGesturesPanel() { + resetGesturesPrompt(); + + mGesturesAdd.setEnabled(false); + mGesturesAdd.setAlpha(128); + + mGesturesOverlay.clear(false); + + PopupWindow window; + if (mGesturesWindow == null) { + mGesturesWindow = new PopupWindow(this); + window = mGesturesWindow; + window.setFocusable(true); + window.setTouchable(true); + window.setBackgroundDrawable(null); + window.setContentView(mGesturesPanel); + } else { + window = mGesturesWindow; + } + window.setAnimationStyle(com.android.internal.R.style.Animation_SlidingCard); + + final int[] xy = new int[2]; + final DragLayer dragLayer = mDragLayer; + dragLayer.getLocationOnScreen(xy); + + window.setWidth(dragLayer.getWidth()); + window.setHeight(dragLayer.getHeight() - 1); + window.showAtLocation(dragLayer, Gravity.TOP | Gravity.LEFT, xy[0], xy[1] + 1); + } + + private void resetGesturesPrompt() { + mGesturesAction.intent = null; + final TextView prompt = (TextView) mGesturesPrompt.getCurrentView(); + prompt.setText(R.string.gestures_instructions); + prompt.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); + prompt.setClickable(false); + } + + private void resetGesturesNextPrompt() { + mGesturesAction.intent = null; + setGesturesNextPrompt(null, getString(R.string.gestures_instructions)); + mGesturesPrompt.getNextView().setClickable(false); + } + + private void setGesturesNextPrompt(Drawable icon, CharSequence title) { + final TextView prompt = (TextView) mGesturesPrompt.getNextView(); + prompt.setText(title); + prompt.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); + prompt.setClickable(true); + mGesturesPrompt.showNext(); + } + + void hideGesturesPanel() { + if (mGesturesWindow != null) { + mGesturesWindow.setAnimationStyle(com.android.internal.R.style.Animation_SlidingCard); + mGesturesWindow.update(); + mGesturesWindow.dismiss(); + } + } + @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { // Do not call super here @@ -791,6 +996,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On outState.putBoolean(RUNTIME_STATE_PENDING_FOLDER_RENAME, true); outState.putLong(RUNTIME_STATE_PENDING_FOLDER_RENAME_ID, mFolderInfo.id); } + + if (mCurrentGesture != null && mWaitingForResult) { + outState.putParcelable(RUNTIME_STATE_PENDING_GESTURE, mCurrentGesture); + } } @Override @@ -911,6 +1120,11 @@ public final class Launcher extends Activity implements View.OnClickListener, On .setIcon(com.android.internal.R.drawable.ic_menu_notifications) .setAlphabeticShortcut('N'); + final Intent gestures = new Intent(this, GesturesActivity.class); + menu.add(0, MENU_GESTURES, 0, R.string.menu_gestures) + .setIcon(com.android.internal.R.drawable.ic_menu_compose).setAlphabeticShortcut('G') + .setIntent(gestures); + final Intent settings = new Intent(android.provider.Settings.ACTION_SETTINGS); settings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); @@ -1028,7 +1242,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On mWorkspace.addInCurrentScreen(view, xy[0], xy[1], info.spanX, spanY); } - void addShortcut(Intent intent) { + void processShortcut(Intent intent, int requestCodeApplication, int requestCodeShortcut) { // Handle case where user selected "Applications" String applicationName = getResources().getString(R.string.group_applications); String shortcutName = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); @@ -1039,9 +1253,9 @@ public final class Launcher extends Activity implements View.OnClickListener, On Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent); - startActivityForResult(pickIntent, REQUEST_PICK_APPLICATION); + startActivityForResult(pickIntent, requestCodeApplication); } else { - startActivityForResult(intent, REQUEST_CREATE_SHORTCUT); + startActivityForResult(intent, requestCodeShortcut); } } @@ -1482,7 +1696,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); } catch (SecurityException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - Log.e(LOG_TAG, "Launcher does not have the permission to launch " + intent + + e(LOG_TAG, "Launcher does not have the permission to launch " + intent + ". Make sure to create a MAIN intent-filter for the corresponding activity " + "or use the exported attribute for this activity.", e); } @@ -1601,6 +1815,10 @@ public final class Launcher extends Activity implements View.OnClickListener, On return sModel; } + static GestureLibrary getGestureLibrary() { + return sLibrary; + } + void closeAllApplications() { mDrawer.close(); } @@ -1669,6 +1887,26 @@ public final class Launcher extends Activity implements View.OnClickListener, On showDialog(DIALOG_CREATE_SHORTCUT); } + private void pickShortcut(int requestCode, int title) { + Bundle bundle = new Bundle(); + + ArrayList shortcutNames = new ArrayList(); + shortcutNames.add(getString(R.string.group_applications)); + bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); + + ArrayList shortcutIcons = new ArrayList(); + shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this, + R.drawable.ic_launcher_application)); + bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); + + Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); + pickIntent.putExtra(Intent.EXTRA_INTENT, new Intent(Intent.ACTION_CREATE_SHORTCUT)); + pickIntent.putExtra(Intent.EXTRA_TITLE, getText(title)); + pickIntent.putExtras(bundle); + + startActivityForResult(pickIntent, requestCode); + } + private class RenameFolder { private EditText mInput; @@ -1789,26 +2027,7 @@ public final class Launcher extends Activity implements View.OnClickListener, On switch (which) { case AddAdapter.ITEM_SHORTCUT: { // Insert extra item to handle picking application - Bundle bundle = new Bundle(); - - ArrayList shortcutNames = new ArrayList(); - shortcutNames.add(res.getString(R.string.group_applications)); - bundle.putStringArrayList(Intent.EXTRA_SHORTCUT_NAME, shortcutNames); - - ArrayList shortcutIcons = - new ArrayList(); - shortcutIcons.add(ShortcutIconResource.fromContext(Launcher.this, - R.drawable.ic_launcher_application)); - bundle.putParcelableArrayList(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, shortcutIcons); - - Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY); - pickIntent.putExtra(Intent.EXTRA_INTENT, - new Intent(Intent.ACTION_CREATE_SHORTCUT)); - pickIntent.putExtra(Intent.EXTRA_TITLE, - getText(R.string.title_select_shortcut)); - pickIntent.putExtras(bundle); - - startActivityForResult(pickIntent, REQUEST_PICK_SHORTCUT); + pickShortcut(REQUEST_PICK_SHORTCUT, R.string.title_select_shortcut); break; } @@ -2109,5 +2328,113 @@ public final class Launcher extends Activity implements View.OnClickListener, On } } } + + private class GesturesProcessor implements GestureOverlayView.OnGestureListener, + GestureOverlayView.OnGesturePerformedListener { + + private final GestureMatcher mMatcher = new GestureMatcher(); + + GesturesProcessor() { + // TODO: Maybe the load should happen on a background thread? + sLibrary.load(); + } + + public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) { + overlay.removeCallbacks(mMatcher); + resetGesturesNextPrompt(); + + mGesturesAdd.setAlpha(128); + mGesturesAdd.setEnabled(false); + } + + public void onGesture(GestureOverlayView overlay, MotionEvent event) { + } + + public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) { + } + + public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) { + overlay.removeCallbacks(mMatcher); + + mMatcher.gesture = overlay.getGesture(); + if (mMatcher.gesture.getLength() < GesturesConstants.LENGTH_THRESHOLD) { + overlay.clear(false); + } else { + overlay.postDelayed(mMatcher, GesturesConstants.MATCH_DELAY); + } + } + + private void matchGesture(Gesture gesture) { + mGesturesAdd.setAlpha(255); + mGesturesAdd.setEnabled(true); + + if (gesture != null) { + final ArrayList predictions = sLibrary.recognize(gesture); + + if (DEBUG_GESTURES) { + for (Prediction p : predictions) { + d(LOG_TAG, String.format("name=%s, score=%f", p.name, p.score)); + } + } + + boolean match = false; + if (predictions.size() > 0) { + final Prediction prediction = predictions.get(0); + if (prediction.score > GesturesConstants.PREDICTION_THRESHOLD) { + match = true; + + ApplicationInfo info = sModel.queryGesture(Launcher.this, prediction.name); + if (info != null) { + updatePrompt(info); + } + } + } + + if (!match){ + setGesturesNextPrompt(null, getString(R.string.gestures_unknown)); + } + } + } + + private void updatePrompt(ApplicationInfo info) { + setGesturesNextPrompt(info.icon, info.title); + mGesturesAction.intent = info.intent; + } + + public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) { + overlay.removeCallbacks(mMatcher); + } + + void addGesture(String name, Gesture gesture) { + sLibrary.addGesture(name, gesture); + // TODO: On a background thread? + sLibrary.save(); + } + + void update(ApplicationInfo info, Gesture gesture) { + mGesturesOverlay.setGesture(gesture); + updatePrompt(info); + } + + class GestureMatcher implements Runnable { + Gesture gesture; + + public void run() { + if (gesture != null) { + matchGesture(gesture); + } + } + } + } + + private class GesturesAction implements View.OnClickListener { + Intent intent; + + public void onClick(View v) { + if (intent != null) { + startActivitySafely(intent); + } + } + } } diff --git a/src/com/android/launcher/LauncherModel.java b/src/com/android/launcher/LauncherModel.java index 19f6e9b..271f9f4 100644 --- a/src/com/android/launcher/LauncherModel.java +++ b/src/com/android/launcher/LauncherModel.java @@ -560,7 +560,7 @@ public class LauncherModel { } } - private static class ApplicationInfoComparator implements Comparator { + static class ApplicationInfoComparator implements Comparator { public final int compare(ApplicationInfo a, ApplicationInfo b) { return sCollator.compare(a.title.toString(), b.title.toString()); } @@ -614,11 +614,11 @@ public class LauncherModel { private static void updateShortcutLabels(ContentResolver resolver, PackageManager manager) { final Cursor c = resolver.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.TITLE, + new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.TITLE, LauncherSettings.Favorites.INTENT, LauncherSettings.Favorites.ITEM_TYPE }, null, null, null); - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID); + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); @@ -725,7 +725,7 @@ public class LauncherModel { LauncherSettings.Favorites.CONTENT_URI, null, null, null, null); try { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID); + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); @@ -1143,7 +1143,7 @@ public class LauncherModel { /** * Make an ApplicationInfo object for a sortcut */ - private ApplicationInfo getApplicationInfoShortcut(Cursor c, Launcher launcher, + private ApplicationInfo getApplicationInfoShortcut(Cursor c, Context context, int iconTypeIndex, int iconPackageIndex, int iconResourceIndex, int iconIndex) { final ApplicationInfo info = new ApplicationInfo(); @@ -1154,11 +1154,11 @@ public class LauncherModel { case LauncherSettings.Favorites.ICON_TYPE_RESOURCE: String packageName = c.getString(iconPackageIndex); String resourceName = c.getString(iconResourceIndex); - PackageManager packageManager = launcher.getPackageManager(); + PackageManager packageManager = context.getPackageManager(); try { Resources resources = packageManager.getResourcesForApplication(packageName); final int id = resources.getIdentifier(resourceName, null, null); - info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), launcher); + info.icon = Utilities.createIconThumbnail(resources.getDrawable(id), context); } catch (Exception e) { info.icon = packageManager.getDefaultActivityIcon(); } @@ -1172,16 +1172,16 @@ public class LauncherModel { try { Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); info.icon = new FastBitmapDrawable( - Utilities.createBitmapThumbnail(bitmap, launcher)); + Utilities.createBitmapThumbnail(bitmap, context)); } catch (Exception e) { - packageManager = launcher.getPackageManager(); + packageManager = context.getPackageManager(); info.icon = packageManager.getDefaultActivityIcon(); } info.filtered = true; info.customIcon = true; break; default: - info.icon = launcher.getPackageManager().getDefaultActivityIcon(); + info.icon = context.getPackageManager().getDefaultActivityIcon(); info.customIcon = false; break; } @@ -1326,6 +1326,26 @@ public class LauncherModel { } /** + * Add an item to the database in a specified container. Sets the container, screen, cellX and + * cellY fields of the item. Also assigns an ID to the item. + */ + static boolean addGestureToDatabase(Context context, ItemInfo item, boolean notify) { + final ContentValues values = new ContentValues(); + final ContentResolver cr = context.getContentResolver(); + + item.onAddToDatabase(values); + + Uri result = cr.insert(notify ? LauncherSettings.Gestures.CONTENT_URI : + LauncherSettings.Gestures.CONTENT_URI_NO_NOTIFICATION, values); + + if (result != null) { + item.id = Integer.parseInt(result.getPathSegments().get(1)); + } + + return result != null; + } + + /** * Update an item to the database in a specified container. */ static void updateItemInDatabase(Context context, ItemInfo item) { @@ -1359,4 +1379,84 @@ public class LauncherModel { cr.delete(LauncherSettings.Favorites.CONTENT_URI, LauncherSettings.Favorites.CONTAINER + "=" + info.id, null); } + + static void deleteGestureFromDatabase(Context context, ItemInfo item) { + final ContentResolver cr = context.getContentResolver(); + + cr.delete(LauncherSettings.Gestures.getContentUri(item.id, false), null, null); + } + + static void updateGestureInDatabase(Context context, ItemInfo item) { + final ContentValues values = new ContentValues(); + final ContentResolver cr = context.getContentResolver(); + + item.onAddToDatabase(values); + + cr.update(LauncherSettings.Gestures.getContentUri(item.id, false), values, null, null); + } + + + ApplicationInfo queryGesture(Context context, String id) { + final ContentResolver contentResolver = context.getContentResolver(); + final PackageManager manager = context.getPackageManager(); + final Cursor c = contentResolver.query( + LauncherSettings.Gestures.CONTENT_URI, null, LauncherSettings.Gestures._ID + "=?", + new String[] { id }, null); + + ApplicationInfo info = null; + + try { + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures._ID); + final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.INTENT); + final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.TITLE); + final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_TYPE); + final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON); + final int iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_PACKAGE); + final int iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ICON_RESOURCE); + final int itemTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Gestures.ITEM_TYPE); + + String intentDescription; + Intent intent; + + if (c.moveToNext()) { + int itemType = c.getInt(itemTypeIndex); + + switch (itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + intentDescription = c.getString(intentIndex); + try { + intent = Intent.getIntent(intentDescription); + } catch (java.net.URISyntaxException e) { + return null; + } + + if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + info = getApplicationInfo(manager, intent, context); + } else { + info = getApplicationInfoShortcut(c, context, iconTypeIndex, + iconPackageIndex, iconResourceIndex, iconIndex); + } + + if (info == null) { + info = new ApplicationInfo(); + info.icon = manager.getDefaultActivityIcon(); + } + + info.isGesture = true; + info.title = c.getString(titleIndex); + info.intent = intent; + info.id = c.getLong(idIndex); + + break; + } + } + } catch (Exception e) { + w(LOG_TAG, "Could not load gesture with name " + id); + } finally { + c.close(); + } + + return info; + } } diff --git a/src/com/android/launcher/LauncherProvider.java b/src/com/android/launcher/LauncherProvider.java index a27b746..ba8ebda 100644 --- a/src/com/android/launcher/LauncherProvider.java +++ b/src/com/android/launcher/LauncherProvider.java @@ -55,7 +55,7 @@ public class LauncherProvider extends ContentProvider { private static final String DATABASE_NAME = "launcher.db"; - private static final int DATABASE_VERSION = 3; + private static final int DATABASE_VERSION = 4; static final String AUTHORITY = "com.android.launcher.settings"; @@ -63,10 +63,11 @@ public class LauncherProvider extends ContentProvider { static final String EXTRA_BIND_TARGETS = "com.android.launcher.settings.bindtargets"; static final String TABLE_FAVORITES = "favorites"; + static final String TABLE_GESTURES = "gestures"; static final String PARAMETER_NOTIFY = "notify"; /** - * {@link Uri} triggered at any registered {@link ContentObserver} when + * {@link Uri} triggered at any registered {@link android.database.ContentObserver} when * {@link AppWidgetHost#deleteHost()} is called during database creation. * Use this to recall {@link AppWidgetHost#startListening()} if needed. */ @@ -99,7 +100,7 @@ public class LauncherProvider extends ContentProvider { SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(args.table); - SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder); result.setNotificationUri(getContext().getContentResolver(), uri); @@ -220,6 +221,17 @@ public class LauncherProvider extends ContentProvider { "displayMode INTEGER" + ");"); + db.execSQL("CREATE TABLE gestures (" + + "_id INTEGER PRIMARY KEY," + + "title TEXT," + + "intent TEXT," + + "itemType INTEGER," + + "iconType INTEGER," + + "iconPackage TEXT," + + "iconResource TEXT," + + "icon BLOB" + + ");"); + // Database was just created, so wipe any previous widgets if (mAppWidgetHost != null) { mAppWidgetHost.deleteHost(); @@ -270,7 +282,7 @@ public class LauncherProvider extends ContentProvider { } private int copyFromCursor(SQLiteDatabase db, Cursor c) { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ID); + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); final int titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); final int iconTypeIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); @@ -289,7 +301,7 @@ public class LauncherProvider extends ContentProvider { int i = 0; while (c.moveToNext()) { ContentValues values = new ContentValues(c.getColumnCount()); - values.put(LauncherSettings.Favorites.ID, c.getLong(idIndex)); + values.put(LauncherSettings.Favorites._ID, c.getLong(idIndex)); values.put(LauncherSettings.Favorites.INTENT, c.getString(intentIndex)); values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); values.put(LauncherSettings.Favorites.ICON_TYPE, c.getInt(iconTypeIndex)); @@ -352,6 +364,29 @@ public class LauncherProvider extends ContentProvider { convertWidgets(db); } } + + if (version < 4) { + db.beginTransaction(); + try { + db.execSQL("CREATE TABLE gestures (" + + "_id INTEGER PRIMARY KEY," + + "title TEXT," + + "intent TEXT," + + "itemType INTEGER," + + "iconType INTEGER," + + "iconPackage TEXT," + + "iconResource TEXT," + + "icon BLOB" + + ");"); + db.setTransactionSuccessful(); + version = 4; + } catch (SQLException ex) { + // Old version remains, which means we wipe old data + Log.e(LOG_TAG, ex.getMessage(), ex); + } finally { + db.endTransaction(); + } + } if (version != DATABASE_VERSION) { Log.w(LOG_TAG, "Destroying all old data."); diff --git a/src/com/android/launcher/LauncherSettings.java b/src/com/android/launcher/LauncherSettings.java index 60ea0df..062c8a6 100644 --- a/src/com/android/launcher/LauncherSettings.java +++ b/src/com/android/launcher/LauncherSettings.java @@ -23,16 +23,79 @@ import android.net.Uri; * Settings related utilities. */ class LauncherSettings { - /** - * Favorites. When changing these values, be sure to update - * {@link com.android.settings.LauncherAppWidgetBinder} as needed. - */ - static final class Favorites implements BaseColumns { + static interface BaseLauncherColumns extends BaseColumns { + /** + * Descriptive name of the gesture that can be displayed to the user. + *

Type: TEXT

+ */ + static final String TITLE = "title"; + /** + * The Intent URL of the gesture, describing what it points to. This + * value is given to {@link android.content.Intent#getIntent} to create + * an Intent that can be launched. + *

Type: TEXT

+ */ + static final String INTENT = "intent"; + + /** + * The type of the gesture + * + *

Type: INTEGER

+ */ + static final String ITEM_TYPE = "itemType"; + + /** + * The gesture is an application + */ + static final int ITEM_TYPE_APPLICATION = 0; + + /** + * The gesture is an application created shortcut + */ + static final int ITEM_TYPE_SHORTCUT = 1; + + /** + * The icon type. + *

Type: INTEGER

+ */ + static final String ICON_TYPE = "iconType"; + + /** + * The icon is a resource identified by a package name and an integer id. + */ + static final int ICON_TYPE_RESOURCE = 0; + + /** + * The icon is a bitmap. + */ + static final int ICON_TYPE_BITMAP = 1; + + /** + * The icon package name, if icon type is ICON_TYPE_RESOURCE. + *

Type: TEXT

+ */ + static final String ICON_PACKAGE = "iconPackage"; + + /** + * The icon resource id, if icon type is ICON_TYPE_RESOURCE. + *

Type: TEXT

+ */ + static final String ICON_RESOURCE = "iconResource"; + + /** + * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP. + *

Type: BLOB

+ */ + static final String ICON = "icon"; + } + + static final class Gestures implements BaseLauncherColumns { + /** * The content:// style URL for this table */ static final Uri CONTENT_URI = Uri.parse("content://" + - LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + + LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES + "?" + LauncherProvider.PARAMETER_NOTIFY + "=true"); /** @@ -40,7 +103,7 @@ class LauncherSettings { * sent if the content changes. */ static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" + - LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + + LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_GESTURES + "?" + LauncherProvider.PARAMETER_NOTIFY + "=false"); /** @@ -53,29 +116,44 @@ class LauncherSettings { */ static Uri getContentUri(long id, boolean notify) { return Uri.parse("content://" + LauncherProvider.AUTHORITY + - "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" + + "/" + LauncherProvider.TABLE_GESTURES + "/" + id + "?" + LauncherProvider.PARAMETER_NOTIFY + "=" + notify); } + } + /** + * Favorites. When changing these values, be sure to update + * {@link com.android.settings.LauncherAppWidgetBinder} as needed. + */ + static final class Favorites implements BaseLauncherColumns { /** - * The row ID. - *

Type: INTEGER

+ * The content:// style URL for this table */ - static final String ID = "_id"; + static final Uri CONTENT_URI = Uri.parse("content://" + + LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + + "?" + LauncherProvider.PARAMETER_NOTIFY + "=true"); /** - * Descriptive name of the favorite that can be displayed to the user. - *

Type: TEXT

+ * The content:// style URL for this table. When this Uri is used, no notification is + * sent if the content changes. */ - static final String TITLE = "title"; + static final Uri CONTENT_URI_NO_NOTIFICATION = Uri.parse("content://" + + LauncherProvider.AUTHORITY + "/" + LauncherProvider.TABLE_FAVORITES + + "?" + LauncherProvider.PARAMETER_NOTIFY + "=false"); /** - * The Intent URL of the favorite, describing what it points to. This - * value is given to {@link android.content.Intent#getIntent} to create - * an Intent that can be launched. - *

Type: TEXT

+ * The content:// style URL for a given row, identified by its id. + * + * @param id The row id. + * @param notify True to send a notification is the content changes. + * + * @return The unique content URL for the specified row. */ - static final String INTENT = "intent"; + static Uri getContentUri(long id, boolean notify) { + return Uri.parse("content://" + LauncherProvider.AUTHORITY + + "/" + LauncherProvider.TABLE_FAVORITES + "/" + id + "?" + + LauncherProvider.PARAMETER_NOTIFY + "=" + notify); + } /** * The container holding the favorite @@ -121,23 +199,6 @@ class LauncherSettings { static final String SPANY = "spanY"; /** - * The type of the favorite - * - *

Type: INTEGER

- */ - static final String ITEM_TYPE = "itemType"; - - /** - * The favorite is an application - */ - static final int ITEM_TYPE_APPLICATION = 0; - - /** - * The favorite is an application created shortcut - */ - static final int ITEM_TYPE_SHORTCUT = 1; - - /** * The favorite is a user created folder */ static final int ITEM_TYPE_USER_FOLDER = 2; @@ -180,43 +241,10 @@ class LauncherSettings { * value is 1, it is an application-created shortcut. *

Type: INTEGER

*/ + @Deprecated static final String IS_SHORTCUT = "isShortcut"; /** - * The icon type. - *

Type: INTEGER

- */ - static final String ICON_TYPE = "iconType"; - - /** - * The icon is a resource identified by a package name and an integer id. - */ - static final int ICON_TYPE_RESOURCE = 0; - - /** - * The icon is a bitmap. - */ - static final int ICON_TYPE_BITMAP = 1; - - /** - * The icon package name, if icon type is ICON_TYPE_RESOURCE. - *

Type: TEXT

- */ - static final String ICON_PACKAGE = "iconPackage"; - - /** - * The icon resource id, if icon type is ICON_TYPE_RESOURCE. - *

Type: TEXT

- */ - static final String ICON_RESOURCE = "iconResource"; - - /** - * The custom icon bitmap, if icon type is ICON_TYPE_BITMAP. - *

Type: BLOB

- */ - static final String ICON = "icon"; - - /** * The URI associated with the favorite. It is used, for instance, by * live folders to find the content provider. *

Type: TEXT

diff --git a/src/com/android/launcher/UninstallShortcutReceiver.java b/src/com/android/launcher/UninstallShortcutReceiver.java index 334fbc2..bf71815 100644 --- a/src/com/android/launcher/UninstallShortcutReceiver.java +++ b/src/com/android/launcher/UninstallShortcutReceiver.java @@ -35,7 +35,7 @@ public class UninstallShortcutReceiver extends BroadcastReceiver { if (intent != null && name != null) { final ContentResolver cr = context.getContentResolver(); Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI, - new String[] { LauncherSettings.Favorites.ID, LauncherSettings.Favorites.INTENT }, + new String[] { LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT }, LauncherSettings.Favorites.TITLE + "=?", new String[] { name }, null); final int intentIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); -- 2.11.0