private void executeAPDUCommands(final byte[][] commands) {
    reader.sleep();

    cordova
        .getThreadPool()
        .execute(
            new Runnable() {
              @Override
              public void run() {
                reader.setMute(false);

                reader.reset(
                    new AudioJackReader.OnResetCompleteListener() {
                      @Override
                      public void onResetComplete(AudioJackReader audioJackReader) {

                        if (!reader.piccPowerOn(5, 0x8F)) Log.w(TAG, "Error");
                        for (byte[] command : commands) {
                          reader.piccTransmit(5, command);
                        }

                        reader.piccPowerOff();
                      }
                    });
              }
            });
  }
    @Override
    public void run() {
      Log.w("Timing out", "Timing out");
      this.controller.timedOut = true;

      PluginResult dataResult = new PluginResult(PluginResult.Status.OK, "TIMEDOUT");
      callbackContext.sendPluginResult(dataResult);

      reader.reset(
          new AudioJackReader.OnResetCompleteListener() {
            @Override
            public void onResetComplete(AudioJackReader audioJackReader) {

              // reader.sleep();
            }
          });
    }
  @Override
  public void initialize(final CordovaInterface cordova, final CordovaWebView webView) {
    super.initialize(cordova, webView);
    am = (AudioManager) cordova.getActivity().getSystemService(Context.AUDIO_SERVICE);
    reader = new AudioJackReader(am, true);

    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_HEADSET_PLUG);
    cordova
        .getActivity()
        .registerReceiver(
            new BroadcastReceiver() {
              @Override
              public void onReceive(Context context, Intent intent) {

                if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {

                  boolean plugged = (intent.getIntExtra("state", 0) == 1);

                  /* Mute the audio output if the reader is unplugged. */
                  reader.setMute(!plugged);
                  mute = !plugged;
                }
              }
            },
            filter);

    final StringBuffer buffer = new StringBuffer();
    final StringBuffer atrBuffer = new StringBuffer();

    reader.setSleepTimeout(30);

    reader.setOnPiccAtrAvailableListener(
        new AudioJackReader.OnPiccAtrAvailableListener() {
          @Override
          public void onPiccAtrAvailable(AudioJackReader audioJackReader, byte[] bytes) {
            Log.w(TAG, bytesToHex(bytes));

            atrBuffer.append(bytesToHex(bytes));
          }
        });

    reader.setOnPiccResponseApduAvailableListener(
        new AudioJackReader.OnPiccResponseApduAvailableListener() {
          @Override
          public void onPiccResponseApduAvailable(AudioJackReader audioJackReader, byte[] bytes) {
            byte[] resultBytes = new byte[bytes.length - 2];
            byte[] statusBytes = new byte[2];

            System.arraycopy(bytes, 0, resultBytes, 0, bytes.length - 2);
            System.arraycopy(bytes, bytes.length - 2, statusBytes, 0, 2);

            Log.w(TAG, bytesToHex(statusBytes));

            buffer.append(bytesToHex(resultBytes));
            buffer.append("");
          }
        });

    reader.setOnStatusAvailableListener(
        new AudioJackReader.OnStatusAvailableListener() {
          @Override
          public void onStatusAvailable(AudioJackReader audioJackReader, final Status status) {
            timer.cancel();

            cordova
                .getActivity()
                .runOnUiThread(
                    new Runnable() {
                      @Override
                      public void run() {
                        JSONArray resultArray = new JSONArray();
                        resultArray.put(Integer.toString(status.getBatteryLevel()));
                        resultArray.put(Integer.toString(status.getSleepTimeout()));

                        PluginResult dataResult =
                            new PluginResult(PluginResult.Status.OK, resultArray);
                        callbackContext.sendPluginResult(dataResult);
                      }
                    });
          }
        });

    reader.setOnDeviceIdAvailableListener(
        new AudioJackReader.OnDeviceIdAvailableListener() {
          @Override
          public void onDeviceIdAvailable(AudioJackReader audioJackReader, final byte[] bytes) {
            // reader.sleep();
            timer.cancel();

            cordova
                .getActivity()
                .runOnUiThread(
                    new Runnable() {
                      @Override
                      public void run() {
                        PluginResult dataResult =
                            new PluginResult(PluginResult.Status.OK, bytesToHex(bytes));
                        callbackContext.sendPluginResult(dataResult);
                      }
                    });
          }
        });

    reader.setOnResultAvailableListener(
        new AudioJackReader.OnResultAvailableListener() {
          @Override
          public void onResultAvailable(AudioJackReader audioJackReader, Result result) {
            // reader.sleep();
            timer.cancel();

            final String stringResult = buffer.toString();
            final String atrResult = atrBuffer.toString();

            Log.i(TAG, "Result Available");
            Log.i(TAG, stringResult);

            cordova
                .getActivity()
                .runOnUiThread(
                    new Runnable() {
                      @Override
                      public void run() {
                        if (timedOut) {
                          PluginResult dataResult =
                              new PluginResult(PluginResult.Status.OK, "TIMEDOUT");
                          callbackContext.sendPluginResult(dataResult);
                        } else {
                          JSONArray resultArray = new JSONArray();
                          resultArray.put(stringResult.replaceAll("\\s", ""));
                          resultArray.put(atrResult.replaceAll("\\s", ""));

                          PluginResult dataResult =
                              new PluginResult(PluginResult.Status.OK, resultArray);
                          callbackContext.sendPluginResult(dataResult);
                        }
                      }
                    });
            buffer.delete(0, buffer.length());
            atrBuffer.delete(0, atrBuffer.length());
          }
        });
  }
  @Override
  public boolean execute(String action, JSONArray data, CallbackContext callbackContext)
      throws JSONException {
    reader.start();

    am.setStreamVolume(
        AudioManager.STREAM_MUSIC, am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
    this.callbackContext = callbackContext;
    timedOut = false;

    Log.w(TAG, action);
    if (mute) {
      PluginResult dataResult = new PluginResult(PluginResult.Status.OK, "NOTFOUND");
      callbackContext.sendPluginResult(dataResult);
      return true;
    }

    final ACR35Controller self = this;

    final String loadKeyCommand = "FF 82 00 00 06 %s";
    final String authCommand = "FF 86 00 00 05 01 00 %s 60 00";
    final String defaultKey = "FF FF FF FF FF FF";
    String authKeyCommand = "FF 86 00 00 05 01 00 00 60 00";

    if (action.equals("getDeviceId")) {
      cordova
          .getThreadPool()
          .execute(
              new Runnable() {
                @Override
                public void run() {
                  reader.setMute(false);

                  reader.reset(
                      new AudioJackReader.OnResetCompleteListener() {
                        @Override
                        public void onResetComplete(AudioJackReader audioJackReader) {
                          reader.getDeviceId();
                        }
                      });
                }
              });
    }

    if (action.equals("getDeviceStatus")) {
      cordova
          .getThreadPool()
          .execute(
              new Runnable() {
                @Override
                public void run() {
                  reader.setMute(false);

                  reader.reset(
                      new AudioJackReader.OnResetCompleteListener() {
                        @Override
                        public void onResetComplete(AudioJackReader audioJackReader) {
                          reader.getStatus();
                        }
                      });
                }
              });
    }

    if (action.equals("readIdFromTag")) {
      executeAPDUCommands(new byte[][] {hexToBytes("FFCA000000")});
    }
    if (action.equals("readDataFromTag")) {
      executeAPDUCommands(
          new byte[][] {
            hexToBytes(String.format(loadKeyCommand, defaultKey)),
            hexToBytes(String.format(authCommand, "04")),
            hexToBytes("FF B0 00 04 10"),
            hexToBytes("FF B0 00 05 10"),
            hexToBytes("FF B0 00 06 10"),
            hexToBytes(String.format(authCommand, "08")),
            hexToBytes("FF B0 00 08 10"),
            hexToBytes("FF B0 00 09 10"),
            hexToBytes("FF B0 00 0A 10"),
            hexToBytes(String.format(authCommand, "10")),
            hexToBytes("FF B0 00 10 10"),
            hexToBytes("FF B0 00 11 10")
          });
    }
    if (action.equals("writeDataIntoTag")) {
      try {
        String dataString = data.get(0).toString();
        byte[] dataToWrite = new byte[128];
        Arrays.fill(dataToWrite, (byte) 0);
        byte[] dataBytes = hexToBytes(dataString);
        System.arraycopy(dataBytes, 0, dataToWrite, 0, dataBytes.length);

        String dataStringToWrite = bytesToHex(dataToWrite).replaceAll("\\s", "");
        String commandString1 = "FF D6 00 04 30" + dataStringToWrite.substring(0, 95);
        String commandString2 = "FF D6 00 08 30" + dataStringToWrite.substring(96, (96 * 2) - 1);
        String commandString3 =
            "FF D6 00 10 20" + dataStringToWrite.substring(96 * 2, (96 * 2 + 64) - 1);

        Log.w(TAG, dataStringToWrite);
        executeAPDUCommands(
            new byte[][] {
              hexToBytes(String.format(loadKeyCommand, defaultKey)),
              hexToBytes(String.format(authCommand, "04")),
              hexToBytes(commandString1),
              hexToBytes(String.format(authCommand, "08")),
              hexToBytes(commandString2),
              hexToBytes(String.format(authCommand, "10")),
              hexToBytes(commandString3),
            });
      } catch (java.lang.Exception e) {
        Log.w(TAG, e);
      }
    }

    Calendar calendar =
        Calendar.getInstance(); // gets a calendar using the default time zone and locale.
    calendar.add(Calendar.SECOND, timeOut);

    timer = new Timer();
    timer.schedule(new TimeoutClass(reader, self), calendar.getTime());

    PluginResult dataResult = new PluginResult(PluginResult.Status.OK, "IGNORE");
    dataResult.setKeepCallback(true);

    callbackContext.sendPluginResult(dataResult);
    return true;
  }