/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <sys/queue.h>
#include <hardware/hardware.h>
#include <hardware/nfc.h>
#include <cutils/properties.h>
#include <ScopedLocalRef.h>

#include "com_android_nfc.h"

#define ERROR_BUFFER_TOO_SMALL       -12
#define ERROR_INSUFFICIENT_RESOURCES -9

extern uint32_t libnfc_llc_error_count;

static phLibNfc_sConfig_t   gDrvCfg;
void   *gHWRef;
static phNfc_sData_t gInputParam;
static phNfc_sData_t gOutputParam;

uint8_t device_connected_flag;
static bool driverConfigured = FALSE;

static phLibNfc_Handle              hLlcpHandle;
static NFCSTATUS                    lastErrorStatus = NFCSTATUS_FAILED;
static phLibNfc_Llcp_eLinkStatus_t  g_eLinkStatus = phFriNfc_LlcpMac_eLinkDefault;

static jmethodID cached_NfcManager_notifyNdefMessageListeners;
static jmethodID cached_NfcManager_notifyLlcpLinkActivation;
static jmethodID cached_NfcManager_notifyLlcpLinkDeactivated;
static jmethodID cached_NfcManager_notifyTargetDeselected;

static jmethodID cached_NfcManager_notifyRfFieldActivated;
static jmethodID cached_NfcManager_notifyRfFieldDeactivated;
namespace android {

phLibNfc_Handle     storedHandle = 0;

struct nfc_jni_native_data *exported_nat = NULL;

/* Internal functions declaration */
static void *nfc_jni_client_thread(void *arg);
static void nfc_jni_init_callback(void *pContext, NFCSTATUS status);
static void nfc_jni_deinit_callback(void *pContext, NFCSTATUS status);
static void nfc_jni_discover_callback(void *pContext, NFCSTATUS status);
static void nfc_jni_se_set_mode_callback(void *context,
        phLibNfc_Handle handle, NFCSTATUS status);
static void nfc_jni_llcpcfg_callback(void *pContext, NFCSTATUS status);
static void nfc_jni_start_discovery_locked(struct nfc_jni_native_data *nat, bool resume);
static void nfc_jni_Discovery_notification_callback(void *pContext,
        phLibNfc_RemoteDevList_t *psRemoteDevList,
        uint8_t uNofRemoteDev, NFCSTATUS status);
static void nfc_jni_transaction_callback(void *context,
        phLibNfc_eSE_EvtType_t evt_type, phLibNfc_Handle handle,
        phLibNfc_uSeEvtInfo_t *evt_info, NFCSTATUS status);
static bool performDownload(struct nfc_jni_native_data *nat, bool takeLock);

extern void set_target_activationBytes(JNIEnv *e, jobject tag,
        phLibNfc_sRemoteDevInformation_t *psRemoteDevInfo);
extern void set_target_pollBytes(JNIEnv *e, jobject tag,
        phLibNfc_sRemoteDevInformation_t *psRemoteDevInfo);

/*
 * Deferred callback called when client thread must be exited
 */
static void client_kill_deferred_call(void* arg)
{
   struct nfc_jni_native_data *nat = (struct nfc_jni_native_data *)arg;

   nat->running = FALSE;
}

static void kill_client(nfc_jni_native_data *nat)
{
   phDal4Nfc_Message_Wrapper_t  wrapper;
   phLibNfc_DeferredCall_t     *pMsg;

   usleep(50000);

   ALOGD("Terminating client thread...");

   pMsg = (phLibNfc_DeferredCall_t*)malloc(sizeof(phLibNfc_DeferredCall_t));
   pMsg->pCallback = client_kill_deferred_call;
   pMsg->pParameter = (void*)nat;

   wrapper.msg.eMsgType = PH_LIBNFC_DEFERREDCALL_MSG;
   wrapper.msg.pMsgData = pMsg;
   wrapper.msg.Size     = sizeof(phLibNfc_DeferredCall_t);

   phDal4Nfc_msgsnd(gDrvCfg.nClientId, (struct msgbuf *)&wrapper, sizeof(phLibNfc_Message_t), 0);
}

static void nfc_jni_ioctl_callback(void *pContext, phNfc_sData_t* /*pOutput*/, NFCSTATUS status) {
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_ioctl_callback", status);

   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
}

static void nfc_jni_deinit_download_callback(void *pContext, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_deinit_download_callback", status);

   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
}

static int nfc_jni_download_locked(struct nfc_jni_native_data *nat, uint8_t update)
{
    uint8_t OutputBuffer[1];
    uint8_t InputBuffer[1];
    struct timespec ts;
    NFCSTATUS status = NFCSTATUS_FAILED;
    phLibNfc_StackCapabilities_t caps;
    struct nfc_jni_callback_data cb_data;
    bool result;

    /* Create the local semaphore */
    if (!nfc_cb_data_init(&cb_data, NULL))
    {
       goto clean_and_return;
    }

    if(update)
    {
        //deinit
        TRACE("phLibNfc_Mgt_DeInitialize() (download)");
        REENTRANCE_LOCK();
        status = phLibNfc_Mgt_DeInitialize(gHWRef, nfc_jni_deinit_download_callback, (void *)&cb_data);
        REENTRANCE_UNLOCK();
        if (status != NFCSTATUS_PENDING)
        {
            ALOGE("phLibNfc_Mgt_DeInitialize() (download) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
        }

        clock_gettime(CLOCK_REALTIME, &ts);
        ts.tv_sec += 5;

        /* Wait for callback response */
        if(sem_timedwait(&cb_data.sem, &ts))
        {
            ALOGW("Deinitialization timed out (download)");
        }

        if(cb_data.status != NFCSTATUS_SUCCESS)
        {
            ALOGW("Deinitialization FAILED (download)");
        }
        TRACE("Deinitialization SUCCESS (download)");
    }

    result = performDownload(nat, false);

    if (!result) {
        status = NFCSTATUS_FAILED;
        goto clean_and_return;
    }

    TRACE("phLibNfc_Mgt_Initialize()");
    REENTRANCE_LOCK();
    status = phLibNfc_Mgt_Initialize(gHWRef, nfc_jni_init_callback, (void *)&cb_data);
    REENTRANCE_UNLOCK();
    if(status != NFCSTATUS_PENDING)
    {
        ALOGE("phLibNfc_Mgt_Initialize() (download) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
        goto clean_and_return;
    }
    TRACE("phLibNfc_Mgt_Initialize() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

    if(sem_wait(&cb_data.sem))
    {
       ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
       status = NFCSTATUS_FAILED;
       goto clean_and_return;
    }

    /* Initialization Status */
    if(cb_data.status != NFCSTATUS_SUCCESS)
    {
        status = cb_data.status;
        goto clean_and_return;
    }

    /* ====== CAPABILITIES ======= */
    REENTRANCE_LOCK();
    status = phLibNfc_Mgt_GetstackCapabilities(&caps, (void*)nat);
    REENTRANCE_UNLOCK();
    if (status != NFCSTATUS_SUCCESS)
    {
       ALOGW("phLibNfc_Mgt_GetstackCapabilities returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
    }
    else
    {
        ALOGD("NFC capabilities: HAL = %x, FW = %x, HW = %x, Model = %x, HCI = %x, Full_FW = %d, Rev = %d, FW Update Info = %d",
              caps.psDevCapabilities.hal_version,
              caps.psDevCapabilities.fw_version,
              caps.psDevCapabilities.hw_version,
              caps.psDevCapabilities.model_id,
              caps.psDevCapabilities.hci_version,
              caps.psDevCapabilities.full_version[NXP_FULL_VERSION_LEN-1],
              caps.psDevCapabilities.full_version[NXP_FULL_VERSION_LEN-2],
              caps.psDevCapabilities.firmware_update_info);
    }

    /*Download is successful*/
    status = NFCSTATUS_SUCCESS;

clean_and_return:
   nfc_cb_data_deinit(&cb_data);
   return status;
}

static int nfc_jni_configure_driver(struct nfc_jni_native_data *nat)
{
    char value[PROPERTY_VALUE_MAX];
    int result = FALSE;
    NFCSTATUS status;

    /* ====== CONFIGURE DRIVER ======= */
    /* Configure hardware link */
    gDrvCfg.nClientId = phDal4Nfc_msgget(0, 0600);

    TRACE("phLibNfc_Mgt_ConfigureDriver(0x%08x)", gDrvCfg.nClientId);
    REENTRANCE_LOCK();
    status = phLibNfc_Mgt_ConfigureDriver(&gDrvCfg, &gHWRef);
    REENTRANCE_UNLOCK();
    if(status == NFCSTATUS_ALREADY_INITIALISED) {
           ALOGW("phLibNfc_Mgt_ConfigureDriver() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
    }
    else if(status != NFCSTATUS_SUCCESS)
    {
        ALOGE("phLibNfc_Mgt_ConfigureDriver() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
        goto clean_and_return;
    }
    TRACE("phLibNfc_Mgt_ConfigureDriver() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

    if(pthread_create(&(nat->thread), NULL, nfc_jni_client_thread, nat) != 0)
    {
        ALOGE("pthread_create failed");
        goto clean_and_return;
    }

    driverConfigured = TRUE;

clean_and_return:
    return result;
}

static int nfc_jni_unconfigure_driver(struct nfc_jni_native_data* /*nat*/)
{
    int result = FALSE;
    NFCSTATUS status;

    /* Unconfigure driver */
    TRACE("phLibNfc_Mgt_UnConfigureDriver()");
    REENTRANCE_LOCK();
    status = phLibNfc_Mgt_UnConfigureDriver(gHWRef);
    REENTRANCE_UNLOCK();
    if(status != NFCSTATUS_SUCCESS)
    {
        ALOGE("phLibNfc_Mgt_UnConfigureDriver() returned error 0x%04x[%s] -- this should never happen", status, nfc_jni_get_status_name( status));
    }
    else
    {
       ALOGD("phLibNfc_Mgt_UnConfigureDriver() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
       result = TRUE;
    }

    driverConfigured = FALSE;

    return result;
}

/* Initialization function */
static int nfc_jni_initialize(struct nfc_jni_native_data *nat) {
   struct timespec ts;
   uint8_t resp[16];
   NFCSTATUS status;
   phLibNfc_StackCapabilities_t caps;
   phLibNfc_SE_List_t SE_List[PHLIBNFC_MAXNO_OF_SE];
   uint8_t i, No_SE = PHLIBNFC_MAXNO_OF_SE, SmartMX_index = 0, SmartMX_detected = 0;
   phLibNfc_Llcp_sLinkParameters_t LlcpConfigInfo;
   struct nfc_jni_callback_data cb_data;
   uint8_t firmware_status;
   uint8_t update = TRUE;
   int result = JNI_FALSE;
   const hw_module_t* hw_module;
   nfc_pn544_device_t* pn544_dev = NULL;
   int ret = 0;
   ALOGD("Start Initialization\n");

   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, NULL))
   {
      goto clean_and_return;
   }
   /* Get EEPROM values and device port from product-specific settings */
   ret = hw_get_module(NFC_HARDWARE_MODULE_ID, &hw_module);
   if (ret) {
      ALOGE("hw_get_module() failed.");
      goto clean_and_return;
   }
   ret = nfc_pn544_open(hw_module, &pn544_dev);
   if (ret) {
      ALOGE("Could not open pn544 hw_module.");
      goto clean_and_return;
   }
   if (pn544_dev->num_eeprom_settings == 0 || pn544_dev->eeprom_settings == NULL) {
       ALOGE("Could not load EEPROM settings");
       goto clean_and_return;
   }

   /* Reset device connected handle */
   device_connected_flag = 0;

   /* Reset stored handle */
   storedHandle = 0;

   /* Initialize Driver */
   if(!driverConfigured)
   {
       nfc_jni_configure_driver(nat);
   }

   /* ====== INITIALIZE ======= */

   TRACE("phLibNfc_Mgt_Initialize()");
   REENTRANCE_LOCK();
   status = phLibNfc_Mgt_Initialize(gHWRef, nfc_jni_init_callback, (void *)&cb_data);
   REENTRANCE_UNLOCK();
   if(status != NFCSTATUS_PENDING)
   {
      ALOGE("phLibNfc_Mgt_Initialize() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
      update = FALSE;
      goto force_download;
   }
   TRACE("phLibNfc_Mgt_Initialize returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

   /* Wait for callback response */
   if(sem_wait(&cb_data.sem))
   {
      ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

   /* Initialization Status */
   if(cb_data.status != NFCSTATUS_SUCCESS)
   {
      update = FALSE;
      goto force_download;
   }

   /* ====== CAPABILITIES ======= */

   REENTRANCE_LOCK();
   status = phLibNfc_Mgt_GetstackCapabilities(&caps, (void*)nat);
   REENTRANCE_UNLOCK();
   if (status != NFCSTATUS_SUCCESS)
   {
      ALOGW("phLibNfc_Mgt_GetstackCapabilities returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
   }
   else
   {
       ALOGD("NFC capabilities: HAL = %x, FW = %x, HW = %x, Model = %x, HCI = %x, Full_FW = %d, Rev = %d, FW Update Info = %d",
             caps.psDevCapabilities.hal_version,
             caps.psDevCapabilities.fw_version,
             caps.psDevCapabilities.hw_version,
             caps.psDevCapabilities.model_id,
             caps.psDevCapabilities.hci_version,
             caps.psDevCapabilities.full_version[NXP_FULL_VERSION_LEN-1],
             caps.psDevCapabilities.full_version[NXP_FULL_VERSION_LEN-2],
             caps.psDevCapabilities.firmware_update_info);
   }

   /* ====== FIRMWARE VERSION ======= */
   if(caps.psDevCapabilities.firmware_update_info)
   {
force_download:
       for (i=0; i<3; i++)
       {
           TRACE("Firmware version not UpToDate");
           status = nfc_jni_download_locked(nat, update);
           if(status == NFCSTATUS_SUCCESS)
           {
               ALOGI("Firmware update SUCCESS");
               break;
           }
           ALOGW("Firmware update FAILED");
           update = FALSE;
       }
       if(i>=3)
       {
           ALOGE("Unable to update firmware, giving up");
           goto clean_and_return;
       }
   }
   else
   {
       TRACE("Firmware version UpToDate");
   }
   /* ====== EEPROM SETTINGS ======= */

   // Update EEPROM settings
   TRACE("******  START EEPROM SETTINGS UPDATE ******");
   for (i = 0; i < pn544_dev->num_eeprom_settings; i++)
   {
      char eeprom_property[PROPERTY_KEY_MAX];
      char eeprom_value[PROPERTY_VALUE_MAX];
      uint8_t* eeprom_base = &(pn544_dev->eeprom_settings[i*4]);
      TRACE("> EEPROM SETTING: %d", i);

      // Check for override of this EEPROM value in properties
      snprintf(eeprom_property, sizeof(eeprom_property), "debug.nfc.eeprom.%02X%02X",
              eeprom_base[1], eeprom_base[2]);
      TRACE(">> Checking property: %s", eeprom_property);
      if (property_get(eeprom_property, eeprom_value, "") == 2) {
          int eeprom_value_num = (int)strtol(eeprom_value, (char**)NULL, 16);
          ALOGD(">> Override EEPROM addr 0x%02X%02X with value %02X",
                  eeprom_base[1], eeprom_base[2], eeprom_value_num);
          eeprom_base[3] = eeprom_value_num;
      }

      TRACE(">> Addr: 0x%02X%02X set to: 0x%02X", eeprom_base[1], eeprom_base[2],
              eeprom_base[3]);
      gInputParam.buffer = eeprom_base;
      gInputParam.length = 0x04;
      gOutputParam.buffer = resp;

      REENTRANCE_LOCK();
      status = phLibNfc_Mgt_IoCtl(gHWRef, NFC_MEM_WRITE, &gInputParam, &gOutputParam, nfc_jni_ioctl_callback, (void *)&cb_data);
      REENTRANCE_UNLOCK();
      if (status != NFCSTATUS_PENDING) {
         ALOGE("phLibNfc_Mgt_IoCtl() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
         goto clean_and_return;
      }
      /* Wait for callback response */
      if(sem_wait(&cb_data.sem))
      {
         ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
         goto clean_and_return;
      }

      /* Initialization Status */
      if (cb_data.status != NFCSTATUS_SUCCESS)
      {
         goto clean_and_return;
      }
   }
   TRACE("******  ALL EEPROM SETTINGS UPDATED  ******");

   /* ====== SECURE ELEMENTS ======= */

   REENTRANCE_LOCK();
   ALOGD("phLibNfc_SE_GetSecureElementList()");
   status = phLibNfc_SE_GetSecureElementList(SE_List, &No_SE);
   REENTRANCE_UNLOCK();
   if (status != NFCSTATUS_SUCCESS)
   {
      ALOGD("phLibNfc_SE_GetSecureElementList(): Error");
      goto clean_and_return;
   }

   ALOGD("\n> Number of Secure Element(s) : %d\n", No_SE);
   /* Display Secure Element information */
   for (i = 0; i < No_SE; i++)
   {
      if (SE_List[i].eSE_Type == phLibNfc_SE_Type_SmartMX) {
         ALOGD("phLibNfc_SE_GetSecureElementList(): SMX detected, handle=%p", (void*)SE_List[i].hSecureElement);
      } else if (SE_List[i].eSE_Type == phLibNfc_SE_Type_UICC) {
         ALOGD("phLibNfc_SE_GetSecureElementList(): UICC detected, handle=%p", (void*)SE_List[i].hSecureElement);
      }

      /* Set SE mode - Off */
      REENTRANCE_LOCK();
      status = phLibNfc_SE_SetMode(SE_List[i].hSecureElement,
            phLibNfc_SE_ActModeOff, nfc_jni_se_set_mode_callback,
            (void *)&cb_data);
      REENTRANCE_UNLOCK();
      if (status != NFCSTATUS_PENDING)
      {
         ALOGE("phLibNfc_SE_SetMode() returned 0x%04x[%s]", status,
               nfc_jni_get_status_name(status));
         goto clean_and_return;
      }
      ALOGD("phLibNfc_SE_SetMode() returned 0x%04x[%s]", status,
            nfc_jni_get_status_name(status));

      /* Wait for callback response */
      if(sem_wait(&cb_data.sem))
      {
         ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
         goto clean_and_return;
      }
   }

   /* ====== LLCP ======= */

   /* LLCP Params */
   TRACE("******  NFC Config Mode NFCIP1 - LLCP ******");
   LlcpConfigInfo.miu    = nat->miu;
   LlcpConfigInfo.lto    = nat->lto;
   LlcpConfigInfo.wks    = nat->wks;
   LlcpConfigInfo.option = nat->opt;

   REENTRANCE_LOCK();
   status = phLibNfc_Mgt_SetLlcp_ConfigParams(&LlcpConfigInfo,
                                              nfc_jni_llcpcfg_callback,
                                              (void *)&cb_data);
   REENTRANCE_UNLOCK();
   if(status != NFCSTATUS_PENDING)
   {
      ALOGE("phLibNfc_Mgt_SetLlcp_ConfigParams returned 0x%04x[%s]", status,
           nfc_jni_get_status_name(status));
      goto clean_and_return;
   }
   TRACE("phLibNfc_Mgt_SetLlcp_ConfigParams returned 0x%04x[%s]", status,
         nfc_jni_get_status_name(status));

   /* Wait for callback response */
   if(sem_wait(&cb_data.sem))
   {
      ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

   /* ===== DISCOVERY ==== */
   nat->discovery_cfg.NfcIP_Mode = nat->p2p_initiator_modes;  //initiator
   nat->discovery_cfg.NfcIP_Target_Mode = nat->p2p_target_modes;  //target
   nat->discovery_cfg.Duration = 300000; /* in ms */
   nat->discovery_cfg.NfcIP_Tgt_Disable = FALSE;

   /* Register for the card emulation mode */
   REENTRANCE_LOCK();
   ret = phLibNfc_SE_NtfRegister(nfc_jni_transaction_callback,(void *)nat);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_SUCCESS)
   {
        ALOGD("phLibNfc_SE_NtfRegister returned 0x%02x",ret);
        goto clean_and_return;
   }
   TRACE("phLibNfc_SE_NtfRegister returned 0x%x\n", ret);


   /* ====== END ======= */

   ALOGI("NFC Initialized");

   result = TRUE;

clean_and_return:
   if (result != TRUE)
   {
      if(nat)
      {
         kill_client(nat);
      }
   }
   if (pn544_dev != NULL) {
       nfc_pn544_close(pn544_dev);
   }
   nfc_cb_data_deinit(&cb_data);

   return result;
}

static int is_user_build() {
    char value[PROPERTY_VALUE_MAX];
    property_get("ro.build.type", value, "");
    return !strncmp("user", value, PROPERTY_VALUE_MAX);
}

/*
 * Last-chance fallback when there is no clean way to recover
 * Performs a software reset
  */
void emergency_recovery(struct nfc_jni_native_data* /*nat*/) {
   if (is_user_build()) {
       ALOGE("emergency_recovery: force restart of NFC service");
   } else {
       // dont recover immediately, so we can debug
       unsigned int t;
       for (t=1; t < 1000000; t <<= 1) {
           ALOGE("emergency_recovery: NFC stack dead-locked");
           sleep(t);
       }
   }
   phLibNfc_Mgt_Recovery();
   abort();  // force a noisy crash
}

void nfc_jni_reset_timeout_values()
{
    REENTRANCE_LOCK();
    phLibNfc_SetIsoXchgTimeout(NXP_ISO_XCHG_TIMEOUT);
    phLibNfc_SetHciTimeout(NXP_NFC_HCI_TIMEOUT);
    phLibNfc_SetFelicaTimeout(NXP_FELICA_XCHG_TIMEOUT);
    phLibNfc_SetMifareRawTimeout(NXP_MIFARE_XCHG_TIMEOUT);
    REENTRANCE_UNLOCK();
}

/*
 * Restart the polling loop when unable to perform disconnect
  */
void nfc_jni_restart_discovery_locked(struct nfc_jni_native_data *nat)
{
    nfc_jni_start_discovery_locked(nat, true);
}

 /*
  *  Utility to recover UID from target infos
  */
static phNfc_sData_t get_target_uid(phLibNfc_sRemoteDevInformation_t *psRemoteDevInfo)
{
    phNfc_sData_t uid;

    switch(psRemoteDevInfo->RemDevType)
    {
    case phNfc_eISO14443_A_PICC:
    case phNfc_eISO14443_4A_PICC:
    case phNfc_eISO14443_3A_PICC:
    case phNfc_eMifare_PICC:
        uid.buffer = psRemoteDevInfo->RemoteDevInfo.Iso14443A_Info.Uid;
        uid.length = psRemoteDevInfo->RemoteDevInfo.Iso14443A_Info.UidLength;
        break;
    case phNfc_eISO14443_B_PICC:
    case phNfc_eISO14443_4B_PICC:
        uid.buffer = psRemoteDevInfo->RemoteDevInfo.Iso14443B_Info.AtqB.AtqResInfo.Pupi;
        uid.length = sizeof(psRemoteDevInfo->RemoteDevInfo.Iso14443B_Info.AtqB.AtqResInfo.Pupi);
        break;
    case phNfc_eFelica_PICC:
        uid.buffer = psRemoteDevInfo->RemoteDevInfo.Felica_Info.IDm;
        uid.length = psRemoteDevInfo->RemoteDevInfo.Felica_Info.IDmLength;
        break;
    case phNfc_eJewel_PICC:
        uid.buffer = psRemoteDevInfo->RemoteDevInfo.Jewel_Info.Uid;
        uid.length = psRemoteDevInfo->RemoteDevInfo.Jewel_Info.UidLength;
        break;
    case phNfc_eISO15693_PICC:
        uid.buffer = psRemoteDevInfo->RemoteDevInfo.Iso15693_Info.Uid;
        uid.length = psRemoteDevInfo->RemoteDevInfo.Iso15693_Info.UidLength;
        break;
    case phNfc_eNfcIP1_Target:
    case phNfc_eNfcIP1_Initiator:
        uid.buffer = psRemoteDevInfo->RemoteDevInfo.NfcIP_Info.NFCID;
        uid.length = psRemoteDevInfo->RemoteDevInfo.NfcIP_Info.NFCID_Length;
        break;
    default:
        uid.buffer = NULL;
        uid.length = 0;
        break;
    }

    return uid;
}

/*
 * NFC stack message processing
 */
static void *nfc_jni_client_thread(void *arg)
{
   struct nfc_jni_native_data *nat;
   JNIEnv *e;
   JavaVMAttachArgs thread_args;
   phDal4Nfc_Message_Wrapper_t wrapper;

   nat = (struct nfc_jni_native_data *)arg;

   thread_args.name = "NFC Message Loop";
   thread_args.version = nat->env_version;
   thread_args.group = NULL;

   nat->vm->AttachCurrentThread(&e, &thread_args);
   pthread_setname_np(pthread_self(), "message");

   TRACE("NFC client started");
   nat->running = TRUE;
   while(nat->running == TRUE)
   {
      /* Fetch next message from the NFC stack message queue */
      if(phDal4Nfc_msgrcv(gDrvCfg.nClientId, (void *)&wrapper,
         sizeof(phLibNfc_Message_t), 0, 0) == -1)
      {
         ALOGE("NFC client received bad message");
         continue;
      }

      switch(wrapper.msg.eMsgType)
      {
         case PH_LIBNFC_DEFERREDCALL_MSG:
         {
            phLibNfc_DeferredCall_t *msg =
               (phLibNfc_DeferredCall_t *)(wrapper.msg.pMsgData);

            REENTRANCE_LOCK();
            msg->pCallback(msg->pParameter);
            REENTRANCE_UNLOCK();

            break;
         }
      }
   }
   TRACE("NFC client stopped");

   nat->vm->DetachCurrentThread();

   return NULL;
}

extern uint8_t nfc_jni_is_ndef;
extern uint8_t *nfc_jni_ndef_buf;
extern uint32_t nfc_jni_ndef_buf_len;

static phLibNfc_sNfcIPCfg_t nfc_jni_nfcip1_cfg =
{
   3,
   { 0x46, 0x66, 0x6D }
};

/*
 * Callbacks
 */

/* P2P - LLCP callbacks */
static void nfc_jni_llcp_linkStatus_callback(void *pContext,
                                                    phFriNfc_LlcpMac_eLinkStatus_t   eLinkStatus)
{
   phFriNfc_Llcp_sLinkParameters_t  sLinkParams;
   JNIEnv *e;
   NFCSTATUS status;

   struct nfc_jni_callback_data * pContextData =  (struct nfc_jni_callback_data*)pContext;

   struct nfc_jni_native_data *nat = (nfc_jni_native_data *)pContextData->pContext;

   nfc_jni_listen_data_t * pListenData = NULL;
   nfc_jni_native_monitor * pMonitor = nfc_jni_get_monitor();

   TRACE("Callback: nfc_jni_llcp_linkStatus_callback()");

   nat->vm->GetEnv( (void **)&e, nat->env_version);

   /* Update link status */
   g_eLinkStatus = eLinkStatus;

   if(eLinkStatus == phFriNfc_LlcpMac_eLinkActivated)
   {
      REENTRANCE_LOCK();
      status = phLibNfc_Llcp_GetRemoteInfo(hLlcpHandle, &sLinkParams);
      REENTRANCE_UNLOCK();
      if(status != NFCSTATUS_SUCCESS)
      {
           ALOGW("GetRemote Info failded - Status = %02x",status);
      }
      else
      {
           ALOGI("LLCP Link activated (LTO=%d, MIU=%d, OPTION=0x%02x, WKS=0x%02x)",sLinkParams.lto,
                                                                                  sLinkParams.miu,
                                                                                  sLinkParams.option,
                                                                                  sLinkParams.wks);
           device_connected_flag = 1;
      }
   }
   else if(eLinkStatus == phFriNfc_LlcpMac_eLinkDeactivated)
   {
      ALOGI("LLCP Link deactivated");
      free(pContextData);
      /* Reset device connected flag */
      device_connected_flag = 0;

      /* Reset incoming socket list */
      while (!LIST_EMPTY(&pMonitor->incoming_socket_head))
      {
         pListenData = LIST_FIRST(&pMonitor->incoming_socket_head);
         LIST_REMOVE(pListenData, entries);
         free(pListenData);
      }

      /* Notify manager that the LLCP is lost or deactivated */
      e->CallVoidMethod(nat->manager, cached_NfcManager_notifyLlcpLinkDeactivated, nat->tag);
      if(e->ExceptionCheck())
      {
         ALOGE("Exception occured");
         kill_client(nat);
      }
   }
}

static void nfc_jni_checkLlcp_callback(void *context,
                                              NFCSTATUS status)
{
   struct nfc_jni_callback_data * pContextData =  (struct nfc_jni_callback_data*)context;

   LOG_CALLBACK("nfc_jni_checkLlcp_callback", status);

   pContextData->status = status;
   sem_post(&pContextData->sem);
}

static void nfc_jni_llcpcfg_callback(void *pContext, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pCallbackData = (struct nfc_jni_callback_data *) pContext;
   LOG_CALLBACK("nfc_jni_llcpcfg_callback", status);

   /* Report the callback status and wake up the caller */
   pCallbackData->status = status;
   sem_post(&pCallbackData->sem);
}

static void nfc_jni_llcp_transport_listen_socket_callback(void              *pContext,
                                                          phLibNfc_Handle   hIncomingSocket)
{
   phLibNfc_Handle hServiceSocket = (phLibNfc_Handle)pContext;
   nfc_jni_listen_data_t * pListenData = NULL;
   nfc_jni_native_monitor * pMonitor = nfc_jni_get_monitor();

   TRACE("nfc_jni_llcp_transport_listen_socket_callback socket handle = %p", (void*)hIncomingSocket);

   pthread_mutex_lock(&pMonitor->incoming_socket_mutex);

   /* Store the connection request */
   pListenData = (nfc_jni_listen_data_t*)malloc(sizeof(nfc_jni_listen_data_t));
   if (pListenData == NULL)
   {
      ALOGE("Failed to create structure to handle incoming LLCP connection request");
      goto clean_and_return;
   }
   pListenData->pServerSocket = hServiceSocket;
   pListenData->pIncomingSocket = hIncomingSocket;
   LIST_INSERT_HEAD(&pMonitor->incoming_socket_head, pListenData, entries);

   /* Signal pending accept operations that the list is updated */
   pthread_cond_broadcast(&pMonitor->incoming_socket_cond);

clean_and_return:
   pthread_mutex_unlock(&pMonitor->incoming_socket_mutex);
}

void nfc_jni_llcp_transport_socket_err_callback(void*      pContext,
                                                       uint8_t    nErrCode)
{
   PHNFC_UNUSED_VARIABLE(pContext);

   TRACE("Callback: nfc_jni_llcp_transport_socket_err_callback()");

   if(nErrCode == PHFRINFC_LLCP_ERR_FRAME_REJECTED)
   {
      ALOGW("Frame Rejected - Disconnected");
   }
   else if(nErrCode == PHFRINFC_LLCP_ERR_DISCONNECTED)
   {
      ALOGD("Socket Disconnected");
   }
}


static void nfc_jni_discover_callback(void *pContext, NFCSTATUS status)
{
    struct nfc_jni_callback_data * pContextData =  (struct nfc_jni_callback_data*)pContext;

    LOG_CALLBACK("nfc_jni_discover_callback", status);

    pContextData->status = status;
    sem_post(&pContextData->sem);
}

static uint8_t find_preferred_target(phLibNfc_RemoteDevList_t *psRemoteDevList,
        uint8_t uNoOfRemoteDev)
{
    // Always prefer p2p targets over other targets. Otherwise, select the first target
    // reported.
    uint8_t preferred_index = 0;
    for (uint8_t i = 0; i < uNoOfRemoteDev; i++) {
        if((psRemoteDevList[i].psRemoteDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
                || (psRemoteDevList[i].psRemoteDevInfo->RemDevType == phNfc_eNfcIP1_Target)) {
            preferred_index = i;
        }
    }
    return preferred_index;
}

static void nfc_jni_Discovery_notification_callback(void *pContext,
   phLibNfc_RemoteDevList_t *psRemoteDevList,
   uint8_t uNofRemoteDev, NFCSTATUS status)
{
   NFCSTATUS ret;
   const char * typeName;
   struct timespec ts;
   phNfc_sData_t data;
   int i;
   int target_index = 0; // Target that will be reported (if multiple can be >0)

   struct nfc_jni_native_data* nat = (struct nfc_jni_native_data *)pContext;

   JNIEnv *e;
   nat->vm->GetEnv( (void **)&e, nat->env_version);

   if(status == NFCSTATUS_DESELECTED)
   {
      LOG_CALLBACK("nfc_jni_Discovery_notification_callback: Target deselected", status);

      /* Notify manager that a target was deselected */
      e->CallVoidMethod(nat->manager, cached_NfcManager_notifyTargetDeselected);
      if(e->ExceptionCheck())
      {
         ALOGE("Exception occurred");
         kill_client(nat);
      }
   }
   else
   {
      LOG_CALLBACK("nfc_jni_Discovery_notification_callback", status);
      TRACE("Discovered %d tags", uNofRemoteDev);

      target_index = find_preferred_target(psRemoteDevList, uNofRemoteDev);

      ScopedLocalRef<jobject> tag(e, NULL);

      /* Reset device connected flag */
      device_connected_flag = 1;
      phLibNfc_sRemoteDevInformation_t *remDevInfo = psRemoteDevList[target_index].psRemoteDevInfo;
      phLibNfc_Handle remDevHandle = psRemoteDevList[target_index].hTargetDev;
      if((remDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
          || (remDevInfo->RemDevType == phNfc_eNfcIP1_Target))
      {
         ScopedLocalRef<jclass> tag_cls(e, e->GetObjectClass(nat->cached_P2pDevice));
         if(e->ExceptionCheck())
         {
            ALOGE("Get Object Class Error");
            kill_client(nat);
            return;
         }

         /* New target instance */
         jmethodID ctor = e->GetMethodID(tag_cls.get(), "<init>", "()V");
         tag.reset(e->NewObject(tag_cls.get(), ctor));

         /* Set P2P Target mode */
         jfieldID f = e->GetFieldID(tag_cls.get(), "mMode", "I");

         if(remDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
         {
            ALOGD("Discovered P2P Initiator");
            e->SetIntField(tag.get(), f, (jint)MODE_P2P_INITIATOR);
         }
         else
         {
            ALOGD("Discovered P2P Target");
            e->SetIntField(tag.get(), f, (jint)MODE_P2P_TARGET);
         }

         if(remDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
         {
            /* Set General Bytes */
            f = e->GetFieldID(tag_cls.get(), "mGeneralBytes", "[B");

           TRACE("General Bytes length =");
           for(i=0;i<remDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo_Length;i++)
           {
               ALOGD("%02x ", remDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo[i]);
           }

            ScopedLocalRef<jbyteArray> generalBytes(e, e->NewByteArray(remDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo_Length));

            e->SetByteArrayRegion(generalBytes.get(), 0,
                                  remDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo_Length,
                                  (jbyte *)remDevInfo->RemoteDevInfo.NfcIP_Info.ATRInfo);
            e->SetObjectField(tag.get(), f, generalBytes.get());
         }

         /* Set tag handle */
         f = e->GetFieldID(tag_cls.get(), "mHandle", "I");
         e->SetIntField(tag.get(), f,(jint)remDevHandle);
         TRACE("Target handle = 0x%08x",remDevHandle);
      }
      else
      {
        ScopedLocalRef<jclass> tag_cls(e, e->GetObjectClass(nat->cached_NfcTag));
        if(e->ExceptionCheck())
        {
            kill_client(nat);
            return;
        }

        /* New tag instance */
        jmethodID ctor = e->GetMethodID(tag_cls.get(), "<init>", "()V");
        tag.reset(e->NewObject(tag_cls.get(), ctor));

        bool multi_protocol = false;

        if(status == NFCSTATUS_MULTIPLE_PROTOCOLS)
        {
            TRACE("Multiple Protocol TAG detected\n");
            multi_protocol = true;
        }

        /* Set tag UID */
        jfieldID f = e->GetFieldID(tag_cls.get(), "mUid", "[B");
        data = get_target_uid(remDevInfo);
        ScopedLocalRef<jbyteArray> tagUid(e, e->NewByteArray(data.length));
        if(data.length > 0)
        {
           e->SetByteArrayRegion(tagUid.get(), 0, data.length, (jbyte *)data.buffer);
        }
        e->SetObjectField(tag.get(), f, tagUid.get());

        /* Generate technology list */
        ScopedLocalRef<jintArray> techList(e, NULL);
        ScopedLocalRef<jintArray> handleList(e, NULL);
        ScopedLocalRef<jintArray> typeList(e, NULL);
        nfc_jni_get_technology_tree(e, psRemoteDevList,
                multi_protocol ? uNofRemoteDev : 1,
                &techList, &handleList, &typeList);

        /* Push the technology list into the java object */
        f = e->GetFieldID(tag_cls.get(), "mTechList", "[I");
        e->SetObjectField(tag.get(), f, techList.get());

        f = e->GetFieldID(tag_cls.get(), "mTechHandles", "[I");
        e->SetObjectField(tag.get(), f, handleList.get());

        f = e->GetFieldID(tag_cls.get(), "mTechLibNfcTypes", "[I");
        e->SetObjectField(tag.get(), f, typeList.get());

        f = e->GetFieldID(tag_cls.get(), "mConnectedTechIndex", "I");
        e->SetIntField(tag.get(), f,(jint)-1);

        f = e->GetFieldID(tag_cls.get(), "mConnectedHandle", "I");
        e->SetIntField(tag.get(), f,(jint)-1);

        set_target_pollBytes(e, tag.get(), psRemoteDevList->psRemoteDevInfo);

        set_target_activationBytes(e, tag.get(), psRemoteDevList->psRemoteDevInfo);
      }

      storedHandle = remDevHandle;
      if (nat->tag != NULL) {
          e->DeleteGlobalRef(nat->tag);
      }
      nat->tag = e->NewGlobalRef(tag.get());

      /* Notify the service */
      TRACE("Notify Nfc Service");
      if((remDevInfo->RemDevType == phNfc_eNfcIP1_Initiator)
          || (remDevInfo->RemDevType == phNfc_eNfcIP1_Target))
      {
         /* Store the handle of the P2P device */
         hLlcpHandle = remDevHandle;

         /* Notify manager that new a P2P device was found */
         e->CallVoidMethod(nat->manager, cached_NfcManager_notifyLlcpLinkActivation, tag.get());
         if(e->ExceptionCheck())
         {
            ALOGE("Exception occurred");
            kill_client(nat);
         }
      }
      else
      {
         /* Notify manager that new a tag was found */
         e->CallVoidMethod(nat->manager, cached_NfcManager_notifyNdefMessageListeners, tag.get());
         if(e->ExceptionCheck())
         {
            ALOGE("Exception occurred");
            kill_client(nat);
         }
      }
   }
}

static void nfc_jni_init_callback(void *pContext, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pContextData =  (struct nfc_jni_callback_data*)pContext;

   LOG_CALLBACK("nfc_jni_init_callback", status);

   pContextData->status = status;
   sem_post(&pContextData->sem);
}

static void nfc_jni_deinit_callback(void *pContext, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pContextData =  (struct nfc_jni_callback_data*)pContext;

   LOG_CALLBACK("nfc_jni_deinit_callback", status);

   pContextData->status = status;
   sem_post(&pContextData->sem);
}

/* Set Secure Element mode callback*/
static void nfc_jni_smartMX_setModeCb (void*            pContext,
                                       phLibNfc_Handle  /*hSecureElement*/,
                                       NFCSTATUS        status)
{
   struct nfc_jni_callback_data * pContextData =  (struct nfc_jni_callback_data*)pContext;

   LOG_CALLBACK("nfc_jni_smartMX_setModeCb", status);

   pContextData->status = status;
   sem_post(&pContextData->sem);
}

/* Card Emulation callback */
static void nfc_jni_transaction_callback(void *context,
   phLibNfc_eSE_EvtType_t evt_type, phLibNfc_Handle /*handle*/,
   phLibNfc_uSeEvtInfo_t *evt_info, NFCSTATUS status)
{
    JNIEnv *e;
    jobject tmp_array = NULL;
    jobject mifare_block = NULL;
    struct nfc_jni_native_data *nat;
    phNfc_sData_t *aid;
    phNfc_sData_t *mifare_command;
    struct nfc_jni_callback_data *pCallbackData;
    int i=0;

    LOG_CALLBACK("nfc_jni_transaction_callback", status);

    nat = (struct nfc_jni_native_data *)context;

    nat->vm->GetEnv( (void **)&e, nat->env_version);

    if(status == NFCSTATUS_SUCCESS)
    {
        switch(evt_type)
        {
            case phLibNfc_eSE_EvtStartTransaction:
            {
                TRACE("> SE EVT_START_TRANSACTION");
            }break;

            case phLibNfc_eSE_EvtApduReceived:
            {
                TRACE("> SE EVT_APDU_RECEIVED");
            }break;

            case phLibNfc_eSE_EvtCardRemoval:
            {
                TRACE("> SE EVT_EMV_CARD_REMOVAL");
            }break;

            case phLibNfc_eSE_EvtMifareAccess:
            {
                TRACE("> SE EVT_MIFARE_ACCESS");
            }break;

            case phLibNfc_eSE_EvtFieldOn:
            {
                TRACE("> SE EVT_FIELD_ON");
                e->CallVoidMethod(nat->manager, cached_NfcManager_notifyRfFieldActivated);
            }break;

            case phLibNfc_eSE_EvtFieldOff:
            {
                TRACE("> SE EVT_FIELD_OFF");
                e->CallVoidMethod(nat->manager, cached_NfcManager_notifyRfFieldDeactivated);
            }break;

            default:
            {
                TRACE("Unknown SE event");
            }break;
        }
    }
    else
    {
        ALOGE("SE transaction notification error");
        goto error;
    }

    /* Function finished, now clean and return */
    goto clean_and_return;

 error:
    /* In case of error, just discard the notification */
    ALOGE("Failed to send SE transaction notification");
    e->ExceptionClear();

 clean_and_return:
    if(tmp_array != NULL)
    {
       e->DeleteLocalRef(tmp_array);
    }
}

static void nfc_jni_se_set_mode_callback(void *pContext,
   phLibNfc_Handle /*handle*/, NFCSTATUS status)
{
   struct nfc_jni_callback_data * pContextData =  (struct nfc_jni_callback_data*)pContext;

   LOG_CALLBACK("nfc_jni_se_set_mode_callback", status);

   pContextData->status = status;
   sem_post(&pContextData->sem);
}

/*
 * NFCManager methods
 */

static void nfc_jni_start_discovery_locked(struct nfc_jni_native_data *nat, bool resume)
{
   NFCSTATUS ret;
   struct nfc_jni_callback_data cb_data;
   int numRetries = 3;

   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, NULL))
   {
      goto clean_and_return;
   }
   /* Reset the PN544 ISO XCHG / sw watchdog timeouts */
   nfc_jni_reset_timeout_values();

   /* Reload the p2p modes */
   nat->discovery_cfg.NfcIP_Mode = nat->p2p_initiator_modes;  //initiator
   nat->discovery_cfg.NfcIP_Target_Mode = nat->p2p_target_modes;  //target
   nat->discovery_cfg.NfcIP_Tgt_Disable = FALSE;

   /* Reset device connected flag */
   device_connected_flag = 0;
configure:
   /* Start Polling loop */
   TRACE("******  Start NFC Discovery ******");
   REENTRANCE_LOCK();
   ret = phLibNfc_Mgt_ConfigureDiscovery(resume ? NFC_DISCOVERY_RESUME : NFC_DISCOVERY_CONFIG,
      nat->discovery_cfg, nfc_jni_discover_callback, (void *)&cb_data);
   REENTRANCE_UNLOCK();
   TRACE("phLibNfc_Mgt_ConfigureDiscovery(%s-%s-%s-%s-%s-%s, %s-%x-%x) returned 0x%08x\n",
      nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443A==TRUE?"3A":"",
      nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443B==TRUE?"3B":"",
      nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica212==TRUE?"F2":"",
      nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica424==TRUE?"F4":"",
      nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableNfcActive==TRUE?"NFC":"",
      nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso15693==TRUE?"RFID":"",
      nat->discovery_cfg.PollDevInfo.PollCfgInfo.DisableCardEmulation==FALSE?"CE":"",
      nat->discovery_cfg.NfcIP_Mode, nat->discovery_cfg.Duration, ret);

   if (ret == NFCSTATUS_BUSY && numRetries-- > 0)
   {
      TRACE("ConfigDiscovery BUSY, retrying");
      usleep(1000000);
      goto configure;
   }

   if(ret != NFCSTATUS_PENDING)
   {
      emergency_recovery(nat);
      goto clean_and_return;
   }

   /* Wait for callback response */
   if(sem_wait(&cb_data.sem))
   {
      ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

clean_and_return:
   nfc_cb_data_deinit(&cb_data);
}

static void nfc_jni_stop_discovery_locked(struct nfc_jni_native_data *nat)
{
   phLibNfc_sADD_Cfg_t discovery_cfg;
   NFCSTATUS ret;
   struct nfc_jni_callback_data cb_data;
   int numRetries = 3;

   /* Create the local semaphore */
   if (!nfc_cb_data_init(&cb_data, NULL))
   {
      goto clean_and_return;
   }

   discovery_cfg.PollDevInfo.PollEnabled = 0;
   discovery_cfg.NfcIP_Mode = phNfc_eDefaultP2PMode;
   discovery_cfg.NfcIP_Target_Mode = 0;
   discovery_cfg.NfcIP_Tgt_Disable = TRUE;

configure:
   /* Start Polling loop */
   TRACE("******  Stop NFC Discovery ******");
   REENTRANCE_LOCK();
   ret = phLibNfc_Mgt_ConfigureDiscovery(NFC_DISCOVERY_CONFIG,discovery_cfg, nfc_jni_discover_callback, (void *)&cb_data);
   REENTRANCE_UNLOCK();
   TRACE("phLibNfc_Mgt_ConfigureDiscovery(%s-%s-%s-%s-%s-%s, %s-%x-%x) returned 0x%08x\n",
      discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443A==TRUE?"3A":"",
      discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443B==TRUE?"3B":"",
      discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica212==TRUE?"F2":"",
      discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica424==TRUE?"F4":"",
      discovery_cfg.PollDevInfo.PollCfgInfo.EnableNfcActive==TRUE?"NFC":"",
      discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso15693==TRUE?"RFID":"",
      discovery_cfg.PollDevInfo.PollCfgInfo.DisableCardEmulation==FALSE?"CE":"",
      discovery_cfg.NfcIP_Mode, discovery_cfg.Duration, ret);

   if (ret == NFCSTATUS_BUSY && numRetries-- > 0)
   {
      TRACE("ConfigDiscovery BUSY, retrying");
      usleep(1000000);
      goto configure;
   }

   if(ret != NFCSTATUS_PENDING)
   {
      ALOGE("[STOP] ConfigDiscovery returned %x", ret);
      emergency_recovery(nat);
   }

   /* Wait for callback response */
   if(sem_wait(&cb_data.sem))
   {
      ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

clean_and_return:
   nfc_cb_data_deinit(&cb_data);
}


static void com_android_nfc_NfcManager_disableDiscovery(JNIEnv *e, jobject o)
{
    struct nfc_jni_native_data *nat;

    CONCURRENCY_LOCK();

    /* Retrieve native structure address */
    nat = nfc_jni_get_nat(e, o);

    nfc_jni_stop_discovery_locked(nat);

    CONCURRENCY_UNLOCK();

}

// TODO: use enable_lptd
static void com_android_nfc_NfcManager_enableDiscovery(JNIEnv *e, jobject o, jint modes,
        jboolean, jboolean reader_mode, jboolean enable_p2p, jboolean restart)
{
    NFCSTATUS ret;
    struct nfc_jni_native_data *nat;

    CONCURRENCY_LOCK();

    nat = nfc_jni_get_nat(e, o);

   /* Register callback for remote device notifications.
    * Must re-register every time we turn on discovery, since other operations
    * (such as opening the Secure Element) can change the remote device
    * notification callback*/
   REENTRANCE_LOCK();
   ret = phLibNfc_RemoteDev_NtfRegister(&nat->registry_info, nfc_jni_Discovery_notification_callback, (void *)nat);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_SUCCESS)
   {
        ALOGD("pphLibNfc_RemoteDev_NtfRegister returned 0x%02x",ret);
        goto clean_and_return;
   }
   TRACE("phLibNfc_RemoteDev_NtfRegister(%s-%s-%s-%s-%s-%s-%s-%s) returned 0x%x\n",
      nat->registry_info.Jewel==TRUE?"J":"",
      nat->registry_info.MifareUL==TRUE?"UL":"",
      nat->registry_info.MifareStd==TRUE?"Mi":"",
      nat->registry_info.Felica==TRUE?"F":"",
      nat->registry_info.ISO14443_4A==TRUE?"4A":"",
      nat->registry_info.ISO14443_4B==TRUE?"4B":"",
      nat->registry_info.NFC==TRUE?"P2P":"",
      nat->registry_info.ISO15693==TRUE?"R":"", ret);

    if (modes != 0)
    {

        if (enable_p2p)
        {
            nat->p2p_initiator_modes = phNfc_eP2P_ALL;
            nat->p2p_target_modes = 0x0E; // All passive except 106, active
            nat->discovery_cfg.Duration = 300000; /* in ms */
        }
        else
        {
            nat->p2p_initiator_modes = 0;
            nat->p2p_target_modes = 0;
            nat->discovery_cfg.Duration = 200000; /* in ms */

        }

        if (reader_mode)
        {
            nat->discovery_cfg.PollDevInfo.PollCfgInfo.DisableCardEmulation = TRUE;
            nat->discovery_cfg.Duration = 200000; /* in ms */
        }
        else
        {
            nat->discovery_cfg.PollDevInfo.PollCfgInfo.DisableCardEmulation = FALSE;
            nat->discovery_cfg.Duration = 300000; /* in ms */
        }
        nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443A = (modes & 0x01) != 0;
        nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443B = (modes & 0x02) != 0;
        nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica212 = (modes & 0x04) != 0;
        nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica424 = (modes & 0x04) != 0;
        nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso15693 = (modes & 0x08) != 0;
    }

    nfc_jni_start_discovery_locked(nat, false);
clean_and_return:
    CONCURRENCY_UNLOCK();
}

static void com_android_nfc_NfcManager_doResetTimeouts(JNIEnv*, jobject) {
    CONCURRENCY_LOCK();
    nfc_jni_reset_timeout_values();
    CONCURRENCY_UNLOCK();
}

static void setFelicaTimeout(jint timeout) {
   // The Felica timeout is configurable in the PN544 upto a maximum of 255 ms.
   // It can be set to 0 to disable the timeout altogether, in which case we
   // use the sw watchdog as a fallback.
   if (timeout <= 255) {
       phLibNfc_SetFelicaTimeout(timeout);
   } else {
       // Disable hw timeout, use sw watchdog for timeout
       phLibNfc_SetFelicaTimeout(0);
       phLibNfc_SetHciTimeout(timeout);
   }

}
// Calculates ceiling log2 of value
static unsigned int log2(int value) {
    unsigned int ret = 0;
    bool isPowerOf2 = ((value & (value - 1)) == 0);
    while ( (value >> ret) > 1 ) ret++;
    if (!isPowerOf2) ret++;
    return ret;
}

// The Iso/Mifare Xchg timeout in PN544 is a non-linear function over X
// spanning 0 - 4.9s: timeout in seconds = (256 * 16 / 13560000) * 2 ^ X
//
// We keep the constant part of the formula in a static; note the factor
// 1000 off, which is due to the fact that the formula calculates seconds,
// but this method gets milliseconds as an argument.
static double nxp_nfc_timeout_factor = (256 * 16) / 13560.0;

static int calcTimeout(int timeout_in_ms) {
   // timeout = (256 * 16 / 13560000) * 2 ^ X
   // First find the first X for which timeout > requested timeout
   return (log2(ceil(((double) timeout_in_ms) / nxp_nfc_timeout_factor)));
}

static void setIsoDepTimeout(jint timeout) {
   if (timeout <= 4900) {
       int value = calcTimeout(timeout);
       // Then re-compute the actual timeout based on X
       double actual_timeout = nxp_nfc_timeout_factor * (1 << value);
       // Set the sw watchdog a bit longer (The PN544 timeout is very accurate,
       // but it will take some time to get back through the sw layers.
       // 500 ms should be enough).
       phLibNfc_SetHciTimeout(ceil(actual_timeout + 500));
       value |= 0x10; // bit 4 to enable timeout
       phLibNfc_SetIsoXchgTimeout(value);
   }
   else {
       // Also note that if we desire a timeout > 4.9s, the Iso Xchg timeout
       // must be disabled completely, to prevent the PN544 from aborting
       // the transaction. We reuse the HCI sw watchdog to catch the timeout
       // in that case.
       phLibNfc_SetIsoXchgTimeout(0x00);
       phLibNfc_SetHciTimeout(timeout);
   }
}

static void setNfcATimeout(jint timeout) {
   if (timeout <= 4900) {
       int value = calcTimeout(timeout);
       phLibNfc_SetMifareRawTimeout(value);
   }
   else {
       // Disable mifare raw timeout, use HCI sw watchdog instead
       phLibNfc_SetMifareRawTimeout(0x00);
       phLibNfc_SetHciTimeout(timeout);
   }
}

static bool com_android_nfc_NfcManager_doSetTimeout(JNIEnv*, jobject,
        jint tech, jint timeout) {
    bool success = false;
    CONCURRENCY_LOCK();
    if (timeout <= 0) {
        ALOGE("Timeout must be positive.");
        success = false;
    } else {
        switch (tech) {
            case TARGET_TYPE_MIFARE_CLASSIC:
            case TARGET_TYPE_MIFARE_UL:
                // Intentional fall-through, Mifare UL, Classic
                // transceive just uses raw 3A frames
            case TARGET_TYPE_ISO14443_3A:
                setNfcATimeout(timeout);
                success = true;
                break;
            case TARGET_TYPE_ISO14443_4:
                setIsoDepTimeout(timeout);
                success = true;
                break;
            case TARGET_TYPE_FELICA:
                setFelicaTimeout(timeout);
                success = true;
                break;
            default:
                ALOGW("doSetTimeout: Timeout not supported for tech %d", tech);
                success = false;
        }
    }
    CONCURRENCY_UNLOCK();
    return success;
}

static jint com_android_nfc_NfcManager_doGetTimeout(JNIEnv*, jobject,
        jint tech) {
    int timeout = -1;
    CONCURRENCY_LOCK();
    switch (tech) {
        case TARGET_TYPE_MIFARE_CLASSIC:
        case TARGET_TYPE_MIFARE_UL:
            // Intentional fall-through, Mifare UL, Classic
            // transceive just uses raw 3A frames
        case TARGET_TYPE_ISO14443_3A:
            timeout = phLibNfc_GetMifareRawTimeout();
            if (timeout == 0) {
                timeout = phLibNfc_GetHciTimeout();
            } else {
                // Timeout returned from libnfc needs conversion to ms
                timeout = (nxp_nfc_timeout_factor * (1 << timeout));
            }
            break;
        case TARGET_TYPE_ISO14443_4:
            timeout = phLibNfc_GetIsoXchgTimeout() & 0x0F; // lower 4 bits only
            if (timeout == 0) {
                timeout = phLibNfc_GetHciTimeout();
            } else {
                // Timeout returned from libnfc needs conversion to ms
                timeout = (nxp_nfc_timeout_factor * (1 << timeout));
            }
            break;
        case TARGET_TYPE_FELICA:
            timeout = phLibNfc_GetFelicaTimeout();
            if (timeout == 0) {
                timeout = phLibNfc_GetHciTimeout();
            } else {
                // Felica timeout already in ms
            }
            break;
        default:
            ALOGW("doGetTimeout: Timeout not supported for tech %d", tech);
            break;
    }
    CONCURRENCY_UNLOCK();
    return timeout;
}


static jboolean com_android_nfc_NfcManager_init_native_struc(JNIEnv *e, jobject o)
{
   NFCSTATUS status;
   struct nfc_jni_native_data *nat = NULL;
   jclass cls;
   jobject obj;
   jfieldID f;

   TRACE("******  Init Native Structure ******");

   /* Initialize native structure */
   nat = (nfc_jni_native_data*)malloc(sizeof(struct nfc_jni_native_data));
   if(nat == NULL)
   {
      ALOGD("malloc of nfc_jni_native_data failed");
      return FALSE;
   }
   memset(nat, 0, sizeof(*nat));
   e->GetJavaVM(&(nat->vm));
   nat->env_version = e->GetVersion();
   nat->manager = e->NewGlobalRef(o);

   cls = e->GetObjectClass(o);
   f = e->GetFieldID(cls, "mNative", "J");
   e->SetLongField(o, f, (jlong)nat);

   /* Initialize native cached references */
   cached_NfcManager_notifyNdefMessageListeners = e->GetMethodID(cls,
      "notifyNdefMessageListeners","(Lcom/android/nfc/dhimpl/NativeNfcTag;)V");

   cached_NfcManager_notifyLlcpLinkActivation = e->GetMethodID(cls,
      "notifyLlcpLinkActivation","(Lcom/android/nfc/dhimpl/NativeP2pDevice;)V");

   cached_NfcManager_notifyLlcpLinkDeactivated = e->GetMethodID(cls,
      "notifyLlcpLinkDeactivated","(Lcom/android/nfc/dhimpl/NativeP2pDevice;)V");

   cached_NfcManager_notifyRfFieldActivated = e->GetMethodID(cls,
      "notifyRfFieldActivated", "()V");

   cached_NfcManager_notifyRfFieldDeactivated = e->GetMethodID(cls,
      "notifyRfFieldDeactivated", "()V");

   if(nfc_jni_cache_object(e,"com/android/nfc/dhimpl/NativeNfcTag",&(nat->cached_NfcTag)) == -1)
   {
      ALOGD("Native Structure initialization failed");
      return FALSE;
   }

   if(nfc_jni_cache_object(e,"com/android/nfc/dhimpl/NativeP2pDevice",&(nat->cached_P2pDevice)) == -1)
   {
      ALOGD("Native Structure initialization failed");
      return FALSE;
   }
   TRACE("****** Init Native Structure OK ******");
   return TRUE;

}

/* Init/Deinit method */
static jboolean com_android_nfc_NfcManager_initialize(JNIEnv *e, jobject o)
{
   struct nfc_jni_native_data *nat = NULL;
   int init_result = JNI_FALSE;
#ifdef TNFC_EMULATOR_ONLY
   char value[PROPERTY_VALUE_MAX];
#endif
   jboolean result;

   CONCURRENCY_LOCK();

#ifdef TNFC_EMULATOR_ONLY
   if (!property_get("ro.kernel.qemu", value, 0))
   {
      ALOGE("NFC Initialization failed: not running in an emulator\n");
      goto clean_and_return;
   }
#endif

   /* Retrieve native structure address */
   nat = nfc_jni_get_nat(e, o);

   nat->seId = SMX_SECURE_ELEMENT_ID;

   nat->lto = 150;  // LLCP_LTO
   nat->miu = 128; // LLCP_MIU
   // WKS indicates well-known services; 1 << sap for each supported SAP.
   // We support Link mgmt (SAP 0), SDP (SAP 1) and SNEP (SAP 4)
   nat->wks = 0x13;  // LLCP_WKS
   nat->opt = 0;  // LLCP_OPT
   nat->p2p_initiator_modes = phNfc_eP2P_ALL;
   nat->p2p_target_modes = 0x0E; // All passive except 106, active
   nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443A = TRUE;
   nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso14443B = TRUE;
   nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica212 = TRUE;
   nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableFelica424 = TRUE;
   nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableIso15693 = TRUE;
   nat->discovery_cfg.PollDevInfo.PollCfgInfo.EnableNfcActive = TRUE;
   nat->discovery_cfg.PollDevInfo.PollCfgInfo.DisableCardEmulation = FALSE;

   nat->registry_info.MifareUL = TRUE;
   nat->registry_info.MifareStd = TRUE;
   nat->registry_info.ISO14443_4A = TRUE;
   nat->registry_info.ISO14443_4B = TRUE;
   nat->registry_info.Jewel = TRUE;
   nat->registry_info.Felica = TRUE;
   nat->registry_info.NFC = TRUE;
   nat->registry_info.ISO15693 = TRUE;

   exported_nat = nat;

   /* Perform the initialization */
   init_result = nfc_jni_initialize(nat);

clean_and_return:
   CONCURRENCY_UNLOCK();

   /* Convert the result and return */
   return (init_result==TRUE)?JNI_TRUE:JNI_FALSE;
}

static jboolean com_android_nfc_NfcManager_deinitialize(JNIEnv *e, jobject o)
{
   struct timespec ts;
   NFCSTATUS status;
   int result = JNI_FALSE;
   struct nfc_jni_native_data *nat;
   int bStackReset = FALSE;
   struct nfc_jni_callback_data cb_data;

   CONCURRENCY_LOCK();

   /* Retrieve native structure address */
   nat = nfc_jni_get_nat(e, o);

   /* Clear previous configuration */
   memset(&nat->discovery_cfg, 0, sizeof(phLibNfc_sADD_Cfg_t));
   memset(&nat->registry_info, 0, sizeof(phLibNfc_Registry_Info_t));

   /* Create the local semaphore */
   if (nfc_cb_data_init(&cb_data, NULL))
   {
      TRACE("phLibNfc_Mgt_DeInitialize()");
      REENTRANCE_LOCK();
      status = phLibNfc_Mgt_DeInitialize(gHWRef, nfc_jni_deinit_callback, (void *)&cb_data);
      REENTRANCE_UNLOCK();
      if (status == NFCSTATUS_PENDING)
      {
         TRACE("phLibNfc_Mgt_DeInitialize() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

         clock_gettime(CLOCK_REALTIME, &ts);
         ts.tv_sec += 5;

         /* Wait for callback response */
         if(sem_timedwait(&cb_data.sem, &ts) == -1)
         {
            ALOGW("Operation timed out");
            bStackReset = TRUE;
         }

         if(cb_data.status != NFCSTATUS_SUCCESS)
         {
            ALOGE("Failed to deinit the stack");
            bStackReset = TRUE;
         }
      }
      else
      {
         TRACE("phLibNfc_Mgt_DeInitialize() returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
         bStackReset = TRUE;
      }
      nfc_cb_data_deinit(&cb_data);
   }
   else
   {
       ALOGE("Failed to create semaphore (errno=0x%08x)", errno);
       bStackReset = TRUE;
   }

   kill_client(nat);

   if(bStackReset == TRUE)
   {
      /* Complete deinit. failed, try hard restart of NFC */
      ALOGW("Reseting stack...");
      emergency_recovery(nat);
   }

   result = nfc_jni_unconfigure_driver(nat);

   TRACE("NFC Deinitialized");

   CONCURRENCY_UNLOCK();

   return TRUE;
}

/* Llcp methods */

static jboolean com_android_nfc_NfcManager_doCheckLlcp(JNIEnv *e, jobject o)
{
   NFCSTATUS ret;
   bool freeData = false;
   jboolean result = JNI_FALSE;
   struct nfc_jni_native_data *nat;
   struct nfc_jni_callback_data  *cb_data;


   CONCURRENCY_LOCK();

   /* Memory allocation for cb_data
    * This is on the heap because it is used by libnfc
    * even after this call has succesfully finished. It is only freed
    * upon link closure in nfc_jni_llcp_linkStatus_callback.
    */
   cb_data = (struct nfc_jni_callback_data*) malloc (sizeof(nfc_jni_callback_data));

   /* Retrieve native structure address */
   nat = nfc_jni_get_nat(e, o);

   /* Create the local semaphore */
   if (!nfc_cb_data_init(cb_data, (void*)nat))
   {
      goto clean_and_return;
   }

   /* Check LLCP compliancy */
   TRACE("phLibNfc_Llcp_CheckLlcp(hLlcpHandle=0x%08x)", hLlcpHandle);
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_CheckLlcp(hLlcpHandle,
                                 nfc_jni_checkLlcp_callback,
                                 nfc_jni_llcp_linkStatus_callback,
                                 (void*)cb_data);
   REENTRANCE_UNLOCK();
   /* In case of a NFCIP return NFCSTATUS_SUCCESS and in case of an another protocol
    * NFCSTATUS_PENDING. In this case NFCSTATUS_SUCCESS will also cause the callback. */
   if(ret != NFCSTATUS_PENDING && ret != NFCSTATUS_SUCCESS)
   {
      ALOGE("phLibNfc_Llcp_CheckLlcp() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      freeData = true;
      goto clean_and_return;
   }
   TRACE("phLibNfc_Llcp_CheckLlcp() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   /* Wait for callback response */
   if(sem_wait(&cb_data->sem))
   {
      ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
      goto clean_and_return;
   }

   if(cb_data->status == NFCSTATUS_SUCCESS)
   {
      result = JNI_TRUE;
   }

clean_and_return:
   nfc_cb_data_deinit(cb_data);
   if (freeData) {
       free(cb_data);
   }
   CONCURRENCY_UNLOCK();
   return result;
}

static jboolean com_android_nfc_NfcManager_doActivateLlcp(JNIEnv*, jobject)
{
   NFCSTATUS ret;
   TRACE("phLibNfc_Llcp_Activate(hRemoteDevice=0x%08x)", hLlcpHandle);
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Activate(hLlcpHandle);
   REENTRANCE_UNLOCK();
   if(ret == NFCSTATUS_SUCCESS)
   {
      TRACE("phLibNfc_Llcp_Activate() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      return JNI_TRUE;
   }
   else
   {
      ALOGE("phLibNfc_Llcp_Activate() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      return JNI_FALSE;
   }
}



static jobject com_android_nfc_NfcManager_doCreateLlcpConnectionlessSocket(JNIEnv *e, jobject o,
        jint nSap, jstring sn)
{
   NFCSTATUS ret;
   jobject connectionlessSocket = NULL;
   phLibNfc_Handle hLlcpSocket;
   struct nfc_jni_native_data *nat;
   phNfc_sData_t sWorkingBuffer = {NULL, 0};
   phNfc_sData_t serviceName = {NULL, 0};
   phLibNfc_Llcp_sLinkParameters_t sParams;
   jclass clsNativeConnectionlessSocket;
   jfieldID f;

   /* Retrieve native structure address */
   nat = nfc_jni_get_nat(e, o);

   /* Allocate Working buffer length */
   phLibNfc_Llcp_GetLocalInfo(hLlcpHandle, &sParams);
   sWorkingBuffer.length = sParams.miu + 1; // extra byte for SAP
   sWorkingBuffer.buffer = (uint8_t*)malloc(sWorkingBuffer.length);

   /* Create socket */
   TRACE("phLibNfc_Llcp_Socket(eType=phFriNfc_LlcpTransport_eConnectionLess, ...)");
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Socket(phFriNfc_LlcpTransport_eConnectionLess,
                              NULL,
                              &sWorkingBuffer,
                              &hLlcpSocket,
                              nfc_jni_llcp_transport_socket_err_callback,
                              (void*)nat);
   REENTRANCE_UNLOCK();

   if(ret != NFCSTATUS_SUCCESS)
   {
      lastErrorStatus = ret;
      ALOGE("phLibNfc_Llcp_Socket() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      goto error;
   }
   TRACE("phLibNfc_Llcp_Socket() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   /* Service socket */
   if (sn == NULL) {
       serviceName.buffer = NULL;
       serviceName.length = 0;
   } else {
       serviceName.buffer = (uint8_t*)e->GetStringUTFChars(sn, NULL);
       serviceName.length = (uint32_t)e->GetStringUTFLength(sn);
   }

   /* Bind socket */
   TRACE("phLibNfc_Llcp_Bind(hSocket=0x%08x, nSap=0x%02x)", hLlcpSocket, nSap);
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Bind(hLlcpSocket,nSap, &serviceName);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_SUCCESS)
   {
      lastErrorStatus = ret;
      ALOGE("phLibNfc_Llcp_Bind() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      /* Close socket created */
      REENTRANCE_LOCK();
      ret = phLibNfc_Llcp_Close(hLlcpSocket);
      REENTRANCE_UNLOCK();
      goto error;
   }
   TRACE("phLibNfc_Llcp_Bind() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));


   /* Create new NativeLlcpConnectionlessSocket object */
   if(nfc_jni_cache_object(e,"com/android/nfc/dhimpl/NativeLlcpConnectionlessSocket",&(connectionlessSocket)) == -1)
   {
      goto error;
   }

   /* Get NativeConnectionless class object */
   clsNativeConnectionlessSocket = e->GetObjectClass(connectionlessSocket);
   if(e->ExceptionCheck())
   {
      goto error;
   }

   /* Set socket handle */
   f = e->GetFieldID(clsNativeConnectionlessSocket, "mHandle", "I");
   e->SetIntField(connectionlessSocket, f,(jint)hLlcpSocket);
   TRACE("Connectionless socket Handle = %02x\n",hLlcpSocket);

   /* Set the miu link of the connectionless socket */
   f = e->GetFieldID(clsNativeConnectionlessSocket, "mLinkMiu", "I");
   e->SetIntField(connectionlessSocket, f,(jint)PHFRINFC_LLCP_MIU_DEFAULT);
   TRACE("Connectionless socket Link MIU = %d\n",PHFRINFC_LLCP_MIU_DEFAULT);

   /* Set socket SAP */
   f = e->GetFieldID(clsNativeConnectionlessSocket, "mSap", "I");
   e->SetIntField(connectionlessSocket, f,(jint)nSap);
   TRACE("Connectionless socket SAP = %d\n",nSap);

   return connectionlessSocket;
error:
   if (serviceName.buffer != NULL) {
      e->ReleaseStringUTFChars(sn, (const char *)serviceName.buffer);
   }

   if (sWorkingBuffer.buffer != NULL) {
       free(sWorkingBuffer.buffer);
   }

   return NULL;
}

static jobject com_android_nfc_NfcManager_doCreateLlcpServiceSocket(JNIEnv *e, jobject o, jint nSap, jstring sn, jint miu, jint rw, jint linearBufferLength)
{
   NFCSTATUS ret;
   phLibNfc_Handle hLlcpSocket;
   phLibNfc_Llcp_sSocketOptions_t sOptions;
   phNfc_sData_t sWorkingBuffer;
   phNfc_sData_t serviceName;
   struct nfc_jni_native_data *nat;
   jobject serviceSocket = NULL;
   jclass clsNativeLlcpServiceSocket;
   jfieldID f;

   /* Retrieve native structure address */
   nat = nfc_jni_get_nat(e, o);

   /* Set Connection Oriented socket options */
   sOptions.miu = miu;
   sOptions.rw  = rw;

   /* Allocate Working buffer length */
   sWorkingBuffer.length = (miu*rw)+ miu + linearBufferLength;
   sWorkingBuffer.buffer = (uint8_t*)malloc(sWorkingBuffer.length);


   /* Create socket */
   TRACE("phLibNfc_Llcp_Socket(hRemoteDevice=0x%08x, eType=phFriNfc_LlcpTransport_eConnectionOriented, ...)", hLlcpHandle);
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Socket(phFriNfc_LlcpTransport_eConnectionOriented,
                              &sOptions,
                              &sWorkingBuffer,
                              &hLlcpSocket,
                              nfc_jni_llcp_transport_socket_err_callback,
                              (void*)nat);
   REENTRANCE_UNLOCK();

   if(ret != NFCSTATUS_SUCCESS)
   {
      ALOGE("phLibNfc_Llcp_Socket() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      lastErrorStatus = ret;
      goto error;
   }
   TRACE("phLibNfc_Llcp_Socket() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   /* Service socket */
   if (sn == NULL) {
       serviceName.buffer = NULL;
       serviceName.length = 0;
   } else {
       serviceName.buffer = (uint8_t*)e->GetStringUTFChars(sn, NULL);
       serviceName.length = (uint32_t)e->GetStringUTFLength(sn);
   }

   /* Bind socket */
   TRACE("phLibNfc_Llcp_Bind(hSocket=0x%08x, nSap=0x%02x)", hLlcpSocket, nSap);
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Bind(hLlcpSocket,nSap, &serviceName);
   REENTRANCE_UNLOCK();
   if(ret != NFCSTATUS_SUCCESS)
   {
      lastErrorStatus = ret;
      ALOGE("phLibNfc_Llcp_Bind() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      /* Close socket created */
      ret = phLibNfc_Llcp_Close(hLlcpSocket);
      goto error;
   }
   TRACE("phLibNfc_Llcp_Bind() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   TRACE("phLibNfc_Llcp_Listen(hSocket=0x%08x, ...)", hLlcpSocket);
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Listen( hLlcpSocket,
                               nfc_jni_llcp_transport_listen_socket_callback,
                               (void*)hLlcpSocket);
   REENTRANCE_UNLOCK();

   if(ret != NFCSTATUS_SUCCESS)
   {
      ALOGE("phLibNfc_Llcp_Listen() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      lastErrorStatus = ret;
      /* Close created socket */
      REENTRANCE_LOCK();
      ret = phLibNfc_Llcp_Close(hLlcpSocket);
      REENTRANCE_UNLOCK();
      goto error;
   }
   TRACE("phLibNfc_Llcp_Listen() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   /* Create new NativeLlcpServiceSocket object */
   if(nfc_jni_cache_object(e,"com/android/nfc/dhimpl/NativeLlcpServiceSocket",&(serviceSocket)) == -1)
   {
      ALOGE("Llcp Socket object creation error");
      goto error;
   }

   /* Get NativeLlcpServiceSocket class object */
   clsNativeLlcpServiceSocket = e->GetObjectClass(serviceSocket);
   if(e->ExceptionCheck())
   {
      ALOGE("Llcp Socket get object class error");
      goto error;
   }

   /* Set socket handle */
   f = e->GetFieldID(clsNativeLlcpServiceSocket, "mHandle", "I");
   e->SetIntField(serviceSocket, f,(jint)hLlcpSocket);
   TRACE("Service socket Handle = %02x\n",hLlcpSocket);

   /* Set socket linear buffer length */
   f = e->GetFieldID(clsNativeLlcpServiceSocket, "mLocalLinearBufferLength", "I");
   e->SetIntField(serviceSocket, f,(jint)linearBufferLength);
   TRACE("Service socket Linear buffer length = %02x\n",linearBufferLength);

   /* Set socket MIU */
   f = e->GetFieldID(clsNativeLlcpServiceSocket, "mLocalMiu", "I");
   e->SetIntField(serviceSocket, f,(jint)miu);
   TRACE("Service socket MIU = %d\n",miu);

   /* Set socket RW */
   f = e->GetFieldID(clsNativeLlcpServiceSocket, "mLocalRw", "I");
   e->SetIntField(serviceSocket, f,(jint)rw);
   TRACE("Service socket RW = %d\n",rw);

   return serviceSocket;
error:
   if (serviceName.buffer != NULL) {
      e->ReleaseStringUTFChars(sn, (const char *)serviceName.buffer);
   }
   return NULL;
}

static jobject com_android_nfc_NfcManager_doCreateLlcpSocket(JNIEnv *e, jobject o, jint nSap, jint miu, jint rw, jint linearBufferLength)
{
   jobject clientSocket = NULL;
   NFCSTATUS ret;
   phLibNfc_Handle hLlcpSocket;
   phLibNfc_Llcp_sSocketOptions_t sOptions;
   phNfc_sData_t sWorkingBuffer;
   struct nfc_jni_native_data *nat;
   jclass clsNativeLlcpSocket;
   jfieldID f;

   /* Retrieve native structure address */
   nat = nfc_jni_get_nat(e, o);

   /* Set Connection Oriented socket options */
   sOptions.miu = miu;
   sOptions.rw  = rw;

   /* Allocate Working buffer length */
   sWorkingBuffer.length = (miu*rw)+ miu + linearBufferLength;
   sWorkingBuffer.buffer = (uint8_t*)malloc(sWorkingBuffer.length);

   /* Create socket */
   TRACE("phLibNfc_Llcp_Socket(eType=phFriNfc_LlcpTransport_eConnectionOriented, ...)");
   REENTRANCE_LOCK();
   ret = phLibNfc_Llcp_Socket(phFriNfc_LlcpTransport_eConnectionOriented,
                              &sOptions,
                              &sWorkingBuffer,
                              &hLlcpSocket,
                              nfc_jni_llcp_transport_socket_err_callback,
                              (void*)nat);
   REENTRANCE_UNLOCK();

   if(ret != NFCSTATUS_SUCCESS)
   {
      ALOGE("phLibNfc_Llcp_Socket() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
      lastErrorStatus = ret;
      return NULL;
   }
   TRACE("phLibNfc_Llcp_Socket() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

   /* Create new NativeLlcpSocket object */
   if(nfc_jni_cache_object(e,"com/android/nfc/dhimpl/NativeLlcpSocket",&(clientSocket)) == -1)
   {
      ALOGE("Llcp socket object creation error");
      return NULL;
   }

   /* Get NativeConnectionless class object */
   clsNativeLlcpSocket = e->GetObjectClass(clientSocket);
   if(e->ExceptionCheck())
   {
      ALOGE("Get class object error");
      return NULL;
   }

   /* Test if an SAP number is present */
   if(nSap != 0)
   {
      /* Bind socket */
      TRACE("phLibNfc_Llcp_Bind(hSocket=0x%08x, nSap=0x%02x)", hLlcpSocket, nSap);
      REENTRANCE_LOCK();
      ret = phLibNfc_Llcp_Bind(hLlcpSocket,nSap, NULL);
      REENTRANCE_UNLOCK();
      if(ret != NFCSTATUS_SUCCESS)
      {
         lastErrorStatus = ret;
         ALOGE("phLibNfc_Llcp_Bind() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));
         /* Close socket created */
         REENTRANCE_LOCK();
         ret = phLibNfc_Llcp_Close(hLlcpSocket);
         REENTRANCE_UNLOCK();
         return NULL;
      }
      TRACE("phLibNfc_Llcp_Bind() returned 0x%04x[%s]", ret, nfc_jni_get_status_name(ret));

      /* Set socket SAP */
      f = e->GetFieldID(clsNativeLlcpSocket, "mSap", "I");
      e->SetIntField(clientSocket, f,(jint)nSap);
      TRACE("socket SAP = %d\n",nSap);
   }

   /* Set socket handle */
   f = e->GetFieldID(clsNativeLlcpSocket, "mHandle", "I");
   e->SetIntField(clientSocket, f,(jint)hLlcpSocket);
   TRACE("socket Handle = %02x\n",hLlcpSocket);

   /* Set socket MIU */
   f = e->GetFieldID(clsNativeLlcpSocket, "mLocalMiu", "I");
   e->SetIntField(clientSocket, f,(jint)miu);
   TRACE("socket MIU = %d\n",miu);

   /* Set socket RW */
   f = e->GetFieldID(clsNativeLlcpSocket, "mLocalRw", "I");
   e->SetIntField(clientSocket, f,(jint)rw);
   TRACE("socket RW = %d\n",rw);


   return clientSocket;
}

static jint com_android_nfc_NfcManager_doGetLastError(JNIEnv*, jobject)
{
   TRACE("Last Error Status = 0x%02x",lastErrorStatus);

   if(lastErrorStatus == NFCSTATUS_BUFFER_TOO_SMALL)
   {
      return ERROR_BUFFER_TOO_SMALL;
   }
   else if(lastErrorStatus == NFCSTATUS_INSUFFICIENT_RESOURCES)
   {
      return  ERROR_INSUFFICIENT_RESOURCES;
   }
   else
   {
      return lastErrorStatus;
   }
}

static void com_android_nfc_NfcManager_doAbort(JNIEnv*, jobject)
{
    emergency_recovery(NULL);
}

static void com_android_nfc_NfcManager_doSetP2pInitiatorModes(JNIEnv *e, jobject o,
        jint modes)
{
    ALOGE("Setting init modes to %x", modes);
    struct nfc_jni_native_data *nat = NULL;
    nat = nfc_jni_get_nat(e, o);
    nat->p2p_initiator_modes = modes;
}

static void com_android_nfc_NfcManager_doSetP2pTargetModes(JNIEnv *e, jobject o,
        jint modes)
{
    ALOGE("Setting target modes to %x", modes);
    struct nfc_jni_native_data *nat = NULL;
    nat = nfc_jni_get_nat(e, o);
    nat->p2p_target_modes = modes;
}

static bool performDownload(struct nfc_jni_native_data* nat, bool takeLock) {
    bool result = FALSE;
    int load_result;
    bool wasDisabled = FALSE;
    uint8_t OutputBuffer[1];
    uint8_t InputBuffer[1];
    NFCSTATUS status = NFCSTATUS_FAILED;
    struct nfc_jni_callback_data cb_data;

    /* Create the local semaphore */
    if (!nfc_cb_data_init(&cb_data, NULL))
    {
       result = FALSE;
       goto clean_and_return;
    }

    if (takeLock)
    {
        CONCURRENCY_LOCK();
    }

    /* Initialize Driver */
    if(!driverConfigured)
    {
        result = nfc_jni_configure_driver(nat);
        wasDisabled = TRUE;
    }
    TRACE("com_android_nfc_NfcManager_doDownload()");

    TRACE("Go in Download Mode");
    phLibNfc_Download_Mode();

    TRACE("Load new Firmware Image");
    load_result = phLibNfc_Load_Firmware_Image();
    if(load_result != 0)
    {
        TRACE("Load new Firmware Image - status = %d",load_result);
        result = FALSE;
        goto clean_and_return;
    }

    // Download
    gInputParam.buffer  = InputBuffer;
    gInputParam.length  = 0x01;
    gOutputParam.buffer = OutputBuffer;
    gOutputParam.length = 0x01;

    ALOGD("Download new Firmware");
    REENTRANCE_LOCK();
    status = phLibNfc_Mgt_IoCtl(gHWRef,NFC_FW_DOWNLOAD, &gInputParam, &gOutputParam, nfc_jni_ioctl_callback, (void *)&cb_data);
    REENTRANCE_UNLOCK();
    if(status != NFCSTATUS_PENDING)
    {
        ALOGE("phLibNfc_Mgt_IoCtl() (download) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
        result = FALSE;
        goto clean_and_return;
    }
    TRACE("phLibNfc_Mgt_IoCtl() (download) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));

    /* Wait for callback response */
    if(sem_wait(&cb_data.sem))
    {
       ALOGE("Failed to wait for semaphore (errno=0x%08x)", errno);
       result = FALSE;
       goto clean_and_return;
    }

    /* NOTE: we will get NFCSTATUS_FEATURE_NOT_SUPPORTED when we
       try to download an old-style firmware on top of a new-style
       firmware.  Hence, this is expected behavior, and not an
       error condition. */
    if(cb_data.status != NFCSTATUS_SUCCESS && cb_data.status != NFCSTATUS_FEATURE_NOT_SUPPORTED)
    {
        TRACE("phLibNfc_Mgt_IoCtl() (download) returned 0x%04x[%s]", status, nfc_jni_get_status_name(status));
        result = FALSE;
        goto clean_and_return;
    }

    if(cb_data.status == NFCSTATUS_FEATURE_NOT_SUPPORTED)
    {
        ALOGW("Old-style firmware not installed on top of new-style firmware. Using existing firmware in the chip.");
    }

    /*Download is successful*/
    result = TRUE;
clean_and_return:
    TRACE("phLibNfc_HW_Reset()");
    phLibNfc_HW_Reset();
    /* Deinitialize Driver */
    if(wasDisabled)
    {
        result = nfc_jni_unconfigure_driver(nat);
    }
    if (takeLock)
    {
        CONCURRENCY_UNLOCK();
    }
    nfc_cb_data_deinit(&cb_data);
    return result;
}

static jboolean com_android_nfc_NfcManager_doDownload(JNIEnv *e, jobject o)
{
    struct nfc_jni_native_data *nat = NULL;
    nat = nfc_jni_get_nat(e, o);
    return performDownload(nat, true);
}

static jstring com_android_nfc_NfcManager_doDump(JNIEnv *e, jobject)
{
    char buffer[100];
    snprintf(buffer, sizeof(buffer), "libnfc llc error_count=%u", libnfc_llc_error_count);
    return e->NewStringUTF(buffer);
}

/*
 * JNI registration.
 */
static JNINativeMethod gMethods[] =
{
   {"doDownload", "()Z",
        (void *)com_android_nfc_NfcManager_doDownload},

   {"initializeNativeStructure", "()Z",
      (void *)com_android_nfc_NfcManager_init_native_struc},

   {"doInitialize", "()Z",
      (void *)com_android_nfc_NfcManager_initialize},

   {"doDeinitialize", "()Z",
      (void *)com_android_nfc_NfcManager_deinitialize},

   {"doEnableDiscovery", "(IZZZZ)V",
      (void *)com_android_nfc_NfcManager_enableDiscovery},

   {"doCheckLlcp", "()Z",
      (void *)com_android_nfc_NfcManager_doCheckLlcp},

   {"doActivateLlcp", "()Z",
      (void *)com_android_nfc_NfcManager_doActivateLlcp},

   {"doCreateLlcpConnectionlessSocket", "(ILjava/lang/String;)Lcom/android/nfc/dhimpl/NativeLlcpConnectionlessSocket;",
      (void *)com_android_nfc_NfcManager_doCreateLlcpConnectionlessSocket},

   {"doCreateLlcpServiceSocket", "(ILjava/lang/String;III)Lcom/android/nfc/dhimpl/NativeLlcpServiceSocket;",
      (void *)com_android_nfc_NfcManager_doCreateLlcpServiceSocket},

   {"doCreateLlcpSocket", "(IIII)Lcom/android/nfc/dhimpl/NativeLlcpSocket;",
      (void *)com_android_nfc_NfcManager_doCreateLlcpSocket},

   {"doGetLastError", "()I",
      (void *)com_android_nfc_NfcManager_doGetLastError},

   {"disableDiscovery", "()V",
      (void *)com_android_nfc_NfcManager_disableDiscovery},

   {"doSetTimeout", "(II)Z",
      (void *)com_android_nfc_NfcManager_doSetTimeout},

   {"doGetTimeout", "(I)I",
      (void *)com_android_nfc_NfcManager_doGetTimeout},

   {"doResetTimeouts", "()V",
      (void *)com_android_nfc_NfcManager_doResetTimeouts},

   {"doAbort", "()V",
      (void *)com_android_nfc_NfcManager_doAbort},

   {"doSetP2pInitiatorModes","(I)V",
      (void *)com_android_nfc_NfcManager_doSetP2pInitiatorModes},

   {"doSetP2pTargetModes","(I)V",
      (void *)com_android_nfc_NfcManager_doSetP2pTargetModes},

   {"doDump", "()Ljava/lang/String;",
      (void *)com_android_nfc_NfcManager_doDump},
};


int register_com_android_nfc_NativeNfcManager(JNIEnv *e)
{
    nfc_jni_native_monitor_t *nfc_jni_native_monitor;

   nfc_jni_native_monitor = nfc_jni_init_monitor();
   if(nfc_jni_native_monitor == NULL)
   {
      ALOGE("NFC Manager cannot recover native monitor %x\n", errno);
      return -1;
   }

   return jniRegisterNativeMethods(e,
      "com/android/nfc/dhimpl/NativeNfcManager",
      gMethods, NELEM(gMethods));
}

} /* namespace android */