因为PC/SC是Windows的体系,以系统API的层面服务应用。所以一直以来智能卡相关的读卡器和工具都集中在Windows上,而在unix体系下则一直水土不服。值得庆幸的是随着开源组织M.U.S.C.L.E (Movment for the Use of Smart in Linux Environment)的积极努力下,pcsclite作为Xnix下的PC/SC设备框架和应用接口已经成为了事实上的标准,Mac的Lion系统更是已经在发行版里面集成了此服务。下面以ubuntu 12.0.4 发行版为例子。
#首先安装pcsc的守护进程pcscd和工具
sudo apt-get -y install libusb-dev
sudo apt-get -y install pcscd
#然后安装支持pcsc的读卡器驱动(例子为内置的ACR ACS38U,其它读卡器也可以到网站下载安装)
sudo apt-get -y install libacr38u
#连接读卡器,插卡后运行扫描工具验证安装结果
pcsc_scan
结果如下:
PC/SC device scanner
V 1.4.18 (c) 2001-2011, Ludovic Rousseau <ludovic.rousseau@free.fr>
Compiled with PC/SC lite version: 1.7.4
Using reader plug'n play mechanism
Scanning present readers...
0: ACS ACR38U 00 00
Thu Sep 20 12:55:08 2012
Reader 0: ACS ACR38U 00 00
Card state: Card inserted, Shared Mode,
ATR: 3B 1D 94 42 72 6F 61 64 54 68 69 6E 6B 69 00 00
ATR: 3B 1D 94 42 72 6F 61 64 54 68 69 6E 6B 69 00 00
+ TS = 3B --> Direct Convention
+ T0 = 1D, Y(1): 0001, K: 13 (historical bytes)
TA(1) = 94 --> Fi=512, Di=8, 64 cycles/ETU
62500 bits/s at 4 MHz, fMax for Fi = 5 MHz => 78125 bits/s
+ Historical bytes: 42 72 6F 61 64 54 68 69 6E 6B 69 00 00
Category indicator byte: 42 (proprietary format)
Possibly identified card (using /usr/share/pcsc/smartcard_list.txt):
NONE
Your card is not present in the database.
You can get the latest version of the database from
http://ludovic.rousseau.free.fr/softwares/pcsc-tools/smartcard_list.txt
or use: wget http://ludovic.rousseau.free.fr/softwares/pcsc-tools/smartcard_list.txt --output-document=/home/caesarzou/.smartcard_list.txt
If your ATR is still not in the latest version then please send a mail
to <ludovic.rousseau@free.fr> containing:
#到此为止,PC/SC驱动已经打通,现在我们试试发个APDU
sudo apt-get -y install pcsc-tools
gscriptor
这是一个图形界面的工具,在Script框里面输入:
00A4040000
点Run按钮,可以看到连接提示,然后就是结果:
Beginning script execution...
Sending: 00 A4 04 00 00
Received: 6C 12
Wrong length Le: should be 0x12
Script was executed without error...
#恭喜你,卡片访问成功,现在你一定心痒难耐,想创建你自己的应用了吧?
#安装开发库
sudo apt-get install libpcsclite-dev
#安装eclipse的cdt作为开发环境
sudo apt-get -y install g++
sudo apt-get -y install eclipse eclipse-cdt
#打开eclipse,新建一个C工程,在c文件中加入
#include <PCSC/winscard.h>
#链接到pcsclite库:在C/C++ build / GCC Linker / Libraries 增加 pcsclite
#现在你会幸福的发现,winscard.h里面提供的类型定义和接口和windows是一致的,我们从windows中拷贝一段代码过来:
#include <stdio.h> #include <PCSC/winscard.h> int main(void) { SCARDCONTEXT m_hContext; SCARDHANDLE m_hCard; SCARD_IO_REQUEST io; char pmszReaders[100]; BYTE CAPDU[] = {0x00,0xA4,0x04,0x00,0x00}; BYTE RAPDU[256+2]; DWORD cch = 100; DWORD i = 0; //Insert if(SCARD_S_SUCCESS != SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &m_hContext)) { printf("Context error"); return -1; } //List Reader if(SCARD_S_SUCCESS != SCardListReaders(m_hContext, NULL, pmszReaders, &cch)) { printf("List Reader error"); return -2; } printf("List Readers\n"); while(i<cch) { printf("%s\n",pmszReaders+i); i += strlen(pmszReaders); i ++; } //Connect first Reader io.cbPciLength = sizeof(SCARD_IO_REQUEST); if(SCARD_S_SUCCESS != SCardConnect(m_hContext, pmszReaders, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0|SCARD_PROTOCOL_T1, &m_hCard, &io.dwProtocol)) { printf("Connect Card error"); return -3; } //Transmit APDU cch = 256+2; if(SCARD_S_SUCCESS != SCardTransmit(m_hCard, &io, CAPDU, 5, NULL, RAPDU, &cch)) { printf("Transmit APDU error"); return -4; } //echo printf("Transmit APDU\n"); printf("CAPDU: "); for(i=0;i<5;i++) { sprintf(pmszReaders,"%02X",CAPDU[i]); printf("%s",pmszReaders); } printf("\n"); printf("RAPDU: "); for(i=0;i<cch;i++) { sprintf(pmszReaders,"%02X",RAPDU[i]); printf("%s",pmszReaders); } printf("\n"); //DisConnect SCardDisconnect(m_hCard, SCARD_EJECT_CARD); //Eject SCardReleaseContext(m_hContext); //puts("!!!Hello World!!!"); /* prints !!!Hello World!!! */ return 0; }#编译运行,输出:
List Readers
ACS ACR38U 00 00
Transmit APDU
CAPDU: 00A4040000
RAPDU: 6C12
大功告成!下一个攻略我会讲一下在ubuntu上基于JavaCard环境和工具的配置,敬请期待。
注:
ubuntu上libpcsclite的头文件默认位置在/usr/include/PCSC中,多了个目录,有的版本在编译的时候可能有#include 文件错误,可以自行修改如下:
sudo vim /usr/include/PCSC/winscard.h
将#include <pcsclite.h>修改为#include <PCSC/pcsclite>,保存退出
sudo vim /usr/include/PCSC/pcsclite.h
将#include <wintypes.h>修改为#include <PCSC/wintypes.h>,保存推出
附件:
ubuntu上的eclipse工程:猛击下载
Mac上的xcode工程:猛击下载
源代码附上:
//全局变量 bool resumeDownload = false; //是否需要下载的标记位 long downloadFileLenth = 0; //需要下载的总大小, 远程文件的大小
/* 得到本地文件大小的函数, 若不是续传则返回0, 否则返回指定路径地址的文件大小 */ long getLocalFileLenth(const char* localPath){ if (!resumeDownload){ return 0; } return fs_open(localPath).fs_size(); }
/* 得到远程文件的大小, 要下载的文件大小 */ long getDownloadFileLenth(const char *url){ long downloadFileLenth = 0; CURL *handle = curl_easy_init(); curl_easy_setopt(handle, CURLOPT_URL, url); curl_easy_setopt(handle, CURLOPT_HEADER, 1); //只需要header头 curl_easy_setopt(handle, CURLOPT_NOBODY, 1); //不需要body if (curl_easy_perform(handle) == CURLE_OK) { curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLenth); } else { downloadFileLenth = -1; } return downloadFileLenth; }
/* scomoDownload回调的计算进度条的函数 */ void getProgressValue(const char* localSize, double dt, double dn, double ult, double uln){ double showTotal, showNow; showTotal = downloadFileLenth; int localNow = atoi (localSize.c_str()); showNow = localNow + dn; showProgressBar(showTotal, showNow); }
/* 直接进行下载的函数 */ public CurlCode scomoDownload(long timeout) { long localFileLenth = getLocalFileLenth(); const char *localFileLenthStr; sprint(localFileLenthStr, %ld, localFileLenth); curl_easy_setopt(handle, CURLOPT_URL, mUrl); curl_easy_setopt(handle, CURLOPT_HEADER, 0); curl_easy_setopt(handle, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(handle, CURLOPT_CONNECTIONTIMEOUT, 0); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &writeDataCallback); curl_easy_setopt(handle, CURLOPT_WRITEDATA, this); curl_easy_setopt(handle, CURLOPT_RESUME_FROM_LARGE, localFileLenth); curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0); curl_easy_setopt(handle, CURLOPT_ PROGRESSFUNCTION, getProgressValue); curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, localFileLenthStr); if (curl_easy_perform) { resumeDownload = true; return DS_FAILED; } else { resumeDownload = false; return DS_FINISHED; } }
/* downloadControl函数用来控制整个下载过程的节奏, 控制下载的次数, 每次等待的时间等 */ public void downloadControler(){ downloadFileLenth = getDownloadFileLenth(); //下载前得到要下载的文件大小赋值给全局变量 int times = 605; //600次*50ms=5分钟, 以此确保5分钟内的重试次数, 而5次是正常下载的中断次数, 意思即是5次内能正常完成下载. int count = 0; int timeout = 30; DSTATUS dstatus = DS_FAILED; while (count++ < times){ status = scomoDownload(timeout); if (dstatus == DS_FINISHED){ break; } Thread.sleep(500); //每次下载中间间隔500毫秒 } resumeDownload = false; //不管下载成功或失败, 完成while循环后将标志回位 if (dstaus == DS_FINISHED) { updateApp(); //执行软件安装的操作… } SAFE_DELETE(localFile); //流程最后要确保本地文件删除 }
resumeDownload是一个非常重要的标记位, 主要用来标识是否需要续传下载, 在初始化时为false, 在下载完成后也应回位成false, 下载过程中若因时间中断未下载完成也为false.
处理下载中掉电后续传也需要这个标记位, 在程序启动时, 进行检测, 若上次没下载完, 修改标志位为true, 然后调用下载入口函数downloadController:
if (scomo_status == 30){
resumeDownload = true;
downloadController();
}
若下载环境正常, 1个小时内可以完成的下载可以直接使用此方案来下载, 不用修改控制, 但若是超过1小时的下载, 需要将本方案进行改进. 基本上就是将605那里分开判断600+x, 其中600为每次断网后应重试的次数, x为正常下载应该进行的计数, 分别计算即可.
微信聊天窗口的信息效果类似iphone上的短信效果,以气泡的形式展现,在Android上,实现这种效果主要用到ListView和BaseAdapter,配合布局以及相关素材,就可以自己做出这个效果,素材可以下一个微信的APK,然后把后缀名改成zip,直接解压,就可以得到微信里面的所有素材了。首先看一下我实现的效果:
以下是工程目录结构:
接下来就是如何实现这个效果的代码:
main.xml,这个是主布局文件,显示listview和上下两部分内容。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#f0f0e0" > <RelativeLayout android:id="@+id/rl_top" android:layout_width="fill_parent" android:layout_alignParentTop="true" android:layout_height="wrap_content"> <TextView android:layout_width="fill_parent" android:layout_height="44dp" android:gravity="center" android:textSize="18sp" android:background="#486a9a" android:textColor="@android:color/white" android:text="Chat"/> </RelativeLayout> <RelativeLayout android:id="@+id/rl_bottom" android:layout_alignParentBottom="true" android:layout_width="fill_parent" android:background="#486a9a" android:paddingTop="5dp" android:layout_height="wrap_content"> <Button android:id="@+id/btn_send" android:layout_width="70dp" android:layout_height="50dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:text="Send" /> <EditText android:id="@+id/et_content" android:layout_width="fill_parent" android:layout_height="50dp" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_toLeftOf="@id/btn_send" android:textSize="16sp"/> </RelativeLayout> <ListView android:id="@+id/listview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_above="@id/rl_bottom" android:layout_below="@id/rl_top" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="10dp" android:cacheColorHint="#00000000" android:divider="@null" android:listSelector="#00000000" android:dividerHeight="3dp" android:scrollbars="none"/> </RelativeLayout>
然后就是listview中两种类型item的布局文件,分别是接收信息的item效果和发送信息的item效果
chat_from_item.xml是接收信息的item布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:orientation="vertical" android:paddingBottom="5dp" android:layout_height="wrap_content" > <TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:background="#bfbfbf" android:paddingTop="2dp" android:paddingBottom="2dp" android:paddingLeft="4dp" android:paddingRight="4dp" android:textColor="#ffffff" android:textSize="12sp" /> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" > <ImageView android:id="@+id/iv_user_image" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:background="@drawable/mypic" android:focusable="false" /> <TextView android:id="@+id/tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_toRightOf="@+id/iv_user_image" android:background="@drawable/chatfrom_bg" android:gravity="left|center" android:clickable="true" android:focusable="true" android:lineSpacingExtra="2dp" android:minHeight="50dp" android:textColor="#ff000000" android:textSize="14sp" /> </RelativeLayout> </LinearLayout>
chat_to_item.xml是发送信息item的布局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:orientation="vertical" android:paddingBottom="5dp" android:layout_height="wrap_content" > <TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="#bfbfbf" android:layout_gravity="center_horizontal" android:paddingTop="2dp" android:paddingBottom="2dp" android:paddingLeft="4dp" android:paddingRight="4dp" android:textColor="#ffffff" android:textSize="12sp" /> <RelativeLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" > <ImageView android:id="@+id/iv_user_image" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:background="@drawable/mypic" android:focusable="false" /> <TextView android:id="@+id/tv_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="5dp" android:layout_toLeftOf="@+id/iv_user_image" android:background="@drawable/chatto_bg" android:gravity="left|center" android:clickable="true" android:focusable="true" android:lineSpacingExtra="2dp" android:textColor="#ff000000" android:textSize="14sp" /> </RelativeLayout> </LinearLayout>
布局完成后新建一个实体类ChatEntity.java:
public class ChatEntity { private int userImage; private String content; private String chatTime; private boolean isComeMsg; public int getUserImage() { return userImage; } public void setUserImage(int userImage) { this.userImage = userImage; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getChatTime() { return chatTime; } public void setChatTime(String chatTime) { this.chatTime = chatTime; } public boolean isComeMsg() { return isComeMsg; } public void setComeMsg(boolean isComeMsg) { this.isComeMsg = isComeMsg; } }
最后就是主Activity,这里面包括了自己写的BaseAdapter:
public class ChatDemoActivity extends Activity { private Button sendButton = null; private EditText contentEditText = null; private ListView chatListView = null; private List<ChatEntity> chatList = null; private ChatAdapter chatAdapter = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); contentEditText = (EditText) this.findViewById(R.id.et_content); sendButton = (Button) this.findViewById(R.id.btn_send); chatListView = (ListView) this.findViewById(R.id.listview); chatList = new ArrayList<ChatEntity>(); ChatEntity chatEntity = null; for (int i = 0; i < 2; i++) { chatEntity = new ChatEntity(); if (i % 2 == 0) { chatEntity.setComeMsg(false); chatEntity.setContent("Hello"); chatEntity.setChatTime("2012-09-20 15:12:32"); }else { chatEntity.setComeMsg(true); chatEntity.setContent("Hello,nice to meet you!"); chatEntity.setChatTime("2012-09-20 15:13:32"); } chatList.add(chatEntity); } chatAdapter = new ChatAdapter(this,chatList); chatListView.setAdapter(chatAdapter); sendButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (!contentEditText.getText().toString().equals("")) { //发送消息 send(); }else { Toast.makeText(ChatDemoActivity.this, "Content is empty", Toast.LENGTH_SHORT).show(); } } }); } private void send(){ ChatEntity chatEntity = new ChatEntity(); chatEntity.setChatTime("2012-09-20 15:16:34"); chatEntity.setContent(contentEditText.getText().toString()); chatEntity.setComeMsg(false); chatList.add(chatEntity); chatAdapter.notifyDataSetChanged(); chatListView.setSelection(chatList.size() - 1); contentEditText.setText(""); } private class ChatAdapter extends BaseAdapter{ private Context context = null; private List<ChatEntity> chatList = null; private LayoutInflater inflater = null; private int COME_MSG = 0; private int TO_MSG = 1; public ChatAdapter(Context context,List<ChatEntity> chatList){ this.context = context; this.chatList = chatList; inflater = LayoutInflater.from(this.context); } @Override public int getCount() { return chatList.size(); } @Override public Object getItem(int position) { return chatList.get(position); } @Override public long getItemId(int position) { return position; } @Override public int getItemViewType(int position) { // 区别两种view的类型,标注两个不同的变量来分别表示各自的类型 ChatEntity entity = chatList.get(position); if (entity.isComeMsg()) { return COME_MSG; }else{ return TO_MSG; } } @Override public int getViewTypeCount() { // 这个方法默认返回1,如果希望listview的item都是一样的就返回1,我们这里有两种风格,返回2 return 2; } @Override public View getView(int position, View convertView, ViewGroup parent) { ChatHolder chatHolder = null; if (convertView == null) { chatHolder = new ChatHolder(); if (chatList.get(position).isComeMsg()) { convertView = inflater.inflate(R.layout.chat_from_item, null); }else { convertView = inflater.inflate(R.layout.chat_to_item, null); } chatHolder.timeTextView = (TextView) convertView.findViewById(R.id.tv_time); chatHolder.contentTextView = (TextView) convertView.findViewById(R.id.tv_content); chatHolder.userImageView = (ImageView) convertView.findViewById(R.id.iv_user_image); convertView.setTag(chatHolder); }else { chatHolder = (ChatHolder)convertView.getTag(); } chatHolder.timeTextView.setText(chatList.get(position).getChatTime()); chatHolder.contentTextView.setText(chatList.get(position).getContent()); chatHolder.userImageView.setImageResource(chatList.get(position).getUserImage()); return convertView; } private class ChatHolder{ private TextView timeTextView; private ImageView userImageView; private TextView contentTextView; } } }
欢迎关注我的新浪微博和我交流:@唐韧_Ryan