From a2e81242e01af6619310003a6a035ce82b6f7e09 Mon Sep 17 00:00:00 2001 From: Brett Chabot Date: Mon, 11 Oct 2010 16:21:20 -0700 Subject: [PATCH 1/7] Add utility for toggling settings from the cmd line. Change-Id: I4dc7052c22fac3edf66e2d7db0c8d7039be4f196 --- apps/SettingsCmd/Android.mk | 29 +++++++ apps/SettingsCmd/AndroidManifest.xml | 32 ++++++++ .../settingscmd/SettingsInstrument.java | 82 +++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 apps/SettingsCmd/Android.mk create mode 100644 apps/SettingsCmd/AndroidManifest.xml create mode 100644 apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java diff --git a/apps/SettingsCmd/Android.mk b/apps/SettingsCmd/Android.mk new file mode 100644 index 000000000..282b89b74 --- /dev/null +++ b/apps/SettingsCmd/Android.mk @@ -0,0 +1,29 @@ +# Copyright (C) 2010 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. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := SettingsCmd +LOCAL_CERTIFICATE := platform + +LOCAL_SDK_VERSION := 4 + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) diff --git a/apps/SettingsCmd/AndroidManifest.xml b/apps/SettingsCmd/AndroidManifest.xml new file mode 100644 index 000000000..63553faf8 --- /dev/null +++ b/apps/SettingsCmd/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + diff --git a/apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java b/apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java new file mode 100644 index 000000000..1f8d9ecb2 --- /dev/null +++ b/apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java @@ -0,0 +1,82 @@ +/* + * Copyright 2010, 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.settingscmd; + +import android.app.Activity; +import android.app.Instrumentation; +import android.os.Bundle; +import android.provider.Settings; + +/** + * Utility for modifying system settings. + *

+ * Usage: + *

+ * adb shell am instrument -w [-e secure true/false] -e name + * -e value -w com.android.settingscmd/.SettingsInstrument + *

+ * This apk must be signed with the platform certificate to modify secure settings. + */ +public class SettingsInstrument extends Instrumentation { + + private boolean mSecure; + private String mName; + private String mValue; + + public SettingsInstrument() { + super(); + } + + @Override + public void onCreate(Bundle arguments) { + mSecure = getBooleanArgument(arguments, "secure"); + mName = arguments.getString("name"); + mValue = arguments.getString("value"); + start(); + } + + private boolean getBooleanArgument(Bundle arguments, String tag) { + String tagString = arguments.getString(tag); + return tagString != null && Boolean.parseBoolean(tagString); + } + + @Override + public void onStart() { + if (mName == null || mValue == null) { + reportError("Missing arguments. Usage:\n [-e secure true] " + + "-e name -e value "); + return; + } + boolean status = false; + if (mSecure) { + status = Settings.Secure.putString(getTargetContext().getContentResolver(), mName, + mValue); + } else { + status = Settings.System.putString(getTargetContext().getContentResolver(), mName, + mValue); + } + Bundle bundleResponse = new Bundle(); + bundleResponse.putBoolean("result", status); + finish(Activity.RESULT_OK, bundleResponse); + } + + private void reportError(String msg) { + Bundle bundleResponse = new Bundle(); + bundleResponse.putString("error", msg); + finish(Activity.RESULT_CANCELED, bundleResponse); + } +} From 037515476560b1277960d0bb781e4be5610558c0 Mon Sep 17 00:00:00 2001 From: Gilles Debunne Date: Thu, 14 Oct 2010 10:11:57 -0700 Subject: [PATCH 2/7] Assets moved to hdpi directory, mdpi version created Change-Id: I960fa28d2596c26e62cb6914c59c5db2def4a03e --- .../ic_launcher_nfc.png | Bin .../ic_menu_desk_clock.png | Bin .../{drawable => drawable-hdpi}/ic_menu_tag.png | Bin apps/Tag/res/drawable-mdpi/ic_launcher_nfc.png | Bin 0 -> 2789 bytes .../Tag/res/drawable-mdpi/ic_menu_desk_clock.png | Bin 0 -> 1462 bytes apps/Tag/res/drawable-mdpi/ic_menu_tag.png | Bin 0 -> 2510 bytes 6 files changed, 0 insertions(+), 0 deletions(-) rename apps/Tag/res/{drawable => drawable-hdpi}/ic_launcher_nfc.png (100%) rename apps/Tag/res/{drawable => drawable-hdpi}/ic_menu_desk_clock.png (100%) rename apps/Tag/res/{drawable => drawable-hdpi}/ic_menu_tag.png (100%) create mode 100644 apps/Tag/res/drawable-mdpi/ic_launcher_nfc.png create mode 100644 apps/Tag/res/drawable-mdpi/ic_menu_desk_clock.png create mode 100644 apps/Tag/res/drawable-mdpi/ic_menu_tag.png diff --git a/apps/Tag/res/drawable/ic_launcher_nfc.png b/apps/Tag/res/drawable-hdpi/ic_launcher_nfc.png similarity index 100% rename from apps/Tag/res/drawable/ic_launcher_nfc.png rename to apps/Tag/res/drawable-hdpi/ic_launcher_nfc.png diff --git a/apps/Tag/res/drawable/ic_menu_desk_clock.png b/apps/Tag/res/drawable-hdpi/ic_menu_desk_clock.png similarity index 100% rename from apps/Tag/res/drawable/ic_menu_desk_clock.png rename to apps/Tag/res/drawable-hdpi/ic_menu_desk_clock.png diff --git a/apps/Tag/res/drawable/ic_menu_tag.png b/apps/Tag/res/drawable-hdpi/ic_menu_tag.png similarity index 100% rename from apps/Tag/res/drawable/ic_menu_tag.png rename to apps/Tag/res/drawable-hdpi/ic_menu_tag.png diff --git a/apps/Tag/res/drawable-mdpi/ic_launcher_nfc.png b/apps/Tag/res/drawable-mdpi/ic_launcher_nfc.png new file mode 100644 index 0000000000000000000000000000000000000000..525f5b500f77a462328c3d3fd707f6114b486fcb GIT binary patch literal 2789 zcmZ{mX*d*I8^`TiwrnLkLo;5>7!(teu_TNzWY5!J?AxFsTlSFMWZxb}iJ=h2mKqt` z*n6xcTgBML5R+HWxA(()opYbxb^WgMzs~*P{&14a?-+7%T;ZUjqvJBVt&cn>_)oF1 zoS!xf$BE})aW*m3r#t(z$~s?UpBwgo+jc?cy!~(8>i35Ikq-ml?&thBPdB{v%;wy! z8R^5VJict^+xgu4C`^h98YWN(h4uCIEsn~Vm~5RPA?K&jObBe1Y>v)TAd(r+>uXiL zgiF`@iAS3M2OuwRp<^x%G|~g{0N)4{0s@VNSOb=xVEWdXw+VzUN`5|UIw`B+9_xZM z0Jf$j>gm?CdPaM9?8XwAv2CqiE#%bV_Zdr})Ya8(=Q37#wwX>e#qlQOGx^dx9Ro9ZyvTocb5Nd(9F&Mj1r zCGdU_^@R5b#ihNhDF+gaH{#wU{Sxr59bL%Gr>?iF6Z2tmBO@NN1gp{ zY4({Jce1v&W^F}GsMUSq4(j6fnY(4|3B;X_d8l>y&NY*+`OY>SH_!Qnl{GXpSc90m z`?If-4!1tHpG}zE6k}szShQ6T8nx$7(*GNa#imdwlw~{~FUZO*su8ifWIzIQlD4T7 z-E#AWvO--pGZ&ZT2M#TMw3+v@tdDrOxl3}9UL;I#xq0iU5$K@Mzw6iwz-PC)8{+qP z6>H;^fpk*u#fU26CS_z~h}zoP#b!5YKZ#^Ad60f2oWWs3hd$1_*4E-|qj3cGV7rqF zn;#z!d<4Gy42T>3mGV&*YkV)Spx}z1mzVrs?;>g1=WVVHVC=n{{+VtTdrDeVp#lG% zy#bqwa!!=a{c^P585n3FSP*aRa+#&KR=SBL^;UH}{1zzwe0UUF8FOC|yLfIc>nwyK z?c$}%6u$Nzw%Iz0NeS&Wx>|6SPH|nCRIe=LjX}s|GbT%a=-}n`;O2R@TQ4J{(F*sN zXR&3;nIrZv_*pBbj+qND9SS};>h!Bb4v~0>U}HUJoWOAmv3~mt%Xds244;fFi#WHve+waLN1NHprws)6PTMiLs|P3sNc~ z#WeVe=DL?GLitd18lKSdEr<=d+5NnYA_xjvIr z`kcgYCYqm+jUoGy_IyX*pd!13gMH0qQ=yPZDxLq&XN^?-t0I1NsO3%aUxQpA^x7f) zXz&5|p5KV%Y+kWfJC`mjTkx$EpGsjfgITY@3TgeFxE(mo!dNo-M#q}&8^?TB1pfWAj}Nhlu<_A z)yr$-xBCLyaJlbMFhwmbkMbzE8Z{p?p8P3=Sf(B|4I8dYzjeIkn}V_XcIhykuut4b zXdGn&;x6YSAQ~kAn~YXOY{8x+{9WSglf;52g>OxAGcioc4~uap*%o&?=E;mTUIc3I zV|x)st(mU4;>3g_2NFiSt0L2*J4%p4mo1Rshly+s+^PVVUdPFA ztZs>Ty-a4YFh&)~r8isi^AJL+m!Wn%WtAe;O$M2bq7y`deX?-nwP``4g8jmr-Zukm~q_*j|WlFqq5 ze}fd_YO2k3_hRG&cP_J{%gb-KQ2!{@e}JUc6y=${Q&(oHL;?op)p|~!=y~H}YR>c)<*T`twZ3WK{DC)=J`f+R0wAw@!&xjVJ4(*4Z;V^wYpaoZCZUUc+6oG z#_aXStp-pr!UaOjAA6)fGy1uCHlg z^T1mm`O=>A4>I>_uvn^U{+?9Vl?p3SY|(G^Hc-co(K$@r-CYaF+LUnoI*h(lt6R?p*WNhtPH@8SMZl_*@@lhz3!!l9H$czQlkW|k%A5h517J#Ss(GS2IdR9Wn~W`QkR01UK2i&n#rkZ0Iwq|DKw4qS=Pw+<9@D-TU|;B!vz6a z>_mt{t7m5V`^u#sGby)mKptDB2%)Q_eW0u|1<1~-)ZaY{APpn328>?~bksWJcT+5C zeYsVi5A80_01gD{2V!9BgN{Fokep`jo^_w%I^-nt5GGB?Jcd&xIP?WbgHP()RMM~r zJzhY@Gdt)Q9NQMN8Bt)fAadVB%gDPpk<~$0R3m$t7R}C-j1v^E8sF)^ErbKW>*3ul z;NbW0VS~00*ZKw2suA>ci(8&wL~T0(y#u$ht(+R$r&g9NV~fN}%OOiw-7Y|oV9n(x z{~X<8;2K&HH1_0_3UG)iQuTg`V(UqHO<8ySsyYCDDK5YqG=V&5U z6{~_8*+LPfUo2K~bd2K%ne;rWAbfcDi@_oVKJZ12M$Z)pb7*xyPFEJ=g)c*EKGzBd zRs+WyY6;hw;c6=`=zDPqA#By^Qgee11(IC5l1Px2Ukc?_tXfW(QVWMkO^jooF=9eT z-qaGEQC(!zP0{r>l`DJz$wxHUvL;7JfM@6Cw!QuR7uwv&1*}{yLK=f=nD%=sd!dz9 zmlL8l_p_7*`c^NnNxi1QDsx;L8n*=_OE2lMA=fzR#M4D`J+qA6Gmzt-Khs9i($X%q zwY5P(OKyMLGFd%(L^WItI7*$Jot>)~^BCHE=wS109Y=gDk8N9za&!Ck>MR&a!=0@K zLvLzoY6h^p3Dbo4DZ3fmZx%O!5kY97>bw6}-CcjWhkO5Y6XgHxgYx z!;4X@Gnw4}?ZsFcmG)V*JuEfl&M+r`43Pw*uXZ}6%~wzx&)%nK?eb= zfEE92RV$V2P!)A3SXB{x9SR0>l0h~96Y%wS^>hpU|AB~%%uDA1;Xf}}`nv@`LItAf j;I978Xh|bqlsg)UM!AM!`q5hFm*|WP?&#O)-jDe&5o$LP literal 0 HcmV?d00001 diff --git a/apps/Tag/res/drawable-mdpi/ic_menu_desk_clock.png b/apps/Tag/res/drawable-mdpi/ic_menu_desk_clock.png new file mode 100644 index 0000000000000000000000000000000000000000..e4fa67196840d60577c7629de3515a0065ce7902 GIT binary patch literal 1462 zcmZ{kdpOg39LIldhlJ_T+zP`c_p?#aB9}wkavhOdYO`_VGMl+RXi;g$t&=kMMZ}qG z$t^_6aVJ7V3r~jV2y;0ooawJ~{y5L`{d`}~_w)L^pU;0^y0arnT2fgO003zW+Qvmd z#eIRBb(Ym35J}s8jo>mkpmn7BEWrWS~+@g8k z)iCnUox=Qn zJ#KP-dw;Tbt>x#H;*{)AJl_*tYXZ>_frFAvt0n%TE<%UJY9`=n&DuwWfhq}4Ah+>QHiiaSj-+`lIYgw%uwoph-c%$9vD(M>{+2J*E=BN} zNjtUN77lmQcD>w}AH=9}?k`c1wr1uZhHI2&a!JOd8}7+2e(-Gwyep~BL`fFk zk}mqnaVQ1Vo!L?`t}-uQo1Khr6ell`EhrkT`_aK=n{M%UqGUs`OZv@U8evIb?;ifZ zhi#YE)|kDX+x#m0MMUw^Sp6{c%K?`abizf`_OGQdme0fj`jI%9!in* zhT|KW+B?<=p>m|xOHR8nZbMPE$!OzriSmRk5x}_E;JlEH1CaXe#xkZOHuz~F*pNT< ztYsp4lU*YjCJbQp(=8(%hiBt7mHc;~chgE{ zzr1pac^kI-yD=YyChlE*I+a;+RkF$srbERhS~X&$XweN0n6Ub6OoE)q zS!23^i|2daq8YwE+r3|kci*rEd@)wpz^z%n5JnfGb>Ejjq#?gDH~P1whL1?2;tusXjyAT(omsp!2wgQ_GRv5m`w5`{>8#;& zja4>jYEqMV1)4Ez<7-Iht&b(-NX_nY#fAm&sd&GRqQ7J5SowmYPKxMj?mtDl;w}!m{+Spu6Lb zZn5`jH>g+EywX0rYg=a6@A^1-?`PjN)o3Mj(*hrY>BE(In~VkvA$D^tfU~5@brm7@ zxYY^j>uQpt1EYO2fyRO#3P!c{pb~IYBGQjS6aX-SBj5%H$M_<`1c^9>gc}>cO^|T7 zRKHHee*z&SLZE-l{|Dml{;nVhfDShJ2L@A#6eK=4f&#z|5w6sLIDwM+mkK7~iQ&M> xh;j)Hfl)dzt|WhI6plg!tOx=L!Gz!fh%Q7NA%@&dG!qN~7+Xghw)NG-e*hlYixB_- literal 0 HcmV?d00001 diff --git a/apps/Tag/res/drawable-mdpi/ic_menu_tag.png b/apps/Tag/res/drawable-mdpi/ic_menu_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..e0b3eb73d933982cfe70da9e66bc2c2fc49b2fcc GIT binary patch literal 2510 zcmZ|RbyySH8wc2Z}gS1V)F1qyu$?#Hi8XXoixPP(mdfaOr^)Mo!&w0=HdEV!|&-wR6>g%erT!dT%001nS8mdTYa-0_%1NAJL z|4pU_16oU6_3Xa|LhX%HJ0Ld=qnFgw_^;G{ILed5GreKqY{Ty7%vw&XPv&Ef|%*JgQ>Lezm>N>cR)FD6g2@uO)q7RBN+GNs)L z>m4%dbK4!;fxq^`&X&`Ocdg7?zP<4!1ix9l(JxL&X|G*OY&;vYILmNyt&kjZbRM-B zqk>iuJZ$GW+CDBw$XerkYwtW9cbb=?2d|%1R`JwO;V4JG?OTa+a3-96 z(Gj$Eo&IHn^mk6)u`N#D7+Lj&l4kqpesEqVB;EKumAGA1Y47NQQ)>FBF;B8fY(d=T zIfp6)Iuut%g~dw{Mb1qR3zQnz=5?@=^(2UDgQ!>D&el{pSwtO&_dj98!US~)rk-<{ z>UmE>YNvF{%ZIUesVZBK&RKjOcy!vyy`u7go~klc<3dWVzJTGygh0Z`)f6-nr;Ge> z0O`PVw9_xmEN|FeuT^FyZL>?DWYpdZpQhxf!T-_P{Rg>|YIXcsdh7hAuWtAU$2sGw zUG!>g0nX^Cq)V<;#@BmmZ8q?zZd#nxx_)%c$-81KsS;w!LJD7r8u@c>JfQ_)l~ycN zsmgp!rCEO{@?kIkPsOgJ?YRJhtfr+RM8OQf1K*zt<}hVQYOtv_7w%!A&{|ykjN)Tq z*$ee=lpDr5=@b)C23IP`cvBq`q5rECdn%|aR4}P7(``G11%k3i>Vfy7p6LYwaz{PcZsyPM<(CxQVXJuP<@S@4Ik6 zfkmlamZQ^2VuWPC`@r_mYz~peS&2L!r1;b#X>C4;nMTstL|n-nQ53z)-^?6kBr2$s zfw~%vJ~;hStv8Xkc1P8u06z3Lug?aE9u!?P)f2niO0oJLt#f$O?^497VMIBP=T=48 z6%_BBn{b32FaA!DT{CzSbU`wyPG{*CKhNCbDx^@vCp0lxfLmn3zPdkEctS<~pgxM* zN%8EX!6*1Pi_O*;4l2Nj<&NxEALw`8|n(`stn21nI^6aT6C|g;A3jS%9~5+lctF z97mr$69`Jv%%qU+JxLRJa`{Hzy^Zi)oy)>a6$>SeEm!pYXw89bN}y-fkAhyyOtB7N zawoRQ^WL$;u8;uQF!bjUhC>JEv|3dzytVa_fzmb?LfVdo#_pTML5TPyxn2(S^#F~51> zo^$g9yiJsk=|MC&2S`)62`OaZry+Cj@+xs>#}2-4Fm#aT#zJzi1`&LWZMMT^8#PSo zlZde;BbN7Gtg+0JL1hy?4wTo)xm?I?9?L4ZP9zOnP^Q{{?{$q3%pz_otLb^KIg?yG zPFWh@SLxFK7eEsh|A1|xe4yTam1&hepNJ>0GJ1NRah$PXC+KD4X4ptfF>Tl4gmus_a!2f-fej8!dXT#ChbDVq35d{1b4u?C1FU)E z%b#ui^_`W4T5i%WsCVPO-Nxja zAUuEZ*6*zlwv)d$wADB?rzc7{Q&vI^B z+XIc=(WW_GuVT&iwdIw!&TjrB9}-t-rNM;ExD0|D60vTq&a^AJskhq6$=I(*niD6F+5)&^naVM7go zn24wd{CtZWib^6xB@iOwa1lv_hzN71z~}!NaCWt}xAp!11Gu>O>(qf8=Pwwz+G4#e mJ!}9<)~;w99!+OUI~$~prM0j74;xwPC4lB5T~(5@Mex7miOUlJ literal 0 HcmV?d00001 From cca5d187d59ec95457e1d369bea96bb33b0e052c Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 14 Oct 2010 11:25:01 -0700 Subject: [PATCH 3/7] GB SDK: don't use HV2.bat when generating tools. That's because GB uses the Tools_r& which do not have hierarchyviewer2 so we should not try to pull the .bat from hv2. We don't care since we'll ignore these old tools anyway when packaging the final SDK. Change-Id: Ie7d161bad59d6e919613db2e854f48964e59c4a9 --- build/tools/patch_windows_sdk.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build/tools/patch_windows_sdk.sh b/build/tools/patch_windows_sdk.sh index 3cc087527..687526172 100755 --- a/build/tools/patch_windows_sdk.sh +++ b/build/tools/patch_windows_sdk.sh @@ -55,7 +55,12 @@ cp $V ${TOPDIR}sdk/files/find_java.bat $LIB/ cp $V ${TOPDIR}sdk/apkbuilder/etc/apkbuilder.bat $TOOLS/ cp $V ${TOPDIR}sdk/ddms/app/etc/ddms.bat $TOOLS/ cp $V ${TOPDIR}sdk/traceview/etc/traceview.bat $TOOLS/ -cp $V ${TOPDIR}sdk/hierarchyviewer2/app/etc/hierarchyviewer.bat $TOOLS/ +if [ -f ${TOPDIR}sdk/hierarchyviewer2/app/etc/hierarchyviewer.bat ]; then + cp $V ${TOPDIR}sdk/hierarchyviewer2/app/etc/hierarchyviewer.bat $TOOLS/ +else + # That's ok because currently GB uses Tools_r7 but we'll ship Tools_r8 from master-open. + echo "WARNING: Ignoring ${TOPDIR}sdk/hierarchyviewer2/app/etc/hierarchyviewer.bat [ok for GB+Tools r8]" +fi cp $V ${TOPDIR}sdk/layoutopt/app/etc/layoutopt.bat $TOOLS/ cp $V ${TOPDIR}sdk/draw9patch/etc/draw9patch.bat $TOOLS/ cp $V ${TOPDIR}sdk/sdkmanager/app/etc/android.bat $TOOLS/ From 928f3aa3533096608fc6edf0bab14d442438071b Mon Sep 17 00:00:00 2001 From: Jeff Hamilton Date: Thu, 14 Oct 2010 10:13:25 -0500 Subject: [PATCH 4/7] Checkpoint work on the Tag app. Change-Id: I375aeabe00528d30ce49e8d3d9f78aaac10b0071 --- apps/Tag/AndroidManifest.xml | 29 ++-- .../styles.xml => layout/tag_divider.xml} | 11 +- apps/Tag/res/layout/tag_text.xml | 26 ++++ apps/Tag/res/layout/tag_viewer_list.xml | 21 +++ apps/Tag/res/values/strings.xml | 6 + .../src/com/android/apps/tag/NdefUtil.java | 55 +++---- .../Tag/src/com/android/apps/tag/SaveTag.java | 96 ------------ .../src/com/android/apps/tag/SmartPoster.java | 28 ++-- .../src/com/android/apps/tag/TagAdapter.java | 86 +++++++++++ .../apps/tag/TagBroadcastReceiver.java | 43 ------ .../android/apps/tag/TagBrowserActivity.java | 24 ++- .../android/apps/tag/TagCursorAdapter.java | 43 ------ .../src/com/android/apps/tag/TagDBHelper.java | 104 ++++++++----- .../Tag/src/com/android/apps/tag/TagList.java | 82 ++++++++--- .../src/com/android/apps/tag/TagViewer.java | 137 ++++++++++++++++++ .../com/android/apps/tag/SmartPosterTest.java | 2 +- 16 files changed, 473 insertions(+), 320 deletions(-) rename apps/Tag/res/{values/styles.xml => layout/tag_divider.xml} (77%) create mode 100644 apps/Tag/res/layout/tag_text.xml create mode 100644 apps/Tag/res/layout/tag_viewer_list.xml delete mode 100644 apps/Tag/src/com/android/apps/tag/SaveTag.java create mode 100644 apps/Tag/src/com/android/apps/tag/TagAdapter.java delete mode 100644 apps/Tag/src/com/android/apps/tag/TagBroadcastReceiver.java delete mode 100644 apps/Tag/src/com/android/apps/tag/TagCursorAdapter.java create mode 100644 apps/Tag/src/com/android/apps/tag/TagViewer.java diff --git a/apps/Tag/AndroidManifest.xml b/apps/Tag/AndroidManifest.xml index 4414d7360..ba01fff2b 100644 --- a/apps/Tag/AndroidManifest.xml +++ b/apps/Tag/AndroidManifest.xml @@ -20,26 +20,33 @@ own application, the package name must be changed from "com.example.*" to come from a domain that you own or have control over. --> + package="com.android.apps.tag" +> + + + + - + - - - + + + - + + - + - - diff --git a/apps/Tag/res/values/styles.xml b/apps/Tag/res/layout/tag_divider.xml similarity index 77% rename from apps/Tag/res/values/styles.xml rename to apps/Tag/res/layout/tag_divider.xml index 40e29fb1d..b6b1b7cd4 100644 --- a/apps/Tag/res/values/styles.xml +++ b/apps/Tag/res/layout/tag_divider.xml @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - - - + \ No newline at end of file diff --git a/apps/Tag/res/layout/tag_text.xml b/apps/Tag/res/layout/tag_text.xml new file mode 100644 index 000000000..903865055 --- /dev/null +++ b/apps/Tag/res/layout/tag_text.xml @@ -0,0 +1,26 @@ + + + + \ No newline at end of file diff --git a/apps/Tag/res/layout/tag_viewer_list.xml b/apps/Tag/res/layout/tag_viewer_list.xml new file mode 100644 index 000000000..3d322d539 --- /dev/null +++ b/apps/Tag/res/layout/tag_viewer_list.xml @@ -0,0 +1,21 @@ + + + + diff --git a/apps/Tag/res/values/strings.xml b/apps/Tag/res/values/strings.xml index 3d0432765..9b736784e 100644 --- a/apps/Tag/res/values/strings.xml +++ b/apps/Tag/res/values/strings.xml @@ -20,4 +20,10 @@ help and info Saved + + Recent + + + Saved + diff --git a/apps/Tag/src/com/android/apps/tag/NdefUtil.java b/apps/Tag/src/com/android/apps/tag/NdefUtil.java index 87efb9e43..40f5bcf7e 100644 --- a/apps/Tag/src/com/android/apps/tag/NdefUtil.java +++ b/apps/Tag/src/com/android/apps/tag/NdefUtil.java @@ -16,17 +16,17 @@ package com.android.apps.tag; -import android.util.Log; import com.google.common.base.Preconditions; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.Iterables; import com.google.common.primitives.Bytes; + +import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import java.io.UnsupportedEncodingException; -import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charsets; import java.util.ArrayList; @@ -47,8 +47,7 @@ public class NdefUtil { * This is a mapping of "URI Identifier Codes" to URI string prefixes, * per section 3.2.2 of the NFC Forum URI Record Type Definition document. */ - private static final - BiMap URI_PREFIX_MAP = ImmutableBiMap.builder() + private static final BiMap URI_PREFIX_MAP = ImmutableBiMap.builder() .put((byte) 0x00, "") .put((byte) 0x01, "http://www.") .put((byte) 0x02, "https://www.") @@ -88,9 +87,9 @@ public class NdefUtil { .build(); /** - * Create a new {@link NdefRecord} containing the supplied {@link URI}. + * Create a new {@link NdefRecord} containing the supplied {@link Uri}. */ - public static NdefRecord toUriRecord(URI uri) { + public static NdefRecord toUriRecord(Uri uri) { byte[] uriBytes = uri.toString().getBytes(Charsets.UTF_8); /* @@ -110,18 +109,16 @@ public class NdefUtil { } /** - * Convert {@link NdefRecord} into a {@link URI}. + * Convert {@link NdefRecord} into a {@link Uri}. * * TODO: This class does not handle NdefRecords where the TNF * (Type Name Format) of the class is {@link NdefRecord#TNF_ABSOLUTE_URI}. * This should be fixed. * - * @throws URISyntaxException if the {@code NdefRecord} contains an - * invalid URI. * @throws IllegalArgumentException if the NdefRecord is not a * record containing a URI. */ - public static URI toURI(NdefRecord record) throws URISyntaxException { + public static Uri toUri(NdefRecord record) { Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI)); @@ -140,17 +137,15 @@ public class NdefUtil { prefix.getBytes(Charsets.UTF_8), Arrays.copyOfRange(payload, 1, payload.length)); - return new URI(new String(fullUri, Charsets.UTF_8)); + return Uri.parse(new String(fullUri, Charsets.UTF_8)); } - public static boolean isURI(NdefRecord record) { + public static boolean isUri(NdefRecord record) { try { - toURI(record); + toUri(record); return true; } catch (IllegalArgumentException e) { return false; - } catch (URISyntaxException e) { - return false; } } @@ -207,33 +202,29 @@ public class NdefUtil { return Iterables.filter(getObjects(message), String.class); } - public static Iterable getURIs(NdefMessage message) { - return Iterables.filter(getObjects(message), URI.class); + public static Iterable getUris(NdefMessage message) { + return Iterables.filter(getObjects(message), Uri.class); } /** * Parse the provided {@code NdefMessage}, extracting all known * objects from the message. Typically this list will consist of - * {@link String}s corresponding to NDEF text records, or {@link URI}s + * {@link String}s corresponding to NDEF text records, or {@link Uri}s * corresponding to NDEF URI records. *

* TODO: Is this API too generic? Should we keep it? */ - private static Iterable getObjects(NdefMessage message) { - try { - List retval = new ArrayList(); - for (NdefRecord record : message.getRecords()) { - if (isURI(record)) { - retval.add(toURI(record)); - } else if (isText(record)) { - retval.add(toText(record)); - } else if (SmartPoster.isPoster(record)) { - retval.add(SmartPoster.from(record)); - } + public static Iterable getObjects(NdefMessage message) { + List retval = new ArrayList(); + for (NdefRecord record : message.getRecords()) { + if (isUri(record)) { + retval.add(toUri(record)); + } else if (isText(record)) { + retval.add(toText(record)); + } else if (SmartPoster.isPoster(record)) { + retval.add(SmartPoster.from(record)); } - return retval; - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); } + return retval; } } diff --git a/apps/Tag/src/com/android/apps/tag/SaveTag.java b/apps/Tag/src/com/android/apps/tag/SaveTag.java deleted file mode 100644 index 6a5f66fcc..000000000 --- a/apps/Tag/src/com/android/apps/tag/SaveTag.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2010 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.apps.tag; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.content.DialogInterface; -import android.nfc.NdefMessage; -import android.nfc.NdefTag; -import android.nfc.NfcAdapter; -import android.os.Bundle; -import android.util.Log; -import android.view.WindowManager; -import android.widget.Toast; - -/** - * An {@code Activity} which handles a broadcast of a new tag that the device just discovered. - */ -public class SaveTag extends Activity implements DialogInterface.OnClickListener { - private static final String TAG = "SaveTag"; - - @Override - protected void onStart() { - super.onStart(); - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD - | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON - | WindowManager.LayoutParams.FLAG_DIM_BEHIND - ); - - showDialog(1); - NdefTag tag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG); - NdefMessage[] msgs = tag.getNdefMessages(); - - if (msgs.length == 0) { - Log.d(TAG, "No NDEF messages"); - return; - } - if (msgs.length > 1) { - Log.d(TAG, "Multiple NDEF messages, only saving first"); - } - String s = toHexString(msgs[0].toByteArray()); - Log.d("SaveTag", s); - Toast.makeText(this.getBaseContext(), "SaveTag: " + s, Toast.LENGTH_SHORT).show(); - } - - @Override - protected Dialog onCreateDialog(int id, Bundle args) { - return new AlertDialog.Builder(this) - .setTitle("Welcome! T2000 Festival") - .setPositiveButton("Save", this) - .setNegativeButton("Cancel", this) - .create(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - } - - @Override - protected void onStop() { - super.onStop(); - dismissDialog(1); - } - - private static final char[] hexDigits = "0123456789abcdef".toCharArray(); - - private static String toHexString(byte[] bytes) { - StringBuilder sb = new StringBuilder(3 * bytes.length); - for (byte b : bytes) { - sb.append("(byte) 0x") - .append(hexDigits[(b >> 4) & 0xf]) - .append(hexDigits[b & 0xf]).append(", "); - } - return sb.toString(); - } -} diff --git a/apps/Tag/src/com/android/apps/tag/SmartPoster.java b/apps/Tag/src/com/android/apps/tag/SmartPoster.java index 1e107235b..dd6f518fb 100644 --- a/apps/Tag/src/com/android/apps/tag/SmartPoster.java +++ b/apps/Tag/src/com/android/apps/tag/SmartPoster.java @@ -18,13 +18,15 @@ package com.android.apps.tag; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; + +import android.net.Uri; +import android.nfc.FormatException; import android.nfc.NdefMessage; import android.nfc.NdefRecord; -import android.nfc.FormatException; + +import java.util.Arrays; import javax.annotation.Nullable; -import java.net.URI; -import java.util.Arrays; /** * A representation of an NFC Forum "Smart Poster". @@ -39,7 +41,7 @@ public class SmartPoster { * This record is optional." */ - private final String titleRecord; + private final String mTitleRecord; /** * NFC Forum Smart Poster Record Type Definition section 3.2.1. @@ -48,22 +50,22 @@ public class SmartPoster { * records are just metadata about this record. There MUST be one URI * record and there MUST NOT be more than one." */ - private final URI uriRecord; + private final Uri mUriRecord; - private SmartPoster(URI uri, @Nullable String title) { - uriRecord = Preconditions.checkNotNull(uri); - titleRecord = title; + private SmartPoster(Uri uri, @Nullable String title) { + mUriRecord = Preconditions.checkNotNull(uri); + mTitleRecord = title; } - public URI getURI() { - return uriRecord; + public Uri getUri() { + return mUriRecord; } /** - * Returns the title of the smartposter. This may be {@code null}. + * Returns the title of the smart poster. This may be {@code null}. */ public String getTitle() { - return titleRecord; + return mTitleRecord; } public static SmartPoster from(NdefRecord record) { @@ -71,7 +73,7 @@ public class SmartPoster { Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)); try { NdefMessage subRecords = new NdefMessage(record.getPayload()); - URI uri = Iterables.getOnlyElement(NdefUtil.getURIs(subRecords)); + Uri uri = Iterables.getOnlyElement(NdefUtil.getUris(subRecords)); Iterable textFields = NdefUtil.getTextFields(subRecords); String title = null; if (!Iterables.isEmpty(textFields)) { diff --git a/apps/Tag/src/com/android/apps/tag/TagAdapter.java b/apps/Tag/src/com/android/apps/tag/TagAdapter.java new file mode 100644 index 000000000..f69c3ea4a --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/TagAdapter.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2010 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.apps.tag; + +import com.android.apps.tag.TagDBHelper.NdefMessagesTable; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.text.format.DateUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.CursorAdapter; +import android.widget.TextView; + +/** + * A custom {@link Adapter} that renders tag entries for a list. + */ +public class TagAdapter extends CursorAdapter { + + private final LayoutInflater mInflater; + + public TagAdapter(Context context) { + super(context, null, false); + mInflater = LayoutInflater.from(context); + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView mainLine = (TextView) view.findViewById(R.id.title); + TextView dateLine = (TextView) view.findViewById(R.id.date); + + NdefMessage msg = null; + try { + msg = new NdefMessage(cursor.getBlob(cursor.getColumnIndex(NdefMessagesTable.BYTES))); + } catch (FormatException e) { + Log.e("foo", "poorly formatted message", e); + } + + if (msg == null) { + mainLine.setText("Invalid tag"); + } else { + try { + SmartPoster poster = SmartPoster.from(msg.getRecords()[0]); + mainLine.setText(poster.getTitle()); + } catch (IllegalArgumentException e) { + // Not a smart poster + NdefRecord record = msg.getRecords()[0]; + Uri uri = null; + try { + uri = NdefUtil.toUri(record); + mainLine.setText(uri.toString()); + } catch (IllegalArgumentException e2) { + mainLine.setText("Not a smart poster or URL"); + } + } + } + dateLine.setText(DateUtils.getRelativeTimeSpanString( + context, cursor.getLong(cursor.getColumnIndex(NdefMessagesTable.DATE)))); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.tag_list_item, null); + } +} diff --git a/apps/Tag/src/com/android/apps/tag/TagBroadcastReceiver.java b/apps/Tag/src/com/android/apps/tag/TagBroadcastReceiver.java deleted file mode 100644 index b5ef1e740..000000000 --- a/apps/Tag/src/com/android/apps/tag/TagBroadcastReceiver.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2010 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.apps.tag; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.nfc.NdefTag; - -import android.nfc.NdefMessage; -import android.nfc.NfcAdapter; - -/** - * When we receive a new NDEF tag, start the activity to - * process the tag. - */ -public class TagBroadcastReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(NfcAdapter.ACTION_NDEF_TAG_DISCOVERED)) { - NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); - Intent i = new Intent(context, SaveTag.class) - .putExtra(NfcAdapter.EXTRA_TAG, tag) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - } -} diff --git a/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java b/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java index a4cbb86a6..38d3bfab8 100644 --- a/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java +++ b/apps/Tag/src/com/android/apps/tag/TagBrowserActivity.java @@ -16,6 +16,7 @@ package com.android.apps.tag; +import android.app.Activity; import android.app.TabActivity; import android.content.Intent; import android.content.res.Resources; @@ -23,34 +24,27 @@ import android.os.Bundle; import android.widget.TabHost; /** - * A browsing {@code Activity} that displays the saved tags in categories under tabs. + * A browsing {@link Activity} that displays the saved tags in categories under tabs. */ public class TagBrowserActivity extends TabActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // While we're doing development, delete the database every time we start. - getBaseContext().getDatabasePath("Tags.db").delete(); - setContentView(R.layout.main); Resources res = getResources(); TabHost tabHost = getTabHost(); - Intent i = new Intent().setClass(this, TagList.class); - Intent iSavedList = new Intent().setClass(this, TagList.class) - .putExtra(TagList.SHOW_SAVED_ONLY, true); - Intent iRecentList = new Intent().setClass(this, TagList.class); - - TabHost.TabSpec spec1 = tabHost.newTabSpec("1") - .setIndicator("Saved", res.getDrawable(R.drawable.ic_menu_tag)) - .setContent(iSavedList); + TabHost.TabSpec spec1 = tabHost.newTabSpec("saved") + .setIndicator(getText(R.string.tab_saved), res.getDrawable(R.drawable.ic_menu_tag)) + .setContent(new Intent().setClass(this, TagList.class) + .putExtra(TagList.EXTRA_SHOW_SAVED_ONLY, true)); tabHost.addTab(spec1); - TabHost.TabSpec spec2 = tabHost.newTabSpec("2") - .setIndicator("Recent", res.getDrawable(R.drawable.ic_menu_desk_clock)) - .setContent(iRecentList); + TabHost.TabSpec spec2 = tabHost.newTabSpec("recent") + .setIndicator(getText(R.string.tab_recent), res.getDrawable(R.drawable.ic_menu_desk_clock)) + .setContent(new Intent().setClass(this, TagList.class)); tabHost.addTab(spec2); } } diff --git a/apps/Tag/src/com/android/apps/tag/TagCursorAdapter.java b/apps/Tag/src/com/android/apps/tag/TagCursorAdapter.java deleted file mode 100644 index a658268e6..000000000 --- a/apps/Tag/src/com/android/apps/tag/TagCursorAdapter.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2010 Google Inc. All Rights Reserved. - -package com.android.apps.tag; - -import android.content.Context; -import android.database.Cursor; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Adapter; -import android.widget.CursorAdapter; -import android.widget.TextView; - -/** - * A custom {@link Adapter} that renders tag entries for a list. - */ -public class TagCursorAdapter extends CursorAdapter { - - private final LayoutInflater mInflater; - - public TagCursorAdapter(Context context, Cursor c) { - super(context, c); - - mInflater = LayoutInflater.from(context); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - TextView mainLine = (TextView) view.findViewById(R.id.title); - TextView dateLine = (TextView) view.findViewById(R.id.date); - - // TODO(benkomalo): either write a cursor abstraction, or use constants for column indices. - mainLine.setText(cursor.getString(cursor.getColumnIndex("bytes"))); - dateLine.setText(DateUtils.getRelativeTimeSpanString( - context, cursor.getLong(cursor.getColumnIndex("date")))); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.tag_list_item, null); - } -} diff --git a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java index 06fa9e001..654cb47f6 100644 --- a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java +++ b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java @@ -22,34 +22,32 @@ import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; +import android.net.Uri; import android.nfc.FormatException; import android.nfc.NdefMessage; import android.nfc.NdefRecord; -import java.net.URI; -import java.util.Date; - /** * Database utilities for the saved tags. */ public class TagDBHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "tags.db"; + private static final int DATABASE_VERSION = 3; - private static final String NDEF_MSG = "create table NdefMessage (" - + "_id INTEGER NOT NULL, " - + "bytes BLOB NOT NULL, " - + "date INTEGER NOT NULL, " - + "saved TEXT NOT NULL default 0," // boolean - + "PRIMARY KEY(_id)" - + ")"; - - private static final String INSERT = - "INSERT INTO NdefMessage (bytes, date, saved) values (?, ?, ?)"; + public interface NdefMessagesTable { + public static final String TABLE_NAME = "nedf_msg"; + public static final String _ID = "_id"; + public static final String TITLE = "title"; + public static final String BYTES = "bytes"; + public static final String DATE = "date"; + public static final String SAVED = "saved"; + } + /** * A real NFC tag containing an NFC "smart poster". This smart poster - * consists of the text "NFC Forum Type 4 Tag" in english combined with + * consists of the text "NFC Forum Type 4 Tag" in English combined with * the URL "http://www.nxp.com/nfc" */ public static final byte[] REAL_NFC_MSG = new byte[] { @@ -115,53 +113,85 @@ public class TagDBHelper extends SQLiteOpenHelper { // end smart poster payload }; - public TagDBHelper(Context context) { - this(context, "Tags.db"); + private static TagDBHelper sInstance; + + public static synchronized TagDBHelper getInstance(Context context) { + if (sInstance == null) { + sInstance = new TagDBHelper(context.getApplicationContext()); + } + return sInstance; + } + + private TagDBHelper(Context context) { + this(context, DATABASE_NAME); } @VisibleForTesting - public TagDBHelper(Context context, String dbFile) { + TagDBHelper(Context context, String dbFile) { super(context, dbFile, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { - db.execSQL(NDEF_MSG); + db.execSQL("CREATE TABLE " + NdefMessagesTable.TABLE_NAME + " (" + + NdefMessagesTable._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + NdefMessagesTable.TITLE + " TEXT NOT NULL DEFAULT ''," + + NdefMessagesTable.BYTES + " BLOB NOT NULL, " + + NdefMessagesTable.DATE + " INTEGER NOT NULL, " + + NdefMessagesTable.SAVED + " INTEGER NOT NULL DEFAULT 0" + // boolean + ");"); + db.execSQL("CREATE INDEX msgIndex ON " + NdefMessagesTable.TABLE_NAME + " (" + + NdefMessagesTable.DATE + " DESC, " + + NdefMessagesTable.SAVED + " ASC" + + ")"); + + addTestData(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + // Drop everything and recreate it for now + db.execSQL("DROP TABLE IF EXISTS " + NdefMessagesTable.TABLE_NAME); + onCreate(db); + } + + private void addTestData(SQLiteDatabase db) { // A fake message containing 1 URL NdefMessage msg1 = new NdefMessage(new NdefRecord[] { - NdefUtil.toUriRecord(URI.create("http://www.google.com")) + NdefUtil.toUriRecord(Uri.parse("http://www.google.com")) }); // A fake message containing 2 URLs NdefMessage msg2 = new NdefMessage(new NdefRecord[] { - NdefUtil.toUriRecord(URI.create("http://www.youtube.com")), - NdefUtil.toUriRecord(URI.create("http://www.android.com")) + NdefUtil.toUriRecord(Uri.parse("http://www.youtube.com")), + NdefUtil.toUriRecord(Uri.parse("http://www.android.com")) }); - insert(db, msg1, false); - insert(db, msg2, true); + insertNdefMessage(db, msg1, false); + insertNdefMessage(db, msg2, true); try { // A real message obtained from an NFC Forum Type 4 tag. NdefMessage msg3 = new NdefMessage(REAL_NFC_MSG); - insert(db, msg3, false); + insertNdefMessage(db, msg3, false); } catch (FormatException e) { throw new RuntimeException(e); } } - private void insert(SQLiteDatabase db, NdefMessage msg, boolean isSaved) { - SQLiteStatement stmt = db.compileStatement(INSERT); - stmt.bindString(1, new String(msg.toByteArray())); // TODO: This should be a blob - stmt.bindLong(2, System.currentTimeMillis()); - String isSavedStr = isSaved ? "1" : "0"; - stmt.bindString(3, isSavedStr); - stmt.executeInsert(); - stmt.close(); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + public void insertNdefMessage(SQLiteDatabase db, NdefMessage msg, boolean isSaved) { + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement("INSERT INTO " + NdefMessagesTable.TABLE_NAME + + "(" + NdefMessagesTable.BYTES + ", " + NdefMessagesTable.DATE + ", " + + NdefMessagesTable.SAVED + ") values (?, ?, ?)"); + stmt.bindBlob(1, msg.toByteArray()); + stmt.bindLong(2, System.currentTimeMillis()); + stmt.bindLong(3, isSaved ? 1 : 0); + stmt.executeInsert(); + } finally { + if (stmt != null) stmt.close(); + } } } diff --git a/apps/Tag/src/com/android/apps/tag/TagList.java b/apps/Tag/src/com/android/apps/tag/TagList.java index 369ef6557..45f6f6577 100644 --- a/apps/Tag/src/com/android/apps/tag/TagList.java +++ b/apps/Tag/src/com/android/apps/tag/TagList.java @@ -16,42 +16,47 @@ package com.android.apps.tag; +import com.android.apps.tag.TagDBHelper.NdefMessagesTable; + +import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ListActivity; import android.content.DialogInterface; +import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.os.AsyncTask; import android.os.Bundle; +import android.util.Log; import android.view.Menu; import android.view.View; import android.widget.ListView; -import android.widget.SimpleCursorAdapter; /** - * An {@code Activity} that displays a flat list of tags that can be "opened". + * An {@link Activity} that displays a flat list of tags that can be "opened". */ public class TagList extends ListActivity implements DialogInterface.OnClickListener { - private SQLiteDatabase db; - private Cursor cursor; - static final String SHOW_SAVED_ONLY = "show_saved_only"; + static final String TAG = "TagList"; + + static final String EXTRA_SHOW_SAVED_ONLY = "show_saved_only"; + + SQLiteDatabase mDatabase; + TagAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - boolean showSavedOnly = getIntent().getBooleanExtra(SHOW_SAVED_ONLY, false); - db = new TagDBHelper(getBaseContext()).getReadableDatabase(); - String selection = showSavedOnly ? "saved=1" : null; + boolean showSavedOnly = getIntent().getBooleanExtra(EXTRA_SHOW_SAVED_ONLY, false); + mDatabase = TagDBHelper.getInstance(this).getReadableDatabase(); + String selection = showSavedOnly ? NdefMessagesTable.SAVED + "=1" : null; - // TODO: Use an AsyncQueryHandler so that DB queries are not done on UI thread. - cursor = db.query( - "NdefMessage", - new String[] { "_id", "bytes", "date" }, - selection, - null, null, null, null); - - setListAdapter(new TagCursorAdapter(this, cursor)); + new TagLoaderTask().execute(selection); + mAdapter = new TagAdapter(this); + setListAdapter(mAdapter); registerForContextMenu(getListView()); } @@ -75,22 +80,53 @@ public class TagList extends ListActivity implements DialogInterface.OnClickList @Override protected void onDestroy() { - if (cursor != null) { - cursor.close(); - } - if (db != null) { - db.close(); + if (mAdapter != null) { + mAdapter.changeCursor(null); } super.onDestroy(); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { - showDialog(1); - super.onListItemClick(l, v, position, id); + Cursor cursor = mAdapter.getCursor(); + cursor.moveToPosition(position); + byte[] tagBytes = cursor.getBlob(cursor.getColumnIndexOrThrow(NdefMessagesTable.BYTES)); + try { + NdefMessage msg = new NdefMessage(tagBytes); + Intent intent = new Intent(this, TagViewer.class); + intent.putExtra(TagViewer.EXTRA_MESSAGE, msg); + intent.putExtra(TagViewer.EXTRA_TAG_DB_ID, id); + startActivity(intent); + } catch (FormatException e) { + Log.e(TAG, "bad format for tag " + id + ": " + tagBytes, e); + return; + } } @Override public void onClick(DialogInterface dialog, int which) { } + + final class TagLoaderTask extends AsyncTask { + @Override + public Cursor doInBackground(String... args) { + String selection = args[0]; + Cursor cursor = mDatabase.query( + NdefMessagesTable.TABLE_NAME, + new String[] { + NdefMessagesTable._ID, + NdefMessagesTable.BYTES, + NdefMessagesTable.DATE, + NdefMessagesTable.TITLE }, + selection, + null, null, null, null); + cursor.getCount(); + return cursor; + } + + @Override + protected void onPostExecute(Cursor cursor) { + mAdapter.changeCursor(cursor); + } + } } diff --git a/apps/Tag/src/com/android/apps/tag/TagViewer.java b/apps/Tag/src/com/android/apps/tag/TagViewer.java new file mode 100644 index 000000000..21f64e161 --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/TagViewer.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2010 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.apps.tag; + +import android.app.Activity; +import android.content.Intent; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Color; +import android.net.Uri; +import android.nfc.NdefMessage; +import android.nfc.NdefTag; +import android.nfc.NfcAdapter; +import android.os.AsyncTask; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * An {@link Activity} which handles a broadcast of a new tag that the device just discovered. + */ +public class TagViewer extends Activity { + static final String TAG = "SaveTag"; + static final String EXTRA_TAG_DB_ID = "db_id"; + static final String EXTRA_MESSAGE = "msg"; + + long mTagDatabaseId; + + @Override + protected void onStart() { + super.onStart(); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_DIM_BEHIND + ); + + Intent intent = getIntent(); + NdefMessage[] msgs = null; + NdefTag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); + if (tag == null) { + // Maybe it came from the database? + mTagDatabaseId = intent.getLongExtra(EXTRA_TAG_DB_ID, -1); + NdefMessage msg = intent.getParcelableExtra(EXTRA_MESSAGE); + if (msg != null) { + msgs = new NdefMessage[] { msg }; + } + } else { + msgs = tag.getNdefMessages(); + // TODO use a service to avoid the process getting reaped during saving + new SaveTagTask().execute(msgs); + } + + if (msgs == null || msgs.length == 0) { + Log.e(TAG, "No NDEF messages"); + finish(); + return; + } + + + LayoutInflater inflater = LayoutInflater.from( + new ContextThemeWrapper(this, android.R.style.Theme_Light)); + LinearLayout list = (LinearLayout) inflater.inflate(R.layout.tag_viewer_list, null, false); + // TODO figure out why the background isn't white, the CTW should force that... + list.setBackgroundColor(Color.WHITE); + setContentView(list); + buildTagViews(list, inflater, msgs); + } + + private void buildTagViews(LinearLayout list, LayoutInflater inflater, NdefMessage[] msgs) { + // The body of the dialog should use the light theme + + // Build the views from the logical records in the messages + boolean first = true; + for (NdefMessage msg : msgs) { + Iterable objects = NdefUtil.getObjects(msg); + for (Object object : objects) { + if (!first) { + list.addView(inflater.inflate(R.layout.tag_divider, list, false)); + first = false; + } + + if (object instanceof String) { + TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); + text.setText((CharSequence) object); + list.addView(text); + } else if (object instanceof Uri) { + TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); + text.setText(object.toString()); + list.addView(text); + } else if (object instanceof SmartPoster) { + TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); + SmartPoster poster = (SmartPoster) object; + text.setText(poster.getTitle()); + list.addView(text); + } + } + } + } + + final class SaveTagTask extends AsyncTask { + @Override + public Void doInBackground(NdefMessage... msgs) { + TagDBHelper helper = TagDBHelper.getInstance(TagViewer.this); + SQLiteDatabase db = helper.getWritableDatabase(); + db.beginTransaction(); + try { + for (NdefMessage msg : msgs) { + helper.insertNdefMessage(db, msg, false); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return null; + } + } +} diff --git a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java index b7eb86973..e55e07d2d 100644 --- a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java +++ b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java @@ -28,6 +28,6 @@ public class SmartPosterTest extends AndroidTestCase { SmartPoster poster = SmartPoster.from(msg.getRecords()[0]); assertEquals("NFC Forum Type 4 Tag", poster.getTitle()); - assertEquals("http://www.nxp.com/nfc", poster.getURI().toString()); + assertEquals("http://www.nxp.com/nfc", poster.getUri().toString()); } } From 85122d2605a22c8e7bcde868c2b7a7d8d744e613 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Thu, 14 Oct 2010 13:26:48 -0700 Subject: [PATCH 5/7] add some new mock ndef messages for testing. Change-Id: Ib238bc427c7350cec4b6765a171d6f3fa0907151 --- .../android/apps/tag/MockNdefMessages.java | 224 ++++++++++++++++++ .../src/com/android/apps/tag/TagDBHelper.java | 76 +----- .../com/android/apps/tag/SmartPosterTest.java | 2 +- 3 files changed, 230 insertions(+), 72 deletions(-) create mode 100644 apps/Tag/src/com/android/apps/tag/MockNdefMessages.java diff --git a/apps/Tag/src/com/android/apps/tag/MockNdefMessages.java b/apps/Tag/src/com/android/apps/tag/MockNdefMessages.java new file mode 100644 index 000000000..3acdc5c64 --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/MockNdefMessages.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2010 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.apps.tag; + +/** + * Tags that we've seen in the field, for testing purposes. + */ +public class MockNdefMessages { + + /** + * A real NFC tag containing an NFC "smart poster". This smart poster + * consists of the text "NFC Forum Type 4 Tag" in english combined with + * the URL "http://www.nxp.com/nfc" + */ + public static final byte[] REAL_NFC_MSG = new byte[] { + (byte) 0xd1, // MB=1 ME=1 CF=0 SR=1 IL=0 TNF=001 + (byte) 0x02, // Type Length = 2 + (byte) 0x2b, // Payload Length = 43 + (byte) 0x53, (byte) 0x70, // Type = {'S', 'p'} (smart poster) + + // begin smart poster payload + // begin smart poster record #1 + (byte) 0x91, // MB=1 ME=0 CF=0 SR=1 IL=0 TNF=001 + (byte) 0x01, // Type Length = 1 + (byte) 0x17, // Payload Length = 23 + (byte) 0x54, // Type = {'T'} (Text data) + (byte) 0x02, // UTF-8 encoding, language code length = 2 + (byte) 0x65, (byte) 0x6e, // language = {'e', 'n'} (english) + + // Begin text data within smart poster record #1 + (byte) 0x4e, // 'N' + (byte) 0x46, // 'F' + (byte) 0x43, // 'C' + (byte) 0x20, // ' ' + (byte) 0x46, // 'F' + (byte) 0x6f, // 'o' + (byte) 0x72, // 'r' + (byte) 0x75, // 'u' + (byte) 0x6d, // 'm' + (byte) 0x20, // ' ' + (byte) 0x54, // 'T' + (byte) 0x79, // 'y' + (byte) 0x70, // 'p' + (byte) 0x65, // 'e' + (byte) 0x20, // ' ' + (byte) 0x34, // '4' + (byte) 0x20, // ' ' + (byte) 0x54, // 'T' + (byte) 0x61, // 'a' + (byte) 0x67, // 'g' + // end Text data within smart poster record #1 + // end smart poster record #1 + + // begin smart poster record #2 + (byte) 0x51, // MB=0 ME=1 CF=0 SR=1 IL=0 TNF=001 + (byte) 0x01, // Type Length = 1 + (byte) 0x0c, // Payload Length = 12 + (byte) 0x55, // Type = { 'U' } (URI) + + // begin URI data within smart poster record #2 + (byte) 0x01, // URI Prefix = 1 ("http://www.") + (byte) 0x6e, // 'n' + (byte) 0x78, // 'x' + (byte) 0x70, // 'p' + (byte) 0x2e, // '.' + (byte) 0x63, // 'c' + (byte) 0x6f, // 'o' + (byte) 0x6d, // 'm' + (byte) 0x2f, // '/' + (byte) 0x6e, // 'n' + (byte) 0x66, // 'f' + (byte) 0x63 // 'c' + // end URI data within smart poster record #2 + // end smart poster record #2 + // end smart poster payload + }; + + + /** + * A Smart Poster containing a URL and no text. This message was created + * using the NXP reference phone. + */ + private static final byte[] SMART_POSTER_URL_NO_TEXT = new byte[] { + (byte) 0xd1, (byte) 0x02, (byte) 0x0f, (byte) 0x53, (byte) 0x70, (byte) 0xd1, + (byte) 0x01, (byte) 0x0b, (byte) 0x55, (byte) 0x01, (byte) 0x67, (byte) 0x6f, + (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, + (byte) 0x6f, (byte) 0x6d + }; + + /** + * A plain text tag in english. Generated using the NXP evaluation tool. + */ + private static final byte[] ENGLISH_PLAIN_TEXT = new byte[] { + (byte) 0xd1, (byte) 0x01, (byte) 0x1c, (byte) 0x54, (byte) 0x02, (byte) 0x65, + (byte) 0x6e, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x20, + (byte) 0x72, (byte) 0x61, (byte) 0x6e, (byte) 0x64, (byte) 0x6f, (byte) 0x6d, + (byte) 0x20, (byte) 0x65, (byte) 0x6e, (byte) 0x67, (byte) 0x6c, (byte) 0x69, + (byte) 0x73, (byte) 0x68, (byte) 0x20, (byte) 0x74, (byte) 0x65, (byte) 0x78, + (byte) 0x74, (byte) 0x2e + }; + + /** + * Smart Poster containing a URL and Text. Generated using the NXP + * evaluation tool. + */ + private static final byte[] SMART_POSTER_URL_AND_TEXT = new byte[] { + (byte) 0xd1, (byte) 0x02, (byte) 0x1c, (byte) 0x53, (byte) 0x70, (byte) 0x91, + (byte) 0x01, (byte) 0x09, (byte) 0x54, (byte) 0x02, (byte) 0x65, (byte) 0x6e, + (byte) 0x47, (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, + (byte) 0x51, (byte) 0x01, (byte) 0x0b, (byte) 0x55, (byte) 0x01, (byte) 0x67, + (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, + (byte) 0x63, (byte) 0x6f, (byte) 0x6d + }; + + /** + * A plain URI. Generated using the NXP evaluation tool. + */ + private static final byte[] URI = new byte[] { + (byte) 0xd1, (byte) 0x01, (byte) 0x0b, (byte) 0x55, (byte) 0x01, (byte) 0x67, + (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, + (byte) 0x63, (byte) 0x6f, (byte) 0x6d + }; + + /** + * A vcard. Generated using the NXP evaluation tool. + */ + private static final byte[] VCARD = new byte[] { + (byte) 0xc2, (byte) 0x0c, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x05, + (byte) 0x74, (byte) 0x65, (byte) 0x78, (byte) 0x74, (byte) 0x2f, (byte) 0x78, + (byte) 0x2d, (byte) 0x76, (byte) 0x43, (byte) 0x61, (byte) 0x72, (byte) 0x64, + (byte) 0x42, (byte) 0x45, (byte) 0x47, (byte) 0x49, (byte) 0x4e, (byte) 0x3a, + (byte) 0x56, (byte) 0x43, (byte) 0x41, (byte) 0x52, (byte) 0x44, (byte) 0x0d, + (byte) 0x0a, (byte) 0x56, (byte) 0x45, (byte) 0x52, (byte) 0x53, (byte) 0x49, + (byte) 0x4f, (byte) 0x4e, (byte) 0x3a, (byte) 0x33, (byte) 0x2e, (byte) 0x30, + (byte) 0x0d, (byte) 0x0a, (byte) 0x46, (byte) 0x4e, (byte) 0x3a, (byte) 0x4a, + (byte) 0x6f, (byte) 0x65, (byte) 0x20, (byte) 0x47, (byte) 0x6f, (byte) 0x6f, + (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x20, (byte) 0x45, (byte) 0x6d, + (byte) 0x70, (byte) 0x6c, (byte) 0x6f, (byte) 0x79, (byte) 0x65, (byte) 0x65, + (byte) 0x0d, (byte) 0x0a, (byte) 0x41, (byte) 0x44, (byte) 0x52, (byte) 0x3b, + (byte) 0x54, (byte) 0x59, (byte) 0x50, (byte) 0x45, (byte) 0x3d, (byte) 0x57, + (byte) 0x4f, (byte) 0x52, (byte) 0x4b, (byte) 0x3a, (byte) 0x3b, (byte) 0x3b, + (byte) 0x31, (byte) 0x36, (byte) 0x30, (byte) 0x30, (byte) 0x20, (byte) 0x41, + (byte) 0x6d, (byte) 0x70, (byte) 0x68, (byte) 0x69, (byte) 0x74, (byte) 0x68, + (byte) 0x65, (byte) 0x61, (byte) 0x74, (byte) 0x72, (byte) 0x65, (byte) 0x20, + (byte) 0x50, (byte) 0x61, (byte) 0x72, (byte) 0x6b, (byte) 0x77, (byte) 0x61, + (byte) 0x79, (byte) 0x3b, (byte) 0x39, (byte) 0x34, (byte) 0x30, (byte) 0x34, + (byte) 0x33, (byte) 0x20, (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, + (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, + (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x0d, (byte) 0x0a, (byte) 0x54, + (byte) 0x45, (byte) 0x4c, (byte) 0x3b, (byte) 0x54, (byte) 0x59, (byte) 0x50, + (byte) 0x45, (byte) 0x3d, (byte) 0x50, (byte) 0x52, (byte) 0x45, (byte) 0x46, + (byte) 0x2c, (byte) 0x57, (byte) 0x4f, (byte) 0x52, (byte) 0x4b, (byte) 0x3a, + (byte) 0x36, (byte) 0x35, (byte) 0x30, (byte) 0x2d, (byte) 0x32, (byte) 0x35, + (byte) 0x33, (byte) 0x2d, (byte) 0x30, (byte) 0x30, (byte) 0x30, (byte) 0x30, + (byte) 0x0d, (byte) 0x0a, (byte) 0x45, (byte) 0x4d, (byte) 0x41, (byte) 0x49, + (byte) 0x4c, (byte) 0x3b, (byte) 0x54, (byte) 0x59, (byte) 0x50, (byte) 0x45, + (byte) 0x3d, (byte) 0x49, (byte) 0x4e, (byte) 0x54, (byte) 0x45, (byte) 0x52, + (byte) 0x4e, (byte) 0x45, (byte) 0x54, (byte) 0x3a, (byte) 0x73, (byte) 0x75, + (byte) 0x70, (byte) 0x70, (byte) 0x6f, (byte) 0x72, (byte) 0x74, (byte) 0x40, + (byte) 0x67, (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, + (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x0d, (byte) 0x0a, + (byte) 0x54, (byte) 0x49, (byte) 0x54, (byte) 0x4c, (byte) 0x45, (byte) 0x3a, + (byte) 0x53, (byte) 0x6f, (byte) 0x66, (byte) 0x74, (byte) 0x77, (byte) 0x61, + (byte) 0x72, (byte) 0x65, (byte) 0x20, (byte) 0x45, (byte) 0x6e, (byte) 0x67, + (byte) 0x69, (byte) 0x6e, (byte) 0x65, (byte) 0x65, (byte) 0x72, (byte) 0x0d, + (byte) 0x0a, (byte) 0x4f, (byte) 0x52, (byte) 0x47, (byte) 0x3a, (byte) 0x47, + (byte) 0x6f, (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x0d, + (byte) 0x0a, (byte) 0x55, (byte) 0x52, (byte) 0x4c, (byte) 0x3a, (byte) 0x68, + (byte) 0x74, (byte) 0x74, (byte) 0x70, (byte) 0x3a, (byte) 0x2f, (byte) 0x2f, + (byte) 0x77, (byte) 0x77, (byte) 0x77, (byte) 0x2e, (byte) 0x67, (byte) 0x6f, + (byte) 0x6f, (byte) 0x67, (byte) 0x6c, (byte) 0x65, (byte) 0x2e, (byte) 0x63, + (byte) 0x6f, (byte) 0x6d, (byte) 0x0d, (byte) 0x0a, (byte) 0x45, (byte) 0x4e, + (byte) 0x44, (byte) 0x3a, (byte) 0x56, (byte) 0x43, (byte) 0x41, (byte) 0x52, + (byte) 0x44, (byte) 0x0d, (byte) 0x0a + }; + + /** + * Send the text message "hello world" to a phone number. This was generated using + * the NXP reference phone. + */ + private static final byte[] SEND_TEXT_MESSAGE = new byte[] { + (byte) 0xd1, (byte) 0x02, (byte) 0x25, (byte) 0x53, (byte) 0x70, (byte) 0xd1, + (byte) 0x01, (byte) 0x21, (byte) 0x55, (byte) 0x00, (byte) 0x73, (byte) 0x6d, + (byte) 0x73, (byte) 0x3a, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x30, + (byte) 0x32, (byte) 0x35, (byte) 0x33, (byte) 0x30, (byte) 0x30, (byte) 0x30, + (byte) 0x30, (byte) 0x3f, (byte) 0x62, (byte) 0x6f, (byte) 0x64, (byte) 0x79, + (byte) 0x3d, (byte) 0x48, (byte) 0x65, (byte) 0x6c, (byte) 0x6c, (byte) 0x6f, + (byte) 0x20, (byte) 0x77, (byte) 0x6f, (byte) 0x72, (byte) 0x6c, (byte) 0x64 + }; + + /** + * Call Google. Generated using the NXP reference phone. + */ + private static final byte[] CALL_GOOGLE = new byte[] { + (byte) 0xd1, (byte) 0x02, (byte) 0x10, (byte) 0x53, (byte) 0x70, (byte) 0xd1, + (byte) 0x01, (byte) 0x0c, (byte) 0x55, (byte) 0x05, (byte) 0x31, (byte) 0x36, + (byte) 0x35, (byte) 0x30, (byte) 0x32, (byte) 0x35, (byte) 0x33, (byte) 0x30, + (byte) 0x30, (byte) 0x30, (byte) 0x30 + }; + + /** + * All the real ndef messages we've seen in the field. + */ + public static final byte[][] ALL_MOCK_MESSAGES = new byte[][] { + REAL_NFC_MSG, SMART_POSTER_URL_NO_TEXT, ENGLISH_PLAIN_TEXT, + SMART_POSTER_URL_AND_TEXT, URI, VCARD, SEND_TEXT_MESSAGE, + CALL_GOOGLE + }; + +} diff --git a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java index 654cb47f6..3cb7844c1 100644 --- a/apps/Tag/src/com/android/apps/tag/TagDBHelper.java +++ b/apps/Tag/src/com/android/apps/tag/TagDBHelper.java @@ -44,74 +44,6 @@ public class TagDBHelper extends SQLiteOpenHelper { public static final String DATE = "date"; public static final String SAVED = "saved"; } - - /** - * A real NFC tag containing an NFC "smart poster". This smart poster - * consists of the text "NFC Forum Type 4 Tag" in English combined with - * the URL "http://www.nxp.com/nfc" - */ - public static final byte[] REAL_NFC_MSG = new byte[] { - (byte) 0xd1, // MB=1 ME=1 CF=0 SR=1 IL=0 TNF=001 - (byte) 0x02, // Type Length = 2 - (byte) 0x2b, // Payload Length = 43 - (byte) 0x53, (byte) 0x70, // Type = {'S', 'p'} (smart poster) - - // begin smart poster payload - // begin smart poster record #1 - (byte) 0x91, // MB=1 ME=0 CF=0 SR=1 IL=0 TNF=001 - (byte) 0x01, // Type Length = 1 - (byte) 0x17, // Payload Length = 23 - (byte) 0x54, // Type = {'T'} (Text data) - (byte) 0x02, // UTF-8 encoding, language code length = 2 - (byte) 0x65, (byte) 0x6e, // language = {'e', 'n'} (english) - - // Begin text data within smart poster record #1 - (byte) 0x4e, // 'N' - (byte) 0x46, // 'F' - (byte) 0x43, // 'C' - (byte) 0x20, // ' ' - (byte) 0x46, // 'F' - (byte) 0x6f, // 'o' - (byte) 0x72, // 'r' - (byte) 0x75, // 'u' - (byte) 0x6d, // 'm' - (byte) 0x20, // ' ' - (byte) 0x54, // 'T' - (byte) 0x79, // 'y' - (byte) 0x70, // 'p' - (byte) 0x65, // 'e' - (byte) 0x20, // ' ' - (byte) 0x34, // '4' - (byte) 0x20, // ' ' - (byte) 0x54, // 'T' - (byte) 0x61, // 'a' - (byte) 0x67, // 'g' - // end Text data within smart poster record #1 - // end smart poster record #1 - - // begin smart poster record #2 - (byte) 0x51, // MB=0 ME=1 CF=0 SR=1 IL=0 TNF=001 - (byte) 0x01, // Type Length = 1 - (byte) 0x0c, // Payload Length = 12 - (byte) 0x55, // Type = { 'U' } (URI) - - // begin URI data within smart poster record #2 - (byte) 0x01, // URI Prefix = 1 ("http://www.") - (byte) 0x6e, // 'n' - (byte) 0x78, // 'x' - (byte) 0x70, // 'p' - (byte) 0x2e, // '.' - (byte) 0x63, // 'c' - (byte) 0x6f, // 'o' - (byte) 0x6d, // 'm' - (byte) 0x2f, // '/' - (byte) 0x6e, // 'n' - (byte) 0x66, // 'f' - (byte) 0x63 // 'c' - // end URI data within smart poster record #2 - // end smart poster record #2 - // end smart poster payload - }; private static TagDBHelper sInstance; @@ -172,9 +104,11 @@ public class TagDBHelper extends SQLiteOpenHelper { insertNdefMessage(db, msg2, true); try { - // A real message obtained from an NFC Forum Type 4 tag. - NdefMessage msg3 = new NdefMessage(REAL_NFC_MSG); - insertNdefMessage(db, msg3, false); + // insert some real messages we found in the field. + for (byte[] msg : MockNdefMessages.ALL_MOCK_MESSAGES) { + NdefMessage msg3 = new NdefMessage(msg); + insertNdefMessage(db, msg3, false); + } } catch (FormatException e) { throw new RuntimeException(e); } diff --git a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java index e55e07d2d..01ac817c4 100644 --- a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java +++ b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java @@ -24,7 +24,7 @@ import android.nfc.NdefMessage; */ public class SmartPosterTest extends AndroidTestCase { public void testSmartPoster() throws Exception { - NdefMessage msg = new NdefMessage(TagDBHelper.REAL_NFC_MSG); + NdefMessage msg = new NdefMessage(MockNdefMessages.REAL_NFC_MSG); SmartPoster poster = SmartPoster.from(msg.getRecords()[0]); assertEquals("NFC Forum Type 4 Tag", poster.getTitle()); From 4f3dcc857e002d1b5611d0b00bb787eec6318967 Mon Sep 17 00:00:00 2001 From: Brett Chabot Date: Thu, 14 Oct 2010 14:15:33 -0700 Subject: [PATCH 6/7] Revert "Add utility for toggling settings from the cmd line." This reverts commit a2e81242e01af6619310003a6a035ce82b6f7e09. --- apps/SettingsCmd/Android.mk | 29 ------- apps/SettingsCmd/AndroidManifest.xml | 32 -------- .../settingscmd/SettingsInstrument.java | 82 ------------------- 3 files changed, 143 deletions(-) delete mode 100644 apps/SettingsCmd/Android.mk delete mode 100644 apps/SettingsCmd/AndroidManifest.xml delete mode 100644 apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java diff --git a/apps/SettingsCmd/Android.mk b/apps/SettingsCmd/Android.mk deleted file mode 100644 index 282b89b74..000000000 --- a/apps/SettingsCmd/Android.mk +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2010 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. - -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SRC_FILES := $(call all-subdir-java-files) - -LOCAL_PACKAGE_NAME := SettingsCmd -LOCAL_CERTIFICATE := platform - -LOCAL_SDK_VERSION := 4 - -LOCAL_PROGUARD_ENABLED := disabled - -include $(BUILD_PACKAGE) diff --git a/apps/SettingsCmd/AndroidManifest.xml b/apps/SettingsCmd/AndroidManifest.xml deleted file mode 100644 index 63553faf8..000000000 --- a/apps/SettingsCmd/AndroidManifest.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java b/apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java deleted file mode 100644 index 1f8d9ecb2..000000000 --- a/apps/SettingsCmd/src/com/android/settingscmd/SettingsInstrument.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2010, 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.settingscmd; - -import android.app.Activity; -import android.app.Instrumentation; -import android.os.Bundle; -import android.provider.Settings; - -/** - * Utility for modifying system settings. - *

- * Usage: - *

- * adb shell am instrument -w [-e secure true/false] -e name - * -e value -w com.android.settingscmd/.SettingsInstrument - *

- * This apk must be signed with the platform certificate to modify secure settings. - */ -public class SettingsInstrument extends Instrumentation { - - private boolean mSecure; - private String mName; - private String mValue; - - public SettingsInstrument() { - super(); - } - - @Override - public void onCreate(Bundle arguments) { - mSecure = getBooleanArgument(arguments, "secure"); - mName = arguments.getString("name"); - mValue = arguments.getString("value"); - start(); - } - - private boolean getBooleanArgument(Bundle arguments, String tag) { - String tagString = arguments.getString(tag); - return tagString != null && Boolean.parseBoolean(tagString); - } - - @Override - public void onStart() { - if (mName == null || mValue == null) { - reportError("Missing arguments. Usage:\n [-e secure true] " - + "-e name -e value "); - return; - } - boolean status = false; - if (mSecure) { - status = Settings.Secure.putString(getTargetContext().getContentResolver(), mName, - mValue); - } else { - status = Settings.System.putString(getTargetContext().getContentResolver(), mName, - mValue); - } - Bundle bundleResponse = new Bundle(); - bundleResponse.putBoolean("result", status); - finish(Activity.RESULT_OK, bundleResponse); - } - - private void reportError(String msg) { - Bundle bundleResponse = new Bundle(); - bundleResponse.putString("error", msg); - finish(Activity.RESULT_CANCELED, bundleResponse); - } -} From da3b9b719623985934d89c34d4c4a9a7816c57b3 Mon Sep 17 00:00:00 2001 From: Nick Kralevich Date: Thu, 14 Oct 2010 14:34:08 -0700 Subject: [PATCH 7/7] Have getObjects() return ParsedNdefRecords Change-Id: I7e5f8fdf2ae113f55cc5843f517a8a8b20ab00ee --- .../src/com/android/apps/tag/NdefUtil.java | 167 ++---------------- .../src/com/android/apps/tag/TagAdapter.java | 12 +- .../src/com/android/apps/tag/TagViewer.java | 24 ++- .../apps/tag/record/ParsedNdefRecord.java | 26 +++ .../apps/tag/{ => record}/SmartPoster.java | 31 ++-- .../android/apps/tag/record/TextRecord.java | 97 ++++++++++ .../android/apps/tag/record/UriRecord.java | 135 ++++++++++++++ .../com/android/apps/tag/NdefUtilTest.java | 3 +- .../com/android/apps/tag/SmartPosterTest.java | 7 +- 9 files changed, 322 insertions(+), 180 deletions(-) create mode 100644 apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java rename apps/Tag/src/com/android/apps/tag/{ => record}/SmartPoster.java (77%) create mode 100644 apps/Tag/src/com/android/apps/tag/record/TextRecord.java create mode 100644 apps/Tag/src/com/android/apps/tag/record/UriRecord.java diff --git a/apps/Tag/src/com/android/apps/tag/NdefUtil.java b/apps/Tag/src/com/android/apps/tag/NdefUtil.java index 40f5bcf7e..eec7c1918 100644 --- a/apps/Tag/src/com/android/apps/tag/NdefUtil.java +++ b/apps/Tag/src/com/android/apps/tag/NdefUtil.java @@ -16,9 +16,10 @@ package com.android.apps.tag; -import com.google.common.base.Preconditions; -import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableBiMap; +import com.android.apps.tag.record.ParsedNdefRecord; +import com.android.apps.tag.record.SmartPoster; +import com.android.apps.tag.record.TextRecord; +import com.android.apps.tag.record.UriRecord; import com.google.common.collect.Iterables; import com.google.common.primitives.Bytes; @@ -26,11 +27,8 @@ import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; -import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; import java.nio.charset.Charsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -41,51 +39,6 @@ import java.util.List; public class NdefUtil { private static final byte[] EMPTY = new byte[0]; - /** - * NFC Forum "URI Record Type Definition" - * - * This is a mapping of "URI Identifier Codes" to URI string prefixes, - * per section 3.2.2 of the NFC Forum URI Record Type Definition document. - */ - private static final BiMap URI_PREFIX_MAP = ImmutableBiMap.builder() - .put((byte) 0x00, "") - .put((byte) 0x01, "http://www.") - .put((byte) 0x02, "https://www.") - .put((byte) 0x03, "http://") - .put((byte) 0x04, "https://") - .put((byte) 0x05, "tel:") - .put((byte) 0x06, "mailto:") - .put((byte) 0x07, "ftp://anonymous:anonymous@") - .put((byte) 0x08, "ftp://ftp.") - .put((byte) 0x09, "ftps://") - .put((byte) 0x0A, "sftp://") - .put((byte) 0x0B, "smb://") - .put((byte) 0x0C, "nfs://") - .put((byte) 0x0D, "ftp://") - .put((byte) 0x0E, "dav://") - .put((byte) 0x0F, "news:") - .put((byte) 0x10, "telnet://") - .put((byte) 0x11, "imap:") - .put((byte) 0x12, "rtsp://") - .put((byte) 0x13, "urn:") - .put((byte) 0x14, "pop:") - .put((byte) 0x15, "sip:") - .put((byte) 0x16, "sips:") - .put((byte) 0x17, "tftp:") - .put((byte) 0x18, "btspp://") - .put((byte) 0x19, "btl2cap://") - .put((byte) 0x1A, "btgoep://") - .put((byte) 0x1B, "tcpobex://") - .put((byte) 0x1C, "irdaobex://") - .put((byte) 0x1D, "file://") - .put((byte) 0x1E, "urn:epc:id:") - .put((byte) 0x1F, "urn:epc:tag:") - .put((byte) 0x20, "urn:epc:pat:") - .put((byte) 0x21, "urn:epc:raw:") - .put((byte) 0x22, "urn:epc:") - .put((byte) 0x23, "urn:nfc:") - .build(); - /** * Create a new {@link NdefRecord} containing the supplied {@link Uri}. */ @@ -108,102 +61,12 @@ public class NdefUtil { NdefRecord.RTD_URI, EMPTY, payload); } - /** - * Convert {@link NdefRecord} into a {@link Uri}. - * - * TODO: This class does not handle NdefRecords where the TNF - * (Type Name Format) of the class is {@link NdefRecord#TNF_ABSOLUTE_URI}. - * This should be fixed. - * - * @throws IllegalArgumentException if the NdefRecord is not a - * record containing a URI. - */ - public static Uri toUri(NdefRecord record) { - Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); - Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI)); - - byte[] payload = record.getPayload(); - - /* - * payload[0] contains the URI Identifier Code, per the - * NFC Forum "URI Record Type Definition" section 3.2.2. - * - * payload[1]...payload[payload.length - 1] contains the rest of - * the URI. - */ - - String prefix = URI_PREFIX_MAP.get(payload[0]); - byte[] fullUri = Bytes.concat( - prefix.getBytes(Charsets.UTF_8), - Arrays.copyOfRange(payload, 1, payload.length)); - - return Uri.parse(new String(fullUri, Charsets.UTF_8)); + public static Iterable getTextFields(NdefMessage message) { + return Iterables.filter(getObjects(message), TextRecord.class); } - public static boolean isUri(NdefRecord record) { - try { - toUri(record); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } - - /** - * Extracts payload text from Text type ndef record. - * - * @param record A ndef record. Must be {@link NdefRecord#TYPE_TEXT}. - * @return text payload. - */ - public static String toText(NdefRecord record) { - Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); - Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)); - try { - - byte[] payload = record.getPayload(); - - /* - * payload[0] contains the "Status Byte Encodings" field, per - * the NFC Forum "Text Record Type Definition" section 3.2.1. - * - * bit7 is the Text Encoding Field. - * - * if (Bit_7 == 0): The text is encoded in UTF-8 - * if (Bit_7 == 1): The text is encoded in UTF16 - * - * Bit_6 is reserved for future use and must be set to zero. - * - * Bits 5 to 0 are the length of the IANA language code. - */ - - String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16"; - int languageCodeLength = payload[0] & 0077; - - return new String(payload, - languageCodeLength + 1, - payload.length - languageCodeLength - 1, - textEncoding); - } catch (UnsupportedEncodingException e) { - // should never happen unless we get a malformed tag. - throw new IllegalArgumentException(e); - } - } - - public static boolean isText(NdefRecord record) { - try { - toText(record); - return true; - } catch (IllegalArgumentException e) { - return false; - } - } - - public static Iterable getTextFields(NdefMessage message) { - return Iterables.filter(getObjects(message), String.class); - } - - public static Iterable getUris(NdefMessage message) { - return Iterables.filter(getObjects(message), Uri.class); + public static Iterable getUris(NdefMessage message) { + return Iterables.filter(getObjects(message), UriRecord.class); } /** @@ -214,15 +77,15 @@ public class NdefUtil { *

* TODO: Is this API too generic? Should we keep it? */ - public static Iterable getObjects(NdefMessage message) { - List retval = new ArrayList(); + public static Iterable getObjects(NdefMessage message) { + List retval = new ArrayList(); for (NdefRecord record : message.getRecords()) { - if (isUri(record)) { - retval.add(toUri(record)); - } else if (isText(record)) { - retval.add(toText(record)); + if (UriRecord.isUri(record)) { + retval.add(UriRecord.parse(record)); + } else if (TextRecord.isText(record)) { + retval.add(TextRecord.parse(record)); } else if (SmartPoster.isPoster(record)) { - retval.add(SmartPoster.from(record)); + retval.add(SmartPoster.parse(record)); } } return retval; diff --git a/apps/Tag/src/com/android/apps/tag/TagAdapter.java b/apps/Tag/src/com/android/apps/tag/TagAdapter.java index f69c3ea4a..04bc46aa4 100644 --- a/apps/Tag/src/com/android/apps/tag/TagAdapter.java +++ b/apps/Tag/src/com/android/apps/tag/TagAdapter.java @@ -32,6 +32,9 @@ import android.view.ViewGroup; import android.widget.Adapter; import android.widget.CursorAdapter; import android.widget.TextView; +import com.android.apps.tag.record.SmartPoster; +import com.android.apps.tag.record.TextRecord; +import com.android.apps.tag.record.UriRecord; /** * A custom {@link Adapter} that renders tag entries for a list. @@ -61,14 +64,17 @@ public class TagAdapter extends CursorAdapter { mainLine.setText("Invalid tag"); } else { try { - SmartPoster poster = SmartPoster.from(msg.getRecords()[0]); - mainLine.setText(poster.getTitle()); + SmartPoster poster = SmartPoster.parse(msg.getRecords()[0]); + TextRecord title = poster.getTitle(); + if (title != null) { + mainLine.setText(title.getText()); + } } catch (IllegalArgumentException e) { // Not a smart poster NdefRecord record = msg.getRecords()[0]; Uri uri = null; try { - uri = NdefUtil.toUri(record); + uri = UriRecord.parse(record).getUri(); mainLine.setText(uri.toString()); } catch (IllegalArgumentException e2) { mainLine.setText("Not a smart poster or URL"); diff --git a/apps/Tag/src/com/android/apps/tag/TagViewer.java b/apps/Tag/src/com/android/apps/tag/TagViewer.java index 21f64e161..aac93c6d1 100644 --- a/apps/Tag/src/com/android/apps/tag/TagViewer.java +++ b/apps/Tag/src/com/android/apps/tag/TagViewer.java @@ -20,7 +20,6 @@ import android.app.Activity; import android.content.Intent; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; -import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefTag; import android.nfc.NfcAdapter; @@ -31,6 +30,10 @@ import android.view.LayoutInflater; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.apps.tag.record.ParsedNdefRecord; +import com.android.apps.tag.record.SmartPoster; +import com.android.apps.tag.record.TextRecord; +import com.android.apps.tag.record.UriRecord; /** * An {@link Activity} which handles a broadcast of a new tag that the device just discovered. @@ -92,25 +95,30 @@ public class TagViewer extends Activity { // Build the views from the logical records in the messages boolean first = true; for (NdefMessage msg : msgs) { - Iterable objects = NdefUtil.getObjects(msg); - for (Object object : objects) { + Iterable objects = NdefUtil.getObjects(msg); + for (ParsedNdefRecord object : objects) { if (!first) { list.addView(inflater.inflate(R.layout.tag_divider, list, false)); first = false; } - if (object instanceof String) { + if (object instanceof TextRecord) { + TextRecord textRecord = (TextRecord) object; TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); - text.setText((CharSequence) object); + text.setText(textRecord.getText()); list.addView(text); - } else if (object instanceof Uri) { + } else if (object instanceof UriRecord) { + UriRecord uriRecord = (UriRecord) object; TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); - text.setText(object.toString()); + text.setText(uriRecord.getUri().toString()); list.addView(text); } else if (object instanceof SmartPoster) { TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false); SmartPoster poster = (SmartPoster) object; - text.setText(poster.getTitle()); + TextRecord title = poster.getTitle(); + if (title != null) { + text.setText(title.getText()); + } list.addView(text); } } diff --git a/apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java b/apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java new file mode 100644 index 000000000..8de4a3f23 --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/record/ParsedNdefRecord.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2010 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.apps.tag.record; + +/** + * TODO: come up with a better name. + */ +public interface ParsedNdefRecord { + + // Just a placeholder for now. Probably not needed nor desired. + public String getRecordType(); +} diff --git a/apps/Tag/src/com/android/apps/tag/SmartPoster.java b/apps/Tag/src/com/android/apps/tag/record/SmartPoster.java similarity index 77% rename from apps/Tag/src/com/android/apps/tag/SmartPoster.java rename to apps/Tag/src/com/android/apps/tag/record/SmartPoster.java index dd6f518fb..54ac42405 100644 --- a/apps/Tag/src/com/android/apps/tag/SmartPoster.java +++ b/apps/Tag/src/com/android/apps/tag/record/SmartPoster.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.apps.tag; +package com.android.apps.tag.record; +import com.android.apps.tag.NdefUtil; import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; -import android.net.Uri; import android.nfc.FormatException; import android.nfc.NdefMessage; import android.nfc.NdefRecord; @@ -31,7 +31,7 @@ import javax.annotation.Nullable; /** * A representation of an NFC Forum "Smart Poster". */ -public class SmartPoster { +public class SmartPoster implements ParsedNdefRecord { /** * NFC Forum Smart Poster Record Type Definition section 3.2.1. @@ -41,7 +41,7 @@ public class SmartPoster { * This record is optional." */ - private final String mTitleRecord; + private final TextRecord mTitleRecord; /** * NFC Forum Smart Poster Record Type Definition section 3.2.1. @@ -50,32 +50,32 @@ public class SmartPoster { * records are just metadata about this record. There MUST be one URI * record and there MUST NOT be more than one." */ - private final Uri mUriRecord; + private final UriRecord mUriRecord; - private SmartPoster(Uri uri, @Nullable String title) { + private SmartPoster(UriRecord uri, @Nullable TextRecord title) { mUriRecord = Preconditions.checkNotNull(uri); mTitleRecord = title; } - public Uri getUri() { + public UriRecord getUriRecord() { return mUriRecord; } /** * Returns the title of the smart poster. This may be {@code null}. */ - public String getTitle() { + public TextRecord getTitle() { return mTitleRecord; } - public static SmartPoster from(NdefRecord record) { + public static SmartPoster parse(NdefRecord record) { Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)); try { NdefMessage subRecords = new NdefMessage(record.getPayload()); - Uri uri = Iterables.getOnlyElement(NdefUtil.getUris(subRecords)); - Iterable textFields = NdefUtil.getTextFields(subRecords); - String title = null; + UriRecord uri = Iterables.getOnlyElement(NdefUtil.getUris(subRecords)); + Iterable textFields = NdefUtil.getTextFields(subRecords); + TextRecord title = null; if (!Iterables.isEmpty(textFields)) { title = Iterables.get(textFields, 0); } @@ -88,10 +88,15 @@ public class SmartPoster { public static boolean isPoster(NdefRecord record) { try { - from(record); + parse(record); return true; } catch (IllegalArgumentException e) { return false; } } + + @Override + public String getRecordType() { + return "SmartPoster"; + } } diff --git a/apps/Tag/src/com/android/apps/tag/record/TextRecord.java b/apps/Tag/src/com/android/apps/tag/record/TextRecord.java new file mode 100644 index 000000000..29d380d85 --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/record/TextRecord.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2010 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.apps.tag.record; + +import android.nfc.NdefRecord; +import com.google.common.base.Preconditions; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; + +/** + * An NFC Text Record + */ +public class TextRecord implements ParsedNdefRecord { + + private final String mLanguageCode; + private final String mText; + + private TextRecord(String languageCode, String text) { + mLanguageCode = Preconditions.checkNotNull(languageCode); + mText = Preconditions.checkNotNull(text); + } + + @Override + public String getRecordType() { + return "Text"; + } + + public String getText() { + return mText; + } + + public String getLanguageCode() { + return mLanguageCode; + } + + // TODO: deal with text fields which span multiple NdefRecords + public static TextRecord parse(NdefRecord record) { + Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); + Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT)); + try { + + byte[] payload = record.getPayload(); + + /* + * payload[0] contains the "Status Byte Encodings" field, per + * the NFC Forum "Text Record Type Definition" section 3.2.1. + * + * bit7 is the Text Encoding Field. + * + * if (Bit_7 == 0): The text is encoded in UTF-8 + * if (Bit_7 == 1): The text is encoded in UTF16 + * + * Bit_6 is reserved for future use and must be set to zero. + * + * Bits 5 to 0 are the length of the IANA language code. + */ + + String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16"; + int languageCodeLength = payload[0] & 0077; + + String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII"); + String text = new String(payload, + languageCodeLength + 1, + payload.length - languageCodeLength - 1, + textEncoding); + return new TextRecord(languageCode, text); + + } catch (UnsupportedEncodingException e) { + // should never happen unless we get a malformed tag. + throw new IllegalArgumentException(e); + } + } + + public static boolean isText(NdefRecord record) { + try { + parse(record); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/apps/Tag/src/com/android/apps/tag/record/UriRecord.java b/apps/Tag/src/com/android/apps/tag/record/UriRecord.java new file mode 100644 index 000000000..eeba30bd8 --- /dev/null +++ b/apps/Tag/src/com/android/apps/tag/record/UriRecord.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2010 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.apps.tag.record; + +import android.net.Uri; +import android.nfc.NdefRecord; +import com.google.common.base.Preconditions; +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.primitives.Bytes; + +import java.nio.charset.Charsets; +import java.util.Arrays; + +/** + * A parsed record containing a Uri. + */ +public class UriRecord implements ParsedNdefRecord { + private static final byte[] EMPTY = new byte[0]; + + /** + * NFC Forum "URI Record Type Definition" + * + * This is a mapping of "URI Identifier Codes" to URI string prefixes, + * per section 3.2.2 of the NFC Forum URI Record Type Definition document. + */ + private static final BiMap URI_PREFIX_MAP = ImmutableBiMap.builder() + .put((byte) 0x00, "") + .put((byte) 0x01, "http://www.") + .put((byte) 0x02, "https://www.") + .put((byte) 0x03, "http://") + .put((byte) 0x04, "https://") + .put((byte) 0x05, "tel:") + .put((byte) 0x06, "mailto:") + .put((byte) 0x07, "ftp://anonymous:anonymous@") + .put((byte) 0x08, "ftp://ftp.") + .put((byte) 0x09, "ftps://") + .put((byte) 0x0A, "sftp://") + .put((byte) 0x0B, "smb://") + .put((byte) 0x0C, "nfs://") + .put((byte) 0x0D, "ftp://") + .put((byte) 0x0E, "dav://") + .put((byte) 0x0F, "news:") + .put((byte) 0x10, "telnet://") + .put((byte) 0x11, "imap:") + .put((byte) 0x12, "rtsp://") + .put((byte) 0x13, "urn:") + .put((byte) 0x14, "pop:") + .put((byte) 0x15, "sip:") + .put((byte) 0x16, "sips:") + .put((byte) 0x17, "tftp:") + .put((byte) 0x18, "btspp://") + .put((byte) 0x19, "btl2cap://") + .put((byte) 0x1A, "btgoep://") + .put((byte) 0x1B, "tcpobex://") + .put((byte) 0x1C, "irdaobex://") + .put((byte) 0x1D, "file://") + .put((byte) 0x1E, "urn:epc:id:") + .put((byte) 0x1F, "urn:epc:tag:") + .put((byte) 0x20, "urn:epc:pat:") + .put((byte) 0x21, "urn:epc:raw:") + .put((byte) 0x22, "urn:epc:") + .put((byte) 0x23, "urn:nfc:") + .build(); + + private final Uri mUri; + + private UriRecord(Uri uri) { + this.mUri = Preconditions.checkNotNull(uri); + } + + @Override + public String getRecordType() { + return "Uri"; + } + + public Uri getUri() { + return mUri; + } + + /** + * Convert {@link android.nfc.NdefRecord} into a {@link android.net.Uri}. + * + * TODO: This class does not handle NdefRecords where the TNF + * (Type Name Format) of the class is {@link android.nfc.NdefRecord#TNF_ABSOLUTE_URI}. + * This should be fixed. + * + * @throws IllegalArgumentException if the NdefRecord is not a + * record containing a URI. + */ + public static UriRecord parse(NdefRecord record) { + Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN); + Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI)); + + byte[] payload = record.getPayload(); + + /* + * payload[0] contains the URI Identifier Code, per the + * NFC Forum "URI Record Type Definition" section 3.2.2. + * + * payload[1]...payload[payload.length - 1] contains the rest of + * the URI. + */ + + String prefix = URI_PREFIX_MAP.get(payload[0]); + byte[] fullUri = Bytes.concat( + prefix.getBytes(Charsets.UTF_8), + Arrays.copyOfRange(payload, 1, payload.length)); + + return new UriRecord(Uri.parse(new String(fullUri, Charsets.UTF_8))); + } + + public static boolean isUri(NdefRecord record) { + try { + parse(record); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/apps/Tag/tests/src/com/android/apps/tag/NdefUtilTest.java b/apps/Tag/tests/src/com/android/apps/tag/NdefUtilTest.java index 12baf6f9c..24070ae12 100644 --- a/apps/Tag/tests/src/com/android/apps/tag/NdefUtilTest.java +++ b/apps/Tag/tests/src/com/android/apps/tag/NdefUtilTest.java @@ -17,6 +17,7 @@ package com.android.apps.tag; import android.test.AndroidTestCase; +import com.android.apps.tag.record.TextRecord; import com.google.common.primitives.Bytes; import android.nfc.NdefRecord; @@ -46,6 +47,6 @@ public class NdefUtilTest extends AndroidTestCase { ); NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data); - assertEquals(word, NdefUtil.toText(record)); + assertEquals(word, TextRecord.parse(record).getText()); } } diff --git a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java index 01ac817c4..8225197ce 100644 --- a/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java +++ b/apps/Tag/tests/src/com/android/apps/tag/SmartPosterTest.java @@ -18,6 +18,7 @@ package com.android.apps.tag; import android.test.AndroidTestCase; import android.nfc.NdefMessage; +import com.android.apps.tag.record.SmartPoster; /** * Tests for {@link SmartPoster}. @@ -26,8 +27,8 @@ public class SmartPosterTest extends AndroidTestCase { public void testSmartPoster() throws Exception { NdefMessage msg = new NdefMessage(MockNdefMessages.REAL_NFC_MSG); - SmartPoster poster = SmartPoster.from(msg.getRecords()[0]); - assertEquals("NFC Forum Type 4 Tag", poster.getTitle()); - assertEquals("http://www.nxp.com/nfc", poster.getUri().toString()); + SmartPoster poster = SmartPoster.parse(msg.getRecords()[0]); + assertEquals("NFC Forum Type 4 Tag", poster.getTitle().getText()); + assertEquals("http://www.nxp.com/nfc", poster.getUriRecord().getUri().toString()); } }