原料:Android 帶NFC功能手機、M1卡
怕你們沒耐心先上demo
1.在AndroidManifest中添權重限控制
activity中需要添加
android:resource="@xml/tag_type" />
需要使用前台排程的話,就需要在中添加需要過濾的标簽,這裡使用自定義過濾
2.開始編寫代碼
最主要的是在OnNewIntent中添加對卡片控制的代碼。主要的流程大概是這樣滴:
1.判斷是否支援NFC、是否打開NFC
2.通過Intent擷取卡類型,進行類型判斷
3.獲得Adapter對象、獲得Tag對象、獲得MifareClassic對象
4.讀寫卡操作分為兩個步驟:密碼校驗、讀寫卡。隻有對目前要讀寫的塊資料所在的扇區進行密碼校驗之後才能進行接下來的讀寫操作
public class NfcActivity extends AppCompatActivity {
private NfcAdapter mNfcAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNfcAdapter = M1CardUtils.isNfcAble(this);
M1CardUtils.setPendingIntent(PendingIntent.getActivity(this, 0, new Intent(this,
getClass()), 0));
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
mNfcAdapter = M1CardUtils.isNfcAble(this);
M1CardUtils.setPendingIntent(PendingIntent.getActivity(this, 0, new Intent(this,
getClass()), 0));
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
M1CardUtils.isMifareClassic(tag,this);
try {
if (M1CardUtils.writeBlock(tag, 25,"9966332211445566".getBytes())){
Log.e("onNewIntent","寫入成功");
} else {
Log.e("onNewIntent","寫入失敗");
}
} catch (IOException e) {
e.printStackTrace();
}
try {
M1CardUtils.readCard(tag);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onPause() {
super.onPause();
if (mNfcAdapter != null) {
mNfcAdapter.disableForegroundDispatch(this);
}
}
@Override
public void onResume() {
super.onResume();
if (mNfcAdapter != null) {
mNfcAdapter.enableForegroundDispatch(this, M1CardUtils.getPendingIntent(),
null, null);
}
}
}
下面是抽離的工具類
public class M1CardUtils {
private static PendingIntent pendingIntent;
public static PendingIntent getPendingIntent(){
return pendingIntent;
}
public static void setPendingIntent(PendingIntent pendingIntent){
M1CardUtils.pendingIntent = pendingIntent;
}
public static NfcAdapter isNfcAble(Activity mContext){
NfcAdapter mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext);
if (mNfcAdapter == null) {
Toast.makeText(mContext, "裝置不支援NFC!", Toast.LENGTH_LONG).show();
}
if (!mNfcAdapter.isEnabled()) {
Toast.makeText(mContext, "請在系統設定中先啟用NFC功能!", Toast.LENGTH_LONG).show();
}
return mNfcAdapter;
}
public static boolean isMifareClassic(Tag tag,Activity activity){
String[] techList = tag.getTechList();
boolean haveMifareUltralight = false;
for (String tech : techList) {
if (tech.contains("MifareClassic")) {
haveMifareUltralight = true;
break;
}
}
if (!haveMifareUltralight) {
Toast.makeText(activity, "不支援MifareClassic", Toast.LENGTH_LONG).show();
return false;
}
return true;
}
public static String[][] readCard(Tag tag) throws IOException{
MifareClassic mifareClassic = MifareClassic.get(tag);
try {
mifareClassic.connect();
String[][] metaInfo = new String[16][4];
// 擷取TAG中包含的扇區數
int sectorCount = mifareClassic.getSectorCount();
for (int j = 0; j < sectorCount; j++) {
int bCount;//目前扇區的塊數
int bIndex;//目前扇區第一塊
if (m1Auth(mifareClassic,j)) {
bCount = mifareClassic.getBlockCountInSector(j);
bIndex = mifareClassic.sectorToBlock(j);
for (int i = 0; i < bCount; i++) {
byte[] data = mifareClassic.readBlock(bIndex);
String dataString = bytesToHexString(data);
metaInfo[j][i] = dataString;
Log.e("擷取到資訊",dataString);
bIndex++;
}
} else {
Log.e("readCard","密碼校驗失敗");
}
}
return metaInfo;
} catch (IOException e){
throw new IOException(e);
} finally {
try {
mifareClassic.close();
}catch (IOException e){
throw new IOException(e);
}
}
}
public static boolean writeBlock(Tag tag, int block, byte[] blockbyte) throws IOException {
MifareClassic mifareClassic = MifareClassic.get(tag);
try {
mifareClassic.connect();
if (m1Auth(mifareClassic,block/4)) {
mifareClassic.writeBlock(block, blockbyte);
Log.e("writeBlock","寫入成功");
} else {
Log.e("密碼是", "沒有找到密碼");
return false;
}
} catch (IOException e){
throw new IOException(e);
} finally {
try {
mifareClassic.close();
}catch (IOException e){
throw new IOException(e);
}
}
return true;
}
public static boolean m1Auth(MifareClassic mTag,int position) throws IOException {
if (mTag.authenticateSectorWithKeyA(position, MifareClassic.KEY_DEFAULT)) {
return true;
} else if (mTag.authenticateSectorWithKeyB(position, MifareClassic.KEY_DEFAULT)) {
return true;
}
return false;
}
private static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
char[] buffer = new char[2];
for (int i = 0; i < src.length; i++) {
buffer[0] = Character.forDigit((src[i] >>> 4) & 0x0F, 16);
buffer[1] = Character.forDigit(src[i] & 0x0F, 16);
System.out.println(buffer);
stringBuilder.append(buffer);
}
return stringBuilder.toString();
}
}
遇坑指南
1.鄙人所用的M1卡資料一個塊為16位元組,卡資料存儲的是16進制的byte數組。讀取的時候要将16進制byte數組轉換為10進制的;寫卡的時候要進行轉換為16進制的byte數組,而且資料必須為16位元組
2.第3塊一般不進行資料存儲(0、1、2、3塊)
3.一般來說第0個扇區的第0塊為卡商初始化資料,不能進行寫操作
4.要關注Activity的聲明周期。onNewIntent中要進行掃描卡片的處理,onResume要禁止前台卡片活動的排程處理, onPause要啟用前台卡片活動的排程處理。
5.要修改密鑰需要先校驗密鑰之後修改控制位資料、密鑰資料。