我们都知道在不修改源代码的情况下,只能是解锁之后才能使用NFC功能。而在锁屏和黑屏2个状态下是没办法用NFC的,但是最近有个客户要求手机在黑屏状态下能够使用NFC,因此我们需要去修改Android源代码关于NFC模块。
最开始可以通过查看分析源代码,找到到NfcService的相关代码,如下: packages\apps\Nfc\src\com\android\nfc\NfcService.java
找到186行,这句是定义NFC能够使用的屏幕最小状态
// minimum screen state that enables NFC polling static final int NFC_POLLING_MODE = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED;
这几个状态分别是:
SCREEN_STATE_OFF黑屏状态
SCREEN_STATE_ON_LOCKED屏幕亮了,但是是锁屏状态
SCREEN_STATE_ON_UNLOCKED 屏幕亮了,并且是解锁状态
代码定义如下,在packages\apps\Nfc\src\com\android\nfc\ScreenStateHelper中定义
static final int SCREEN_STATE_UNKNOWN = 0; static final int SCREEN_STATE_OFF = 1; static final int SCREEN_STATE_ON_LOCKED = 2; static final int SCREEN_STATE_ON_UNLOCKED = 3;
上面的这个最小状态在 NfcService.java的第1706行,computeDiscoveryParameters(int screenState)方法中被调用,用来判断的,方法代码如下:
private NfcDiscoveryParameters computeDiscoveryParameters(int screenState) { Log.d(TAG, "computeDiscoveryParameters() screenState:"+describeScreenState(screenState)); if(screenState == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) Log.d(TAG, "!!!! SCREEN_STATE_ON_LOCKED,, mNfcUnlockManager.isLockscreenPollingEnabled():"+mNfcUnlockManager.isLockscreenPollingEnabled()); // Recompute discovery parameters based on screen state NfcDiscoveryParameters.Builder paramsBuilder = NfcDiscoveryParameters.newBuilder(); // Polling if (screenState >= NFC_POLLING_MODE) {//这里被调用 // Check if reader-mode is enabled if (mReaderModeParams != null) { int techMask = 0; if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_A) != 0) techMask |= NFC_POLL_A; if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_B) != 0) techMask |= NFC_POLL_B; if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_F) != 0) techMask |= NFC_POLL_F; if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_V) != 0) techMask |= NFC_POLL_ISO15693; if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_BARCODE) != 0) techMask |= NFC_POLL_KOVIO; Log.d(TAG, " mReaderModeParams != null paramsBuilder.setTechMask:"+techMask); paramsBuilder.setTechMask(techMask); paramsBuilder.setEnableReaderMode(true); } else { Log.d(TAG, " mReaderModeParams == null paramsBuilder.setTechMask:"+NfcDiscoveryParameters.NFC_POLL_DEFAULT +" NFC_POLL_DEFAULT"); paramsBuilder.setTechMask(NfcDiscoveryParameters.NFC_POLL_DEFAULT); paramsBuilder.setEnableP2p(mIsNdefPushEnabled); } } else if (screenState == ScreenStateHelper.SCREEN_STATE_ON_LOCKED && mInProvisionMode) { paramsBuilder.setTechMask(NfcDiscoveryParameters.NFC_POLL_DEFAULT); // enable P2P for MFM/EDU/Corp provisioning paramsBuilder.setEnableP2p(true); } else if (screenState == ScreenStateHelper.SCREEN_STATE_ON_LOCKED && mNfcUnlockManager.isLockscreenPollingEnabled()) { Log.d(TAG, "!!!! SCREEN_STATE_ON_LOCKED setTechMask "); // For lock-screen tags, no low-power polling paramsBuilder.setTechMask(mNfcUnlockManager.getLockscreenPollMask()); paramsBuilder.setEnableLowPowerDiscovery(false); paramsBuilder.setEnableP2p(false); } if (mIsHceCapable && mScreenState >= ScreenStateHelper.SCREEN_STATE_ON_LOCKED) { // Host routing is always enabled at lock screen or later Log.d(TAG, " >= SCREEN_STATE_ON_LOCKED paramsBuilder.setEnableHostRouting(true) "); paramsBuilder.setEnableHostRouting(true); } return paramsBuilder.build(); }因此如果要改成黑屏状态下可以使用NFC的话,只要将变量NFC_POLLING_MODE改成
ScreenStateHelper.SCREEN_STATE_OFF即可,代码如下:
// minimum screen state that enables NFC polling //static final int NFC_POLLING_MODE = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED; static final int NFC_POLLING_MODE = ScreenStateHelper.SCREEN_STATE_OFF;
但是这样的话,手机会一直不休眠,观察电池的电流和电压发现,一直在跳动,这样在黑屏状态下,手机不会休眠,会很耗电,因此还要优化。
客户的要求是:当双击物理按键Camera键的时候,可以在黑屏状态下使用NFC十分钟,十分钟之类,差不多关于NFC的工作完成了,之后将状态改回来,即:只能在解锁状态下使用NFC,这样的话就可以黑屏使用NFC又节电。
因此,思路如下:
1、接收物理按键Camera键发送的广播,来判断是双击,并将NFC_POLLING_MODE的最小模式改为ScreenStateHelper.SCREEN_STATE_OFF。
2、需要写一个定时器来处理十分钟之后将NFC_POLLING_MODE的最小模式改为会原来的ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED。
因此,首先先定义几个常量,从第185行static final int NFC_POLLING_MODE= ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED;处开始修改,修改代码如下:
// minimum screen state that enables NFC polling // edited by ouyang [2015-10-19] start // static final int NFC_POLLING_MODE= ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED; //默认为要解锁才能使用NFC static final int NFC_POLLING_MODE_DEFALUT = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED; //在黑屏情况下也可以使用NFC static final int NFC_POLLING_MODE_SCREEN_OFF = ScreenStateHelper.SCREEN_STATE_OFF; //默认能使用NFC时的屏幕状态 static int NFC_POLLING_MODE = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED; public static int getNFC_POLLING_MODE() { return NFC_POLLING_MODE; } public static void setNFC_POLLING_MODE(int mNFC_POLLING_MODE) { NFC_POLLING_MODE = mNFC_POLLING_MODE; } //是否是双击Camera键 static boolean isDoublePress=false; //从黑屏可用NFC恢复到要解锁才能用NFC的时间 static final int TIME_TO_Restore_Default_Values=(60*1000)*10;//10分钟 10*1000*60 // edited by ouyang [2015-10-19] end
第二步:写一个广播接收者来处理物理按键Camera,按下和松开时发出的广播。
因为是要判断双击Camera,所以这里只要接收松开Camera键时发出的广播即可。这个广播是公司自己定义的,定义的广播为:"com.runbo.camera.key.up"。所以现在处理这个广播。因为代码中本来就动态注册了一个广播接收者,因此我们在这个广播接收者种再注册一个Intent即可。代码如下:在第450行
// Intents for all users IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_USER_PRESENT); filter.addAction(Intent.ACTION_USER_SWITCHED); //added by ouyang start [2015-10-19] //Camera物理键按下后松开 发出的广播 filter.addAction("com.runbo.camera.key.up"); //added by ouyang end [2015-10-19] registerForAirplaneMode(filter); mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
这样我们处理这个双击 Camera键可以在mReceiver中处理了,在mReceiver中的onReceive方法中,判断action是否是"com.runbo.camera.key.up",2295行代码如下:
//added by ouyang start [2015-10-19] Camera物理键按下后松开 else if (action.equals("com.runbo.camera.key.up")) { Log.d("oyp", "<----com.runbo.camera.key.up---->"); Handler checkHandler=new Handler(); Handler restoreHandler=new Handler(); //单击 if (!isDoublePress) { isDoublePress=true;//500ms之类再单击Camera键的话,就是双击了,直接进入 else语句块 //500ms后触发该线程,查看是单击还是双击 Runnable CheckDoubleRunnable=new Runnable(){ @Override public void run() { if (isDoublePress) { Log.i("oyp", "<----Single Press the Camera Key---->"); //显示为单击 }else{ Log.i("oyp", "<----Double Press the Camera Key---->"); //显示为双击 } isDoublePress=false;//500ms后在单击Camera键的话 依旧是单击 ,还是进入了 if语句块 } }; checkHandler.postDelayed(CheckDoubleRunnable, 500);// 500ms内两次单击,触发双击 } // 500ms内两次单击,触发双击 else{ isDoublePress=false;//双击后,将该值设为false,下次在单击Camera键的话 依旧是单击 ,还是进入了 if语句块 //设置在屏幕关闭情况下仍然可以使用NFC setNFC_POLLING_MODE(NFC_POLLING_MODE_SCREEN_OFF); applyRouting(true); Log.d("oyp", "2、NFC_POLLING_MODE="+getNFC_POLLING_MODE()); //10分钟后触发该线程,恢复原来值 Runnable RestoreDefaultValues=new Runnable(){ @Override public void run() { //设置在屏幕解锁情况下可以使用NFC setNFC_POLLING_MODE(NFC_POLLING_MODE_DEFALUT); applyRouting(true); Log.d("oyp", "3、NFC_POLLING_MODE="+getNFC_POLLING_MODE()); } }; restoreHandler.removeCallbacks(RestoreDefaultValues);//先取消定时器 restoreHandler.postDelayed(RestoreDefaultValues, TIME_TO_Restore_Default_Values);//10分钟后恢复原来值 } } //added by ouyang end [2015-10-19]
还要将computeDiscoveryParameters()方法中的判断语句改掉,1733行代码如下:
// if (screenState >= NFC_POLLING_MODE) { //edited by ouyang [2015-10-19 11:13:17] if (screenState >= getNFC_POLLING_MODE()) {
====================================================================================
上面代码用Handler做十分钟定时器的时候,时间不准确,改用AlarmManager做定时器,下面是修改的代码
//added by ouyang start [2015-11-4] 40分钟后恢复NFC默认值的广播 else if (action.equals("com.runbo.camera.nfc.restore")) { //设置在屏幕解锁情况下可以使用NFC Log.d("oyp", "<----com.runbo.camera.nfc.restore---->"); setNFC_POLLING_MODE(NFC_POLLING_MODE_DEFALUT); applyRouting(true); Log.d("oyp", "3、NFC_POLLING_MODE="+getNFC_POLLING_MODE()); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date=new Date(); String time=sdf.format(date); Log.d("oyp", "time after="+time); } //added by ouyang start [2015-10-19] Camera物理键按下后松开 else if (action.equals("com.runbo.camera.key.up")) { Log.d("oyp", "<----com.runbo.camera.key.up---->"); Handler checkHandler=new Handler(); Handler restoreHandler=new Handler(); //单击 if (!isDoublePress) { isDoublePress=true;//500ms之类再单击Camera键的话,就是双击了,直接进入 else语句块 //500ms后触发该线程,查看是单击还是双击 Runnable CheckDoubleRunnable=new Runnable(){ @Override public void run() { if (isDoublePress) { Log.i("oyp", "<----Single Press the Camera Key---->"); //显示为单击 }else{ Log.i("oyp", "<----Double Press the Camera Key---->"); //显示为双击 } isDoublePress=false;//500ms后在单击Camera键的话 依旧是单击 ,还是进入了 if语句块 } }; checkHandler.postDelayed(CheckDoubleRunnable, 500);// 500ms内两次单击,触发双击 } // 500ms内两次单击,触发双击 else{ isDoublePress=false;//双击后,将该值设为false,下次在单击Camera键的话 依旧是单击 ,还是进入了 if语句块 //设置在屏幕关闭情况下仍然可以使用NFC setNFC_POLLING_MODE(NFC_POLLING_MODE_SCREEN_OFF); applyRouting(true); Log.d("oyp", "2、NFC_POLLING_MODE="+getNFC_POLLING_MODE()); //使用AlarmManager来做定时 AlarmManager aManager = (AlarmManager)context.getSystemService(Service.ALARM_SERVICE); // 指定启动AlarmActivity组件 Intent nfcRestoreIntent = new Intent("com.runbo.camera.nfc.restore"); // 创建PendingIntent对象 PendingIntent pi = PendingIntent.getBroadcast(context, 0, nfcRestoreIntent, 0); //设定一个40分钟后的时间 Calendar calendar=Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.add(Calendar.MINUTE,TIME_TO_Restore_Default_Values); //先取消定时器 //aManager.cancel(pi); // 设置AlarmManager将在Calendar对应的时间启动指定组件 aManager.set(AlarmManager.RTC_WAKEUP,calendar.getTimeInMillis(), pi); SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date=new Date(); String time=sdf.format(date); Log.d("oyp", "time before="+time); // //10分钟后触发该线程,恢复原来值 // Runnable RestoreDefaultValues=new Runnable(){ // @Override // public void run() { // //设置在屏幕解锁情况下可以使用NFC // setNFC_POLLING_MODE(NFC_POLLING_MODE_DEFALUT); // applyRouting(true); // Log.d("oyp", "3、NFC_POLLING_MODE="+getNFC_POLLING_MODE()); // } // }; // restoreHandler.removeCallbacks(RestoreDefaultValues);//先取消定时器 // restoreHandler.postDelayed(RestoreDefaultValues, TIME_TO_Restore_Default_Values);//10分钟后恢复原来值 } } //added by ouyang end [2015-10-19]
将Handler的代码注释掉了,改用AlarmManager来做定时器,到达指定的时间后,发送一个“com.runbo.camera.nfc.restore”广播,这个广播也让该广播接收者来接收,因此动态注册广播的代码改成:
//如果是Hanbang的定制软件 if (android.os.SystemProperties.isHanbangVersion()) { //接收Camera物理键按下后松开,发出的广播 filter.addAction("com.runbo.camera.key.up"); //接收NFC恢复默认值的广播 filter.addAction("com.runbo.camera.nfc.restore"); } //added by ouyang end [2015-10-19]
因为之前使用秒来计时,现在使用分钟来计时,因此TIME_TO_Restore_Default_Values改成40,即40分钟
//从黑屏可用NFC恢复到要解锁才能用NFC的时间 static final int TIME_TO_Restore_Default_Values=40; //40分钟
====================================================================================
下面呈上修改后的代码和没修改的代码,经验证,完美!修改之后的代码如下:
修改之前的代码如下:
====================================================================================
作者:欧阳鹏 欢迎转载,与人分享是进步的源泉!
转载请保留原文地址:http://blog.csdn.net/ouyang_peng
====================================================================================