我们都知道在不修改源代码的情况下,只能是解锁之后才能使用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分钟
====================================================================================
下面呈上修改后的代码和没修改的代码,经验证,完美!修改之后的代码如下:
/*
* Copyright (C) 2014 MediaTek Inc.
* Modification based on code covered by the mentioned copyright
* and/or permission notice(s).
*/
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nfc;
import android.app.ActivityManager;
import android.app.Application;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.IPackageManager;
import an