现在的位置: 首页 > 综合 > 正文

版本4.0锁屏下的 NFC discover TAG

2018年04月16日 ⁄ 综合 ⁄ 共 12484字 ⁄ 字号 评论关闭

今天发现package中 NFC都没怎么好好看过,导致我写一个新的东西很难,所以决定今天把它剖析完。哈,发现找到我想要的东西就不想写全拉,嘿,果然还是很懒!!

因为新东西是跟NFC TAG有关,故从这里入手,需要看得为package下面的nfc文件夹里的东东

1. NfcService

这个是继承自Application. 应该是方便设一些全局变量。

里面有几个内部类NfcAdapterService,这个在下面介绍。

这其中还用到了keyguardManager 跟powerManager,表明有一段是跟屏幕有关。

定义了一个sharedPreference--NfcServicePrefs用来保存临时数据。

 以NfcAdapter.STATE_OFF 或者 NfcAdapter.STATE_ON定义当前Nfc Adapter状态。

一句 mScreenState = checkScreenState(); 显而易见了PowerManager和KeyguardManager的意义,检查screen的状态

  int checkScreenState() {
        if (!mPowerManager.isScreenOn()) {
            return SCREEN_STATE_OFF;//screen 完全不亮
        } else if (mKeyguard.isKeyguardLocked()) {
            return SCREEN_STATE_ON_LOCKED; //点亮screen,但是有锁
        } else {
            return SCREEN_STATE_ON_UNLOCKED;//点亮screen并且无锁状态
        }
    }

注册了一个broadcastReceiver,该receiver filter很多,如下

 IntentFilter filter = new IntentFilter(NativeNfcManager.INTERNAL_TARGET_DESELECTED_ACTION);//那么这里NativeNfcManager呢就是一个native的接口然后加载libnfc库啊什么的 这个action也是通过监听发出的,比如其中onCardEmulationDeselected,一旦这个方法被回调了,那么间接的下面的receiver就会处理INTERNAL_TARGET_DESELECTED_ACTION
故而要了解下什么是deviceHost和deviceHostListener,应该是跟Nfc adapter有关啦

        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(ACTION_MASTER_CLEAR_NOTIFICATION);
        filter.addAction(Intent.ACTION_USER_PRESENT);
        ......
        registerReceiver(mReceiver, filter); 

        ......

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(
                    NativeNfcManager.INTERNAL_TARGET_DESELECTED_ACTION)) {
                // Perform applyRouting() in AsyncTask to serialize blocking calls
                new ApplyRoutingTask().execute(); //这里可以看下ApplyRoutingTask干啥子了
            } else if (action.equals(Intent.ACTION_SCREEN_ON)
                    || action.equals(Intent.ACTION_SCREEN_OFF)
                    || action.equals(Intent.ACTION_USER_PRESENT)) {//String · ACTION_USER_PRESENT,
Broadcast Action: Sent when the user is present after device wakes up (e.g when the keyguard is gone).

                // Perform applyRouting() in AsyncTask to serialize blocking calls
                int screenState = SCREEN_STATE_OFF;
                if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                    screenState = SCREEN_STATE_OFF;
                } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
                    screenState = mKeyguard.isKeyguardLocked() ?
                            SCREEN_STATE_ON_LOCKED : SCREEN_STATE_ON_UNLOCKED;
                } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
                    screenState = SCREEN_STATE_ON_UNLOCKED;
                }
                new ApplyRoutingTask().execute(Integer.valueOf(screenState));
            } else if (action.equals(ACTION_MASTER_CLEAR_NOTIFICATION)) {
                EnableDisableTask eeWipeTask = new EnableDisableTask();
                eeWipeTask.execute(TASK_EE_WIPE);
                try {
                    eeWipeTask.get();  // blocks until EE wipe is complete
                } catch (ExecutionException e) {
                    Log.w(TAG, "failed to wipe NFC-EE");
                } catch (InterruptedException e) {
                    Log.w(TAG, "failed to wipe NFC-EE");
                }
            } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
                // Clear the NFCEE access cache in case a UID gets recycled
                mNfceeAccessControl.invalidateCache();

                boolean dataRemoved = intent.getBooleanExtra(Intent.EXTRA_DATA_REMOVED, false);
                if (dataRemoved) {
                    Uri data = intent.getData();
                    if (data == null) return;
                    String packageName = data.getSchemeSpecificPart();

                    synchronized (NfcService.this) {
                        if (mSePackages.contains(packageName)) {
                            new EnableDisableTask().execute(TASK_EE_WIPE);
                            mSePackages.remove(packageName);
                        }
                    }
                }
            } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
                boolean isAirplaneModeOn = intent.getBooleanExtra("state", false);
                // Query the airplane mode from Settings.System just to make sure that
                // some random app is not sending this intent
                if (isAirplaneModeOn != isAirplaneModeOn()) {
                    return;
                }
                if (!mIsAirplaneSensitive) {
                    return;
                }
                if (isAirplaneModeOn) {
                    new EnableDisableTask().execute(TASK_DISABLE);
                } else if (!isAirplaneModeOn && mPrefs.getBoolean(PREF_NFC_ON, NFC_ON_DEFAULT)) {
                    new EnableDisableTask().execute(TASK_ENABLE);
                }
            }
        }
    };

以上是onCreate里做的主要的事。

2.NfcDispatcher

在NfcService中会通过实例化一个  NfcDispatcher   mNfcDispatcher = new NfcDispatcher(this, mP2pLinkManager);

发送NFC 事件启动一些activity, 你可以追下dispatchTagInternal()这个方法就会发现NfcAdapter.ACTION_NDEF_DISCOVERED,NfcAdapter.ACTION_TECH_DISCOVERED,NfcAdapter.ACTION_TAG_DISCOVERED这些intent是从哪里来的

// Dispatch to either an override pending intent or a standard startActivity()
    private boolean dispatchTagInternal(Tag tag, NdefMessage[] msgs,
            PendingIntent overrideIntent, IntentFilter[] overrideFilters,
            String[][] overrideTechLists)
            throws CanceledException{
        Intent intent;

        //
        // Try the NDEF content specific dispatch
        //

        if (msgs != null && msgs.length > 0) {
            NdefMessage msg = msgs[0];
            NdefRecord[] records = msg.getRecords();
            if (records.length > 0) {
                // Found valid NDEF data, try to dispatch that first
                NdefRecord record = records[0];

                intent = buildTagIntent(tag, msgs, NfcAdapter.ACTION_NDEF_DISCOVERED);
                if (setTypeOrDataFromNdef(intent, record)) {
                    // The record contains filterable data, try to start a matching activity
                    if (startDispatchActivity(intent, overrideIntent, overrideFilters,
                            overrideTechLists, records)) {
                        // If an activity is found then skip further dispatching
                        return true;
                    } else {
                        if (DBG) Log.d(TAG, "No activities for NDEF handling of " + intent);
                    }
                }
            }
        }

        //
        // Try the technology specific dispatch
        //

        String[] tagTechs = tag.getTechList();
        Arrays.sort(tagTechs);

        if (overrideIntent != null) {
            // There are dispatch overrides in place
            if (overrideTechLists != null) {
                for (String[] filterTechs : overrideTechLists) {
                    if (filterMatch(tagTechs, filterTechs)) {
                        // An override matched, send it to the foreground activity.
                        intent = buildTagIntent(tag, msgs,
                                NfcAdapter.ACTION_TECH_DISCOVERED);
                        overrideIntent.send(mContext, Activity.RESULT_OK, intent);
                        return true;
                    }
                }
            }
        } else {
            // Standard tech dispatch path
            ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
            ArrayList<ComponentInfo> registered = mTechListFilters.getComponents();

            // Check each registered activity to see if it matches
            for (ComponentInfo info : registered) {
                // Don't allow wild card matching
                if (filterMatch(tagTechs, info.techs) &&
                        isComponentEnabled(mPackageManager, info.resolveInfo)) {
                    // Add the activity as a match if it's not already in the list
                    if (!matches.contains(info.resolveInfo)) {
                        matches.add(info.resolveInfo);
                    }
                }
            }

            if (matches.size() == 1) {
                // Single match, launch directly
                intent = buildTagIntent(tag, msgs, NfcAdapter.ACTION_TECH_DISCOVERED);
                ResolveInfo info = matches.get(0);
                intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
                if (startRootActivity(intent)) {
                    return true;
                }
            } else if (matches.size() > 1) {
                // Multiple matches, show a custom activity chooser dialog
                intent = new Intent(mContext, TechListChooserActivity.class);
                intent.putExtra(Intent.EXTRA_INTENT,
                        buildTagIntent(tag, msgs, NfcAdapter.ACTION_TECH_DISCOVERED));
                intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
                        matches);
                if (startRootActivity(intent)) {
                    return true;
                }
            } else {
                // No matches, move on
                if (DBG) Log.w(TAG, "No activities for technology handling");
            }
        }

        //
        // Try the generic intent
        //

        intent = buildTagIntent(tag, msgs, NfcAdapter.ACTION_TAG_DISCOVERED);
        if (startDispatchActivity(intent, overrideIntent, overrideFilters, overrideTechLists,
                null)) {
            return true;
        } else {
            Log.e(TAG, "No tag fallback activity found for " + intent);
            return false;
        }
    }

3.NfcAdapterService

此在NfcService中的内部类,却大有用途,在NfcService 有通过ServiceManager将其添加到后台一直运行

mNfcAdapter = new NfcAdapterService();

ServiceManager.addService(SERVICE_NAME, mNfcAdapter);

4.ApplyRoutingTask

5.EnableDisableTask

引用源码中的介绍此类功能 Manages tasks that involve turning on/off the NFC controller.

这里我们先要了解下可能传递给它的参数值,分别有

*TASK_ENABLE--- enables the NFC adapter, without changing preferences
*TASK_DISABLE--- disables the NFC adapter, without changing
 *TASK_BOOT---- does first boot work and may enable NFC
 * TASK_EE_WIPE---- wipes the Execution Environment, and in the process may temporarily enable the NFC adapter

如果说当前 NfcAdapter处于STATE_TURNING_OFF或者STATE_TURNING_ON,那这个就不要往下做了,直接返回一个null回去

这里有三个函数要去看

enableInternal() --不做介绍

disableInternal()

--这里有介绍说要另起thread以防dviceHost deintialize的时候挂起,哎 为什么不用AsyncTask的处理方式,他说了,当NFC controller 停止响应的时候呢,ui线程和Async task 线程池也会挂起的。这个thread呢如下

 class WatchDogThread extends Thread {
        boolean mWatchDogCanceled = false;
        @Override
        public void run() {
            boolean slept = false;
            while (!slept) {
                try {
                    Thread.sleep(10000);//哎,阻塞当前线程了
                    slept = true;
                } catch (InterruptedException e) { }
            }
            synchronized (this) {
                if (!mWatchDogCanceled) {
                    // Trigger watch-dog
                    Log.e(TAG, "Watch dog triggered");
                    mDeviceHost.doAbort();//啊,自己在这跑东西了,用到native方法来时NFC adpter为off
                }
            }
        }
        public synchronized void cancel() {
            mWatchDogCanceled = true;
        }
    }

当然这个下面又说了,如果有tag出现的话要停止这个thread,那么要恰当的停止这个thread的话就要包含中断对tag的连接 。并且呢在避免tag再次被发现时要停止轮询(polling loop)

applyRouting(true); //这个是我比较关心的一个东东,哎
maybeDisconnectTarget();

当deinitialize devicehost 成功了,就要停止watchDog thread了,并且更新Nfc Adapter状态,broadcast出去


executeEeWipe() 

当参数为TASK_EE_WIPE-就会调到这个函数,这里设计到NFC Execution Environment  fields,干嘛的看最后的Tips,反正这个要在NFC Adapter处于on的状态下执行,具体要调用到native的方法了。

applyRouting()

    /**
     * Read mScreenState and apply NFC-C polling and NFC-EE routing
     */
    void applyRouting(boolean force) {
        synchronized (this) {
            if (!isNfcEnabled() || mOpenEe != null) {
                // PN544 cannot be reconfigured while EE is open
                return;
            }

          //handle screen off, disable NFC discover tag
            if (PN544_QUIRK_DISCONNECT_BEFORE_RECONFIGURE && mScreenState == SCREEN_STATE_OFF) {
                /* TODO undo this after the LLCP stack is fixed.
                 * Use a different sequence when turning the screen off to
                 * workaround race conditions in pn544 libnfc. The race occurs
                 * when we change routing while there is a P2P target connect.
                 * The async LLCP callback will crash since the routing code
                 * is overwriting globals it relies on.
                 */
                if (POLLING_MODE > SCREEN_STATE_OFF) {
                    if (force || mNfcPollingEnabled) {
                        Log.d(TAG, "NFC-C OFF, disconnect");
                        mNfcPollingEnabled = false;
                        mDeviceHost.disableDiscovery();
                        maybeDisconnectTarget();
                    }
                }
                if (mEeRoutingState == ROUTE_ON_WHEN_SCREEN_ON) {
                    if (force || mNfceeRouteEnabled) {
                        Log.d(TAG, "NFC-EE OFF");
                        mNfceeRouteEnabled = false;
                        mDeviceHost.doDeselectSecureElement();
                    }
                }
                return;
            }

            // configure NFC-EE routing
            if (mScreenState >= SCREEN_STATE_ON_LOCKED &&
                    mEeRoutingState == ROUTE_ON_WHEN_SCREEN_ON) {
                if (force || !mNfceeRouteEnabled) {
                    Log.d(TAG, "NFC-EE ON");
                    mNfceeRouteEnabled = true;
                    mDeviceHost.doSelectSecureElement();
                }
            } else {
                if (force ||  mNfceeRouteEnabled) {
                    Log.d(TAG, "NFC-EE OFF");
                    mNfceeRouteEnabled = false;
                    mDeviceHost.doDeselectSecureElement();
                }
            }

            // configure NFC-C polling

             //这里是要解锁了才能让NFC discover TAG,所以也许我可以改成mScreenState >= SCREEN_STATE_ON_LOCKED,

            //那么不管有无锁都可以discover tag了~哎
            if (mScreenState >= POLLING_MODE) {

                if (force || !mNfcPollingEnabled) {
                    Log.d(TAG, "NFC-C ON");
                    mNfcPollingEnabled = true;
                    mDeviceHost.enableDiscovery();
                }
            } else {
                if (force || mNfcPollingEnabled) {
                    Log.d(TAG, "NFC-C OFF");
                    mNfcPollingEnabled = false;
                    mDeviceHost.disableDiscovery();
                }
            }
        }
    }

TIPS: Card emulation(what google wallet does) and tag reading/writing are 2 different features of the NFC chip. On a phone with Google Wallet, the secure Element is enabled when the lock screen is displayed(in the logcat you
will see NFC-EE ON). However, NFC polling for tags is still turned off. It will only be turned on when you unlock the phone(logcat: NFC-C ON). Both are turned off when the screen is turned off. --此处引用我网搜来的,很好阿,促进我理解了NFC-EE和NFC-C

后续:

去面试,说了下实现nfc tag解锁  方法,大致是修改了nfcService.java中 nfcServiceHandler中处理MSG_NDEF_TAG的消息时braodcast出去一个自定义的字串,这样在PhoneWindowManager.java中init中注册接收,然后作解锁处理动作mKeyguardMediator.keyguardDone(true,true);

可是那个面试的人就说其实不用自定义这样的broadcast,系统有的,不过呢我回来还是没找着,对他表示怀疑~~改天再问下~

抱歉!评论已关闭.