diff options
47 files changed, 1450 insertions, 1215 deletions
@@ -1,3 +1,7 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + genrule { name: "statslog-Nfc-java-gen", tools: ["stats-log-api-gen"], diff --git a/AndroidManifest.xml b/AndroidManifest.xml index fba2509d..66a4a8a0 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -20,8 +20,11 @@ > <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> + <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <uses-permission android:name="android.permission.NFC" /> + <uses-permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" /> <uses-permission android:name="android.permission.BIND_NFC_SERVICE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.STATUS_BAR" /> @@ -47,7 +50,7 @@ <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER" /> <uses-permission android:name="android.permission.NFC_HANDOVER_STATUS" /> <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> - <uses-permission android:name="com.android.permission.WHITELIST_BLUETOOTH_DEVICE" /> + <uses-permission android:name="com.android.permission.ALLOWLIST_BLUETOOTH_DEVICE" /> <uses-permission android:name="android.permission.DISPATCH_NFC_MESSAGE" /> <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" /> <uses-permission android:name="android.permission.DEVICE_POWER" /> @@ -71,6 +74,7 @@ android:usesCleartextTraffic="false" android:supportsRtl="true" android:hardwareAccelerated="false" + android:memtagMode="async" > <meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAIbiKKs0wlimxeJ9y8iRIaBOH6aeb2IurmZyBHvg" /> @@ -121,6 +125,7 @@ android:theme="@android:style/Theme.Translucent" android:label="@string/android_beam" android:noHistory="true" + android:exported="true" android:excludeFromRecents="true"> <intent-filter> <action android:name="android.intent.action.SEND" /> @@ -147,7 +152,8 @@ android:process=":beam" /> - <receiver android:name=".NfcBootCompletedReceiver"> + <receiver android:name=".NfcBootCompletedReceiver" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> diff --git a/nci/jni/Android.bp b/nci/jni/Android.bp index 65d065de..def76fc7 100644 --- a/nci/jni/Android.bp +++ b/nci/jni/Android.bp @@ -1,3 +1,7 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_library_shared { name: "libnfc_nci_jni", @@ -48,5 +52,6 @@ cc_library_shared { sanitize: { integer_overflow: true, misc_undefined: ["bounds"], + scs: true, }, } diff --git a/nci/jni/HciEventManager.cpp b/nci/jni/HciEventManager.cpp index 0921f539..4aeaa6b0 100644 --- a/nci/jni/HciEventManager.cpp +++ b/nci/jni/HciEventManager.cpp @@ -16,6 +16,7 @@ #include "HciEventManager.h" #include <android-base/stringprintf.h> #include <base/logging.h> +#include <log/log.h> #include <nativehelper/ScopedLocalRef.h> #include "JavaClassConstants.h" #include "NfcJniUtil.h" @@ -148,7 +149,7 @@ void HciEventManager::nfaHciCallback(tNFA_HCI_EVT event, } else if (eventData->rcvd_evt.pipe == sSimPipe) { evtSrc = "SIM1"; } else { - LOG(ERROR) << "Incorrect Pipe Id"; + LOG(WARNING) << "Incorrect Pipe Id"; return; } @@ -159,20 +160,25 @@ void HciEventManager::nfaHciCallback(tNFA_HCI_EVT event, if (event == NFA_HCI_EVENT_RCVD_EVT && eventData->rcvd_evt.evt_code == NFA_HCI_EVT_TRANSACTION && buffLength > 3 && event_buff[0] == 0x81) { - int aidlen = event_buff[1]; - std::vector<uint8_t> aid(event_buff.begin() + 2, - event_buff.begin() + aidlen + 2); - - int32_t berTlvStart = aidlen + 2 + 1; - int32_t berTlvLen = buffLength - berTlvStart; - std::vector<uint8_t> data; - if (berTlvLen > 0 && event_buff[2 + aidlen] == 0x82) { - std::vector<uint8_t> berTlv(event_buff.begin() + berTlvStart, - event_buff.end()); - // BERTLV decoding here, to support extended data length for params. - data = getInstance().getDataFromBerTlv(berTlv); + uint32_t aidlen = event_buff[1]; + if (aidlen < (buffLength - 1)) { + std::vector<uint8_t> aid(event_buff.begin() + 2, + event_buff.begin() + aidlen + 2); + + int32_t berTlvStart = aidlen + 2 + 1; + int32_t berTlvLen = buffLength - berTlvStart; + std::vector<uint8_t> data; + if (berTlvLen > 0 && event_buff[2 + aidlen] == 0x82) { + std::vector<uint8_t> berTlv(event_buff.begin() + berTlvStart, + event_buff.end()); + // BERTLV decoding here, to support extended data length for params. + data = getInstance().getDataFromBerTlv(berTlv); + } + getInstance().notifyTransactionListenersOfAid(aid, data, evtSrc); + } else { + android_errorWriteLog(0x534e4554, "181346545"); + LOG(ERROR) << StringPrintf("error: aidlen(%d) is too big", aidlen); } - getInstance().notifyTransactionListenersOfAid(aid, data, evtSrc); } } diff --git a/nci/jni/NativeLlcpConnectionlessSocket.cpp b/nci/jni/NativeLlcpConnectionlessSocket.cpp index e43dbff3..7506d80e 100644 --- a/nci/jni/NativeLlcpConnectionlessSocket.cpp +++ b/nci/jni/NativeLlcpConnectionlessSocket.cpp @@ -108,7 +108,7 @@ void nativeLlcpConnectionlessSocket_receiveData(uint8_t* data, uint32_t len, << StringPrintf("%s: waiting for data = %d, len = %d", __func__, sConnlessRecvWaitingForData, len); - // Sanity... + // Quick check... if (sConnlessRecvLen < len) { len = sConnlessRecvLen; } diff --git a/nci/jni/NativeNfcManager.cpp b/nci/jni/NativeNfcManager.cpp index 5048262c..28ae464a 100644 --- a/nci/jni/NativeNfcManager.cpp +++ b/nci/jni/NativeNfcManager.cpp @@ -18,6 +18,7 @@ #include <base/logging.h> #include <cutils/properties.h> #include <errno.h> +#include <nativehelper/JNIPlatformHelp.h> #include <nativehelper/ScopedLocalRef.h> #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> @@ -84,6 +85,8 @@ bool gActivated = false; SyncEvent gDeactivatedEvent; SyncEvent sNfaSetPowerSubState; bool legacy_mfc_reader = true; +int recovery_option = 0; +int nfcee_power_and_link_conf = 0; namespace android { jmethodID gCachedNfcManagerNotifyNdefMessageListeners; @@ -97,6 +100,7 @@ jmethodID gCachedNfcManagerNotifyHostEmuDeactivated; jmethodID gCachedNfcManagerNotifyRfFieldActivated; jmethodID gCachedNfcManagerNotifyRfFieldDeactivated; jmethodID gCachedNfcManagerNotifyEeUpdated; +jmethodID gCachedNfcManagerNotifyHwErrorReported; const char* gNativeP2pDeviceClassName = "com/android/nfc/dhimpl/NativeP2pDevice"; const char* gNativeLlcpServiceSocketClassName = @@ -141,6 +145,7 @@ static bool sP2pActive = false; // whether p2p was last active static bool sAbortConnlessWait = false; static jint sLfT3tMax = 0; static bool sRoutingInitialized = false; +static bool sIsRecovering = false; #define CONFIG_UPDATE_TECH_MASK (1 << 1) #define DEFAULT_TECH_MASK \ @@ -191,13 +196,28 @@ void initializeGlobalDebugEnabledFlag() { } void initializeMfcReaderOption() { legacy_mfc_reader = - (NfcConfig::getUnsigned(NAME_LEGACY_MIFARE_READER, 0) != 0) ? true - : false; + (NfcConfig::getUnsigned(NAME_LEGACY_MIFARE_READER, 0) != 0) ? true : false; DLOG_IF(INFO, nfc_debug_enabled) << __func__ <<": mifare reader option=" << legacy_mfc_reader; } +void initializeRecoveryOption() { + recovery_option = NfcConfig::getUnsigned(NAME_RECOVERY_OPTION, 0); + + DLOG_IF(INFO, nfc_debug_enabled) + << __func__ << ": recovery option=" << recovery_option; +} + +void initializeNfceePowerAndLinkConf() { + nfcee_power_and_link_conf = + NfcConfig::getUnsigned(NAME_ALWAYS_ON_SET_EE_POWER_AND_LINK_CONF, 0); + + DLOG_IF(INFO, nfc_debug_enabled) + << __func__ << ": Always on set NFCEE_POWER_AND_LINK_CONF=" + << nfcee_power_and_link_conf; +} + } // namespace /******************************************************************************* @@ -660,6 +680,8 @@ static void nfaConnectionCallback(uint8_t connEvent, static jboolean nfcManager_initNativeStruc(JNIEnv* e, jobject o) { initializeGlobalDebugEnabledFlag(); initializeMfcReaderOption(); + initializeRecoveryOption(); + initializeNfceePowerAndLinkConf(); DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: enter", __func__); nfc_jni_native_data* nat = @@ -712,6 +734,9 @@ static jboolean nfcManager_initNativeStruc(JNIEnv* e, jobject o) { gCachedNfcManagerNotifyEeUpdated = e->GetMethodID(cls.get(), "notifyEeUpdated", "()V"); + gCachedNfcManagerNotifyHwErrorReported = + e->GetMethodID(cls.get(), "notifyHwErrorReported", "()V"); + if (nfc_jni_cache_object(e, gNativeNfcTagClassName, &(nat->cached_NfcTag)) == -1) { LOG(ERROR) << StringPrintf("%s: fail cache NativeNfcTag", __func__); @@ -822,46 +847,97 @@ void nfaDeviceManagementCallback(uint8_t dmEvent, LOG(ERROR) << StringPrintf("%s: NFA_DM_NFCC_TRANSPORT_ERR_EVT; abort", __func__); - nativeNfcTag_abortWaits(); - NfcTag::getInstance().abort(); - sAbortConnlessWait = true; - nativeLlcpConnectionlessSocket_abortWait(); - { - DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( - "%s: aborting sNfaEnableDisablePollingEvent", __func__); - SyncEventGuard guard(sNfaEnableDisablePollingEvent); - sNfaEnableDisablePollingEvent.notifyOne(); - } - { - DLOG_IF(INFO, nfc_debug_enabled) - << StringPrintf("%s: aborting sNfaEnableEvent", __func__); - SyncEventGuard guard(sNfaEnableEvent); - sNfaEnableEvent.notifyOne(); - } - { - DLOG_IF(INFO, nfc_debug_enabled) - << StringPrintf("%s: aborting sNfaDisableEvent", __func__); - SyncEventGuard guard(sNfaDisableEvent); - sNfaDisableEvent.notifyOne(); - } - sDiscoveryEnabled = false; - sPollingEnabled = false; - PowerSwitch::getInstance().abort(); - - if (!sIsDisabling && sIsNfaEnabled) { - EXTNS_Close(); - NFA_Disable(FALSE); - sIsDisabling = true; + if (recovery_option) { + struct nfc_jni_native_data* nat = getNative(NULL, NULL); + JNIEnv* e = NULL; + ScopedAttach attach(nat->vm, &e); + if (e == NULL) { + LOG(ERROR) << StringPrintf("jni env is null"); + return; + } + LOG(ERROR) << StringPrintf("%s: toggle NFC state to recovery nfc", + __func__); + sIsRecovering = true; + e->CallVoidMethod(nat->manager, + android::gCachedNfcManagerNotifyHwErrorReported); + { + DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( + "%s: aborting sNfaEnableDisablePollingEvent", __func__); + SyncEventGuard guard(sNfaEnableDisablePollingEvent); + sNfaEnableDisablePollingEvent.notifyOne(); + } + { + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s: aborting sNfaEnableEvent", __func__); + SyncEventGuard guard(sNfaEnableEvent); + sNfaEnableEvent.notifyOne(); + } + { + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s: aborting sNfaDisableEvent", __func__); + SyncEventGuard guard(sNfaDisableEvent); + sNfaDisableEvent.notifyOne(); + } + { + DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( + "%s: aborting sNfaSetPowerSubState", __func__); + SyncEventGuard guard(sNfaSetPowerSubState); + sNfaSetPowerSubState.notifyOne(); + } + { + DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( + "%s: aborting sNfaSetConfigEvent", __func__); + SyncEventGuard guard(sNfaSetConfigEvent); + sNfaSetConfigEvent.notifyOne(); + } + { + DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( + "%s: aborting sNfaGetConfigEvent", __func__); + SyncEventGuard guard(sNfaGetConfigEvent); + sNfaGetConfigEvent.notifyOne(); + } } else { - sIsNfaEnabled = false; - sIsDisabling = false; + nativeNfcTag_abortWaits(); + NfcTag::getInstance().abort(); + sAbortConnlessWait = true; + nativeLlcpConnectionlessSocket_abortWait(); + { + DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( + "%s: aborting sNfaEnableDisablePollingEvent", __func__); + SyncEventGuard guard(sNfaEnableDisablePollingEvent); + sNfaEnableDisablePollingEvent.notifyOne(); + } + { + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s: aborting sNfaEnableEvent", __func__); + SyncEventGuard guard(sNfaEnableEvent); + sNfaEnableEvent.notifyOne(); + } + { + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s: aborting sNfaDisableEvent", __func__); + SyncEventGuard guard(sNfaDisableEvent); + sNfaDisableEvent.notifyOne(); + } + sDiscoveryEnabled = false; + sPollingEnabled = false; + PowerSwitch::getInstance().abort(); + + if (!sIsDisabling && sIsNfaEnabled) { + EXTNS_Close(); + NFA_Disable(FALSE); + sIsDisabling = true; + } else { + sIsNfaEnabled = false; + sIsDisabling = false; + } + PowerSwitch::getInstance().initialize(PowerSwitch::UNKNOWN_LEVEL); + LOG(ERROR) << StringPrintf("%s: crash NFC service", __func__); + ////////////////////////////////////////////// + // crash the NFC service process so it can restart automatically + abort(); + ////////////////////////////////////////////// } - PowerSwitch::getInstance().initialize(PowerSwitch::UNKNOWN_LEVEL); - LOG(ERROR) << StringPrintf("%s: crash NFC service", __func__); - ////////////////////////////////////////////// - // crash the NFC service process so it can restart automatically - abort(); - ////////////////////////////////////////////// } break; case NFA_DM_PWR_MODE_CHANGE_EVT: @@ -917,7 +993,7 @@ static jboolean nfcManager_sendRawFrame(JNIEnv* e, jobject, jbyteArray data) { ** *******************************************************************************/ static jboolean nfcManager_routeAid(JNIEnv* e, jobject, jbyteArray aid, - jint route, jint aidInfo) { + jint route, jint aidInfo, jint power) { uint8_t* buf; size_t bufLen; @@ -925,13 +1001,13 @@ static jboolean nfcManager_routeAid(JNIEnv* e, jobject, jbyteArray aid, buf = NULL; bufLen = 0; return RoutingManager::getInstance().addAidRouting(buf, bufLen, route, - aidInfo); + aidInfo, power); } ScopedByteArrayRO bytes(e, aid); buf = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(&bytes[0])); bufLen = bytes.size(); return RoutingManager::getInstance().addAidRouting(buf, bufLen, route, - aidInfo); + aidInfo, power); } /******************************************************************************* @@ -1068,6 +1144,7 @@ static jint nfcManager_getLfT3tMax(JNIEnv*, jobject) { static jboolean nfcManager_doInitialize(JNIEnv* e, jobject o) { initializeGlobalDebugEnabledFlag(); tNFA_STATUS stat = NFA_STATUS_OK; + sIsRecovering = false; PowerSwitch& powerSwitch = PowerSwitch::getInstance(); @@ -1296,6 +1373,20 @@ static void nfcManager_enableDiscovery(JNIEnv* e, jobject o, } } } else { + /* enable_p2p=> request to enable p2p, P2pEnabled=> current state of p2p */ + if (enable_p2p && !sP2pEnabled) { + sP2pEnabled = true; + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s: Enable p2pListening", __func__); + PeerToPeer::getInstance().enableP2pListening(true); + NFA_ResumeP2p(); + } else if (!enable_p2p && sP2pEnabled) { + sP2pEnabled = false; + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s: Disable p2pListening", __func__); + PeerToPeer::getInstance().enableP2pListening(false); + NFA_PauseP2p(); + } // No technologies configured, stop polling stopPolling_rfDiscoveryDisabled(); } @@ -1472,7 +1563,9 @@ static jboolean nfcManager_doDeinitialize(JNIEnv*, jobject) { sIsDisabling = true; - RoutingManager::getInstance().onNfccShutdown(); + if (!recovery_option || !sIsRecovering) { + RoutingManager::getInstance().onNfccShutdown(); + } PowerSwitch::getInstance().initialize(PowerSwitch::UNKNOWN_LEVEL); HciEventManager::getInstance().finalize(); @@ -1811,6 +1904,13 @@ static void nfcManager_doSetScreenState(JNIEnv* e, jobject o, prevScreenState = state; return; } + + // skip remaining SetScreenState tasks when trying to silent recover NFCC + if (recovery_option && sIsRecovering) { + prevScreenState = state; + return; + } + if (prevScreenState == NFA_SCREEN_STATE_OFF_LOCKED || prevScreenState == NFA_SCREEN_STATE_OFF_UNLOCKED || prevScreenState == NFA_SCREEN_STATE_ON_LOCKED) { @@ -1825,6 +1925,12 @@ static void nfcManager_doSetScreenState(JNIEnv* e, jobject o, } } + // skip remaining SetScreenState tasks when trying to silent recover NFCC + if (recovery_option && sIsRecovering) { + prevScreenState = state; + return; + } + if (state == NFA_SCREEN_STATE_OFF_LOCKED || state == NFA_SCREEN_STATE_OFF_UNLOCKED) { // disable poll and enable listen on DH 0x00 @@ -1857,6 +1963,12 @@ static void nfcManager_doSetScreenState(JNIEnv* e, jobject o, return; } + // skip remaining SetScreenState tasks when trying to silent recover NFCC + if (recovery_option && sIsRecovering) { + prevScreenState = state; + return; + } + if (prevScreenState == NFA_SCREEN_STATE_ON_UNLOCKED) { SyncEventGuard guard(sNfaSetPowerSubState); status = NFA_SetPowerSubStateForScreenState(state); @@ -1867,10 +1979,18 @@ static void nfcManager_doSetScreenState(JNIEnv* e, jobject o, sNfaSetPowerSubState.wait(); } } + + // skip remaining SetScreenState tasks when trying to silent recover NFCC + if (recovery_option && sIsRecovering) { + prevScreenState = state; + return; + } + if ((state == NFA_SCREEN_STATE_OFF_LOCKED || state == NFA_SCREEN_STATE_OFF_UNLOCKED) && - prevScreenState == NFA_SCREEN_STATE_ON_UNLOCKED && (!sP2pActive) && - (!sSeRfActive)) { + (prevScreenState == NFA_SCREEN_STATE_ON_UNLOCKED || + prevScreenState == NFA_SCREEN_STATE_ON_LOCKED) && + (!sP2pActive) && (!sSeRfActive)) { // screen turns off, disconnect tag if connected nativeNfcTag_doDisconnect(NULL, NULL); } @@ -1976,6 +2096,23 @@ static jint nfcManager_getAidTableSize(JNIEnv*, jobject) { return NFA_GetAidTableSize(); } +/******************************************************************************* +** +** Function: nfcManager_doStartStopPolling +** +** Description: Start or stop NFC RF polling +** e: JVM environment. +** o: Java object. +** start: start or stop RF polling +** +** Returns: None +** +*******************************************************************************/ +static void nfcManager_doStartStopPolling(JNIEnv* e, jobject o, + jboolean start) { + startStopPolling(start); +} + static jboolean nfcManager_doSetNfcSecure(JNIEnv* e, jobject o, jboolean enable) { RoutingManager& routingManager = RoutingManager::getInstance(); @@ -1996,6 +2133,17 @@ static jstring nfcManager_doGetNfaStorageDir(JNIEnv* e, jobject o) { string nfaStorageDir = NfcConfig::getString(NAME_NFA_STORAGE, "/data/nfc"); return e->NewStringUTF(nfaStorageDir.c_str()); } + +static void nfcManager_doSetNfceePowerAndLinkCtrl(JNIEnv* e, jobject o, + jboolean enable) { + RoutingManager& routingManager = RoutingManager::getInstance(); + if (enable) { + routingManager.eeSetPwrAndLinkCtrl((uint8_t)nfcee_power_and_link_conf); + } else { + routingManager.eeSetPwrAndLinkCtrl(0); + } +} + /***************************************************************************** ** ** JNI functions for android-4.0.1_r1 @@ -2012,7 +2160,7 @@ static JNINativeMethod gMethods[] = { {"sendRawFrame", "([B)Z", (void*)nfcManager_sendRawFrame}, - {"routeAid", "([BII)Z", (void*)nfcManager_routeAid}, + {"routeAid", "([BIII)Z", (void*)nfcManager_routeAid}, {"unrouteAid", "([B)Z", (void*)nfcManager_unrouteAid}, @@ -2028,6 +2176,8 @@ static JNINativeMethod gMethods[] = { {"doEnableDiscovery", "(IZZZZZ)V", (void*)nfcManager_enableDiscovery}, + {"doStartStopPolling", "(Z)V", (void*)nfcManager_doStartStopPolling}, + {"doCheckLlcp", "()Z", (void*)nfcManager_doCheckLlcp}, {"doActivateLlcp", "()Z", (void*)nfcManager_doActivateLlcp}, @@ -2086,6 +2236,9 @@ static JNINativeMethod gMethods[] = { {"getNfaStorageDir", "()Ljava/lang/String;", (void*)nfcManager_doGetNfaStorageDir}, + + {"doSetNfceePowerAndLinkCtrl", "(Z)V", + (void*)nfcManager_doSetNfceePowerAndLinkCtrl}, }; /******************************************************************************* @@ -2204,16 +2357,36 @@ bool nfcManager_isNfcActive() { return sIsNfaEnabled; } ** *******************************************************************************/ void startStopPolling(bool isStartPolling) { + tNFA_STATUS status = NFA_STATUS_FAILED; + uint8_t discovry_param = 0; DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: enter; isStart=%u", __func__, isStartPolling); - startRfDiscovery(false); - if (isStartPolling) - startPolling_rfDiscoveryDisabled(0); - else - stopPolling_rfDiscoveryDisabled(); - - startRfDiscovery(true); + if (NFC_GetNCIVersion() >= NCI_VERSION_2_0) { + SyncEventGuard guard(sNfaSetConfigEvent); + if (isStartPolling) { + discovry_param = + NCI_LISTEN_DH_NFCEE_ENABLE_MASK | NCI_POLLING_DH_ENABLE_MASK; + } else { + discovry_param = + NCI_LISTEN_DH_NFCEE_ENABLE_MASK | NCI_POLLING_DH_DISABLE_MASK; + } + status = NFA_SetConfig(NCI_PARAM_ID_CON_DISCOVERY_PARAM, + NCI_PARAM_LEN_CON_DISCOVERY_PARAM, &discovry_param); + if (status == NFA_STATUS_OK) { + sNfaSetConfigEvent.wait(); + } else { + LOG(ERROR) << StringPrintf("%s: Failed to update CON_DISCOVER_PARAM", + __FUNCTION__); + } + } else { + startRfDiscovery(false); + if (isStartPolling) + startPolling_rfDiscoveryDisabled(0); + else + stopPolling_rfDiscoveryDisabled(); + startRfDiscovery(true); + } DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: exit", __func__); } diff --git a/nci/jni/NativeNfcTag.cpp b/nci/jni/NativeNfcTag.cpp index ddae94e9..cc167e67 100644 --- a/nci/jni/NativeNfcTag.cpp +++ b/nci/jni/NativeNfcTag.cpp @@ -581,11 +581,14 @@ static jint nativeNfcTag_doConnect(JNIEnv*, jobject, jint targetHandle) { if (sCurrentConnectedTargetType == TARGET_TYPE_ISO14443_3A || sCurrentConnectedTargetType == TARGET_TYPE_ISO14443_3B) { - DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( + + if (sCurrentConnectedTargetProtocol != NFC_PROTOCOL_MIFARE) { + DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf( "%s: switching to tech: %d need to switch rf intf to frame", __func__, sCurrentConnectedTargetType); - retCode = switchRfInterface(NFA_INTERFACE_FRAME) ? NFA_STATUS_OK - : NFA_STATUS_FAILED; + retCode = switchRfInterface(NFA_INTERFACE_FRAME) ? NFA_STATUS_OK + : NFA_STATUS_FAILED; + } } else if (sCurrentConnectedTargetType == TARGET_TYPE_MIFARE_CLASSIC) { retCode = switchRfInterface(NFA_INTERFACE_MIFARE) ? NFA_STATUS_OK : NFA_STATUS_FAILED; @@ -863,7 +866,7 @@ jboolean nativeNfcTag_doDisconnect(JNIEnv*, jobject) { NfcTag::getInstance().resetAllTransceiveTimeouts(); if (NfcTag::getInstance().getActivationState() != NfcTag::Active) { - LOG(ERROR) << StringPrintf("%s: tag already deactivated", __func__); + LOG(WARNING) << StringPrintf("%s: tag already deactivated", __func__); goto TheEnd; } diff --git a/nci/jni/PeerToPeer.cpp b/nci/jni/PeerToPeer.cpp index ac51dd93..75027d87 100644 --- a/nci/jni/PeerToPeer.cpp +++ b/nci/jni/PeerToPeer.cpp @@ -973,7 +973,7 @@ bool PeerToPeer::disconnectConnOriented(tJNI_HANDLE jniHandle) { return (false); } - // If this is a client, he may not be connected yet, so unblock him just in + // If this is a client, it may not be connected yet, so unblock it just in // case if (((pClient = findClient(jniHandle)) != NULL) && (pClient->mIsConnecting)) { SyncEventGuard guard(pClient->mConnectingEvent); diff --git a/nci/jni/RoutingManager.cpp b/nci/jni/RoutingManager.cpp index 93ba9e06..4ea3cf0d 100755..100644 --- a/nci/jni/RoutingManager.cpp +++ b/nci/jni/RoutingManager.cpp @@ -299,12 +299,17 @@ void RoutingManager::disableRoutingToHost() { } bool RoutingManager::addAidRouting(const uint8_t* aid, uint8_t aidLen, - int route, int aidInfo) { + int route, int aidInfo, int power) { static const char fn[] = "RoutingManager::addAidRouting"; DLOG_IF(INFO, nfc_debug_enabled) << fn << ": enter"; uint8_t powerState = 0x01; if (!mSecureNfcEnabled) { - powerState = (route != 0x00) ? mOffHostAidRoutingPowerState : 0x11; + if (power == 0x00) { + powerState = (route != 0x00) ? mOffHostAidRoutingPowerState : 0x11; + } else { + powerState = + (route != 0x00) ? mOffHostAidRoutingPowerState & power : power; + } } SyncEventGuard guard(mRoutingEvent); mAidRoutingConfigured = false; @@ -335,7 +340,7 @@ bool RoutingManager::removeAidRouting(const uint8_t* aid, uint8_t aidLen) { DLOG_IF(INFO, nfc_debug_enabled) << fn << ": removed AID"; return true; } else { - LOG(ERROR) << fn << ": failed to remove AID"; + LOG(WARNING) << fn << ": failed to remove AID"; return false; } } @@ -886,6 +891,13 @@ void RoutingManager::nfaEeCallback(tNFA_EE_EVT event, routingManager.mEeUpdateEvent.notifyOne(); } break; + case NFA_EE_PWR_AND_LINK_CTRL_EVT: { + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s: NFA_EE_PWR_AND_LINK_CTRL_EVT", fn); + SyncEventGuard guard(routingManager.mEePwrAndLinkCtrlEvent); + routingManager.mEePwrAndLinkCtrlEvent.notifyOne(); + } break; + default: DLOG_IF(INFO, nfc_debug_enabled) << StringPrintf("%s: unknown event=%u ????", fn, event); @@ -1040,6 +1052,40 @@ bool RoutingManager::setNfcSecure(bool enable) { return true; } +/******************************************************************************* +** +** Function: eeSetPwrAndLinkCtrl +** +** Description: Programs the NCI command NFCEE_POWER_AND_LINK_CTRL_CMD +** +** Returns: None +** +*******************************************************************************/ +void RoutingManager::eeSetPwrAndLinkCtrl(uint8_t config) { + static const char fn[] = "RoutingManager::eeSetPwrAndLinkCtrl"; + tNFA_STATUS status = NFA_STATUS_OK; + + if (mOffHostRouteEse.size() > 0) { + DLOG_IF(INFO, nfc_debug_enabled) + << StringPrintf("%s - nfceeId: 0x%02X, config: 0x%02X", fn, + mOffHostRouteEse[0], config); + + SyncEventGuard guard(mEePwrAndLinkCtrlEvent); + status = + NFA_EePowerAndLinkCtrl( + ((uint8_t)mOffHostRouteEse[0] | NFA_HANDLE_GROUP_EE), config); + if (status != NFA_STATUS_OK) { + LOG(ERROR) << StringPrintf("%s: fail NFA_EePowerAndLinkCtrl; error=0x%X", + __FUNCTION__, status); + return; + } else { + mEePwrAndLinkCtrlEvent.wait(); + } + } else { + LOG(ERROR) << StringPrintf("%s: No ESE specified", __FUNCTION__); + } +} + void RoutingManager::deinitialize() { onNfccShutdown(); NFA_EeDeregister(nfaEeCallback); diff --git a/nci/jni/RoutingManager.h b/nci/jni/RoutingManager.h index 51ef4f42..92815bd8 100755 --- a/nci/jni/RoutingManager.h +++ b/nci/jni/RoutingManager.h @@ -36,8 +36,8 @@ class RoutingManager { void deinitialize(); void enableRoutingToHost(); void disableRoutingToHost(); - bool addAidRouting(const uint8_t* aid, uint8_t aidLen, int route, - int aidInfo); + bool addAidRouting(const uint8_t* aid, uint8_t aidLen, int route, int aidInfo, + int power); bool removeAidRouting(const uint8_t* aid, uint8_t aidLen); bool commitRouting(); int registerT3tIdentifier(uint8_t* t3tId, uint8_t t3tIdLen); @@ -46,6 +46,7 @@ class RoutingManager { int registerJniFunctions(JNIEnv* e); bool setNfcSecure(bool enable); void updateRoutingTable(); + void eeSetPwrAndLinkCtrl(uint8_t config); private: RoutingManager(); @@ -121,4 +122,5 @@ class RoutingManager { SyncEvent mEeUpdateEvent; SyncEvent mEeInfoEvent; SyncEvent mEeSetModeEvent; + SyncEvent mEePwrAndLinkCtrlEvent; }; diff --git a/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java b/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java index 027f4ff0..af9484cf 100755 --- a/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java +++ b/nci/src/com/android/nfc/dhimpl/NativeNfcManager.java @@ -130,7 +130,7 @@ public class NativeNfcManager implements DeviceHost { public native boolean sendRawFrame(byte[] data); @Override - public native boolean routeAid(byte[] aid, int route, int aidInfo); + public native boolean routeAid(byte[] aid, int route, int aidInfo, int power); @Override public native boolean unrouteAid(byte[] aid); @@ -392,6 +392,18 @@ public class NativeNfcManager implements DeviceHost { @Override public native String getNfaStorageDir(); + private native void doStartStopPolling(boolean start); + @Override + public void startStopPolling(boolean start) { + doStartStopPolling(start); + } + + private native void doSetNfceePowerAndLinkCtrl(boolean enable); + @Override + public void setNfceePowerAndLinkCtrl(boolean enable) { + doSetNfceePowerAndLinkCtrl(enable); + } + /** * Notifies Ndef Message (TODO: rename into notifyTargetDiscovered) */ @@ -447,4 +459,8 @@ public class NativeNfcManager implements DeviceHost { private void notifyEeUpdated() { mListener.onEeUpdated(); } + + private void notifyHwErrorReported() { + mListener.onHwErrorReported(); + } } diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index bfa8b03f..884a68c1 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -42,7 +42,7 @@ <string name="prompt_connect_to_network" msgid="8511683573657516114">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> নেটৱৰ্কৰ লগত সংযোগ কৰিবনে?"</string> <string name="beam_requires_nfc_enabled" msgid="2800366967218600534">"Android বীমে NFC সক্ষম কৰিব লাগিব৷ আপুনি ইয়াক সক্ষম কৰিবলৈ বিচাৰেনে?"</string> <string name="android_beam" msgid="1666446406999492763">"Android বীম"</string> - <string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"এপ্লিকেশ্বনটোৰ বাহ্যিক সঞ্চয়াগাৰ ব্যৱহাৰ কৰাৰ অনুমতি নাই৷ এই ফাইলটো বীম কৰিবলৈ এয়া প্ৰয়োজন।"</string> + <string name="beam_requires_external_storage_permission" msgid="8798337545702206901">"এপ্লিকেশ্বনটোৰ বাহ্যিক ষ্ট’ৰেজ ব্যৱহাৰ কৰাৰ অনুমতি নাই৷ এই ফাইলটো বীম কৰিবলৈ এয়া প্ৰয়োজন।"</string> <string name="title_confirm_url_open" msgid="8069968913244794478">"লিংক খুলিবনে?"</string> <string name="summary_confirm_url_open" product="tablet" msgid="3353502750736192055">"আপোনাৰ টেবলেটে NFC যোগে এই লিংক পাইছে:"</string> <string name="summary_confirm_url_open" product="default" msgid="1246398412196449226">"আপোনাৰ ফ\'নে NFC যোগে এই লিংক পাইছে:"</string> diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index 0c4b54e5..7151f0ea 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -21,7 +21,7 @@ <string name="connect_peripheral_failed" msgid="7925702596242839275">"Չհաջողվեց կապակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը"</string> <string name="disconnecting_peripheral" msgid="1443699384809097200">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի կապախզում"</string> <string name="disconnected_peripheral" msgid="4470578100296504366">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը կապախզված է"</string> - <string name="pairing_peripheral" msgid="6983626861540899365">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի զուգավորում"</string> + <string name="pairing_peripheral" msgid="6983626861540899365">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի զուգակցում"</string> <string name="pairing_peripheral_failed" msgid="6087643307743264679">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g> սարքը"</string> <string name="failed_to_enable_bt" msgid="7229153323594758077">"Չհաջողվեց միացնել Bluetooth-ը"</string> <string name="confirm_pairing" msgid="4112568077038265363">"Իսկապե՞ս ուզում եք զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g> Bluetooth սարքը:"</string> diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index e0c77612..0fca22c0 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -33,7 +33,7 @@ <string name="could_not_use_app" msgid="8137587876138569083">"לא ניתן היה להשתמש ב-<xliff:g id="APP">%1$s</xliff:g>."</string> <string name="pay_with" msgid="5531545488795798945">"תשלום באמצעות"</string> <string name="complete_with" msgid="6797459104103012992">"השלמה באמצעות"</string> - <string name="default_pay_app_removed" msgid="4108250545457437360">"השירות המועדף עליך עבור \'הקשה לתשלום\' הוסר. האם לבחור אחר?"</string> + <string name="default_pay_app_removed" msgid="4108250545457437360">"השירות המועדף עליך עבור \'תשלום בקליק\' הוסר. האם לבחור אחר?"</string> <string name="ask_nfc_tap" msgid="2925239870458286340">"יש להקיש על מכשיר אחר להשלמה"</string> <string name="wifi_connect" msgid="6250727951843550671">"התחברות"</string> <string name="status_unable_to_connect" msgid="9183908200295307657">"לא ניתן להתחבר לרשת"</string> diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index 6c5b066e..3081a349 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -12,7 +12,7 @@ <string name="beam_canceled" msgid="5425192751826544741">"பீம் ரத்து செய்யப்பட்டது"</string> <string name="cancel" msgid="61873902552555096">"ரத்துசெய்"</string> <string name="beam_tap_to_view" msgid="7430394753262448349">"பார்க்க, தட்டவும்"</string> - <string name="beam_handover_not_supported" msgid="4083165921751489015">"பெறுநரின் சாதனம் பீம் வழியான பெரிய கோப்புப் பரிமாற்றத்தை ஆதரிக்கவில்லை."</string> + <string name="beam_handover_not_supported" msgid="4083165921751489015">"பெறுநரின் சாதனம் பீம் வழியான பெரிய ஃபைல் பரிமாற்றத்தை ஆதரிக்கவில்லை."</string> <string name="beam_try_again" msgid="3364677301009783455">"மீண்டும் சாதனங்களை ஒன்றாகக் கொண்டுவரவும்"</string> <string name="beam_busy" msgid="5253335587620612576">"பீம் தற்போது பணிமிகுதியில் உள்ளது. முந்தைய இடமாற்றம் முடியும்போது, மீண்டும் முயலவும்."</string> <string name="device" msgid="4459621591392478151">"சாதனம்"</string> diff --git a/res/values/config.xml b/res/values/config.xml index c3501816..e60d3162 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -7,6 +7,7 @@ <bool name="enable_notify_read_failed">false</bool> <bool name="enable_antenna_blocked_alert">false</bool> <bool name="polling_disable_allowed">false</bool> + <bool name="nfcc_always_on_allowed">false</bool> <integer name="max_antenna_blocked_failure_count">10</integer> <integer name="toast_debounce_time_ms">3000</integer> <integer name="unknown_tag_polling_delay">2000</integer> diff --git a/src/com/android/nfc/DeviceHost.java b/src/com/android/nfc/DeviceHost.java index 43722ab6..4ffbe824 100644 --- a/src/com/android/nfc/DeviceHost.java +++ b/src/com/android/nfc/DeviceHost.java @@ -51,6 +51,8 @@ public interface DeviceHost { public void onNfcTransactionEvent(byte[] aid, byte[] data, String seName); public void onEeUpdated(); + + public void onHwErrorReported(); } public interface TagEndpoint { @@ -188,7 +190,7 @@ public interface DeviceHost { public boolean sendRawFrame(byte[] data); - public boolean routeAid(byte[] aid, int route, int aidInfo); + public boolean routeAid(byte[] aid, int route, int aidInfo, int power); public boolean unrouteAid(byte[] aid); @@ -260,4 +262,14 @@ public interface DeviceHost { public boolean setNfcSecure(boolean enable); public String getNfaStorageDir(); + + /** + * Start or stop RF polling + */ + void startStopPolling(boolean enable); + + /** + * Set NFCC power state by sending NFCEE_POWER_AND_LINK_CNTRL_CMD + */ + void setNfceePowerAndLinkCtrl(boolean enable); } diff --git a/src/com/android/nfc/NfcDispatcher.java b/src/com/android/nfc/NfcDispatcher.java index 0f46087d..b22d49d9 100644 --- a/src/com/android/nfc/NfcDispatcher.java +++ b/src/com/android/nfc/NfcDispatcher.java @@ -338,7 +338,7 @@ class NfcDispatcher { // We only allow NDEF-message dispatch in provisioning mode return DISPATCH_FAIL; } - // Restrict to mime-types in whitelist. + // Restrict to mime-types in allowlist. String ndefMimeType = message.getRecords()[0].toMimeType(); if (provisioningMimes == null || !(Arrays.asList(provisioningMimes).contains(ndefMimeType))) { diff --git a/src/com/android/nfc/NfcPermissions.java b/src/com/android/nfc/NfcPermissions.java index c4528a99..6ebb939f 100644 --- a/src/com/android/nfc/NfcPermissions.java +++ b/src/com/android/nfc/NfcPermissions.java @@ -26,6 +26,14 @@ public class NfcPermissions { private static final String NFC_PREFERRED_PAYMENT_INFO_PERM_ERROR = "NFC_PREFERRED_PAYMENT_INFO permission required"; + /** + * NFC SET CONTROLLER ALWAYS ON permission + */ + static final String NFC_SET_CONTROLLER_ALWAYS_ON = + android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON; + private static final String NFC_SET_CONTROLLER_ALWAYS_ON_ERROR = + "NFC_SET_CONTROLLER_ALWAYS_ON permission required"; + public static void validateUserId(int userId) { if (userId != UserHandle.getCallingUserId()) { throw new SecurityException("userId passed in is not the calling user."); @@ -45,4 +53,12 @@ public class NfcPermissions { context.enforceCallingOrSelfPermission(NFC_PREFERRED_PAYMENT_INFO_PERMISSION, NFC_PREFERRED_PAYMENT_INFO_PERM_ERROR); } + + /** + * Permission check for android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON + */ + public static void enforceSetControllerAlwaysOnPermissions(Context context) { + context.enforceCallingOrSelfPermission(NFC_SET_CONTROLLER_ALWAYS_ON, + NFC_SET_CONTROLLER_ALWAYS_ON_ERROR); + } } diff --git a/src/com/android/nfc/NfcRootActivity.java b/src/com/android/nfc/NfcRootActivity.java index cc216f2e..b0b340f5 100644 --- a/src/com/android/nfc/NfcRootActivity.java +++ b/src/com/android/nfc/NfcRootActivity.java @@ -37,6 +37,7 @@ public class NfcRootActivity extends Activity { try { startActivityAsUser(launchIntent, UserHandle.CURRENT); } catch (ActivityNotFoundException e) { + } catch (SecurityException e) { } } } diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java index 837940cf..633b8f46 100644 --- a/src/com/android/nfc/NfcService.java +++ b/src/com/android/nfc/NfcService.java @@ -46,6 +46,7 @@ import android.nfc.IAppCallback; import android.nfc.INfcAdapter; import android.nfc.INfcAdapterExtras; import android.nfc.INfcCardEmulation; +import android.nfc.INfcControllerAlwaysOnListener; import android.nfc.INfcDta; import android.nfc.INfcFCardEmulation; import android.nfc.INfcTag; @@ -106,11 +107,14 @@ import java.nio.ByteBuffer; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Scanner; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -120,6 +124,8 @@ public class NfcService implements DeviceHostListener { public static final String SERVICE_NAME = "nfc"; + private static final String SYSTEM_UI = "com.android.systemui"; + public static final String PREF = "NfcServicePrefs"; static final String PREF_NFC_ON = "nfc_on"; @@ -139,6 +145,7 @@ public class NfcService implements DeviceHostListener { static final String TRON_NFC_TAG = "nfc_tag"; static final String NATIVE_LOG_FILE_NAME = "native_crash_logs"; + static final String NATIVE_LOG_FILE_PATH = "/data/misc/nfc/logs"; static final int NATIVE_CRASH_FILE_SIZE = 1024 * 1024; static final int MSG_NDEF_TAG = 0; @@ -161,6 +168,9 @@ public class NfcService implements DeviceHostListener { static final int MSG_TRANSACTION_EVENT = 17; static final int MSG_PREFERRED_PAYMENT_CHANGED = 18; static final int MSG_TOAST_DEBOUNCE_EVENT = 19; + static final int MSG_DELAY_POLLING = 20; + + static final String MSG_ROUTE_AID_PARAM_TAG = "power"; // Negative value for NO polling delay static final int NO_POLL_DELAY = -1; @@ -174,6 +184,8 @@ public class NfcService implements DeviceHostListener { static final int TASK_ENABLE = 1; static final int TASK_DISABLE = 2; static final int TASK_BOOT = 3; + static final int TASK_ENABLE_ALWAYS_ON = 4; + static final int TASK_DISABLE_ALWAYS_ON = 5; // Polling technology masks static final int NFC_POLL_A = 0x01; @@ -262,6 +274,7 @@ public class NfcService implements DeviceHostListener { private int mUserId; boolean mPollingPaused; + boolean mPollingDelayed; // True if nfc notification message already shown boolean mAntennaBlockedMessageShown; @@ -286,12 +299,16 @@ public class NfcService implements DeviceHostListener { // and the default AsyncTask thread so it is read unprotected from that // thread int mState; // one of NfcAdapter.STATE_ON, STATE_TURNING_ON, etc + // mAlwaysOnState is protected by this, however it is only modified in onCreate() + // and the default AsyncTask thread so it is read unprotected from that thread + int mAlwaysOnState; // one of NfcAdapter.STATE_ON, STATE_TURNING_ON, etc // fields below are final after onCreate() Context mContext; private DeviceHost mDeviceHost; private SharedPreferences mPrefs; private SharedPreferences.Editor mPrefsEditor; private PowerManager.WakeLock mRoutingWakeLock; + private PowerManager.WakeLock mRequireUnlockWakeLock; int mStartSound; int mEndSound; @@ -306,6 +323,8 @@ public class NfcService implements DeviceHostListener { boolean mIsHceFCapable; boolean mIsBeamCapable; boolean mIsSecureNfcCapable; + boolean mIsRequestUnlockShowed; + boolean mIsRecovering; int mPollDelay; boolean mNotifyDispatchFailed; @@ -332,6 +351,10 @@ public class NfcService implements DeviceHostListener { private IVrManager vrManager; boolean mIsVrModeEnabled; + private final boolean mIsAlwaysOnSupported; + private final Set<INfcControllerAlwaysOnListener> mAlwaysOnListeners = + Collections.synchronizedSet(new HashSet<>()); + public static NfcService getInstance() { return sService; } @@ -418,6 +441,14 @@ public class NfcService implements DeviceHostListener { new ApplyRoutingTask().execute(); } + @Override + public void onHwErrorReported() { + mContext.unregisterReceiver(mReceiver); + mIsRecovering = true; + new EnableDisableTask().execute(TASK_DISABLE); + new EnableDisableTask().execute(TASK_ENABLE); + } + final class ReaderModeParams { public int flags; public IAppCallback callback; @@ -461,6 +492,7 @@ public class NfcService implements DeviceHostListener { mPrefsEditor = mPrefs.edit(); mState = NfcAdapter.STATE_OFF; + mAlwaysOnState = NfcAdapter.STATE_OFF; mIsDebugBuild = "userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE); @@ -469,6 +501,10 @@ public class NfcService implements DeviceHostListener { mRoutingWakeLock = mPowerManager.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, "NfcService:mRoutingWakeLock"); + mRequireUnlockWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK + | PowerManager.ACQUIRE_CAUSES_WAKEUP + | PowerManager.ON_AFTER_RELEASE, "NfcService:mRequireUnlockWakeLock"); + mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); @@ -561,6 +597,9 @@ public class NfcService implements DeviceHostListener { // Make sure this is only called when object construction is complete. ServiceManager.addService(SERVICE_NAME, mNfcAdapter); + mIsAlwaysOnSupported = + mContext.getResources().getBoolean(R.bool.nfcc_always_on_allowed); + new EnableDisableTask().execute(TASK_BOOT); // do blocking boot tasks mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATS, STATS_UPDATE_INTERVAL_MS); @@ -665,7 +704,7 @@ public class NfcService implements DeviceHostListener { class EnableDisableTask extends AsyncTask<Integer, Void, Void> { @Override protected Void doInBackground(Integer... params) { - // Sanity check mState + // Quick check mState switch (mState) { case NfcAdapter.STATE_TURNING_OFF: case NfcAdapter.STATE_TURNING_ON: @@ -711,6 +750,12 @@ public class NfcService implements DeviceHostListener { SystemProperties.set("nfc.initialized", "true"); } break; + case TASK_ENABLE_ALWAYS_ON: + enableAlwaysOnInternal(); + break; + case TASK_DISABLE_ALWAYS_ON: + disableAlwaysOnInternal(); + break; } // Restore default AsyncTask priority @@ -736,8 +781,19 @@ public class NfcService implements DeviceHostListener { try { mRoutingWakeLock.acquire(); try { - if (!mDeviceHost.initialize()) { - Log.w(TAG, "Error enabling NFC"); + if (!mIsAlwaysOnSupported || mIsRecovering + || mAlwaysOnState != NfcAdapter.STATE_ON + || mAlwaysOnState != NfcAdapter.STATE_TURNING_OFF) { + if (!mDeviceHost.initialize()) { + Log.w(TAG, "Error enabling NFC"); + updateState(NfcAdapter.STATE_OFF); + return false; + } + } else if (mAlwaysOnState == NfcAdapter.STATE_ON + || mAlwaysOnState == NfcAdapter.STATE_TURNING_OFF) { + Log.i(TAG, "Already initialized"); + } else { + Log.e(TAG, "Unexptected bad state " + mAlwaysOnState); updateState(NfcAdapter.STATE_OFF); return false; } @@ -779,9 +835,24 @@ public class NfcService implements DeviceHostListener { sToast_debounce = false; - /* Start polling loop */ + /* Skip applyRouting if always on state is switching */ + if (!mIsAlwaysOnSupported + || mAlwaysOnState != NfcAdapter.STATE_TURNING_ON + || mAlwaysOnState != NfcAdapter.STATE_TURNING_OFF) { + /* Start polling loop */ + applyRouting(true); + } + + if (mIsRecovering) { + // 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); + mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null); + mIsRecovering = false; + } - applyRouting(true); return true; } @@ -813,6 +884,10 @@ public class NfcService implements DeviceHostListener { mP2pLinkManager.enableDisable(false, false); } + // Disable delay polling when disabling + mPollingDelayed = false; + mHandler.removeMessages(MSG_DELAY_POLLING); + // Stop watchdog if tag present // A convenient way to stop the watchdog properly consists of // disconnecting the tag. The polling loop shall be stopped before @@ -821,9 +896,17 @@ public class NfcService implements DeviceHostListener { mNfcDispatcher.setForegroundDispatch(null, null, null); - - boolean result = mDeviceHost.deinitialize(); - if (DBG) Log.d(TAG, "mDeviceHost.deinitialize() = " + result); + boolean result; + if (!mIsAlwaysOnSupported || mIsRecovering + || (mAlwaysOnState == NfcAdapter.STATE_OFF) + || (mAlwaysOnState == NfcAdapter.STATE_TURNING_OFF)) { + result = mDeviceHost.deinitialize(); + if (DBG) Log.d(TAG, "mDeviceHost.deinitialize() = " + result); + } else { + mDeviceHost.disableDiscovery(); + result = true; + Log.i(TAG, "AlwaysOn set, disableDiscovery()"); + } watchDog.cancel(); @@ -837,6 +920,69 @@ public class NfcService implements DeviceHostListener { return result; } + /** + * Enable always on feature. + */ + void enableAlwaysOnInternal() { + if (mAlwaysOnState == NfcAdapter.STATE_ON) { + return; + } else if (mState == NfcAdapter.STATE_TURNING_ON + || mAlwaysOnState == NfcAdapter.STATE_TURNING_OFF) { + Log.e(TAG, "Processing enableAlwaysOnInternal() from bad state"); + return; + } else if (mState == NfcAdapter.STATE_ON) { + updateAlwaysOnState(NfcAdapter.STATE_TURNING_ON); + mDeviceHost.setNfceePowerAndLinkCtrl(true); + updateAlwaysOnState(NfcAdapter.STATE_ON); + } else if (mState == NfcAdapter.STATE_OFF) { + /* Special case when NFCC is OFF without initialize. + * Temperatorily enable NfcAdapter but don't applyRouting. + * Then disable NfcAdapter without deinitialize to keep the NFCC stays initialized. + * mState will switch back to OFF in the end. + * And the NFCC stays initialized. + */ + updateAlwaysOnState(NfcAdapter.STATE_TURNING_ON); + if (!enableInternal()) { + updateAlwaysOnState(NfcAdapter.STATE_OFF); + return; + } + disableInternal(); + mDeviceHost.setNfceePowerAndLinkCtrl(true); + updateAlwaysOnState(NfcAdapter.STATE_ON); + } + } + + /** + * Disable always on feature. + */ + void disableAlwaysOnInternal() { + if (mAlwaysOnState == NfcAdapter.STATE_OFF) { + return; + } else if (mState == NfcAdapter.STATE_TURNING_ON + || mAlwaysOnState == NfcAdapter.STATE_TURNING_OFF) { + Log.e(TAG, "Processing disableAlwaysOnInternal() from bad state"); + return; + } else if (mState == NfcAdapter.STATE_ON) { + updateAlwaysOnState(NfcAdapter.STATE_TURNING_OFF); + mDeviceHost.setNfceePowerAndLinkCtrl(false); + updateAlwaysOnState(NfcAdapter.STATE_OFF); + } else if (mState == NfcAdapter.STATE_OFF) { + /* Special case when mState is OFF but NFCC is already initialized. + * Temperatorily enable NfcAdapter without initialize NFCC and applyRouting. + * And disable NfcAdapter normally with deinitialize. + * All state will switch back to OFF in the end. + */ + updateAlwaysOnState(NfcAdapter.STATE_TURNING_OFF); + mDeviceHost.setNfceePowerAndLinkCtrl(false); + if (!enableInternal()) { + updateAlwaysOnState(NfcAdapter.STATE_OFF); + return; + } + disableInternal(); + updateAlwaysOnState(NfcAdapter.STATE_OFF); + } + } + void updateState(int newState) { synchronized (NfcService.this) { if (newState == mState) { @@ -849,6 +995,39 @@ public class NfcService implements DeviceHostListener { mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); } } + + void updateAlwaysOnState(int newState) { + synchronized (NfcService.this) { + if (newState == mAlwaysOnState) { + return; + } + mAlwaysOnState = newState; + if (mAlwaysOnState == NfcAdapter.STATE_OFF + || mAlwaysOnState == NfcAdapter.STATE_ON) { + synchronized (mAlwaysOnListeners) { + for (INfcControllerAlwaysOnListener listener + : mAlwaysOnListeners) { + try { + listener.onControllerAlwaysOnChanged( + mAlwaysOnState == NfcAdapter.STATE_ON); + } catch (RemoteException e) { + Log.e(TAG, "error in updateAlwaysOnState"); + } + } + } + } + } + } + + int getAlwaysOnState() { + synchronized (NfcService.this) { + if (!mIsAlwaysOnSupported) { + return NfcAdapter.STATE_OFF; + } else { + return mAlwaysOnState; + } + } + } } void saveNfcOnSetting(boolean on) { @@ -901,7 +1080,7 @@ public class NfcService implements DeviceHostListener { } try { mIpm.setComponentEnabledSetting(new ComponentName( - BeamShareActivity.class.getPackageName$(), + BeamShareActivity.class.getPackageName(), BeamShareActivity.class.getName()), isActiveForUser ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : @@ -1255,7 +1434,7 @@ public class NfcService implements DeviceHostListener { int callingPid = Binder.getCallingPid(); // Allow non-foreground callers with system uid or systemui boolean privilegedCaller = (callingUid == Process.SYSTEM_UID - || getPackageNameFromUid(callingUid).equals("com.android.systemui")); + || getPackageNameFromUid(callingUid).equals(SYSTEM_UI)); if (!privilegedCaller && !mForegroundUtils.isInForeground(callingUid)) { Log.e(TAG, "setReaderMode: Caller is not in foreground and is not system process."); return; @@ -1428,6 +1607,50 @@ public class NfcService implements DeviceHostListener { : DEFAULT_PRESENCE_CHECK_DELAY; } } + + @Override + public boolean setControllerAlwaysOn(boolean value) throws RemoteException { + NfcPermissions.enforceSetControllerAlwaysOnPermissions(mContext); + if (!mIsAlwaysOnSupported) { + return false; + } + if (value) { + new EnableDisableTask().execute(TASK_ENABLE_ALWAYS_ON); + } else { + new EnableDisableTask().execute(TASK_DISABLE_ALWAYS_ON); + } + return true; + } + + @Override + public boolean isControllerAlwaysOn() throws RemoteException { + NfcPermissions.enforceSetControllerAlwaysOnPermissions(mContext); + return mIsAlwaysOnSupported && mAlwaysOnState == NfcAdapter.STATE_ON; + } + + @Override + public boolean isControllerAlwaysOnSupported() throws RemoteException { + NfcPermissions.enforceSetControllerAlwaysOnPermissions(mContext); + return mIsAlwaysOnSupported; + } + + @Override + public void registerControllerAlwaysOnListener( + INfcControllerAlwaysOnListener listener) throws RemoteException { + NfcPermissions.enforceSetControllerAlwaysOnPermissions(mContext); + if (!mIsAlwaysOnSupported) return; + + mAlwaysOnListeners.add(listener); + } + + @Override + public void unregisterControllerAlwaysOnListener( + INfcControllerAlwaysOnListener listener) throws RemoteException { + NfcPermissions.enforceSetControllerAlwaysOnPermissions(mContext); + if (!mIsAlwaysOnSupported) return; + + mAlwaysOnListeners.remove(listener); + } } final class ReaderModeDeathRecipient implements IBinder.DeathRecipient { @@ -2141,12 +2364,17 @@ public class NfcService implements DeviceHostListener { sendMessage(MSG_MOCK_NDEF, msg); } - public void routeAids(String aid, int route, int aidInfo) { + public void routeAids(String aid, int route, int aidInfo, int power) { Message msg = mHandler.obtainMessage(); msg.what = MSG_ROUTE_AID; msg.arg1 = route; msg.obj = aid; msg.arg2 = aidInfo; + + Bundle aidPowerState = new Bundle(); + aidPowerState.putInt(MSG_ROUTE_AID_PARAM_TAG, power); + msg.setData(aidPowerState); + mHandler.sendMessage(msg); } @@ -2220,7 +2448,14 @@ public class NfcService implements DeviceHostListener { int route = msg.arg1; int aidInfo = msg.arg2; String aid = (String) msg.obj; - mDeviceHost.routeAid(hexStringToBytes(aid), route, aidInfo); + + int power = 0x00; + Bundle bundle = msg.getData(); + if (bundle != null) { + power = bundle.getInt(MSG_ROUTE_AID_PARAM_TAG); + } + + mDeviceHost.routeAid(hexStringToBytes(aid), route, aidInfo, power); // Restart polling config break; } @@ -2428,11 +2663,22 @@ public class NfcService implements DeviceHostListener { break; case MSG_RF_FIELD_ACTIVATED: Intent fieldOnIntent = new Intent(ACTION_RF_FIELD_ON_DETECTED); - sendNfcEeAccessProtectedBroadcast(fieldOnIntent); + sendNfcPermissionProtectedBroadcast(fieldOnIntent); + if (!mIsRequestUnlockShowed + && mIsSecureNfcEnabled && mKeyguard.isKeyguardLocked()) { + if (DBG) Log.d(TAG, "Request unlock"); + mIsRequestUnlockShowed = true; + mRequireUnlockWakeLock.acquire(); + Intent requireUnlockIntent = + new Intent(NfcAdapter.ACTION_REQUIRE_UNLOCK_FOR_NFC); + requireUnlockIntent.setPackage(SYSTEM_UI); + mContext.sendBroadcast(requireUnlockIntent); + mRequireUnlockWakeLock.release(); + } break; case MSG_RF_FIELD_DEACTIVATED: Intent fieldOffIntent = new Intent(ACTION_RF_FIELD_OFF_DETECTED); - sendNfcEeAccessProtectedBroadcast(fieldOffIntent); + sendNfcPermissionProtectedBroadcast(fieldOffIntent); break; case MSG_RESUME_POLLING: mNfcAdapter.resumePolling(); @@ -2475,6 +2721,10 @@ public class NfcService implements DeviceHostListener { mScreenState = (Integer)msg.obj; Log.d(TAG, "MSG_APPLY_SCREEN_STATE " + mScreenState); + // Disable delay polling when screen state changed + mPollingDelayed = false; + mHandler.removeMessages(MSG_DELAY_POLLING); + // If NFC is turning off, we shouldn't need any changes here synchronized (NfcService.this) { if (mState == NfcAdapter.STATE_TURNING_OFF) @@ -2482,7 +2732,8 @@ public class NfcService implements DeviceHostListener { } if (mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED) { - applyRouting(false); + applyRouting(false); + mIsRequestUnlockShowed = false; } int screen_state_mask = (mNfcUnlockManager.isLockscreenPollingEnabled()) ? (ScreenStateHelper.SCREEN_POLLING_TAG_MASK | mScreenState) : mScreenState; @@ -2513,6 +2764,16 @@ public class NfcService implements DeviceHostListener { sToast_debounce = false; break; + case MSG_DELAY_POLLING: + synchronized (NfcService.this) { + if (!mPollingDelayed) { + return; + } + mPollingDelayed = false; + mDeviceHost.startStopPolling(true); + } + if (DBG) Log.d(TAG, "Polling is started"); + break; default: Log.e(TAG, "Unknown message received"); break; @@ -2610,35 +2871,14 @@ public class NfcService implements DeviceHostListener { return packages; } - private void sendNfcEeAccessProtectedBroadcast(Intent intent) { + private void sendNfcPermissionProtectedBroadcast(Intent intent) { + if (mNfcEventInstalledPackages.isEmpty()) { + return; + } intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); - // Resume app switches so the receivers can start activites without delay - mNfcDispatcher.resumeAppSwitches(); - synchronized (this) { - ArrayList<String> SEPackages = getSEAccessAllowedPackages(); - if (SEPackages!= null && !SEPackages.isEmpty()) { - for (String packageName : SEPackages) { - intent.setPackage(packageName); - mContext.sendBroadcast(intent); - } - } - PackageManager pm = mContext.getPackageManager(); - for (String packageName : mNfcEventInstalledPackages) { - try { - PackageInfo info = pm.getPackageInfo(packageName, 0); - if (SEPackages != null && SEPackages.contains(packageName)) { - continue; - } - if (info.applicationInfo != null && - ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0 || - (info.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0)) { - intent.setPackage(packageName); - mContext.sendBroadcast(intent); - } - } catch (Exception e) { - Log.e(TAG, "Exception in getPackageInfo " + e); - } - } + for (String packageName : mNfcEventInstalledPackages) { + intent.setPackage(packageName); + mContext.sendBroadcast(intent); } } @@ -2821,7 +3061,11 @@ public class NfcService implements DeviceHostListener { unregisterObject(tagEndpoint.getHandle()); if (mPollDelay > NO_POLL_DELAY) { tagEndpoint.stopPresenceChecking(); - mNfcAdapter.pausePolling(mPollDelay); + mDeviceHost.startStopPolling(false); + mPollingDelayed = true; + if (DBG) Log.d(TAG, "Polling delayed"); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_DELAY_POLLING), mPollDelay); } else { Log.e(TAG, "Keep presence checking."); } @@ -2919,7 +3163,7 @@ public class NfcService implements DeviceHostListener { IPackageManager mIpm = IPackageManager.Stub.asInterface( ServiceManager.getService("package")); beamSetting = mIpm.getComponentEnabledSetting(new ComponentName( - BeamShareActivity.class.getPackageName$(), + BeamShareActivity.class.getPackageName(), BeamShareActivity.class.getName()), userId); } catch(RemoteException e) { @@ -2964,6 +3208,9 @@ public class NfcService implements DeviceHostListener { updatePackageCache(); } else if (action.equals(Intent.ACTION_SHUTDOWN)) { if (DBG) Log.d(TAG, "Device is shutting down."); + if (mIsAlwaysOnSupported && mAlwaysOnState == NfcAdapter.STATE_ON) { + new EnableDisableTask().execute(TASK_DISABLE_ALWAYS_ON); + } if (isNfcEnabled()) { mDeviceHost.shutdown(); } @@ -3031,7 +3278,7 @@ public class NfcService implements DeviceHostListener { private void copyNativeCrashLogsIfAny(PrintWriter pw) { try { - File file = new File(getNfaStorageDir(), NATIVE_LOG_FILE_NAME); + File file = new File(NATIVE_LOG_FILE_PATH, NATIVE_LOG_FILE_NAME); if (!file.exists()) { return; } @@ -3050,7 +3297,7 @@ public class NfcService implements DeviceHostListener { private void storeNativeCrashLogs() { try { - File file = new File(getNfaStorageDir(), NATIVE_LOG_FILE_NAME); + File file = new File(NATIVE_LOG_FILE_PATH, NATIVE_LOG_FILE_NAME); if (file.length() >= NATIVE_CRASH_FILE_SIZE) { file.createNewFile(); } @@ -3086,9 +3333,11 @@ public class NfcService implements DeviceHostListener { synchronized (this) { pw.println("mState=" + stateToString(mState)); + pw.println("mAlwaysOnState=" + stateToString(mAlwaysOnState)); pw.println("mIsZeroClickRequested=" + mIsNdefPushEnabled); pw.println("mScreenState=" + ScreenStateHelper.screenStateToString(mScreenState)); pw.println("mIsSecureNfcEnabled=" + mIsSecureNfcEnabled); + pw.println("mIsAlwaysOnSupported=" + mIsAlwaysOnSupported); pw.println(mCurrentDiscoveryParameters); if (mIsBeamCapable) { mP2pLinkManager.dump(fd, pw, args); @@ -3150,7 +3399,7 @@ public class NfcService implements DeviceHostListener { proto.end(token); // Dump native crash logs if any - File file = new File(getNfaStorageDir(), NATIVE_LOG_FILE_NAME); + File file = new File(NATIVE_LOG_FILE_PATH, NATIVE_LOG_FILE_NAME); if (!file.exists()) { return; } diff --git a/src/com/android/nfc/NfcWifiProtectedSetup.java b/src/com/android/nfc/NfcWifiProtectedSetup.java index af4dd6f6..a078efcd 100644 --- a/src/com/android/nfc/NfcWifiProtectedSetup.java +++ b/src/com/android/nfc/NfcWifiProtectedSetup.java @@ -121,7 +121,7 @@ public final class NfcWifiProtectedSetup { short fieldId = payload.getShort(); int fieldSize = payload.getShort() & 0xFFFF; - // sanity check + // Quick check if (payload.position() + fieldSize > startPosition + size) { return null; } diff --git a/src/com/android/nfc/beam/BeamManager.java b/src/com/android/nfc/beam/BeamManager.java index cf5f3342..a6f2abbf 100644 --- a/src/com/android/nfc/beam/BeamManager.java +++ b/src/com/android/nfc/beam/BeamManager.java @@ -38,7 +38,7 @@ public class BeamManager implements Handler.Callback { private static final String TAG = "BeamManager"; private static final boolean DBG = false; - private static final String ACTION_WHITELIST_DEVICE = + private static final String ACTION_ALLOWLIST_DEVICE = "android.btopp.intent.action.WHITELIST_DEVICE"; public static final int MSG_BEAM_COMPLETE = 0; @@ -89,7 +89,7 @@ public class BeamManager implements Handler.Callback { receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord); receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_COMPLETE_CALLBACK, new Messenger(mCallback)); - whitelistOppDevice(context, handoverData.device); + allowlistOppDevice(context, handoverData.device); context.startServiceAsUser(receiveIntent, UserHandle.CURRENT); return true; } @@ -133,9 +133,9 @@ public class BeamManager implements Handler.Callback { return false; } - void whitelistOppDevice(Context context, BluetoothDevice device) { - if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP"); - Intent intent = new Intent(ACTION_WHITELIST_DEVICE); + void allowlistOppDevice(Context context, BluetoothDevice device) { + if (DBG) Log.d(TAG, "Allowlist " + device + " for BT OPP"); + Intent intent = new Intent(ACTION_ALLOWLIST_DEVICE); intent.setPackage(context.getString(R.string.bluetooth_package)); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); diff --git a/src/com/android/nfc/beam/BeamTransferManager.java b/src/com/android/nfc/beam/BeamTransferManager.java index d728864b..cb59c280 100644 --- a/src/com/android/nfc/beam/BeamTransferManager.java +++ b/src/com/android/nfc/beam/BeamTransferManager.java @@ -105,7 +105,7 @@ public class BeamTransferManager implements Handler.Callback, static final String BEAM_NOTIFICATION_CHANNEL = "beam_notification_channel"; - static final String ACTION_WHITELIST_DEVICE = + static final String ACTION_ALLOWLIST_DEVICE = "android.btopp.intent.action.WHITELIST_DEVICE"; static final String ACTION_STOP_BLUETOOTH_TRANSFER = @@ -180,9 +180,9 @@ public class BeamTransferManager implements Handler.Callback, mNotificationManager.createNotificationChannel(notificationChannel); } - void whitelistOppDevice(BluetoothDevice device) { - if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP"); - Intent intent = new Intent(ACTION_WHITELIST_DEVICE); + void allowlistOppDevice(BluetoothDevice device) { + if (DBG) Log.d(TAG, "Allowlist " + device + " for BT OPP"); + Intent intent = new Intent(ACTION_ALLOWLIST_DEVICE); intent.setPackage(mContext.getString(R.string.bluetooth_package)); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); @@ -212,8 +212,8 @@ public class BeamTransferManager implements Handler.Callback, this.mProgress = progress; // We're still receiving data from this device - keep it in - // the whitelist for a while longer - if (mIncoming && mRemoteDevice != null) whitelistOppDevice(mRemoteDevice); + // the allowlist for a while longer + if (mIncoming && mRemoteDevice != null) allowlistOppDevice(mRemoteDevice); updateStateAndNotification(STATE_IN_PROGRESS); } @@ -554,4 +554,3 @@ public class BeamTransferManager implements Handler.Callback, return newFile; } } - diff --git a/src/com/android/nfc/beam/SendUi.java b/src/com/android/nfc/beam/SendUi.java index 49f893c2..22457295 100644 --- a/src/com/android/nfc/beam/SendUi.java +++ b/src/com/android/nfc/beam/SendUi.java @@ -18,9 +18,6 @@ package com.android.nfc.beam; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import com.android.nfc.R; -import com.android.nfc.beam.FireflyRenderer; - import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; @@ -42,6 +39,7 @@ import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.os.AsyncTask; import android.os.Binder; +import android.os.IBinder; import android.util.DisplayMetrics; import android.util.Log; import android.view.ActionMode; @@ -52,7 +50,6 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.Surface; import android.view.SurfaceControl; @@ -68,7 +65,9 @@ import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; - +import com.android.internal.policy.PhoneWindow; +import com.android.nfc.R; +import com.android.nfc.beam.FireflyRenderer; import java.util.List; /** @@ -613,8 +612,6 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, mDisplayMetrics.heightPixels); float smallestWidthDp = smallestWidth / (mDisplayMetrics.densityDpi / 160f); - int rot = mDisplay.getRotation(); - // TODO this is somewhat device-specific; need generic solution. // The starting crop for the screenshot is the fullscreen without the status bar, which // is always on top. The conditional check will determine how to crop the navbar, @@ -639,7 +636,16 @@ public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, int height = crop.height(); // Take the screenshot. SurfaceControl will generate a hardware bitmap in the correct // orientation and size. - Bitmap bitmap = SurfaceControl.screenshot(crop, width, height, rot); + IBinder displayToken = SurfaceControl.getInternalDisplayToken(); + final SurfaceControl.DisplayCaptureArgs captureArgs = + new SurfaceControl.DisplayCaptureArgs.Builder(displayToken) + .setSourceCrop(crop) + .setSize(width, height) + .build(); + SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer = + SurfaceControl.captureDisplay(captureArgs); + final Bitmap bitmap = screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); + // Bail if we couldn't take the screenshot if (bitmap == null) { return null; diff --git a/src/com/android/nfc/cardemulation/AidRoutingManager.java b/src/com/android/nfc/cardemulation/AidRoutingManager.java index b1e67130..3dc9f578 100644 --- a/src/com/android/nfc/cardemulation/AidRoutingManager.java +++ b/src/com/android/nfc/cardemulation/AidRoutingManager.java @@ -85,6 +85,7 @@ public class AidRoutingManager { String offHostSE; int route; int aidInfo; + int power; } public AidRoutingManager() { @@ -168,9 +169,9 @@ public class AidRoutingManager { NfcService.getInstance().unrouteAids(aid); } - if(NfcService.getInstance().getNciVersion() != NfcService.getInstance().NCI_VERSION_1_0) { - // unRoute EmptyAid - NfcService.getInstance().unrouteAids(""); + if (NfcService.getInstance().getNciVersion() >= NfcService.getInstance().NCI_VERSION_2_0) { + // unRoute EmptyAid + NfcService.getInstance().unrouteAids(""); } } @@ -334,25 +335,62 @@ public class AidRoutingManager { } } - if(mDefaultRoute != mDefaultIsoDepRoute) { - if(NfcService.getInstance().getNciVersion() != NfcService.getInstance().NCI_VERSION_1_0) { - String emptyAid = ""; - AidEntry entry = new AidEntry(); - entry.route = mDefaultRoute; - if(mDefaultRoute==ROUTE_HOST) { - entry.isOnHost = true; - } else{ - entry.isOnHost = false; - } - entry.aidInfo = RegisteredAidCache.AID_ROUTE_QUAL_PREFIX; - aidRoutingTableCache.put(emptyAid, entry); - if (DBG) Log.d(TAG, "Add emptyAid into AidRoutingTable"); - } + // register default route in below cases: + // 1. mDefaultRoute is different with mDefaultIsoDepRoute + // 2. mDefaultRoute and mDefaultIsoDepRoute all equal to ROUTE_HOST + // , which is used for screen off HCE scenarios + if (mDefaultRoute != mDefaultIsoDepRoute || mDefaultIsoDepRoute == ROUTE_HOST) { + if (NfcService.getInstance().getNciVersion() + >= NfcService.getInstance().NCI_VERSION_2_0) { + String emptyAid = ""; + AidEntry entry = new AidEntry(); + int default_route_power_state; + entry.route = mDefaultRoute; + if (mDefaultRoute == ROUTE_HOST) { + entry.isOnHost = true; + default_route_power_state = RegisteredAidCache.POWER_STATE_SWITCH_ON + | RegisteredAidCache.POWER_STATE_SCREEN_ON_LOCKED; + Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute); + if (aidsForDefaultRoute != null) { + for (String aid : aidsForDefaultRoute) { + default_route_power_state |= aidMap.get(aid).power; + } + } + } else { + entry.isOnHost = false; + default_route_power_state = RegisteredAidCache.POWER_STATE_ALL; + } + entry.aidInfo = RegisteredAidCache.AID_ROUTE_QUAL_PREFIX; + entry.power = default_route_power_state; + + aidRoutingTableCache.put(emptyAid, entry); + if (DBG) Log.d(TAG, "Add emptyAid into AidRoutingTable"); + } + } + + // Register additional offhost AIDs when their support power states are + // differernt from the default route entry + if (mDefaultRoute != ROUTE_HOST) { + int default_route_power_state = RegisteredAidCache.POWER_STATE_ALL; + if (NfcService.getInstance().getNciVersion() + < NfcService.getInstance().NCI_VERSION_2_0) { + default_route_power_state = + RegisteredAidCache.POWER_STATE_ALL_NCI_VERSION_1_0; + } + + Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute); + if (aidsForDefaultRoute != null) { + for (String aid : aidsForDefaultRoute) { + if (aidMap.get(aid).power != default_route_power_state) { + aidRoutingTableCache.put(aid, aidMap.get(aid)); + } + } + } } - if( calculateAidRouteSize(aidRoutingTableCache) <= mMaxAidRoutingTableSize) { - aidRouteResolved = true; - break; + if (calculateAidRouteSize(aidRoutingTableCache) <= mMaxAidRoutingTableSize) { + aidRouteResolved = true; + break; } } @@ -375,9 +413,13 @@ public class AidRoutingManager { int route = aidEntry.getValue().route; int aidType = aidEntry.getValue().aidInfo; String aid = aidEntry.getKey(); - if (DBG) Log.d (TAG, "commit aid:"+aid+"route:"+route+"aidtype:"+aidType); + int power = aidEntry.getValue().power; + if (DBG) { + Log.d(TAG, "commit aid:" + aid + ",route:" + route + + ",aidtype:" + aidType + ", power state:" + power); + } - NfcService.getInstance().routeAids(aid, route, aidType); + NfcService.getInstance().routeAids(aid, route, aidType, power); } } diff --git a/src/com/android/nfc/cardemulation/CardEmulationManager.java b/src/com/android/nfc/cardemulation/CardEmulationManager.java index a7b7e9dc..f6353942 100644 --- a/src/com/android/nfc/cardemulation/CardEmulationManager.java +++ b/src/com/android/nfc/cardemulation/CardEmulationManager.java @@ -259,7 +259,7 @@ public class CardEmulationManager implements RegisteredServicesCache.Callback, void verifyDefaults(int userId, List<ApduServiceInfo> services) { ComponentName defaultPaymentService = - getDefaultServiceForCategory(userId, CardEmulation.CATEGORY_PAYMENT, false); + getDefaultServiceForCategory(userId, CardEmulation.CATEGORY_PAYMENT, true); if (DBG) Log.d(TAG, "Current default: " + defaultPaymentService); if (defaultPaymentService == null) { // A payment service may have been removed, leaving only one; @@ -275,6 +275,7 @@ public class CardEmulationManager implements RegisteredServicesCache.Callback, if (numPaymentServices > 1) { // More than one service left, leave default unset if (DBG) Log.d(TAG, "No default set, more than one service left."); + setDefaultServiceForCategoryChecked(userId, null, CardEmulation.CATEGORY_PAYMENT); } else if (numPaymentServices == 1) { // Make single found payment service the default if (DBG) Log.d(TAG, "No default set, making single service default."); @@ -283,6 +284,7 @@ public class CardEmulationManager implements RegisteredServicesCache.Callback, } else { // No payment services left, leave default at null if (DBG) Log.d(TAG, "No default set, last payment service removed."); + setDefaultServiceForCategoryChecked(userId, null, CardEmulation.CATEGORY_PAYMENT); } } } @@ -532,6 +534,13 @@ public class CardEmulationManager implements RegisteredServicesCache.Callback, NfcPermissions.enforcePreferredPaymentInfoPermissions(mContext); return mServiceCache.getService(userId, mAidCache.getPreferredService()); } + + @Override + public boolean isDefaultPaymentRegistered() throws RemoteException { + String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); + return defaultComponent != null ? true : false; + } } /** diff --git a/src/com/android/nfc/cardemulation/HostEmulationManager.java b/src/com/android/nfc/cardemulation/HostEmulationManager.java index 2d763f93..e26f6f2b 100644 --- a/src/com/android/nfc/cardemulation/HostEmulationManager.java +++ b/src/com/android/nfc/cardemulation/HostEmulationManager.java @@ -31,6 +31,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; +import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; @@ -72,6 +73,7 @@ public class HostEmulationManager { final Messenger mMessenger = new Messenger (new MessageHandler()); final KeyguardManager mKeyguard; final Object mLock; + final PowerManager mPowerManager; // All variables below protected by mLock @@ -106,6 +108,7 @@ public class HostEmulationManager { mAidCache = aidCache; mState = STATE_IDLE; mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + mPowerManager = context.getSystemService(PowerManager.class); } public void onPreferredPaymentServiceChanged(final ComponentName service) { @@ -181,6 +184,11 @@ public class HostEmulationManager { launchTapAgain(resolveInfo.defaultService, resolveInfo.category); return; } + if (defaultServiceInfo.requiresScreenOn() && !mPowerManager.isScreenOn()) { + // Just ignore all future APDUs + mState = STATE_W4_DEACTIVATE; + return; + } // In no circumstance should this be an OffHostService - // we should never get this AID on the host in the first place if (!defaultServiceInfo.isOnHost()) { @@ -309,12 +317,15 @@ public class HostEmulationManager { unbindServiceIfNeededLocked(); Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE); aidIntent.setComponent(service); - if (mContext.bindServiceAsUser(aidIntent, mConnection, - Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, - UserHandle.CURRENT)) { - mServiceBound = true; - } else { - Log.e(TAG, "Could not bind service."); + try { + mServiceBound = mContext.bindServiceAsUser(aidIntent, mConnection, + Context.BIND_AUTO_CREATE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS, + UserHandle.CURRENT); + if (!mServiceBound) { + Log.e(TAG, "Could not bind service."); + } + } catch (SecurityException e) { + Log.e(TAG, "Could not bind service due to security exception."); } return null; } @@ -464,6 +475,7 @@ public class HostEmulationManager { } mService = new Messenger(service); mServiceName = name; + mServiceBound = true; Log.d(TAG, "Service bound"); mState = STATE_XFER; // Send pending select APDU @@ -479,6 +491,7 @@ public class HostEmulationManager { synchronized (mLock) { Log.d(TAG, "Service unbound"); mService = null; + mServiceName = null; mServiceBound = false; } } diff --git a/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java b/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java index 8b9e7dee..e2f85541 100644 --- a/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java +++ b/src/com/android/nfc/cardemulation/HostNfcFEmulationManager.java @@ -231,10 +231,14 @@ public class HostNfcFEmulationManager { unbindServiceIfNeededLocked(); Intent bindIntent = new Intent(HostNfcFService.SERVICE_INTERFACE); bindIntent.setComponent(service); - if (mContext.bindServiceAsUser(bindIntent, mConnection, - Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { - } else { - Log.e(TAG, "Could not bind service."); + try { + mServiceBound = mContext.bindServiceAsUser(bindIntent, mConnection, + Context.BIND_AUTO_CREATE, UserHandle.CURRENT); + if (!mServiceBound) { + Log.e(TAG, "Could not bind service."); + } + } catch (SecurityException e) { + Log.e(TAG, "Could not bind service due to security exception."); } return null; } diff --git a/src/com/android/nfc/cardemulation/PreferredServices.java b/src/com/android/nfc/cardemulation/PreferredServices.java index 7b0a6e66..b82c8009 100644 --- a/src/com/android/nfc/cardemulation/PreferredServices.java +++ b/src/com/android/nfc/cardemulation/PreferredServices.java @@ -235,7 +235,7 @@ public class PreferredServices implements com.android.nfc.ForegroundUtils.Callba Log.d(TAG, "Requested foreground service unexpectedly removed"); return false; } - // Do some sanity checking + // Do some quick checking if (!mPaymentDefaults.preferForeground) { // Foreground apps are not allowed to override payment default // Check if this app registers payment AIDs, in which case we'll fail anyway diff --git a/src/com/android/nfc/cardemulation/RegisteredAidCache.java b/src/com/android/nfc/cardemulation/RegisteredAidCache.java index 7618d38c..2e66e3cc 100644 --- a/src/com/android/nfc/cardemulation/RegisteredAidCache.java +++ b/src/com/android/nfc/cardemulation/RegisteredAidCache.java @@ -24,6 +24,7 @@ import android.nfc.cardemulation.CardEmulation; import android.util.Log; import android.util.proto.ProtoOutputStream; +import com.android.nfc.NfcService; import com.google.android.collect.Maps; import java.util.Collections; import java.io.FileDescriptor; @@ -46,6 +47,19 @@ public class RegisteredAidCache { static final int AID_ROUTE_QUAL_SUBSET = 0x20; static final int AID_ROUTE_QUAL_PREFIX = 0x10; + static final int POWER_STATE_SWITCH_ON = 0x1; + static final int POWER_STATE_SWITCH_OFF = 0x2; + static final int POWER_STATE_BATTERY_OFF = 0x4; + static final int POWER_STATE_SCREEN_OFF_UNLOCKED = 0x8; + static final int POWER_STATE_SCREEN_ON_LOCKED = 0x10; + static final int POWER_STATE_SCREEN_OFF_LOCKED = 0x20; + static final int POWER_STATE_ALL = POWER_STATE_SWITCH_ON | POWER_STATE_SWITCH_OFF + | POWER_STATE_BATTERY_OFF | POWER_STATE_SCREEN_OFF_UNLOCKED + | POWER_STATE_SCREEN_ON_LOCKED | POWER_STATE_SCREEN_OFF_LOCKED; + static final int POWER_STATE_ALL_NCI_VERSION_1_0 = POWER_STATE_SWITCH_ON + | POWER_STATE_SWITCH_OFF + | POWER_STATE_BATTERY_OFF; + // mAidServices maps AIDs to services that have registered them. // It's a TreeMap in order to be able to quickly select subsets // of AIDs that conflict with each other. @@ -779,6 +793,33 @@ public class RegisteredAidCache { updateRoutingLocked(false); } + private int computeAidPowerState(boolean isOnHost, boolean requiresScreenOn, + boolean requiresUnlock) { + int power = POWER_STATE_ALL; + if (NfcService.getInstance().getNciVersion() < NfcService.getInstance().NCI_VERSION_2_0) { + power = POWER_STATE_ALL_NCI_VERSION_1_0; + } + + if (isOnHost) { + power &= ~(POWER_STATE_SWITCH_OFF | POWER_STATE_BATTERY_OFF); + } else { + if (requiresUnlock) { + power &= ~POWER_STATE_SCREEN_ON_LOCKED; + } + } + + if (requiresScreenOn) { + power &= ~(POWER_STATE_SWITCH_OFF | POWER_STATE_BATTERY_OFF + | POWER_STATE_SCREEN_OFF_UNLOCKED | POWER_STATE_SCREEN_OFF_LOCKED); + } + if (requiresUnlock) { + power &= ~(POWER_STATE_SWITCH_OFF | POWER_STATE_BATTERY_OFF + | POWER_STATE_SCREEN_OFF_LOCKED); + } + + return power; + } + void updateRoutingLocked(boolean force) { if (!mNfcEnabled) { if (DBG) Log.d(TAG, "Not updating routing table because NFC is off."); @@ -812,6 +853,12 @@ public class RegisteredAidCache { aidType.offHostSE = resolveInfo.defaultService.getOffHostSecureElement(); } + + boolean requiresUnlock = resolveInfo.defaultService.requiresUnlock(); + boolean requiresScreenOn = resolveInfo.defaultService.requiresScreenOn(); + aidType.power = + computeAidPowerState(aidType.isOnHost, requiresScreenOn, requiresUnlock); + routingEntries.put(aid, aidType); } else if (resolveInfo.services.size() == 1) { // Only one service, but not the default, must route to host @@ -826,12 +873,20 @@ public class RegisteredAidCache { resolveInfo.services.get(0).getOffHostSecureElement(); } } + + boolean requiresUnlock = resolveInfo.services.get(0).requiresUnlock(); + boolean requiresScreenOn = resolveInfo.services.get(0).requiresScreenOn(); + aidType.power = + computeAidPowerState(aidType.isOnHost, requiresScreenOn, requiresUnlock); + routingEntries.put(aid, aidType); } else if (resolveInfo.services.size() > 1) { // Multiple services if all the services are routing to same // offhost then the service should be routed to off host. boolean onHost = false; String offHostSE = null; + boolean requiresUnlock = false; + boolean requiresScreenOn = true; for (ApduServiceInfo service : resolveInfo.services) { // In case there is at least one service which routes to host // Route it to host for user to select which service to use @@ -839,18 +894,37 @@ public class RegisteredAidCache { if (!onHost) { if (offHostSE == null) { offHostSE = service.getOffHostSecureElement(); + requiresUnlock = service.requiresUnlock(); + requiresScreenOn = service.requiresScreenOn(); } else if (!offHostSE.equals( service.getOffHostSecureElement())) { // There are registerations to different SEs, route this // to host and have user choose a service for this AID offHostSE = null; onHost = true; + requiresUnlock = false; + requiresScreenOn = true; + break; + } else if (requiresUnlock != service.requiresUnlock() + || requiresScreenOn != service.requiresScreenOn()) { + // There are registrations to the same SE with differernt supported + // power states, route this to host and have user choose a service + // for this AID + offHostSE = null; + onHost = true; + requiresUnlock = false; + requiresScreenOn = true; break; } } } aidType.isOnHost = onHost; aidType.offHostSE = onHost ? null : offHostSE; + requiresUnlock = onHost ? false : requiresUnlock; + requiresScreenOn = onHost ? true : requiresScreenOn; + + aidType.power = computeAidPowerState(onHost, requiresScreenOn, requiresUnlock); + routingEntries.put(aid, aidType); } } diff --git a/src/com/android/nfc/handover/BluetoothPeripheralHandover.java b/src/com/android/nfc/handover/BluetoothPeripheralHandover.java index 98d40578..45b00ae1 100644 --- a/src/com/android/nfc/handover/BluetoothPeripheralHandover.java +++ b/src/com/android/nfc/handover/BluetoothPeripheralHandover.java @@ -453,7 +453,8 @@ public class BluetoothPeripheralHandover implements BluetoothProfile.ServiceList toast(getToastString(R.string.pairing_peripheral)); } if (mOobData != null) { - if (!mDevice.createBondOutOfBand(mTransport, mOobData)) { + if (!mDevice.createBondOutOfBand(mTransport, /* p192 not implemented for LE */ null, + mOobData)) { toast(getToastString(R.string.pairing_peripheral_failed)); complete(false); } diff --git a/src/com/android/nfc/handover/HandoverDataParser.java b/src/com/android/nfc/handover/HandoverDataParser.java index 09bad095..0e04fdf5 100644 --- a/src/com/android/nfc/handover/HandoverDataParser.java +++ b/src/com/android/nfc/handover/HandoverDataParser.java @@ -16,15 +16,6 @@ package com.android.nfc.handover; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Random; - import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; @@ -38,6 +29,14 @@ import android.nfc.NdefRecord; import android.os.ParcelUuid; import android.os.UserHandle; import android.util.Log; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; /** * Manages handover of NFC to other technologies. @@ -462,18 +461,20 @@ public class HandoverDataParser { try { + byte[] bdaddr = null; + byte role = 0xF; // invalid default + byte[] leScC = null; + byte[] leScR = null; + byte[] nameBytes = null; + byte[] securityManagerTK = null; while (payload.remaining() > 0) { int len = payload.get(); int type = payload.get(); switch (type) { case BT_HANDOVER_TYPE_MAC: // mac address - int startpos = payload.position(); - byte[] bdaddr = new byte[7]; // 6 bytes for mac, 1 for addres type + bdaddr = new byte[7]; // 6 bytes for mac, 1 for address type payload.get(bdaddr); - if (result.oobData == null) - result.oobData = new OobData(); - result.oobData.setLeBluetoothDeviceAddress(bdaddr); payload.position(startpos); byte[] address = parseMacFromBluetoothRecord(payload); @@ -481,19 +482,22 @@ public class HandoverDataParser { result.device = mBluetoothAdapter.getRemoteDevice(address); result.valid = true; break; + case BT_HANDOVER_TYPE_LE_ROLE: - byte role = payload.get(); + role = payload.get(); if (role == BT_HANDOVER_LE_ROLE_CENTRAL_ONLY) { // only central role supported, can't pair result.valid = false; return result; } break; + case BT_HANDOVER_TYPE_LONG_LOCAL_NAME: - byte[] nameBytes = new byte[len - 1]; + nameBytes = new byte[len - 1]; payload.get(nameBytes); result.name = new String(nameBytes, StandardCharsets.UTF_8); break; + case BT_HANDOVER_TYPE_SECURITY_MANAGER_TK: if (len-1 != SECURITY_MANAGER_TK_SIZE) { Log.i(TAG, "BT OOB: invalid size of SM TK, should be " + @@ -501,12 +505,8 @@ public class HandoverDataParser { break; } - byte[] securityManagerTK = new byte[len - 1]; + securityManagerTK = new byte[len - 1]; payload.get(securityManagerTK); - - if (result.oobData == null) - result.oobData = new OobData(); - result.oobData.setSecurityManagerTk(securityManagerTK); break; case BT_HANDOVER_TYPE_LE_SC_CONFIRMATION: @@ -516,12 +516,8 @@ public class HandoverDataParser { break; } - byte[] leScC = new byte[len - 1]; + leScC = new byte[len - 1]; payload.get(leScC); - - if (result.oobData == null) - result.oobData = new OobData(); - result.oobData.setLeSecureConnectionsConfirmation(leScC); break; case BT_HANDOVER_TYPE_LE_SC_RANDOM: @@ -531,12 +527,8 @@ public class HandoverDataParser { break; } - byte[] leScR = new byte[len - 1]; + leScR = new byte[len - 1]; payload.get(leScR); - - if (result.oobData == null) - result.oobData = new OobData(); - result.oobData.setLeSecureConnectionsRandom(leScR); break; default: @@ -544,6 +536,11 @@ public class HandoverDataParser { break; } } + result.oobData = new OobData.LeBuilder(leScC, bdaddr, (int)(role & 0xFF)) + .setRandomizerHash(leScR) + .setDeviceName(nameBytes) + .setLeTemporaryKey(securityManagerTK) + .build(); } catch (IllegalArgumentException e) { Log.i(TAG, "BLE OOB: error parsing OOB data", e); } catch (BufferUnderflowException e) { diff --git a/src/com/android/nfc/snep/SnepMessenger.java b/src/com/android/nfc/snep/SnepMessenger.java index 5078fe8c..51ebe03b 100644 --- a/src/com/android/nfc/snep/SnepMessenger.java +++ b/src/com/android/nfc/snep/SnepMessenger.java @@ -202,9 +202,10 @@ public class SnepMessenger { } if (NfcService.sIsDtaMode) { - if (!mIsClient && (requestField == SnepMessage.RESPONSE_CONTINUE)|| // added for TC_S_BIT_B1_01_X + // added for TC_S_BIT_B1_01_X + if (!mIsClient && (requestField == SnepMessage.RESPONSE_CONTINUE || requestField == SnepMessage.RESPONSE_SUCCESS || - requestField == SnepMessage.RESPONSE_NOT_FOUND) { + requestField == SnepMessage.RESPONSE_NOT_FOUND)) { if (DBG) Log.d(TAG, "errorneous response received, disconnecting client"); close(); } diff --git a/tests/Android.bp b/tests/Android.bp deleted file mode 100644 index ce42a9b7..00000000 --- a/tests/Android.bp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2011, 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. - -android_test { - name: "NfcTests", - libs: [ - "android.test.runner", - "android.test.base", - ], - static_libs: ["junit"], - // Include all test java files. - srcs: ["src/**/*.java"], - platform_apis: true, - certificate: "platform", - instrumentation_for: "NfcNci", -} diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml deleted file mode 100644 index fa2380cc..00000000 --- a/tests/AndroidManifest.xml +++ /dev/null @@ -1,37 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2011 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 name must be unique so suffix with "tests" so package loader doesn't ignore us --> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.nfc.tests"> - - <!-- We add an application tag here just so that we can indicate that - this package needs to link against the android.test library, - which is needed when building test cases. --> - <application> - <uses-library android:name="android.test.runner" /> - </application> - - <!-- - This declares that this app uses the instrumentation test runner targeting - the package of com.android.nfc3. To run the tests use the command: - "adb shell am instrument -w com.android.nfc.tests/android.test.InstrumentationTestRunner" - --> - <instrumentation android:name="android.test.InstrumentationTestRunner" - android:targetPackage="com.android.nfc" - android:label="Tests for Nfc."/> - -</manifest> diff --git a/tests/src/com/android/nfc/MockLlcpSocket.java b/tests/src/com/android/nfc/MockLlcpSocket.java deleted file mode 100644 index 95279a73..00000000 --- a/tests/src/com/android/nfc/MockLlcpSocket.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2011 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.util.Log; - -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; - -public class MockLlcpSocket implements DeviceHost.LlcpSocket { - private static final String TAG = "mockLlcpSocket"; - private MockLlcpSocket mPairedSocket; - private List<byte[]> mReceivedPackets = new LinkedList<byte[]>(); - private boolean mClosed = false; - - @Override - public void close() throws IOException { - mClosed = true; - mPairedSocket.mClosed = true; - } - - @Override - public void connectToSap(int sap) throws IOException { - throw new UnsupportedOperationException("Use MockLlcpSocket.bind(client, server)"); - } - - @Override - public void send(byte[] data) throws IOException { - if (mClosed || mPairedSocket == null) { - throw new IOException("Socket not connected"); - } - synchronized (mPairedSocket.mReceivedPackets) { - Log.d(TAG, "sending packet"); - mPairedSocket.mReceivedPackets.add(data); - mPairedSocket.mReceivedPackets.notify(); - } - } - - @Override - public int receive(byte[] receiveBuffer) throws IOException { - synchronized (mReceivedPackets) { - while (!mClosed && mReceivedPackets.size() == 0) { - try { - mReceivedPackets.wait(1000); - } catch (InterruptedException e) {} - } - if (mClosed) { - throw new IOException("Socket closed."); - } - byte[] arr = mReceivedPackets.remove(0); - System.arraycopy(arr, 0, receiveBuffer, 0, arr.length); - return arr.length; - } - } - - public static void bind(MockLlcpSocket client, MockLlcpSocket server) { - client.mPairedSocket = server; - server.mPairedSocket = client; - } - - @Override - public void connectToService(String serviceName) throws IOException { - throw new UnsupportedOperationException(); - } - - @Override - public int getRemoteMiu() { - throw new UnsupportedOperationException(); - } - - @Override - public int getRemoteRw() { - throw new UnsupportedOperationException(); - } - - @Override - public int getLocalSap() { - throw new UnsupportedOperationException(); - } - - @Override - public int getLocalMiu() { - throw new UnsupportedOperationException(); - } - - @Override - public int getLocalRw() { - throw new UnsupportedOperationException(); - } -}
\ No newline at end of file diff --git a/tests/src/com/android/nfc/snep/SnepBasicTests.java b/tests/src/com/android/nfc/snep/SnepBasicTests.java deleted file mode 100644 index 32b5e645..00000000 --- a/tests/src/com/android/nfc/snep/SnepBasicTests.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2011 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.snep; - -import com.android.nfc.MockLlcpSocket; - -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.test.AndroidTestCase; -import android.util.Log; - -import java.io.IOException; - -/** - * Tests the SNEP cleint/server interfaces using a mock LLCP socket. - */ -public class SnepBasicTests extends AndroidTestCase { - private static final String TAG = "snepBasicTests"; - private static final int MIU = 250; - private static final int ACCEPTABLE_LENGTH = 2*1024; - - public void testGetSmallNdef() throws IOException { - MockLlcpSocket clientSocket = new MockLlcpSocket(); - MockLlcpSocket serverSocket = new MockLlcpSocket(); - MockLlcpSocket.bind(clientSocket, serverSocket); - - final SnepMessenger client = new SnepMessenger(true, clientSocket, MIU); - final SnepMessenger server = new SnepMessenger(false, serverSocket, MIU); - - new Thread() { - @Override - public void run() { - try { - SnepServer.handleRequest(server, mCallback); - } catch (Exception e) { - Log.e(TAG, "error getting message", e); - } - }; - }.start(); - - SnepMessage response = null; - try { - client.sendMessage(SnepMessage.getGetRequest(ACCEPTABLE_LENGTH, getSmallNdef())); - response = client.getMessage(); - } catch (SnepException e) { - throw new IOException("Failed to retrieve SNEP message", e); - } - - assertNotNull(response); - assertEquals(SnepMessage.RESPONSE_SUCCESS, response.getField()); - } - - public void testGetLargeNdef() throws IOException { - MockLlcpSocket clientSocket = new MockLlcpSocket(); - MockLlcpSocket serverSocket = new MockLlcpSocket(); - MockLlcpSocket.bind(clientSocket, serverSocket); - - final SnepMessenger client = new SnepMessenger(true, clientSocket, MIU); - final SnepMessenger server = new SnepMessenger(false, serverSocket, MIU); - - new Thread() { - @Override - public void run() { - try { - SnepServer.handleRequest(server, mCallback); - } catch (Exception e) { - Log.e(TAG, "error getting message", e); - } - }; - }.start(); - - SnepMessage response = null; - try { - client.sendMessage(SnepMessage.getGetRequest(ACCEPTABLE_LENGTH, getNdef(900))); - response = client.getMessage(); - } catch (SnepException e) { - throw new IOException("Failed to retrieve SNEP message", e); - } - - assertNotNull(response); - assertEquals(SnepMessage.RESPONSE_SUCCESS, response.getField()); - } - - public void testGetExcessiveNdef() throws IOException { - MockLlcpSocket clientSocket = new MockLlcpSocket(); - MockLlcpSocket serverSocket = new MockLlcpSocket(); - MockLlcpSocket.bind(clientSocket, serverSocket); - - final SnepMessenger client = new SnepMessenger(true, clientSocket, MIU); - final SnepMessenger server = new SnepMessenger(false, serverSocket, MIU); - - new Thread() { - @Override - public void run() { - try { - SnepServer.handleRequest(server, mCallback); - } catch (Exception e) { - Log.e(TAG, "error getting message", e); - } - }; - }.start(); - - SnepMessage response = null; - try { - client.sendMessage(SnepMessage.getGetRequest(10, getSmallNdef())); - response = client.getMessage(); - } catch (SnepException e) { - throw new IOException("Failed to retrieve SNEP message", e); - } - - assertNotNull(response); - assertEquals(SnepMessage.RESPONSE_EXCESS_DATA, response.getField()); - } - - public void testPutSmallNdef() throws IOException { - MockLlcpSocket clientSocket = new MockLlcpSocket(); - MockLlcpSocket serverSocket = new MockLlcpSocket(); - MockLlcpSocket.bind(clientSocket, serverSocket); - - final SnepMessenger client = new SnepMessenger(true, clientSocket, MIU); - final SnepMessenger server = new SnepMessenger(false, serverSocket, MIU); - - new Thread() { - @Override - public void run() { - try { - SnepServer.handleRequest(server, mCallback); - } catch (Exception e) { - Log.e(TAG, "error getting message", e); - } - }; - }.start(); - - SnepMessage response = null; - try { - client.sendMessage(SnepMessage.getPutRequest(getSmallNdef())); - response = client.getMessage(); - } catch (SnepException e) { - throw new IOException("Failed to retrieve SNEP message", e); - } - - assertNotNull(response); - assertEquals(SnepMessage.RESPONSE_SUCCESS, response.getField()); - } - - public void testPutLargeNdef() throws IOException { - MockLlcpSocket clientSocket = new MockLlcpSocket(); - MockLlcpSocket serverSocket = new MockLlcpSocket(); - MockLlcpSocket.bind(clientSocket, serverSocket); - - final SnepMessenger client = new SnepMessenger(true, clientSocket, MIU); - final SnepMessenger server = new SnepMessenger(false, serverSocket, MIU); - - new Thread() { - @Override - public void run() { - try { - SnepServer.handleRequest(server, mCallback); - } catch (Exception e) { - Log.e(TAG, "error getting message", e); - } - }; - }.start(); - - SnepMessage response = null; - try { - client.sendMessage(SnepMessage.getPutRequest(getNdef(900))); - response = client.getMessage(); - } catch (SnepException e) { - throw new IOException("Failed to retrieve SNEP message", e); - } - - assertNotNull(response); - assertEquals(SnepMessage.RESPONSE_SUCCESS, response.getField()); - } - - public void testUnsupportedVersion() throws IOException { - MockLlcpSocket clientSocket = new MockLlcpSocket(); - MockLlcpSocket serverSocket = new MockLlcpSocket(); - MockLlcpSocket.bind(clientSocket, serverSocket); - - final SnepMessenger client = new SnepMessenger(true, clientSocket, MIU); - final SnepMessenger server = new SnepMessenger(false, serverSocket, MIU); - - new Thread() { - @Override - public void run() { - try { - SnepServer.handleRequest(server, mCallback); - } catch (Exception e) { - Log.e(TAG, "error getting message", e); - } - }; - }.start(); - - SnepMessage response = null; - try { - NdefMessage ndef = getSmallNdef(); - SnepMessage request = new SnepMessage( - (byte)2, SnepMessage.REQUEST_PUT, ndef.toByteArray().length, 0, ndef); - client.sendMessage(request); - response = client.getMessage(); - } catch (SnepException e) { - throw new IOException("Failed to retrieve SNEP message", e); - } - - assertNotNull(response); - assertEquals(SnepMessage.RESPONSE_UNSUPPORTED_VERSION, response.getField()); - } - - public void testDifferentMinorVersion() throws IOException { - MockLlcpSocket clientSocket = new MockLlcpSocket(); - MockLlcpSocket serverSocket = new MockLlcpSocket(); - MockLlcpSocket.bind(clientSocket, serverSocket); - - final SnepMessenger client = new SnepMessenger(true, clientSocket, MIU); - final SnepMessenger server = new SnepMessenger(false, serverSocket, MIU); - - new Thread() { - @Override - public void run() { - try { - SnepServer.handleRequest(server, mCallback); - } catch (Exception e) { - Log.e(TAG, "error getting message", e); - } - }; - }.start(); - - byte version = (0xF0 & (SnepMessage.VERSION_MAJOR << 4)) | - (0x0F & (SnepMessage.VERSION_MINOR + 1)); - SnepMessage response = null; - try { - NdefMessage ndef = getSmallNdef(); - SnepMessage request = new SnepMessage( - version, SnepMessage.REQUEST_PUT, ndef.toByteArray().length, 0, ndef); - client.sendMessage(request); - response = client.getMessage(); - } catch (SnepException e) { - throw new IOException("Failed to retrieve SNEP message", e); - } - - assertNotNull(response); - assertEquals(SnepMessage.RESPONSE_SUCCESS, response.getField()); - } - - NdefMessage getSmallNdef() { - NdefRecord rec = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, NdefRecord.RTD_URI, - new byte[0], "http://android.com".getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - NdefMessage getNdef(int size) { - StringBuffer string = new StringBuffer(size); - for (int i = 0; i < size; i++) { - string.append('A' + (i % 26)); - } - NdefRecord rec = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), - new byte[0], string.toString().getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - /** - * A SNEP Server implementation that accepts PUT requests for all ndef - * messages and responds to GET requests with acceptable length greater - * than or equal to 1024. - */ - final SnepServer.Callback mCallback = new SnepServer.Callback() { - private static final int GET_LENGTH = 1024; - - @Override - public SnepMessage doPut(NdefMessage msg) { - return SnepMessage.getSuccessResponse(null); - } - - @Override - public SnepMessage doGet(int acceptableLength, NdefMessage msg) { - if (GET_LENGTH <= acceptableLength) { - return SnepMessage.getSuccessResponse(getSmallNdef()); - } else { - return SnepMessage.getMessage(SnepMessage.RESPONSE_EXCESS_DATA); - } - } - }; -}
\ No newline at end of file diff --git a/tests/src/com/android/nfc/snep/SnepCustomClientTests.java b/tests/src/com/android/nfc/snep/SnepCustomClientTests.java deleted file mode 100644 index 4f96aef2..00000000 --- a/tests/src/com/android/nfc/snep/SnepCustomClientTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2011 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.snep; - -import java.io.IOException; - -import com.android.nfc.snep.SnepClient; -import com.android.nfc.snep.SnepMessage; -import com.android.nfc.snep.SnepServer; - -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.test.AndroidTestCase; -import android.util.Log; - -import java.lang.StringBuffer; - -/** - * Tests connectivity to a custom SNEP server, using a physical NFC device. - */ -public class SnepCustomClientTests extends AndroidTestCase { - private static final String TAG = "nfcTest"; - - public void setUp() { - Log.d(TAG, "Waiting for service to restart..."); - try { - Thread.sleep(6000); - } catch (InterruptedException e) {} - Log.d(TAG, "Running test."); - } - - public void testPutSmall() throws IOException { - SnepClient client = getSnepClient(); - client.connect(); - client.put(getSmallNdef()); - client.close(); - } - - public void testPutLarge() throws IOException { - SnepClient client = getSnepClient(); - client.connect(); - client.put(getLargeNdef()); - client.close(); - } - - public void testPutTwice() throws IOException { - SnepClient client = getSnepClient(); - client.connect(); - client.put(getSmallNdef()); - client.put(getSmallNdef()); - client.close(); - } - - public void testGetSmall() throws IOException { - SnepClient client = getSnepClient(); - client.connect(); - SnepMessage response = client.get(getSmallNdef()); - assertEquals(SnepMessage.RESPONSE_SUCCESS, response.getField()); - } - - private NdefMessage getSmallNdef() { - NdefRecord rec = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, NdefRecord.RTD_URI, - new byte[0], "http://android.com".getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - private NdefMessage getLargeNdef() { - int size = 3000; - StringBuffer string = new StringBuffer(size); - for (int i = 0; i < size; i++) { - string.append('A' + (i % 26)); - } - NdefRecord rec = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), - new byte[0], string.toString().getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - private SnepClient getSnepClient() { - return new SnepClient(SnepValidationServerTests.SERVICE_NAME); - } -} diff --git a/tests/src/com/android/nfc/snep/SnepDefaultClientTests.java b/tests/src/com/android/nfc/snep/SnepDefaultClientTests.java deleted file mode 100644 index b8849435..00000000 --- a/tests/src/com/android/nfc/snep/SnepDefaultClientTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2011 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.snep; - -import java.io.IOException; - -import com.android.nfc.snep.SnepClient; -import com.android.nfc.snep.SnepMessage; -import com.android.nfc.snep.SnepServer; - -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.test.AndroidTestCase; -import android.util.Log; - -import java.lang.StringBuffer; - -/** - * Tests connectivity to a default SNEP server, using a physical NFC device. - */ -public class SnepDefaultClientTests extends AndroidTestCase { - private static final String TAG = "nfcTest"; - - public void setUp() { - Log.d(TAG, "Waiting for service to restart..."); - try { - Thread.sleep(6000); - } catch (InterruptedException e) {} - Log.d(TAG, "Running test."); - } - - public void testPutSmallToDefaultServer() throws IOException { - SnepClient client = new SnepClient(); - client.connect(); - client.put(getSmallNdef()); - client.close(); - } - - public void testPutLargeToDefaultServer() throws IOException { - SnepClient client = new SnepClient(); - client.connect(); - client.put(getLargeNdef()); - client.close(); - } - - public void testPutTwiceToDefaultServer() throws IOException { - SnepClient client = new SnepClient(); - client.connect(); - client.put(getSmallNdef()); - client.put(getSmallNdef()); - client.close(); - } - - public void testGetFail() throws IOException { - SnepClient client = new SnepClient(); - client.connect(); - SnepMessage response = client.get(getSmallNdef()); - assertEquals(SnepMessage.RESPONSE_NOT_IMPLEMENTED, response.getField()); - } - - private NdefMessage getSmallNdef() { - NdefRecord rec = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, NdefRecord.RTD_URI, - new byte[0], "http://android.com".getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - private NdefMessage getLargeNdef() { - int size = 3000; - StringBuffer string = new StringBuffer(size); - for (int i = 0; i < size; i++) { - string.append('A' + (i % 26)); - } - NdefRecord rec = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), - new byte[0], string.toString().getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } -} diff --git a/tests/src/com/android/nfc/snep/SnepValidationClientTests.java b/tests/src/com/android/nfc/snep/SnepValidationClientTests.java deleted file mode 100644 index 1b0cedf0..00000000 --- a/tests/src/com/android/nfc/snep/SnepValidationClientTests.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2011 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.snep; - -import java.io.IOException; - -import com.android.nfc.snep.SnepClient; -import com.android.nfc.snep.SnepMessage; -import com.android.nfc.snep.SnepServer; - -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.test.AndroidTestCase; -import android.util.Log; - -import java.lang.StringBuffer; - -/** - * Tests connectivity to a custom SNEP server, using a physical NFC device. - */ -public class SnepValidationClientTests extends AndroidTestCase { - private static final String TAG = "nfcTest"; - - private static final int FRAGMENT_LENGTH = 50; - - public static final String SERVICE_NAME = SnepValidationServerTests.SERVICE_NAME; - - public void setUp() { - Log.d(TAG, "Waiting for service to restart..."); - try { - Thread.sleep(8000); - } catch (InterruptedException e) { - } - Log.d(TAG, "Running test."); - } - - public void testNonFragmented() throws IOException { - try { - SnepClient client = getSnepClient(); - NdefMessage msg = getSmallNdef(); - Log.d(TAG, "Connecting to service " + SERVICE_NAME + "..."); - client.connect(); - Log.d(TAG, "Putting ndef message..."); - client.put(msg); - - Log.d(TAG, "Getting ndef message..."); - byte[] responseBytes = client.get(msg).getNdefMessage().toByteArray(); - int i = 0; - byte[] msgBytes = msg.toByteArray(); - Log.d(TAG, "Done. Checking " + msgBytes.length + " bytes."); - for (byte b : msgBytes) { - assertEquals(b, responseBytes[i++]); - } - Log.d(TAG, "Closing client."); - client.close(); - } catch (IOException e) { - Log.d(TAG, "Test failed.", e); - throw e; - } - } - - public void testFragmented() throws IOException { - try { - SnepClient client = getSnepClient(); - NdefMessage msg = getLargeNdef(); - Log.d(TAG, "Connecting to service " + SERVICE_NAME + "..."); - client.connect(); - Log.d(TAG, "Putting ndef message of size " + msg.toByteArray().length + "..."); - client.put(msg); - - Log.d(TAG, "Getting ndef message..."); - byte[] responseBytes = client.get(msg).getNdefMessage().toByteArray(); - int i = 0; - byte[] msgBytes = msg.toByteArray(); - Log.d(TAG, "Done. Checking " + msgBytes.length + " bytes."); - for (byte b : msgBytes) { - assertEquals(b, responseBytes[i++]); - } - client.close(); - } catch (IOException e) { - Log.d(TAG, "Error running fragmented", e); - throw e; - } - } - - public void testMultipleNdef() throws IOException { - try { - SnepClient client = getSnepClient(); - Log.d(TAG, "Connecting to service " + SERVICE_NAME + "..."); - client.connect(); - - NdefMessage msgA = getSmallNdef(); - NdefMessage msgB = getLargeNdef(); - - Log.d(TAG, "Putting ndef message A..."); - client.put(msgA); - Log.d(TAG, "Putting ndef message B..."); - client.put(msgB); - - byte[] responseBytes; - byte[] msgBytes; - int i; - - Log.d(TAG, "Getting ndef message A..."); - responseBytes = client.get(msgA).getNdefMessage().toByteArray(); - i = 0; - msgBytes = msgA.toByteArray(); - Log.d(TAG, "Done. Checking " + msgBytes.length + " bytes."); - for (byte b : msgBytes) { - assertEquals(b, responseBytes[i++]); - } - - Log.d(TAG, "Getting ndef message B..."); - responseBytes = client.get(msgB).getNdefMessage().toByteArray(); - i = 0; - msgBytes = msgB.toByteArray(); - Log.d(TAG, "Done. Checking " + msgBytes.length + " bytes."); - for (byte b : msgBytes) { - assertEquals(b, responseBytes[i++]); - } - - Log.d(TAG, "Closing client."); - client.close(); - } catch (IOException e) { - Log.d(TAG, "Test failed.", e); - throw e; - } - } - - public void testUnavailable() throws IOException { - try { - SnepClient client = getSnepClient(); - NdefMessage msg = getSmallNdef(); - Log.d(TAG, "Connecting to service " + SERVICE_NAME + "..."); - client.connect(); - - Log.d(TAG, "Getting ndef message..."); - SnepMessage response = client.get(msg); - assertEquals(SnepMessage.RESPONSE_NOT_FOUND, response.getField()); - Log.d(TAG, "Closing client."); - client.close(); - } catch (IOException e) { - Log.d(TAG, "Test failed.", e); - throw e; - } - } - - public void testUndeliverable() throws IOException { - try { - SnepClient client = new SnepClient(SERVICE_NAME, 100, FRAGMENT_LENGTH); - NdefMessage msg = getLargeNdef(); - Log.d(TAG, "Connecting to service " + SERVICE_NAME + "..."); - client.connect(); - Log.d(TAG, "Putting ndef message of size " + msg.toByteArray().length + "..."); - client.put(msg); - - Log.d(TAG, "Getting ndef message..."); - SnepMessage response = client.get(msg); - assertEquals(SnepMessage.RESPONSE_EXCESS_DATA, response.getField()); - client.close(); - } catch (IOException e) { - Log.d(TAG, "Error running fragmented", e); - throw e; - } - } - - private NdefMessage getSmallNdef() { - NdefRecord rec = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, - new byte[] { 'A' }, "http://android.com".getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - private NdefMessage getLargeNdef() { - int size = 500; - StringBuffer string = new StringBuffer(size); - for (int i = 0; i < size; i++) { - string.append('A' + (i % 26)); - } - NdefRecord rec = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), - new byte[] { 'B' }, string.toString().getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - private SnepClient getSnepClient() { - return new SnepClient(SERVICE_NAME, FRAGMENT_LENGTH); - } -} diff --git a/tests/src/com/android/nfc/snep/SnepValidationServerTests.java b/tests/src/com/android/nfc/snep/SnepValidationServerTests.java deleted file mode 100644 index 1a35764b..00000000 --- a/tests/src/com/android/nfc/snep/SnepValidationServerTests.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2011 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.snep; - -import java.io.IOException; - -import com.android.nfc.snep.SnepClient; -import com.android.nfc.snep.SnepMessage; -import com.android.nfc.snep.SnepServer; - -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.test.AndroidTestCase; -import android.util.Log; - -import java.lang.StringBuffer; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -/** - * Uses the Android testing instrumentation framework to run a listening SNEP - * server. - */ -public class SnepValidationServerTests extends AndroidTestCase implements SnepServer.Callback { - static final int SERVICE_SAP = 0x11; - static final String SERVICE_NAME = "urn:nfc:xsn:nfc-forum.org:snep-validation"; - private static final String TAG = "nfcTest"; - - private Map<ByteArrayWrapper, NdefMessage> mStoredNdef = - new HashMap<ByteArrayWrapper, NdefMessage>(); - private static final ByteArrayWrapper DEFAULT_NDEF = new ByteArrayWrapper(new byte[] {}); - - public void setUp() { - Log.d(TAG, "Waiting for service to restart..."); - try { - Thread.sleep(6000); - } catch (InterruptedException e) { - } - Log.d(TAG, "Running test."); - } - - public void testStartSnepServer() throws IOException { - SnepServer server = new SnepServer(SERVICE_NAME, SERVICE_SAP, this); - server.start(); - - try { - Thread.sleep(24 * 60 * 60 * 1000); - } catch (InterruptedException e) { - - } - } - - private NdefMessage getSmallNdef() { - NdefRecord rec = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, NdefRecord.RTD_URI, - new byte[0], "http://android.com".getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - private NdefMessage getLargeNdef() { - int size = 3000; - StringBuffer string = new StringBuffer(size); - for (int i = 0; i < size; i++) { - string.append('A' + (i % 26)); - } - NdefRecord rec = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, "text/plain".getBytes(), - new byte[0], string.toString().getBytes()); - return new NdefMessage(new NdefRecord[] { rec }); - } - - static class ByteArrayWrapper { - private final byte[] data; - - public ByteArrayWrapper(byte[] data) { - if (data == null) { - throw new NullPointerException(); - } - this.data = data; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof ByteArrayWrapper)) { - return false; - } - return Arrays.equals(data, ((ByteArrayWrapper) other).data); - } - - @Override - public int hashCode() { - return Arrays.hashCode(data); - } - } - - @Override - public SnepMessage doPut(NdefMessage msg) { - Log.d(TAG, "doPut()"); - NdefRecord record = msg.getRecords()[0]; - ByteArrayWrapper id = (record.getId().length > 0) ? - new ByteArrayWrapper(record.getId()) : DEFAULT_NDEF; - mStoredNdef.put(id, msg); - return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS); - } - - @Override - public SnepMessage doGet(int acceptableLength, NdefMessage msg) { - Log.d(TAG, "doGet()"); - NdefRecord record = msg.getRecords()[0]; - ByteArrayWrapper id = (record.getId().length > 0) ? - new ByteArrayWrapper(record.getId()) : DEFAULT_NDEF; - NdefMessage result = mStoredNdef.get(id); - - if (result == null) { - return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_FOUND); - } - if (acceptableLength < result.toByteArray().length) { - return SnepMessage.getMessage(SnepMessage.RESPONSE_EXCESS_DATA); - } - return SnepMessage.getSuccessResponse(result); - } -} diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp new file mode 100644 index 00000000..d1556ddf --- /dev/null +++ b/tests/unit/Android.bp @@ -0,0 +1,32 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +android_test { + name: "NfcNciInstrumentationTests", + + certificate: "platform", + + libs: [ + "android.test.runner", + "android.test.base", + "android.test.mock", + ], + + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.ext.junit", + "mockito-target", + "truth-prebuilt", + ], + + // Include all test java files. + srcs: ["src/**/*.java"], + + platform_apis: true, + + test_suites: ["device-tests"], + + instrumentation_for: "NfcNci", +} diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml new file mode 100644 index 00000000..cf8895dc --- /dev/null +++ b/tests/unit/AndroidManifest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.nfc.tests.unit"> + + <uses-permission android:name="android.permission.NFC" /> + <uses-permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.nfc" + android:label="NfcNci Test Cases"> + </instrumentation> + +</manifest> diff --git a/tests/unit/AndroidTest.xml b/tests/unit/AndroidTest.xml new file mode 100644 index 00000000..976f2dc8 --- /dev/null +++ b/tests/unit/AndroidTest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<configuration description="Runs NfcNci Test Cases."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="NfcNciInstrumentationTests.apk" /> + </target_preparer> + + <option name="test-tag" value="NfcNciInstrumentationTests" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.nfc.tests.unit" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/tests/unit/src/com/android/nfc/NfcFeatureFlagTest.java b/tests/unit/src/com/android/nfc/NfcFeatureFlagTest.java new file mode 100644 index 00000000..348b86a2 --- /dev/null +++ b/tests/unit/src/com/android/nfc/NfcFeatureFlagTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; +import android.os.SystemProperties; +import android.text.TextUtils; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.internal.util.ArrayUtils; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public final class NfcFeatureFlagTest { + + private static final String TAG = NfcFeatureFlagTest.class.getSimpleName(); + private Context mContext; + private NfcAdapter mNfcAdapter; + private boolean mNfcSupported; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + PackageManager pm = mContext.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_ANY)) { + mNfcSupported = false; + return; + } + mNfcSupported = true; + mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext); + Assert.assertNotNull(mNfcAdapter); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testIsSecureNfcSupported() { + if (!mNfcSupported) return; + String[] skuList = mContext.getResources().getStringArray( + R.array.config_skuSupportsSecureNfc); + String sku = SystemProperties.get("ro.boot.hardware.sku"); + if (TextUtils.isEmpty(sku) || !ArrayUtils.contains(skuList, sku)) { + assertFalse(mNfcAdapter.isSecureNfcSupported()); + } else { + assertTrue(mNfcAdapter.isSecureNfcSupported()); + } + } + + @Test + public void testIsControllerAlwaysOnSupported() { + if (!mNfcSupported) return; + assertThat(mContext.getResources() + .getBoolean(R.bool.nfcc_always_on_allowed)) + .isEqualTo(mNfcAdapter.isControllerAlwaysOnSupported()); + } +} diff --git a/tests/unit/src/com/android/nfc/NfcStateTest.java b/tests/unit/src/com/android/nfc/NfcStateTest.java new file mode 100644 index 00000000..26f93a4b --- /dev/null +++ b/tests/unit/src/com/android/nfc/NfcStateTest.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; +import android.nfc.NfcAdapter.ControllerAlwaysOnListener; +import android.util.Log; + +import androidx.test.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Executor; + +@RunWith(AndroidJUnit4.class) +public final class NfcStateTest { + + private static final String TAG = NfcStateTest.class.getSimpleName(); + private static final int MAX_TIMEOUT_MS = 20000; + private Context mContext; + private NfcAdapter mNfcAdapter; + private BroadcastReceiver mAdapterStateChangedReceiver; + private int mState; + private boolean mIsAlwaysOnEnabled; + private boolean mNfcSupported; + private ControllerAlwaysOnListener mListener; + + class SynchronousExecutor implements Executor { + public void execute(Runnable r) { + r.run(); + } + } + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getTargetContext(); + IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); + mAdapterStateChangedReceiver = new AdapterStateChangedReceiver(); + mContext.registerReceiver(mAdapterStateChangedReceiver, filter); + PackageManager pm = mContext.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_ANY)) { + mNfcSupported = false; + return; + } + mNfcSupported = true; + mNfcAdapter = NfcAdapter.getDefaultAdapter(mContext); + Assert.assertNotNull(mNfcAdapter); + if (mNfcAdapter.isEnabled()) { + mState = NfcAdapter.STATE_ON; + } else { + mState = NfcAdapter.STATE_OFF; + } + if (mNfcAdapter.isControllerAlwaysOnSupported()) { + mListener = new AlwaysOnStateListener(); + mNfcAdapter.registerControllerAlwaysOnListener(new SynchronousExecutor(), + mListener); + mIsAlwaysOnEnabled = mNfcAdapter.isControllerAlwaysOn(); + } + } + + @After + public void tearDown() throws Exception { + mContext.unregisterReceiver(mAdapterStateChangedReceiver); + if (mNfcAdapter.isControllerAlwaysOnSupported()) { + mNfcAdapter.unregisterControllerAlwaysOnListener(mListener); + } + } + + @Test + public void testSetControllerAlwaysOnTrueFromFalseWhenDisabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + if (mNfcAdapter.isEnabled()) { + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + } + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + + @Test + public void testSetControllerAlwaysOnFalseFromTrueWhenDisabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + if (mNfcAdapter.isEnabled()) { + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + } + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + + @Test + public void testSetControllerAlwaysOnFalseFromFalseWhenDisabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + if (mNfcAdapter.isEnabled()) { + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + } + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + + @Test + public void testSetControllerAlwaysOnTrueFromTrueWhenDisabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + if (mNfcAdapter.isEnabled()) { + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + } + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + + @Test + public void testSetControllerAlwaysOnTrueFromFalseWhenEnabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + } + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + + @Test + public void testSetAlwaysOnFalseFromTrueWhenEnabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + } + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + + @Test + public void testSetControllerAlwaysOnFalseFromFalseWhenEnabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + } + mNfcAdapter.setControllerAlwaysOn(false); + wait_for_always_on(false); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(false); + } + + @Test + public void testSetControllerAlwaysOnTrueFromTrueWhenEnabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + } + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + + @Test + public void testEnableWhenSetControllerAlwaysOnTrueAndDisabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + if (mNfcAdapter.isEnabled()) { + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(false); + } + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + assertThat(mState).isEqualTo(NfcAdapter.STATE_ON); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(true); + } + + @Test + public void testDisableWhenSetControllerAlwaysOnTrueAndEnabled() { + if (!mNfcSupported || !mNfcAdapter.isControllerAlwaysOnSupported()) return; + if (!mNfcAdapter.isControllerAlwaysOn()) { + mNfcAdapter.setControllerAlwaysOn(true); + wait_for_always_on(true); + assertThat(mNfcAdapter.isControllerAlwaysOn()).isEqualTo(true); + } + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(true); + } + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + assertThat(mState).isEqualTo(NfcAdapter.STATE_OFF); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(false); + } + + @Test + public void testDisableWhenEnabled() { + if (!mNfcSupported) return; + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + } + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + assertThat(mState).isEqualTo(NfcAdapter.STATE_OFF); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(false); + } + + @Test + public void testEnableWhenDisabled() { + if (!mNfcSupported) return; + if (mNfcAdapter.isEnabled()) { + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + } + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + assertThat(mState).isEqualTo(NfcAdapter.STATE_ON); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(true); + } + + @Test + public void testDisableWhenDisabled() { + if (!mNfcSupported) return; + if (mNfcAdapter.isEnabled()) { + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + } + mNfcAdapter.disable(); + wait_for_state(NfcAdapter.STATE_OFF); + assertThat(mState).isEqualTo(NfcAdapter.STATE_OFF); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(false); + } + + @Test + public void testEnableWhenEnabled() { + if (!mNfcSupported) return; + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + } + mNfcAdapter.enable(); + wait_for_state(NfcAdapter.STATE_ON); + assertThat(mState).isEqualTo(NfcAdapter.STATE_ON); + assertThat(mNfcAdapter.isEnabled()).isEqualTo(true); + } + + private class AdapterStateChangedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(intent.getAction())) { + mState = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, + NfcAdapter.STATE_OFF); + Log.i(TAG, "mState = " + mState); + } + } + } + private class AlwaysOnStateListener implements ControllerAlwaysOnListener { + @Override + public void onControllerAlwaysOnChanged(boolean isEnabled) { + Log.i(TAG, "onControllerAlwaysOnChanged, mIsAlwaysOnEnabled = " + isEnabled); + mIsAlwaysOnEnabled = isEnabled; + } + } + private void wait_for_state(int targetState) { + int duration = 100; + for (int i = 0; i < MAX_TIMEOUT_MS / duration; i++) { + msleep(duration); + if (mState == targetState) break; + } + } + private void wait_for_always_on(boolean isEnabled) { + int duration = 1000; + for (int i = 0; i < MAX_TIMEOUT_MS / duration; i++) { + msleep(duration); + if (isEnabled == mIsAlwaysOnEnabled) break; + } + } + + private void msleep(int millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + } + } +} |