8.3.2 NFC系统模块

本节开始时介绍,Android平台中,NFC系统模块运行在com.android.nfc进程中,该进程对应的应用程序文件名为Nfc.apk。NFC系统模块包含的组件非常多,所以通过以下几条分析路线来介绍。

·NFC系统模块的核心NfcService和一些重要成员的作用及之间的关系。

·R/W模式下NFC Tag的处理。

·Android Beam的实现。

·CE模式相关的处理。

1.NfcService介绍

Nfc.apk源码中包含一个NfcApplication类。当该应用启动时,NfcApplication的onCreate函数将被调用。正是在这个onCreate函数中,NFC系统模块的核心成员NfcService得以创建。我们直接来看NfcService的构造函数。

[—>NfcService.java::NfcService]

  1. public NfcService(Application nfcApplication) {
  2. // NFC系统模块重要成员
  3. mNfcTagService = new TagService();// TagService用于和NFC Tag交互
  4. // NfcAdapterService用于和Android系统中其他使用NfcService的客户端交互
  5. mNfcAdapter = new NfcAdapterService();
  6. // NfcAdapterExtrasService用于和Android系统中使用Card Emulation模式的客户端交互
  7. mExtrasService = new NfcAdapterExtrasService();
  8. sService = this; mContext = nfcApplication;
  9. // NativeNfcManager由dhimpl模块实现,用于和具体芯片厂商提供的NFC模块交互
  10. mDeviceHost = new NativeNfcManager(mContext, this);
  11. // HandoverManager处理Connection Handover工作
  12. HandoverManager handoverManager = new HandoverManager(mContext);
  13. // NfcDispatcher用于向客户端派发NFC Tag相关的通知
  14. mNfcDispatcher = new NfcDispatcher(mContext, handoverManager);
  15. // P2pLinkManager用于处理LLCP相关的工作
  16. mP2pLinkManager = new P2pLinkManager(mContext, handoverManager,
  17. mDeviceHost.getDefaultLlcpMiu(),
  18. mDeviceHost.getDefaultLlcpRwSize());
  19. // NativeNfcSecureElement用于和SE交互,它也由dhimpl模块实现
  20. mSecureElement = new NativeNfcSecureElement(mContext);
  21. mEeRoutingState = ROUTE_OFF;
  22. /*
  23. NfceeAccessControl用于判断哪些应用程序有权限操作NFCEE。它将读取/etc/nfcee_access.xml文件的
  24. 内容。nfcee_access.xml内容比较简单,请参考Nfc目录下的etc/sample_nfcee_access.xml来学习。
  25. */
  26. mNfceeAccessControl = new NfceeAccessControl(mContext);
  27. ......
  28. // 向系统注册一个“nfc”服务。注意,SERVICE_NAME的值为“nfc”。该服务对应的对象为mNfcAdapter
  29. ServiceManager.addService(SERVICE_NAME, mNfcAdapter);
  30. /*
  31. 注册广播事件监听对象。NfcService对屏幕状态、应用程序安装和卸载等广播事件感兴趣。这部分内容请读者
  32. 自行研究。
  33. */
  34. ......
  35. // EnableDisableTask为一个AsyncTask,TASK_BOOT用于NfcService其他初始化工作
  36. new EnableDisableTask().execute(TASK_BOOT);
  37. }

由上述代码可知,NfcService在其构造函数中,首先创建了NFC系统模块的几个核心成员。下文将详细介绍它们的作用及之间的关系。NfcService向Binder系统添加了一个名为"nfc"的服务,该服务对应的Binder对象为mNfcAdapter,类型为NfcAdapter。通过一个AysncTask(代码中的EnableDisableTask)完成NfcService其他初始化工作。

下面马上来看NFC系统模块核心成员。

(1)NfcService核心成员

图8-35所示为NfcAdapter、TagService等相关成员的类信息。

8.3.2 NFC系统模块 - 图1

图8-35 NfcAdapter、TagService及相关类成员结构图

图8-35中,NfcAdapter包含一个类型为INfcAdapter的sService成员变量,该变量通过Android Binder机制来和NfcService内部类NfcAdapter的实例(即上述代码中的mAdapter)交互。NfcService内部的NfcAdapter对象即是注册到Android系统服务中的那个名为"nfc"的Binder服务端对象。

NfcAdapter还包含一个类型为INfcTag的sTagService成员变量,该变量通过Android Binder机制来和NfcService内部类TagService的实例(即上述代码中的mNfcTagService)交互。INfcTag接口封装了对Tag操作相关的函数。注意,前面所示的Nfc客户端示例中并没有直接使用INfcTag接口的地方,但表8-12所列的各种Tech类内部需要通过ITag接口来操作NFC Tag。

NfcAdapterExtras包含一个类型为INfcAdapterExtras的sService成员变量,也通过Android Binder机制来和NfcService内部类NfcAdapterService的实例(即上述代码中的mExtrasService)交互。

接着来看NfcService和NativeNfcManager,它们的类家族如图8-36所示。

8.3.2 NFC系统模块 - 图2

图8-36 NativeNfcManager和NfcService类家族

Android NFC系统模块通过接口类DeviceHost和其内部的接口类LlcpServerSocket、DeviceHostListener、LlcpSocket、LlcpConnectionlessSocket、NfcDepEndpoint、TagEndpoint将NFC系统模块中和NFC芯片无关的处理逻辑,以及和芯片相关的处理逻辑进行了有效解耦。图8-36中以"Native"开头的类均定义在packages/app/Nfc/nxp目录下,所以它们和NXP公司的NFC芯片相关。

DeviceHost接口中,DeviceHost类用于和底层NFC芯片交互,TagEndpoint用于和NFC Tag交互,NfcDepEndpoint用于和P2P对端设备交互,LlcpSocket和LlcpServerSocket分别用于LLCP中有链接数据传输服务的客户端和服务器端,LlcpConnectionlessSocket用于LLCP中无连接数据传输服务。另外,DeviceHostListener也非常重要,它用于NativeNfcManager往NfcService传递NFC相关的通知事件。例如其中的onRemoteEndpointDiscovered代表搜索到一个NFC Tag、onLlcpActivited代表本机和对端NFC设备进入Link Activation(链路激活)阶段。

NativeNfcManager实现了DeviceHost接口,以NXP公司的NativeNfcManager为例,它将通过libnfc_jni及libnfc和NXP公司的NFC芯片交互。

NativeNfcSecureElement用来和Secure Element交互。

接下来要出场的是HandoverManager以及P2pLinkManager,它们的家族关系如图8-37所示。

8.3.2 NFC系统模块 - 图3

图8-37 P2pLinkManager家族关系

图8-37所示的P2pLinkManager家族关系非常复杂,图中的各成员说明如下。

·P2pLinkManager包含三个和传输相关的Server,分别是SnepServer、NdefPushServer以及HandoverServer。每一个Server都定义了相关的回调接口类(如SnepServer.callback),这些回调接口类的实现均由P2pLinkManager的内部类来实现。

·HandoverServer负责处理NFC Connection Handover协议,而具体的数据传输则由HandoverManager来实现。由图中HandoverMangar的mBluetoothAdapter可知,Android默认使用蓝牙来传输数据(提示:这部分内容请读者学完本章后自行研究)。

·P2pEventManager用于处理NFC P2P中和用户交互相关的逻辑。例如当搜索到一个P2P设备时,手机将会震动并发出提示音。

SendUi实现了类似图8-29左图的界面及用户触摸事件处理。在Android平台中,当两个设备LLCP链路被激活时,SendUi将显示出来。其界面组成部分包括两个部分,一个是SendUi界面显示之前手机的截屏图像,另外一个是“触摸即可发送”的文本提示信息。当用户触摸SendUi界面时,数据将被发送出去。

(2)enableInternal函数

介绍完NfcService的几个核心成员后,马上来看NfcService构造函数中最后创建的EnableDisableTask,由于设置了参数为TASK_BOOT,故最终被执行的函数为enableInternal。

[—>NfcService.java::EnableDisableTask:enableInternal]

  1. boolean enableInternal() {
  2. ......
  3. // 启动一个WatchDog线程用来监视NFC底层操作是否超时
  4. WatchDogThread watchDog = new WatchDogThread("enableInternal",
  5. INIT_WATCHDOG_MS);
  6. watchDog.start();
  7. try {
  8. mRoutingWakeLock.acquire();
  9. try {
  10. // 初始化NFC底层模块,这部分内容请读者自行阅读
  11. if (!mDeviceHost.initialize()) {......}
  12. } finally {
  13. mRoutingWakeLock.release();
  14. }
  15. } finally {
  16. watchDog.cancel();
  17. }
  18. synchronized(NfcService.this) {
  19. mObjectMap.clear();
  20. // mIsNdefPushEnabled判断是否启用NPP协议,可在Settings中设置
  21. mP2pLinkManager.enableDisable(mIsNdefPushEnabled, true);
  22. updateState(NfcAdapter.STATE_ON);
  23. }
  24. initSoundPool();// 创建SoundPool,用于播放NFC相关事件的通知音
  25. applyRouting(true);// 启动NFC Polling流程,一旦搜索到周围的NFC设备,相关回调将被调用
  26. return true;
  27. }

我们重点关注上面代码中和P2pLinkManager相关的enableDisable函数,其代码如下所示。

[—>P2pLinkManager.java::enableDisable]

  1. public void enableDisable(boolean sendEnable, boolean receiveEnable) {
  2. synchronized (this) {// 假设参数sendEnable和receiveEnable为true
  3. if (!mIsReceiveEnabled && receiveEnable) {
  4. /*
  5. 启动SnepServer、NdefPushServer和HandoverServer。
  6. 下面这三个成员变量均在P2pLinkManager的构造函数中被创建,这部分内容请读者自行阅读
  7. 本章将只分析SnepServer。
  8. */
  9. mDefaultSnepServer.start();
  10. mNdefPushServer.start();
  11. mHandoverServer.start();
  12. if (mEchoServer != null) // EchoServer用于测试,以后代码分析将忽略它
  13. mHandler.sendEmptyMessage(MSG_START_ECHOSERVER);
  14. }......
  15. mIsSendEnabled = sendEnable;
  16. mIsReceiveEnabled = receiveEnable;
  17. }
  18. }

(3)SnepServer的start函数

SnepServer的start函数将创建一个ServerThread线程对象,其run函数代码如下所示。

[—>SnepServer.java::ServerThread:run]

  1. public void run() {// 注意:为了方便阅读,此处代码省略了synchronized和try/catch等一些代码逻辑
  2. boolean threadRunning;
  3. threadRunning = mThreadRunning;
  4. while (threadRunning) {
  5. // 创建一个LlcpServerSocket,其中mServiceSap值为0x04,mServiceName为“urn:nfc:sn:sne”
  6. // mMiu和mRwSize为本机NFC LLCP层的MIU和RW大小。1024为内部缓冲区大小,单位为字节
  7. mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap,
  8. mServiceName, mMiu, mRwSize, 1024);
  9. LlcpServerSocket serverSocket;
  10. serverSocket = mServerSocket;
  11. // 等待客户端的链接
  12. LlcpSocket communicationSocket = serverSocket.accept();
  13. if (communicationSocket != null) {
  14. // 获取客户端设备的MIU
  15. int miu = communicationSocket.getRemoteMiu();
  16. /*
  17. 判断分片大小。mFragmentLength默认为-1。MIU非常重要。例如本机的MIU为1024,而
  18. 对端设备的MIU为512,那么本机在向对端发送数据时,每次发送的数据不能超过对端
  19. MIU即512字节。
  20. */
  21. int fragmentLength = (mFragmentLength == -1) ?
  22. miu : Math.min(miu, mFragmentLength);
  23. // 每一个连接成功的客户端对应一个ConnectionThread,其内容留待下文详细分析
  24. new ConnectionThread(communicationSocket, fragmentLength).start();
  25. }
  26. }
  27. mServerSocket.close();
  28. }
  29. }

NfcService初始化完毕后,手机中的NFC模块就进入工作状态,一旦有Tag或其他设备进入其有效距离,NFC模块即可开展相关工作。

下面先来分析NFC Tag的处理流程。

2.NFC Tag处理流程分析

(1)notifyNdefMessageListeners流程

当NFC设备检测到一个NFC Tag时,NativeNfcManager的notifyNdefMessageListeners函数将被调用(由libnfc_jni在JNI层调用),其代码如下所示。

[—>NativeNfcManager.java::notifyNdefMessageListeners]

  1. private void notifyNdefMessageListeners(NativeNfcTag tag) {
  2. /*
  3. mListener指向NfcService,它实现了DeviceHostListener接口。
  4. 注意,notifyNdefMessageListeners的参数类型为NativeNfcTag,tag对象由jni层直接创建
  5. 并返回给Java层。
  6. */
  7. mListener.onRemoteEndpointDiscovered(tag);
  8. }

上述代码中,mListener指向NfcService,它的onRemoteEndPointDiscovered函数代码如下所示。

[—>NfcService.java::onRemoteEndpointDiscovered]

  1. public void onRemoteEndpointDiscovered(TagEndpoint tag) {
  2. // 注意,onRemoteEndpointDiscovered的参数类型是TagEndpoint
  3. // 由图8-36可知,NativeNfcTag实现了该接口
  4. sendMessage(NfcService.MSG_NDEF_TAG, tag);// 发送一个MSG_NDEF_TAG消息
  5. }

NfcService的onRemoteEndpointDiscovered将给自己发送一个MSG_NDEF_TAG消息。NfcService内部有一个NfcServiceHandler专门用来处理这些消息。其处理函数如下所示。

[—>NfcService.java::NfcServiceHandler:handleMessage]

  1. final class NfcServiceHandler extends Handler {
  2. public void handleMessage(Message msg) {
  3. switch (msg.what) {
  4. ......// 其他消息处理
  5. case MSG_NDEF_TAG:
  6. TagEndpoint tag = (TagEndpoint) msg.obj;
  7. playSound(SOUND_START);// 播放一个通知音
  8. /*
  9. 从该NFC Tag中读取NDEF消息,NativeNfcTag的findAndReadNdef比较复杂,
  10. 其大体工作流程是尝试用该Tag支持的Technology来读取其中的内容。
  11. */
  12. NdefMessage ndefMsg = tag.findAndReadNdef();
  13. // 注意下面这段代码,无论ndefMsg是否为空,dispatchTagEndpoint都会被调用
  14. if (ndefMsg != null) {
  15. tag.startPresenceChecking();// 检测目标Tag是否还在有效距离内
  16. dispatchTagEndpoint(tag);// 重要函数,详情见下节
  17. } else {
  18. if (tag.reconnect()) {// 重新链接到此NFC Tag
  19. tag.startPresenceChecking();
  20. dispatchTagEndpoint(tag);
  21. } ......
  22. }
  23. break;
  24. ......
  25. }
  26. }

由上述代码可知,NfcService先调用TagEndpoint的findAndReadNdef函数来读取Tag中的数据,然后NfcService将调用dispatchTagEndpoint做进一步处理。

提示 findAndReadNdef的实现和具体的NFC芯片有关,而NXP公司的实现函数在NativeNfcTag类中,内容比较复杂,感兴趣的读者可以阅读。

(2)dispatchTagEndpoint流程

代码如下。

[—>NfcService.java::NfcServiceHandler.dispatchTagEndpoint]

  1. private void dispatchTagEndpoint(TagEndpoint tagEndpoint) {
  2. // 构造一个Tag对象。前面的示例中已经见过Tag。对客户端来说,它代表目标NFC Tag
  3. Tag tag = new Tag(tagEndpoint.getUid(), tagEndpoint.getTechList(),
  4. tagEndpoint.getTechExtras(), tagEndpoint.getHandle(), mNfcTagService);
  5. registerTagObject(tagEndpoint);// 保存此tagEndpoint对象
  6. // mNfcDispatcher的类型是NfcDispather,调用它的dispatchTag函数来分发Tag
  7. if (!mNfcDispatcher.dispatchTag(tag)) {......}

[—>NfcDispatcher.java::dispatchTag]

  1. public boolean dispatchTag(Tag tag) {
  2. NdefMessage message = null;
  3. Ndef ndef = Ndef.get(tag);// 构造一个Ndef对象,Ndef属于Tag Technology的一种
  4. /*
  5. 从Ndef获取目标Tag中的NDEF消息。如果目标Tag中保存的是系统支持的NDEF消息,则message不为空。
  6. 特别注意:在前面代码中见到的findAndReadNdef函数内部已经根据表8-11进行了相关处理。
  7. */
  8. if (ndef != null) message = ndef.getCachedNdefMessage();
  9.  
  10. PendingIntent overrideIntent;
  11. IntentFilter[] overrideFilters;
  12. String[][] overrideTechLists;
  13. // ①构造一个DispatchInfo对象,该对象内部有一个用来触发Activity的Intent
  14. DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
  15. synchronized (this) {
  16. // 下面三个变量由前台分发系统相关的NfcAdapter enableForegroundDispatch函数设置
  17. overrideFilters = mOverrideFilters;
  18. overrideIntent = mOverrideIntent;
  19. overrideTechLists = mOverrideTechLists;
  20. }
  21. // 恢复App Switch,详情可参考《深入理解Android:卷Ⅱ》6.3.3节关于resume/stopAppSwitches的介绍
  22. resumeAppSwitches();
  23.  
  24. // 如果前台Activity启用了前台分发功能,则只需要处理前台分发相关工作即可
  25. if (tryOverrides(dispatch, tag, message, overrideIntent,
  26. overrideFilters, overrideTechLists)) return true;
  27. // 处理Handover事件
  28. if (mHandoverManager.tryHandover(message)) return true;
  29.  
  30. // ②下面是Tag分发系统的处理,首先处理ACTION_NDEF_DISCOVERED
  31. if (tryNdef(dispatch, message)) return true;
  32.  
  33. // 如果tryNdef处理失败,则接着处理ACTION_TECH_DISCOVERED
  34. if (tryTech(dispatch, tag)) return true;
  35. // 如图tryTech处理失败,则处理ACTION_TAG_DISCOVERED
  36.  
  37. // 设置DispatchInfo对象的内部Intent对应的ACTION为ACTION_TAG_DISCOVERED
  38. dispatch.setTagIntent();
  39. // 首先从PackageManagerService查询对ACTION_TAG_DICOVERED感兴趣的Activity,如果有则启动它
  40. if (dispatch.tryStartActivity()) return true;
  41. return false;
  42. }

上述代码中有①②两个重要函数,我们先来看第一个。

[—>NfcDispatcher.java::DispatchInfo构造函数]

  1. public DispatchInfo(Context context, Tag tag, NdefMessage message) {
  2. // 这个Intent的内容将派发给NFC的客户端
  3. intent = new Intent();
  4. /*
  5. 不论最终Intent的Action是什么,NFC系统模块都会将tag对象和tag的ID包含在Intent中传递
  6. 给客户端。
  7. */
  8. intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
  9. intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
  10. // 如果NDEF消息不为空,则把它也保存在Intent中
  11. if (message != null) {
  12. intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message});
  13. ndefUri = message.getRecords()[0].toUri();
  14. ndefMimeType = message.getRecords()[0].toMimeType();
  15. } else {
  16. ndefUri = null;
  17. ndefMimeType = null;
  18. }
  19. /*
  20. rootIntent用来启动目标Activity。NfcRootActivity是Nfc.apk中定义的一个Activity。
  21. 目标Activity启动的过程如下。NFC系统模块先启动NfcRootActivity,然后再由NfcRootActivity
  22. 启动目标Activity。由于NfcRootActivity设置了启动标志(FLAG_ACTIVITY_NEW_TASK和
  23. FLAG_ACTIVITY_CLEAR_TASK),所以目标Activity将单独运行在一个Task中。关于ActivityManager
  24. 这部内容,感兴趣的读者可阅读《深入理解Android:卷Ⅱ》第6章。
  25. */
  26. rootIntent = new Intent(context, NfcRootActivity.class);
  27. // 将Intent信息保存到rootIntent中。
  28. rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent);
  29. rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
  30. Intent.FLAG_ACTIVITY_CLEAR_TASK);
  31. this.context = context;
  32. packageManager = context.getPackageManager();
  33. }

接着来看第二个关键函数tryNdef,代码如下所示。

[—>NfcDispatcher.java::tryNdef]

  1. boolean tryNdef(DispatchInfo dispatch, NdefMessage message) {
  2. if (message == null) return false;
  3. /* setNdefIntent:
  4. 设置Dispatcher内部intent对象的Action为ACTION_NDEF_DISCOVERED。
  5. 如果Dispatch对象的ndefUri和ndefMimeType都为null,则函数返回null。
  6. */
  7. Intent intent = dispatch.setNdefIntent();
  8. if (intent == null) return false;
  9.  
  10. // 如果message中包含了AAR信息,则取出它们。AAR信息就是应用程序的包名
  11. List<String> aarPackages = extractAarPackages(message);
  12. for (String pkg : aarPackages) {
  13. dispatch.intent.setPackage(pkg);
  14. /*
  15. tryStartActivity先检查目标Activity是否存在以及目标Activity的IntenFiltert
  16. 是否匹配intent。注意,下面这个tryStartActivity没有参数。
  17. */
  18. if (dispatch.tryStartActivity()) return true;
  19. }
  20.  
  21. // 上面代码对目标Activity进行了精确匹配,如果没有找到,则尝试启动AAR指定的应用程序
  22. if (aarPackages.size() > 0) {
  23. String firstPackage = aarPackages.get(0);
  24. PackageManager pm;
  25. /*
  26. 下面这段代码用于启动目标应用程序的某个Activity,由于AAR只是指定了应用程序的包名而没有指定
  27. Activity,所以getLaunchIntentForPackage将先检查目标应用程序中是否有Activity的Category
  28. 为CATEGORY_INFO或CATEGORY_LAUNCHER,如果有则启动它。
  29. */
  30. UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
  31. pm = mContext.createPackageContextAsUser("android", 0,
  32. currentUser).getPackageManager();
  33. Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage);
  34. if (appLaunchIntent != null && dispatch.tryStartActivity(appLaunchIntent))
  35. return true;
  36.  
  37. /*
  38. 如果上述处理失败,则获得能启动应用市场去下载某个应用程序的的Intent,该Intent的
  39. Data字段取值为“market:// details?id=应用程序包名”。
  40. */
  41. Intent marketIntent = getAppSearchIntent(firstPackage);
  42. if (marketIntent != null && dispatch.tryStartActivity(marketIntent))
  43. return true;
  44. }
  45. // 处理没有AAR的NDEF消息
  46. dispatch.intent.setPackage(null);
  47. if (dispatch.tryStartActivity()) return true;
  48. return false;
  49. }

(3)NFC Tag处理流程总结

NFC Tag的处理流程还算简单,下面总结其中涉及的重要函数调用,如图8-38所示。

8.3.2 NFC系统模块 - 图4

图8-38 NFC Tag处理流程

NFC Tag的处理流程比较简单,其中有的代码逻辑比较复杂,这部分内容主要集中在NativeNfcTag的findAndReadNdef函数中。它和具体NFC芯片厂商的实现有关,故把它留给感兴趣的读者自己来研究。

下面我们将研究Android Beam的工作流程。

3.Android Beam工作流程分析

当本机检测到某个NFC设备进入有效距离并且能处理LLCP协议后,将通过notifyLlcpLinkActivation通知我们。本节就从这个函数开始分析。

(1)notifyLlcpLinkActivation流程

notifyLlcpLinkActivation代码如下所示。

[—>NativeNfcManager.java::notifyLlcpLinkActivation]

  1. private void notifyLlcpLinkActivation(NativeP2pDevice device) {
  2. // mListener指向NfcService
  3. // 它的onLlcpLinkActivated函数将发送一个MSG_LLCP_LINK_ACTIVATION消息
  4. mListener.onLlcpLinkActivated(device);
  5. }

MSG_LLCP_LINK_ACTIVATION消息由NfcService的内部类NfcServiceHandler处理,它将调用llcpActivated函数,代码如下所示。

[—>NfcService.java::NfcServiceHandler:llcpActivated]

  1. private boolean llcpActivated(NfcDepEndpoint device) {
  2. /*
  3. NfcDepEndpoint代表对端设备,其真实类型是NativeP2pDevice。不论对端设备是Target还是
  4. Initiator,P2pLinkManager的onLlcpActivated函数都将被调用。
  5. */
  6. if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) {
  7. if (device.connect()) {// 如果对端是Target,则需要连接上它
  8. if (mDeviceHost.doCheckLlcp()) {
  9. if (mDeviceHost.doActivateLlcp()) {
  10. synchronized (NfcService.this) {
  11. mObjectMap.put(device.getHandle(), device);
  12. }
  13. mP2pLinkManager.onLlcpActivated();
  14. return true;
  15. }......
  16. } else if (device.getMode() == NfcDepEndpoint.MODE_P2P_INITIATOR) {
  17. if (mDeviceHost.doCheckLlcp()) {
  18. if (mDeviceHost.doActivateLlcp()) {
  19. synchronized (NfcService.this) {
  20. mObjectMap.put(device.getHandle(), device);
  21. }
  22. mP2pLinkManager.onLlcpActivated();
  23. return true;
  24. }......
  25. }
  26. return false;
  27. }

P2pLinkManager的onLlcpActivated函数代码如下所示。

[—>P2pLinkManager.java::onLlcpActivated]

  1. public void onLlcpActivated() {
  2. synchronized (P2pLinkManager.this) {
  3. .....
  4. switch (mLinkState) {
  5. case LINK_STATE_DOWN:// 如果之前没有LLCP相关的活动,则mLinkState为LINK_STATE_DOWN
  6. mLinkState = LINK_STATE_UP;
  7. mSendState = SEND_STATE_NOTHING_TO_SEND;
  8. /*
  9. mEventListener指向P2pEventManager,它的onP2pInRange函数中将播放
  10. 通知音以提醒用户,同时它还会通过SendUi截屏。请读者自行阅读该函数。
  11. */
  12. mEventListener.onP2pInRange();
  13. prepareMessageToSend();// ①准备发送数据
  14. if (mMessageToSend != null ||
  15. (mUrisToSend != null && mHandoverManager.isHandoverSupported())) {
  16. mSendState = SEND_STATE_NEED_CONFIRMATION;
  17. // ②显示提示界面,读者可参考图8-29的左图
  18. mEventListener.onP2pSendConfirmationRequested();
  19. }
  20. break;
  21. ......
  22. }
  23. }
  24. }

onLlcpActivated有两个关键函数,我们先来看第一个函数prepareMessageToSend,其代码如下所示。

[—>P2pLinkManager.java::prepareMessageToSend]

  1. void prepareMessageToSend() {
  2. synchronized (P2pLinkManager.this) {
  3. ......
  4. // 还记得NFC P2P模式示例程序吗?可通过setNdefPushMessageCallback设置回调函数
  5. if (mCallbackNdef != null) {
  6. try {
  7. mMessageToSend = mCallbackNdef.createMessage();// 从回调函数那获取要发送的数据
  8. /*
  9. getUris和NfcAdapter的setBeamPushUrisCallback函数有关,它用于发送file或
  10. content类型的数据。由于这些数据需要借助Handover技术,请读者自己来分析。
  11. */
  12. mUrisToSend = mCallbackNdef.getUris();
  13. return;
  14. } ......
  15. }
  16. // 如果没有设置回调函数,则系统会尝试获取前台应用进程的信息
  17. List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
  18. if (tasks.size() > 0) {
  19. // 获取前台应用进程的包名
  20. String pkg = tasks.get(0).baseActivity.getPackageName();
  21. /*
  22. 应用程序可以在其AndroidManifest中设置“android.nfc.disable_beam_default”
  23. 标签为false,以阻止系统通过Android Beam来传递与该应用相关的信息。
  24. */
  25. if (beamDefaultDisabled(pkg)) mMessageToSend = null;
  26. else {
  27. /*
  28. createDefaultNdef将创建一个NDEF消息,该消息包含两个NFC Record。
  29. 第一个NFC Record包含URI类型的数据,其内容为“http://play.google.com/store/apps
  30. /details?id=应用程序包名&feature=beam”。
  31. 第二个NFC Record为AAR类型,其内容为应用程序的包名。
  32. */
  33. mMessageToSend = createDefaultNdef(pkg);// 包名为pkg的应用程序
  34. }
  35. }else
  36. mMessageToSend = null;
  37. }
  38. }

prepareMessageToSend很有意思,其主要工作如下。

·如果设置回调对象,系统将从回调对象中获取要发送的数据。

·如果没有回调对象,系统会获取前台应用程序的包名。如果前台应用程序禁止通过Android Beam分享信息,则prepareMessageToSend直接返回,否则它将创建一个包含了两个NFC Record的NDEF消息。

接下来看第二个关键函数onP2pSendConfirmationRequested,其代码如下所示。

[—>P2pEventManager.java::onP2pSendConfirmationRequested]

  1. public void onP2pSendConfirmationRequested() {
  2. // 对于拥有显示屏幕的Android设备来说,mSendUi不为空
  3. if (mSendUi != null) mSendUi.showPreSend();// 显示类似图8-29左图所示的界面以提醒用户
  4. else mCallback.onP2pSendConfirmed();
  5. /*
  6. 对于那些没有显示屏幕的Android设备来说,直接调用onP2pSendConfirmed。mCallback指向
  7. P2pLinkManager。待会将分析这个函数。
  8. */
  9. }

在onP2pSendConfirmationRequested函数中,SendUi的showPreSend函数将绘制一个通知界面,如图8-29所示。至此,notifyLlcpLinkActivation的工作完毕。在继续分析Android Beam之前,先来总结notifyLlcpLinkActivation的工作流程,如图8-39所示。

8.3.2 NFC系统模块 - 图5

图8-39 notifyLlcpLinkActivation流程

(2)Beam数据发送流程

SendUi界面将提醒用户触摸屏幕以发送数据,触摸屏幕这一动作将导致SendUi的onTouch函数被调用,其代码如下所示。

[—>SendUi.java::onTouch]

  1. public boolean onTouch(View v, MotionEvent event) {
  2. ......
  3. mCallback.onSendConfirmed();// mCallback指向P2pEventManager
  4. return true;
  5. }

[—>P2pEventManager.java::onSendConfirmed]

  1. public void onSendConfirmed() {
  2. if (!mSending) {
  3. if (mSendUi != null) mSendUi.showStartSend();// 显示数据发送动画
  4. mCallback.onP2pSendConfirmed();// mCallback指向P2pLinkManager
  5. }
  6. mSending = true;
  7. }

[—>P2pLinkManager.java::onP2pSendConfirmed]

  1. public void onP2pSendConfirmed() {
  2. synchronized (this) {
  3. ......
  4. mSendState = SEND_STATE_SENDING;
  5. if (mLinkState == LINK_STATE_UP) sendNdefMessage();// 关键函数
  6. }
  7. }

sendNefMessage将创建一个类型为SendTask的AsyncTask实例以处理数据发送相关的工作,我们来看这个SendTask,相关代码如下所示。

[—>P2pLinkManager.java::SendTask:doInBackground]

  1. final class SendTask extends AsyncTask<Void, Void, Void> {
  2. public Void doInBackground(Void... args) {
  3. NdefMessage m; Uri[] uris; boolean result;
  4. m = mMessageToSend; uris = mUrisToSend; // 设置要发送的数据
  5. long time = SystemClock.elapsedRealtime();
  6. try {
  7. int snepResult = doSnepProtocol(mHandoverManager, m, uris,
  8. mDefaultMiu, mDefaultRwSize);
  9. ......
  10. } catch (IOException e) {
  11. // 如果使用SNEP发送失败,则将利用NPP协议再次尝试发送
  12. if (m != null) // 请读者自行研究和NPP相关的代码
  13. result = new NdefPushClient().push(m);
  14. ......
  15. }
  16. time = SystemClock.elapsedRealtime() - time;
  17. if (result) onSendComplete(m, time);// 发送完毕。请读者自行阅读该函数
  18. return null;
  19. }
  20. }

上述代码中的doSnepProtocol函数内容如下。

[—>P2pLinkManager.java::SendTask:doSnepProtocol]

  1. static int doSnepProtocol(HandoverManager handoverManager,
  2. NdefMessage msg, Uri[] uris, int miu, int rwSize) throws IOException {
  3. // 创建一个SnepClient客户端
  4. SnepClient snepClient = new SnepClient(miu, rwSize);
  5. try {
  6. snepClient.connect(); // ①连接远端设备的SnepServer
  7. } ......
  8. try {
  9. if (uris != null) {// 如果uris不为空,需要使用HandoverManager,这部分内容请读者自行阅读
  10. ......
  11. } else if (msg != null) {
  12. snepClient.put(msg);// ②利用SNEP的PUT命令发送数据
  13. }
  14. return SNEP_SUCCESS;
  15. } catch ......
  16. finally {
  17. snepClient.close();
  18. }
  19. return SNEP_FAILURE;
  20. }

重点介绍SnepClient的connect函数以及put函数。connect函数的代码如下所示。

[—>SnepClient.java::connect]

  1. public void connect() throws IOException {
  2. ......
  3. LlcpSocket socket = null;
  4. SnepMessenger messenger; // SnepMessenger用于处理数据发送和接收
  5. try {
  6. socket = NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024);
  7. if (mPort == -1) socket.connectToService(mServiceName);// 通过服务名来连接服务端
  8. else socket.connectToSap(mPort); // 通过SAP连接服务端
  9. // 获取远端设备的MIU
  10. int miu = socket.getRemoteMiu();
  11. int fragmentLength = (mFragmentLength == -1) ? miu : Math.min(miu, mFragmentLength);
  12. messenger = new SnepMessenger(true, socket, fragmentLength);
  13. } ......
  14. ......
  15. }

put函数代码如下所示。

[—>SnepClient.java::put]

  1. public void put(NdefMessage msg) throws IOException {
  2. SnepMessenger messenger;
  3. messenger = mMessenger;
  4. synchronized (mTransmissionLock) {
  5. try {
  6. /*
  7. 获取SNEP PUT命令对应的数据包,然后发送出去。SnepMessenger的sendMessage
  8. 函数将完成具体的发送工作。
  9. */
  10. messenger.sendMessage(SnepMessage.getPutRequest(msg));
  11. messenger.getMessage();
  12. }.....
  13. }

图8-40总结了本节所述的Beam数据发送流程。

8.3.2 NFC系统模块 - 图6

图8-40 Beam数据发送流程

接着来看Beam数据接收流程。

(3)Beam数据接收流程

8.3.2节中曾介绍,SnepServer每接收一个客户端的连接后均会创建一个ConnectionThread,它的代码如下所示。

[—>SnepServer.java::ConnectionThread]

  1. private class ConnectionThread extends Thread {
  2. private final LlcpSocket mSock;
  3. private final SnepMessenger mMessager;
  4. ConnectionThread(LlcpSocket socket, int fragmentLength) {
  5. super(TAG);
  6. mSock = socket;
  7. // 也创建一个SnepMessenger用来处理具体的数据接收
  8. mMessager = new SnepMessenger(false, socket, fragmentLength);
  9. }
  10. public void run() {
  11. ......// 省略一些try/catch逻辑
  12. while (running) {
  13. if (!handleRequest(mMessager, mCallback)) break;
  14. ......
  15. }
  16. mSock.close();
  17. }
  18. }

[—>SnepServer.java::handleRequest]

  1. static boolean handleRequest(SnepMessenger messenger,
  2.          Callback callback) throws IOException {
  3. SnepMessage request;
  4. request = messenger.getMessage();
  5. ......
  6. if (((request.getVersion() & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) {
  7. messenger.sendMessage(SnepMessage.getMessage(
  8. SnepMessage.RESPONSE_UNSUPPORTED_VERSION));
  9. } else if (request.getField() == SnepMessage.REQUEST_GET) {
  10. // 处理GET命令,callback类型为SnepServer的内部类Callback
  11. messenger.sendMessage(callback.doGet(request.getAcceptableLength(),
  12. request.getNdefMessage()));
  13. } else if (request.getField() == SnepMessage.REQUEST_PUT) {
  14. // 处理PUT命令
  15. messenger.sendMessage(callback.doPut(request.getNdefMessage()));
  16. } .....
  17. return true;
  18. }

来看SnepServer.callback的doPut函数,它由P2pLinkManager的内部类实现,代码如下所示。

[—>P2pLinkManager.java::SnepServer.Callback]

  1. final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() {
  2. public SnepMessage doPut(NdefMessage msg) {
  3. onReceiveComplete(msg);
  4. return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS);
  5. }
  6. }

onReceiveComplete的代码如下所示。

[—>P2pLinkManager.java::onReceiveComplete]

  1. void onReceiveComplete(NdefMessage msg) {
  2. // 发送一个MSG_RECEIVE_COMPLETE消息
  3. mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget();
  4. }

MSG_RECEIVE_COMPLETE消息由P2pLinkManager的handleMessge处理,相关代码逻辑如下所示。

[—>P2pLinkManager.java::]

  1. public boolean handleMessage(Message msg) {
  2. switch (msg.what) {
  3. ......
  4. case MSG_RECEIVE_COMPLETE:
  5. NdefMessage m = (NdefMessage) msg.obj;
  6. synchronized (this) {
  7. ......
  8. mSendState = SEND_STATE_NOTHING_TO_SEND;
  9. mEventListener.onP2pReceiveComplete(true);// 取消本机显示的SendUi界面
  10. // sendMockNdefTag将发送一个MSG_MOCK_NDEF消息给NfcService的NfcServiceHandler
  11. NfcService.getInstance().sendMockNdefTag(m);
  12. }
  13. break;
  14. .....
  15. }
  16. }

[—>NfcService.java::NfcServiceHandler:handleMessage]

  1. final class NfcServiceHandler extends Handler {
  2. public void handleMessage(Message msg) {
  3. switch (msg.what) {
  4. case MSG_MOCK_NDEF: {
  5. NdefMessage ndefMsg = (NdefMessage) msg.obj;
  6. Bundle extras = new Bundle();
  7. extras.putParcelable(Ndef.EXTRA_NDEF_MSG, ndefMsg);
  8. extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, 0);
  9. extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, Ndef.NDEF_MODE_READ_ONLY);
  10. extras.putInt(Ndef.EXTRA_NDEF_TYPE, Ndef.TYPE_OTHER);
  11. // 创建一个模拟Tag对象
  12. Tag tag = Tag.createMockTag(new byte[] { 0x00 },
  13. new int[] { TagTechnology.NDEF },new Bundle[] { extras });
  14. // 直接通过分发系统分发这个Tag
  15. boolean delivered = mNfcDispatcher.dispatchTag(tag);
  16. ......
  17. break;
  18. }
  19. ......
  20. }

Beam接收端的处理流程比较巧妙,系统将创建一个模拟的Tag对象,然后利用分发系统来处理它。图8-41总结了Beam接收端的处理流程。

8.3.2 NFC系统模块 - 图7

图8-41 Beam数据接收流程

4.CE模式

NFC系统模块对CE模式的支持集中在以下两点。

·通过NfcAdapterExtras为CE模式的客户端提供INfcAdapterExtras功能实现。这部分内容所涉及的调用流程比较简单,请读者自行研读。当然,我们略过了和芯片相关的部分。

·当NFC设备进入CE模式后,和它交互的另一端是诸如NFC Reader这样的设备。此时对端发起一些操作,而NativeNfcManagement的一些回调函数将被触发。下面代码展示了和CE相关的这些回调函数。

[—>NativeNfcManager.java::CE相关回调函数]

  1. // 根据NFCIP-1协议,该函数表示对端设备发送了DESELECT命令给我们。它属于deactivation阶段的命令
  2. private void notifyTargetDeselected() {// mListern指向NfcService
  3. mListener.onCardEmulationDeselected();
  4. }
  5. /*
  6. 用于通知SE上某个应用程序开始和对端设备进行交互。AID为Application ID的缩写,它代表SE上
  7. 的某个应用程序(称为Applet)。
  8. */
  9. private void notifyTransactionListeners(byte[] aid) {
  10. mListener.onCardEmulationAidSelected(aid);
  11. }
  12. // 用于通知SE模块被激活
  13. private void notifySeFieldActivated() {
  14. mListener.onRemoteFieldActivated();
  15. }
  16. // 用于通知SE模块被禁止
  17. private void notifySeFieldDeactivated() {
  18. mListener.onRemoteFieldDeactivated();
  19. }
  20. // 收到对端设备的APDU命令
  21. private void notifySeApduReceived(byte[] apdu) {
  22. mListener.onSeApduReceived(apdu);
  23. }
  24. /*
  25. EMV是EMV标准是由国际三大银行卡组织Europay(欧陆卡,已被万事达收购)、MasterCard(万事达卡)和
  26. Visa(维萨)共同发起制定的银行卡从磁条卡向智能IC卡转移的技术标准,是基于IC卡的金融支付标准,
  27. 目前已成为公认的全球统一标准。下面这个函数用于通知EMV Card进入Removal阶段。
  28. 该函数只适用于NXP公司的相关芯片。
  29. */
  30. private void notifySeEmvCardRemoval() {
  31. mListener.onSeEmvCardRemoval();// NfcService如何处理它呢
  32. }
  33. // 用于通知MIFARE SMX Emulation被外部设备访问。该函数只适用于NXP公司的相关芯片
  34. private void notifySeMifareAccess(byte[] block) {
  35. mListener.onSeMifareAccess(block);
  36. }

NfcService是如何处理这些回调通知的呢?以notifySeEmvCardRemoval中的onSeEmvCardRemoval为例,NfcService将发送一个MSG_SE_EMV_CARD_REMOVAL消息,而这个消息的处理函数代码如下所示。

[—>NfcService.java::NfcServiceHandler:handleMessage]

  1. ......
  2. case MSG_SE_EMV_CARD_REMOVAL:  // CE相关的消息全是类型的处理方式
  3. Intent cardRemovalIntent = new Intent();
  4. cardRemovalIntent.setAction(ACTION_EMV_CARD_REMOVAL);
  5. sendSeBroadcast(cardRemovalIntent); // 发送广播
  6. ......

由上述代码的注释可知,NfcService对CE相关的处理非常简单,就是接收来自底层的通知事件,然后将其转化为广播事件发送给系统中感兴趣的应用程序。

5.Android中的NFC总结

本节介绍了Android平台中NFC系统模块NfcService及其他重要组件。从整体上来说,NfcService以及与底层芯片无关的模块难度不大,而与底层芯片相关的模块则难度较大(位于com.android.nfc.dhimpl包中)。本章没有讨论dhimpl包的具体代码,希望感兴趣的读者能结合芯片手册自行研究它们。

另外,本节还对NFC R/W模式及P2P模块的工作流程进行了相关介绍,这部分难度不大,相信读者能轻松掌握。同时,作为课后作业,请读者在本节基础上自行学习Handover相关的处理流程。

最后,本节简单介绍了NFC CE模式的处理流程,NfcService本身对CE相关的处理比较简单,它仅根据CE相关的操作向系统发送不同的广播,而这些广播则会由感兴趣的应用程序来处理,例如Google Wallet。

提示 NFC CE模式其实内容相当复杂,涉及很多规范。笔者会在博客上继续介绍NFC CE相关的知识,敬请读者关注。