nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
nxf50051 | 3a018e7 | 2019-04-23 17:11:41 +0530 | [diff] [blame] | 16 | |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 17 | package com.android.nfc.dhimpl; |
| 18 | |
| 19 | import android.annotation.Nullable; |
| 20 | import com.android.nfc.DeviceHost; |
| 21 | import com.android.nfc.DeviceHost.TagEndpoint; |
| 22 | |
| 23 | import android.nfc.FormatException; |
| 24 | import android.nfc.NdefMessage; |
| 25 | import android.nfc.tech.IsoDep; |
| 26 | import android.nfc.tech.MifareClassic; |
| 27 | import android.nfc.tech.MifareUltralight; |
| 28 | import android.nfc.tech.Ndef; |
| 29 | import android.nfc.tech.NfcA; |
| 30 | import android.nfc.tech.NfcB; |
| 31 | import android.nfc.tech.NfcF; |
| 32 | import android.nfc.tech.NfcV; |
| 33 | import android.nfc.tech.NfcBarcode; |
| 34 | import android.nfc.tech.TagTechnology; |
| 35 | import android.os.Bundle; |
| 36 | import android.util.Log; |
| 37 | |
| 38 | /** |
| 39 | * Native interface to the NFC tag functions |
| 40 | */ |
| 41 | public class NativeNfcTag implements TagEndpoint { |
| 42 | static final boolean DBG = true; |
| 43 | |
| 44 | static final int STATUS_CODE_TARGET_LOST = 146; |
| 45 | |
| 46 | private int[] mTechList; |
| 47 | private int[] mTechHandles; |
| 48 | private int[] mTechLibNfcTypes; |
| 49 | private Bundle[] mTechExtras; |
| 50 | private byte[][] mTechPollBytes; |
| 51 | private byte[][] mTechActBytes; |
| 52 | private byte[] mUid; |
| 53 | |
| 54 | // mConnectedHandle stores the *real* libnfc handle |
| 55 | // that we're connected to. |
| 56 | private int mConnectedHandle; |
| 57 | |
| 58 | // mConnectedTechIndex stores to which technology |
| 59 | // the upper layer stack is connected. Note that |
| 60 | // we may be connected to a libnfchandle without being |
| 61 | // connected to a technology - technology changes |
| 62 | // may occur runtime, whereas the underlying handle |
| 63 | // could stay present. Usually all technologies are on the |
| 64 | // same handle, with the exception of multi-protocol |
| 65 | // tags. |
| 66 | private int mConnectedTechIndex; // Index in mTechHandles |
| 67 | |
| 68 | private final String TAG = "NativeNfcTag"; |
| 69 | |
| 70 | private boolean mIsPresent; // Whether the tag is known to be still present |
| 71 | |
| 72 | private PresenceCheckWatchdog mWatchdog; |
| 73 | class PresenceCheckWatchdog extends Thread { |
| 74 | |
| 75 | private final int watchdogTimeout; |
Suhas Suresh | 5efc543 | 2018-04-27 15:31:02 +0530 | [diff] [blame] | 76 | private DeviceHost.TagDisconnectedCallback tagDisconnectedCallback; |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 77 | |
| 78 | private boolean isPresent = true; |
| 79 | private boolean isStopped = false; |
| 80 | private boolean isPaused = false; |
| 81 | private boolean doCheck = true; |
| 82 | |
| 83 | public PresenceCheckWatchdog(int presenceCheckDelay, |
| 84 | @Nullable DeviceHost.TagDisconnectedCallback callback) { |
| 85 | watchdogTimeout = presenceCheckDelay; |
| 86 | tagDisconnectedCallback = callback; |
| 87 | } |
| 88 | |
| 89 | public synchronized void pause() { |
| 90 | isPaused = true; |
| 91 | doCheck = false; |
| 92 | this.notifyAll(); |
| 93 | } |
| 94 | |
| 95 | public synchronized void doResume() { |
| 96 | isPaused = false; |
| 97 | // We don't want to resume presence checking immediately, |
| 98 | // but go through at least one more wait period. |
| 99 | doCheck = false; |
| 100 | this.notifyAll(); |
| 101 | } |
| 102 | |
Suhas Suresh | 5efc543 | 2018-04-27 15:31:02 +0530 | [diff] [blame] | 103 | public synchronized void end(boolean disableCallback) { |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 104 | isStopped = true; |
| 105 | doCheck = false; |
Suhas Suresh | 5efc543 | 2018-04-27 15:31:02 +0530 | [diff] [blame] | 106 | if (disableCallback) { |
| 107 | tagDisconnectedCallback = null; |
| 108 | } |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 109 | this.notifyAll(); |
| 110 | } |
| 111 | |
| 112 | @Override |
| 113 | public void run() { |
| 114 | synchronized (this) { |
| 115 | if (DBG) Log.d(TAG, "Starting background presence check"); |
| 116 | while (isPresent && !isStopped) { |
| 117 | try { |
| 118 | if (!isPaused) { |
| 119 | doCheck = true; |
| 120 | } |
| 121 | this.wait(watchdogTimeout); |
| 122 | if (doCheck) { |
| 123 | isPresent = doPresenceCheck(); |
| 124 | } else { |
| 125 | // 1) We are paused, waiting for unpause |
| 126 | // 2) We just unpaused, do pres check in next iteration |
| 127 | // (after watchdogTimeout ms sleep) |
| 128 | // 3) We just set the timeout, wait for this timeout |
| 129 | // to expire once first. |
| 130 | // 4) We just stopped, exit loop anyway |
| 131 | } |
| 132 | } catch (InterruptedException e) { |
| 133 | // Activity detected, loop |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
nxf50051 | 3a018e7 | 2019-04-23 17:11:41 +0530 | [diff] [blame] | 138 | synchronized (NativeNfcTag.this) { |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 139 | mIsPresent = false; |
nxf50051 | 3a018e7 | 2019-04-23 17:11:41 +0530 | [diff] [blame] | 140 | } |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 141 | // Restart the polling loop |
| 142 | |
| 143 | Log.d(TAG, "Tag lost, restarting polling loop"); |
| 144 | doDisconnect(); |
| 145 | if (tagDisconnectedCallback != null) { |
| 146 | tagDisconnectedCallback.onTagDisconnected(mConnectedHandle); |
| 147 | } |
| 148 | if (DBG) Log.d(TAG, "Stopping background presence check"); |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | private native int doConnect(int handle); |
| 153 | public synchronized int connectWithStatus(int technology) { |
| 154 | if (mWatchdog != null) { |
| 155 | mWatchdog.pause(); |
| 156 | } |
| 157 | int status = -1; |
| 158 | for (int i = 0; i < mTechList.length; i++) { |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 159 | if (mTechList[i] == technology) { |
| 160 | // Get the handle and connect, if not already connected |
| 161 | if (mConnectedHandle != mTechHandles[i]) { |
| 162 | // We're not yet connected to this handle, there are |
| 163 | // a few scenario's here: |
| 164 | // 1) We are not connected to anything yet - allow |
| 165 | // 2) We are connected to a technology which has |
| 166 | // a different handle (multi-protocol tag); we support |
| 167 | // switching to that. |
| 168 | if (mConnectedHandle == -1) { |
| 169 | // Not connected yet |
| 170 | //status = doConnect(mTechHandles[i]); |
| 171 | status = doConnect(i); |
| 172 | } else { |
| 173 | // Connect to a tech with a different handle |
| 174 | Log.d(TAG,"Connect to a tech with a different handle"); |
| 175 | //status = reconnectWithStatus(mTechHandles[i]); |
| 176 | status = reconnectWithStatus(i); |
| 177 | } |
| 178 | if (status == 0) { |
| 179 | mConnectedHandle = mTechHandles[i]; |
| 180 | mConnectedTechIndex = i; |
| 181 | } |
| 182 | } else { |
| 183 | // 1) We are connected to a technology which has the same |
| 184 | // handle; we do not support connecting at a different |
| 185 | // level (libnfc auto-activates to the max level on |
| 186 | // any handle). |
| 187 | // 2) We are connecting to the ndef technology - always |
| 188 | // allowed. |
| 189 | if ((technology == TagTechnology.NDEF) || |
| 190 | (technology == TagTechnology.NDEF_FORMATABLE)) { |
| 191 | // special case for NDEF, this will cause switch to ISO_DEP frame intf |
| 192 | i = 0; |
| 193 | // status = 0; |
nxf35421 | 199851a | 2019-05-28 15:16:07 +0530 | [diff] [blame] | 194 | } |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 195 | status = reconnectWithStatus(i); |
| 196 | /* |
| 197 | if ((technology != TagTechnology.ISO_DEP) && |
| 198 | (hasTechOnHandle(TagTechnology.ISO_DEP, mTechHandles[i]))) { |
| 199 | // Don't allow to connect a -4 tag at a different level |
| 200 | // than IsoDep, as this is not supported by |
| 201 | // libNFC. |
| 202 | // revised for NFCA... do allow to connect a -4 tag at this level. |
| 203 | Log.d(TAG,"Connect to a tech with same different handle (rf intf change)"); |
| 204 | status = reconnectWithStatus(i); |
| 205 | if (status == 0) { |
| 206 | mConnectedHandle = mTechHandles[i]; |
| 207 | mConnectedTechIndex = i; |
| 208 | } |
| 209 | //status = 0; |
| 210 | } else { |
| 211 | status = 0; |
| 212 | } |
| 213 | */ |
nxf35421 | 199851a | 2019-05-28 15:16:07 +0530 | [diff] [blame] | 214 | |
| 215 | |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 216 | if (status == 0) { |
| 217 | mConnectedTechIndex = i; |
| 218 | // Handle was already identical |
| 219 | } |
| 220 | } |
| 221 | break; |
| 222 | } |
| 223 | } |
| 224 | if (mWatchdog != null) { |
| 225 | mWatchdog.doResume(); |
| 226 | } |
| 227 | return status; |
| 228 | } |
| 229 | @Override |
| 230 | public synchronized boolean connect(int technology) { |
| 231 | return connectWithStatus(technology) == 0; |
| 232 | } |
| 233 | |
| 234 | @Override |
Suhas Suresh | 5efc543 | 2018-04-27 15:31:02 +0530 | [diff] [blame] | 235 | public synchronized void stopPresenceChecking() { |
| 236 | mIsPresent = false; |
| 237 | if (mWatchdog != null) { |
| 238 | mWatchdog.end(true); |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | @Override |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 243 | public synchronized void startPresenceChecking(int presenceCheckDelay, |
| 244 | DeviceHost.TagDisconnectedCallback callback) { |
| 245 | // Once we start presence checking, we allow the upper layers |
| 246 | // to know the tag is in the field. |
| 247 | mIsPresent = true; |
| 248 | if (mWatchdog == null) { |
| 249 | mWatchdog = new PresenceCheckWatchdog(presenceCheckDelay, callback); |
| 250 | mWatchdog.start(); |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | @Override |
| 255 | public synchronized boolean isPresent() { |
| 256 | // Returns whether the tag is still in the field to the best |
| 257 | // of our knowledge. |
| 258 | return mIsPresent; |
| 259 | } |
| 260 | native boolean doDisconnect(); |
| 261 | @Override |
nxpandroid | 281eb92 | 2016-08-25 20:27:46 +0530 | [diff] [blame] | 262 | public boolean disconnect() { |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 263 | boolean result = false; |
nxpandroid | 281eb92 | 2016-08-25 20:27:46 +0530 | [diff] [blame] | 264 | PresenceCheckWatchdog watchdog; |
| 265 | synchronized (this) { |
| 266 | mIsPresent = false; |
| 267 | watchdog = mWatchdog; |
| 268 | } |
| 269 | if (watchdog != null) { |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 270 | // Watchdog has already disconnected or will do it |
Suhas Suresh | 5efc543 | 2018-04-27 15:31:02 +0530 | [diff] [blame] | 271 | watchdog.end(false); |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 272 | try { |
nxpandroid | 281eb92 | 2016-08-25 20:27:46 +0530 | [diff] [blame] | 273 | watchdog.join(); |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 274 | } catch (InterruptedException e) { |
| 275 | // Should never happen. |
| 276 | } |
nxpandroid | 281eb92 | 2016-08-25 20:27:46 +0530 | [diff] [blame] | 277 | synchronized (this) { |
| 278 | mWatchdog = null; |
| 279 | } |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 280 | result = true; |
| 281 | } else { |
| 282 | result = doDisconnect(); |
| 283 | } |
| 284 | |
| 285 | mConnectedTechIndex = -1; |
| 286 | mConnectedHandle = -1; |
| 287 | return result; |
| 288 | } |
| 289 | |
| 290 | native int doReconnect(); |
| 291 | public synchronized int reconnectWithStatus() { |
| 292 | if (mWatchdog != null) { |
| 293 | mWatchdog.pause(); |
| 294 | } |
| 295 | int status = doReconnect(); |
| 296 | if (mWatchdog != null) { |
| 297 | mWatchdog.doResume(); |
| 298 | } |
| 299 | return status; |
| 300 | } |
| 301 | @Override |
| 302 | public synchronized boolean reconnect() { |
| 303 | return reconnectWithStatus() == 0; |
| 304 | } |
| 305 | |
| 306 | native int doHandleReconnect(int handle); |
| 307 | public synchronized int reconnectWithStatus(int handle) { |
| 308 | if (mWatchdog != null) { |
| 309 | mWatchdog.pause(); |
| 310 | } |
| 311 | int status = doHandleReconnect(handle); |
| 312 | if (mWatchdog != null) { |
| 313 | mWatchdog.doResume(); |
| 314 | } |
| 315 | return status; |
| 316 | } |
| 317 | |
| 318 | private native byte[] doTransceive(byte[] data, boolean raw, int[] returnCode); |
| 319 | @Override |
| 320 | public synchronized byte[] transceive(byte[] data, boolean raw, int[] returnCode) { |
| 321 | if (mWatchdog != null) { |
| 322 | mWatchdog.pause(); |
| 323 | } |
| 324 | byte[] result = doTransceive(data, raw, returnCode); |
| 325 | if (mWatchdog != null) { |
| 326 | mWatchdog.doResume(); |
| 327 | } |
| 328 | return result; |
| 329 | } |
| 330 | |
| 331 | private native int doCheckNdef(int[] ndefinfo); |
| 332 | private synchronized int checkNdefWithStatus(int[] ndefinfo) { |
| 333 | if (mWatchdog != null) { |
| 334 | mWatchdog.pause(); |
| 335 | } |
| 336 | int status = doCheckNdef(ndefinfo); |
| 337 | if (mWatchdog != null) { |
| 338 | mWatchdog.doResume(); |
| 339 | } |
| 340 | return status; |
| 341 | } |
| 342 | @Override |
| 343 | public synchronized boolean checkNdef(int[] ndefinfo) { |
| 344 | return checkNdefWithStatus(ndefinfo) == 0; |
| 345 | } |
| 346 | |
| 347 | private native byte[] doRead(); |
| 348 | @Override |
| 349 | public synchronized byte[] readNdef() { |
| 350 | if (mWatchdog != null) { |
| 351 | mWatchdog.pause(); |
| 352 | } |
| 353 | byte[] result = doRead(); |
| 354 | if (mWatchdog != null) { |
| 355 | mWatchdog.doResume(); |
| 356 | } |
| 357 | return result; |
| 358 | } |
| 359 | |
| 360 | private native boolean doWrite(byte[] buf); |
| 361 | @Override |
| 362 | public synchronized boolean writeNdef(byte[] buf) { |
| 363 | if (mWatchdog != null) { |
| 364 | mWatchdog.pause(); |
| 365 | } |
| 366 | boolean result = doWrite(buf); |
| 367 | if (mWatchdog != null) { |
| 368 | mWatchdog.doResume(); |
| 369 | } |
| 370 | return result; |
| 371 | } |
| 372 | |
| 373 | native boolean doPresenceCheck(); |
| 374 | @Override |
| 375 | public synchronized boolean presenceCheck() { |
| 376 | if (mWatchdog != null) { |
| 377 | mWatchdog.pause(); |
| 378 | } |
| 379 | boolean result = doPresenceCheck(); |
| 380 | if (mWatchdog != null) { |
| 381 | mWatchdog.doResume(); |
| 382 | } |
| 383 | return result; |
| 384 | } |
| 385 | |
| 386 | native boolean doNdefFormat(byte[] key); |
| 387 | @Override |
| 388 | public synchronized boolean formatNdef(byte[] key) { |
| 389 | if (mWatchdog != null) { |
| 390 | mWatchdog.pause(); |
| 391 | } |
| 392 | boolean result = doNdefFormat(key); |
| 393 | if (mWatchdog != null) { |
| 394 | mWatchdog.doResume(); |
| 395 | } |
| 396 | return result; |
| 397 | } |
| 398 | |
| 399 | native boolean doMakeReadonly(byte[] key); |
| 400 | @Override |
| 401 | public synchronized boolean makeReadOnly() { |
| 402 | if (mWatchdog != null) { |
| 403 | mWatchdog.pause(); |
| 404 | } |
| 405 | boolean result; |
| 406 | if (hasTech(TagTechnology.MIFARE_CLASSIC)) { |
| 407 | result = doMakeReadonly(MifareClassic.KEY_DEFAULT); |
| 408 | } else { |
| 409 | // No key needed for other technologies |
| 410 | result = doMakeReadonly(new byte[] {}); |
| 411 | } |
| 412 | if (mWatchdog != null) { |
| 413 | mWatchdog.doResume(); |
| 414 | } |
| 415 | return result; |
| 416 | } |
| 417 | |
| 418 | native boolean doIsIsoDepNdefFormatable(byte[] poll, byte[] act); |
| 419 | @Override |
| 420 | public synchronized boolean isNdefFormatable() { |
| 421 | // Let native code decide whether the currently activated tag |
| 422 | // is formatable. Although the name of the JNI function refers |
| 423 | // to ISO-DEP, the JNI function checks all tag types. |
| 424 | return doIsIsoDepNdefFormatable(mTechPollBytes[0], |
| 425 | mTechActBytes[0]); |
| 426 | } |
| 427 | |
| 428 | @Override |
| 429 | public int getHandle() { |
| 430 | // This is just a handle for the clients; it can simply use the first |
| 431 | // technology handle we have. |
| 432 | if (mTechHandles.length > 0) { |
| 433 | return mTechHandles[0]; |
| 434 | } else { |
| 435 | return 0; |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | @Override |
| 440 | public byte[] getUid() { |
| 441 | return mUid; |
| 442 | } |
| 443 | |
| 444 | @Override |
| 445 | public int[] getTechList() { |
| 446 | return mTechList; |
| 447 | } |
| 448 | |
| 449 | private int getConnectedHandle() { |
| 450 | return mConnectedHandle; |
| 451 | } |
| 452 | |
| 453 | private int getConnectedLibNfcType() { |
| 454 | if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechLibNfcTypes.length) { |
| 455 | return mTechLibNfcTypes[mConnectedTechIndex]; |
| 456 | } else { |
| 457 | return 0; |
| 458 | } |
| 459 | } |
| 460 | |
| 461 | @Override |
| 462 | public int getConnectedTechnology() { |
| 463 | if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechList.length) { |
| 464 | return mTechList[mConnectedTechIndex]; |
| 465 | } else { |
| 466 | return 0; |
| 467 | } |
| 468 | } |
| 469 | native int doGetNdefType(int libnfctype, int javatype); |
| 470 | private int getNdefType(int libnfctype, int javatype) { |
| 471 | return doGetNdefType(libnfctype, javatype); |
| 472 | } |
| 473 | |
| 474 | private void addTechnology(int tech, int handle, int libnfctype) { |
| 475 | int[] mNewTechList = new int[mTechList.length + 1]; |
| 476 | System.arraycopy(mTechList, 0, mNewTechList, 0, mTechList.length); |
| 477 | mNewTechList[mTechList.length] = tech; |
| 478 | mTechList = mNewTechList; |
| 479 | |
| 480 | int[] mNewHandleList = new int[mTechHandles.length + 1]; |
| 481 | System.arraycopy(mTechHandles, 0, mNewHandleList, 0, mTechHandles.length); |
| 482 | mNewHandleList[mTechHandles.length] = handle; |
| 483 | mTechHandles = mNewHandleList; |
| 484 | |
| 485 | int[] mNewTypeList = new int[mTechLibNfcTypes.length + 1]; |
| 486 | System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, mTechLibNfcTypes.length); |
| 487 | mNewTypeList[mTechLibNfcTypes.length] = libnfctype; |
| 488 | mTechLibNfcTypes = mNewTypeList; |
| 489 | } |
| 490 | |
| 491 | @Override |
| 492 | public void removeTechnology(int tech) { |
| 493 | synchronized (this) { |
| 494 | int techIndex = getTechIndex(tech); |
| 495 | if (techIndex != -1) { |
| 496 | int[] mNewTechList = new int[mTechList.length - 1]; |
| 497 | System.arraycopy(mTechList, 0, mNewTechList, 0, techIndex); |
| 498 | System.arraycopy(mTechList, techIndex + 1, mNewTechList, techIndex, |
| 499 | mTechList.length - techIndex - 1); |
| 500 | mTechList = mNewTechList; |
| 501 | |
| 502 | int[] mNewHandleList = new int[mTechHandles.length - 1]; |
| 503 | System.arraycopy(mTechHandles, 0, mNewHandleList, 0, techIndex); |
| 504 | System.arraycopy(mTechHandles, techIndex + 1, mNewTechList, techIndex, |
| 505 | mTechHandles.length - techIndex - 1); |
| 506 | mTechHandles = mNewHandleList; |
| 507 | |
| 508 | int[] mNewTypeList = new int[mTechLibNfcTypes.length - 1]; |
| 509 | System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, techIndex); |
| 510 | System.arraycopy(mTechLibNfcTypes, techIndex + 1, mNewTypeList, techIndex, |
| 511 | mTechLibNfcTypes.length - techIndex - 1); |
| 512 | mTechLibNfcTypes = mNewTypeList; |
| 513 | |
| 514 | //The technology must be removed from the mTechExtras array, |
| 515 | //just like the above arrays. |
| 516 | //Remove the specified element from the array, |
| 517 | //then shift the remaining elements by one. |
| 518 | if (mTechExtras != null) |
| 519 | { |
| 520 | Bundle[] mNewTechExtras = new Bundle[mTechExtras.length - 1]; |
| 521 | System.arraycopy(mTechExtras, 0, mNewTechExtras, 0, techIndex); |
| 522 | System.arraycopy(mTechExtras, techIndex + 1, mNewTechExtras, techIndex, |
| 523 | mTechExtras.length - techIndex - 1); |
| 524 | mTechExtras = mNewTechExtras; |
| 525 | } |
| 526 | } |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | public void addNdefFormatableTechnology(int handle, int libnfcType) { |
| 531 | synchronized (this) { |
| 532 | addTechnology(TagTechnology.NDEF_FORMATABLE, handle, libnfcType); |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | // This method exists to "patch in" the ndef technologies, |
| 537 | // which is done inside Java instead of the native JNI code. |
| 538 | // To not create some nasty dependencies on the order on which things |
| 539 | // are called (most notably getTechExtras()), it needs some additional |
| 540 | // checking. |
| 541 | public void addNdefTechnology(NdefMessage msg, int handle, int libnfcType, |
| 542 | int javaType, int maxLength, int cardState) { |
| 543 | synchronized (this) { |
| 544 | addTechnology(TagTechnology.NDEF, handle, libnfcType); |
| 545 | |
| 546 | Bundle extras = new Bundle(); |
| 547 | extras.putParcelable(Ndef.EXTRA_NDEF_MSG, msg); |
| 548 | extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, maxLength); |
| 549 | extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, cardState); |
| 550 | extras.putInt(Ndef.EXTRA_NDEF_TYPE, getNdefType(libnfcType, javaType)); |
| 551 | |
| 552 | if (mTechExtras == null) { |
| 553 | // This will build the tech extra's for the first time, |
| 554 | // including a NULL ref for the NDEF tech we generated above. |
| 555 | Bundle[] builtTechExtras = getTechExtras(); |
| 556 | builtTechExtras[builtTechExtras.length - 1] = extras; |
| 557 | } |
| 558 | else { |
| 559 | // Tech extras were built before, patch the NDEF one in |
| 560 | Bundle[] oldTechExtras = getTechExtras(); |
| 561 | Bundle[] newTechExtras = new Bundle[oldTechExtras.length + 1]; |
| 562 | System.arraycopy(oldTechExtras, 0, newTechExtras, 0, oldTechExtras.length); |
| 563 | newTechExtras[oldTechExtras.length] = extras; |
| 564 | mTechExtras = newTechExtras; |
| 565 | } |
| 566 | |
| 567 | |
| 568 | } |
| 569 | } |
| 570 | |
| 571 | private int getTechIndex(int tech) { |
| 572 | int techIndex = -1; |
| 573 | for (int i = 0; i < mTechList.length; i++) { |
| 574 | if (mTechList[i] == tech) { |
| 575 | techIndex = i; |
| 576 | break; |
| 577 | } |
| 578 | } |
| 579 | return techIndex; |
| 580 | } |
| 581 | |
| 582 | private boolean hasTech(int tech) { |
| 583 | boolean hasTech = false; |
| 584 | for (int i = 0; i < mTechList.length; i++) { |
| 585 | if (mTechList[i] == tech) { |
| 586 | hasTech = true; |
| 587 | break; |
| 588 | } |
| 589 | } |
| 590 | return hasTech; |
| 591 | } |
| 592 | |
| 593 | private boolean hasTechOnHandle(int tech, int handle) { |
| 594 | boolean hasTech = false; |
| 595 | for (int i = 0; i < mTechList.length; i++) { |
| 596 | if (mTechList[i] == tech && mTechHandles[i] == handle) { |
| 597 | hasTech = true; |
| 598 | break; |
| 599 | } |
| 600 | } |
| 601 | return hasTech; |
| 602 | |
| 603 | } |
| 604 | |
| 605 | private boolean isUltralightC() { |
| 606 | /* Make a best-effort attempt at classifying ULTRALIGHT |
| 607 | * vs ULTRALIGHT-C (based on NXP's public AN1303). |
| 608 | * The memory layout is as follows: |
| 609 | * Page # BYTE1 BYTE2 BYTE3 BYTE4 |
| 610 | * 2 INT1 INT2 LOCK LOCK |
| 611 | * 3 OTP OTP OTP OTP (NDEF CC if NDEF-formatted) |
| 612 | * 4 DATA DATA DATA DATA (version info if factory-state) |
| 613 | * |
| 614 | * Read four blocks from page 2, which will get us both |
| 615 | * the lock page, the OTP page and the version info. |
| 616 | */ |
| 617 | boolean isUltralightC = false; |
| 618 | byte[] readCmd = { 0x30, 0x02 }; |
| 619 | int[] retCode = new int[2]; |
| 620 | byte[] respData = transceive(readCmd, false, retCode); |
| 621 | if (respData != null && respData.length == 16) { |
| 622 | // Check the lock bits (last 2 bytes in page2) |
| 623 | // and the OTP bytes (entire page 3) |
| 624 | if (respData[2] == 0 && respData[3] == 0 && respData[4] == 0 && |
| 625 | respData[5] == 0 && respData[6] == 0 && respData[7] == 0) { |
| 626 | // Very likely to be a blank card, look at version info |
| 627 | // in page 4. |
| 628 | if ((respData[8] == (byte)0x02) && respData[9] == (byte)0x00) { |
| 629 | // This is Ultralight-C |
| 630 | isUltralightC = true; |
| 631 | } else { |
| 632 | // 0xFF 0xFF would indicate Ultralight, but we also use Ultralight |
| 633 | // as a fallback if it's anything else |
| 634 | isUltralightC = false; |
| 635 | } |
| 636 | } else { |
| 637 | // See if we can find the NDEF CC in the OTP page and if it's |
| 638 | // smaller than major version two |
| 639 | if (respData[4] == (byte)0xE1 && ((respData[5] & 0xff) < 0x20)) { |
| 640 | // OK, got NDEF. Technically we'd have to search for the |
| 641 | // NDEF TLV as well. However, this would add too much |
| 642 | // time for discovery and we can make already make a good guess |
| 643 | // with the data we have here. Byte 2 of the OTP page |
| 644 | // indicates the size of the tag - 0x06 is UL, anything |
| 645 | // above indicates UL-C. |
| 646 | if ((respData[6] & 0xff) > 0x06) { |
| 647 | isUltralightC = true; |
| 648 | } |
| 649 | } else { |
| 650 | // Fall back to ultralight |
| 651 | isUltralightC = false; |
| 652 | } |
| 653 | } |
| 654 | } |
| 655 | return isUltralightC; |
| 656 | } |
| 657 | |
| 658 | @Override |
| 659 | public Bundle[] getTechExtras() { |
| 660 | synchronized (this) { |
| 661 | if (mTechExtras != null) return mTechExtras; |
| 662 | mTechExtras = new Bundle[mTechList.length]; |
| 663 | for (int i = 0; i < mTechList.length; i++) { |
| 664 | Bundle extras = new Bundle(); |
| 665 | switch (mTechList[i]) { |
| 666 | case TagTechnology.NFC_A: { |
| 667 | byte[] actBytes = mTechActBytes[i]; |
| 668 | if ((actBytes != null) && (actBytes.length > 0)) { |
| 669 | extras.putShort(NfcA.EXTRA_SAK, (short) (actBytes[0] & (short) 0xFF)); |
| 670 | } else { |
| 671 | // Unfortunately Jewel doesn't have act bytes, |
| 672 | // ignore this case. |
| 673 | } |
| 674 | extras.putByteArray(NfcA.EXTRA_ATQA, mTechPollBytes[i]); |
| 675 | break; |
| 676 | } |
| 677 | |
| 678 | case TagTechnology.NFC_B: { |
| 679 | // What's returned from the PN544 is actually: |
| 680 | // 4 bytes app data |
| 681 | // 3 bytes prot info |
| 682 | byte[] appData = new byte[4]; |
| 683 | byte[] protInfo = new byte[3]; |
| 684 | if (mTechPollBytes[i].length >= 7) { |
| 685 | System.arraycopy(mTechPollBytes[i], 0, appData, 0, 4); |
| 686 | System.arraycopy(mTechPollBytes[i], 4, protInfo, 0, 3); |
| 687 | |
| 688 | extras.putByteArray(NfcB.EXTRA_APPDATA, appData); |
| 689 | extras.putByteArray(NfcB.EXTRA_PROTINFO, protInfo); |
| 690 | } |
| 691 | break; |
| 692 | } |
| 693 | |
| 694 | case TagTechnology.NFC_F: { |
| 695 | byte[] pmm = new byte[8]; |
| 696 | byte[] sc = new byte[2]; |
| 697 | if (mTechPollBytes[i].length >= 8) { |
| 698 | // At least pmm is present |
| 699 | System.arraycopy(mTechPollBytes[i], 0, pmm, 0, 8); |
| 700 | extras.putByteArray(NfcF.EXTRA_PMM, pmm); |
| 701 | } |
| 702 | if (mTechPollBytes[i].length == 10) { |
| 703 | System.arraycopy(mTechPollBytes[i], 8, sc, 0, 2); |
| 704 | extras.putByteArray(NfcF.EXTRA_SC, sc); |
| 705 | } |
| 706 | break; |
| 707 | } |
| 708 | |
| 709 | case TagTechnology.ISO_DEP: { |
| 710 | if (hasTech(TagTechnology.NFC_A)) { |
| 711 | extras.putByteArray(IsoDep.EXTRA_HIST_BYTES, mTechActBytes[i]); |
| 712 | } |
| 713 | else { |
| 714 | extras.putByteArray(IsoDep.EXTRA_HI_LAYER_RESP, mTechActBytes[i]); |
| 715 | } |
| 716 | break; |
| 717 | } |
| 718 | |
| 719 | case TagTechnology.NFC_V: { |
| 720 | // First byte response flags, second byte DSFID |
| 721 | if (mTechPollBytes[i] != null && mTechPollBytes[i].length >= 2) { |
| 722 | extras.putByte(NfcV.EXTRA_RESP_FLAGS, mTechPollBytes[i][0]); |
| 723 | extras.putByte(NfcV.EXTRA_DSFID, mTechPollBytes[i][1]); |
| 724 | } |
| 725 | break; |
| 726 | } |
| 727 | |
| 728 | case TagTechnology.MIFARE_ULTRALIGHT: { |
| 729 | boolean isUlc = isUltralightC(); |
| 730 | extras.putBoolean(MifareUltralight.EXTRA_IS_UL_C, isUlc); |
| 731 | break; |
| 732 | } |
| 733 | |
| 734 | case TagTechnology.MIFARE_CLASSIC: { |
| 735 | byte[] actBytes = mTechActBytes[i]; |
| 736 | if ((actBytes != null) && (actBytes.length > 0)) { |
| 737 | extras.putShort(NfcA.EXTRA_SAK, (short) (actBytes[0] & (short) 0xFF)); |
| 738 | } else { |
| 739 | // ignore this case. |
| 740 | } |
| 741 | extras.putByteArray(NfcA.EXTRA_ATQA, mTechPollBytes[i]); |
| 742 | break; |
| 743 | } |
| 744 | |
| 745 | case TagTechnology.NFC_BARCODE: { |
| 746 | // hard code this for now, this is the only valid type |
| 747 | extras.putInt(NfcBarcode.EXTRA_BARCODE_TYPE, NfcBarcode.TYPE_KOVIO); |
| 748 | break; |
| 749 | } |
| 750 | |
| 751 | default: { |
| 752 | // Leave the entry in the array null |
| 753 | continue; |
| 754 | } |
| 755 | } |
| 756 | mTechExtras[i] = extras; |
| 757 | } |
| 758 | return mTechExtras; |
| 759 | } |
| 760 | } |
| 761 | |
| 762 | @Override |
| 763 | public NdefMessage findAndReadNdef() { |
| 764 | // Try to find NDEF on any of the technologies. |
| 765 | int[] technologies = getTechList(); |
| 766 | int[] handles = mTechHandles; |
| 767 | NdefMessage ndefMsg = null; |
| 768 | boolean foundFormattable = false; |
| 769 | int formattableHandle = 0; |
| 770 | int formattableLibNfcType = 0; |
| 771 | int status; |
| 772 | |
| 773 | for (int techIndex = 0; techIndex < technologies.length; techIndex++) { |
| 774 | // have we seen this handle before? |
| 775 | for (int i = 0; i < techIndex; i++) { |
| 776 | if (handles[i] == handles[techIndex]) { |
| 777 | continue; // don't check duplicate handles |
| 778 | } |
| 779 | } |
| 780 | |
| 781 | status = connectWithStatus(technologies[techIndex]); |
| 782 | if (status != 0) { |
| 783 | Log.d(TAG, "Connect Failed - status = "+ status); |
| 784 | if (status == STATUS_CODE_TARGET_LOST) { |
| 785 | break; |
| 786 | } |
| 787 | continue; // try next handle |
| 788 | } |
| 789 | // Check if this type is NDEF formatable |
| 790 | if (!foundFormattable) { |
| 791 | if (isNdefFormatable()) { |
| 792 | foundFormattable = true; |
| 793 | formattableHandle = getConnectedHandle(); |
| 794 | formattableLibNfcType = getConnectedLibNfcType(); |
| 795 | // We'll only add formattable tech if no ndef is |
| 796 | // found - this is because libNFC refuses to format |
| 797 | // an already NDEF formatted tag. |
| 798 | } |
| 799 | reconnect(); |
| 800 | } |
| 801 | |
| 802 | int[] ndefinfo = new int[2]; |
| 803 | status = checkNdefWithStatus(ndefinfo); |
| 804 | if (status != 0) { |
| 805 | Log.d(TAG, "Check NDEF Failed - status = " + status); |
| 806 | if (status == STATUS_CODE_TARGET_LOST) { |
| 807 | break; |
| 808 | } |
| 809 | continue; // try next handle |
| 810 | } |
| 811 | |
| 812 | // found our NDEF handle |
| 813 | boolean generateEmptyNdef = false; |
| 814 | |
| 815 | int supportedNdefLength = ndefinfo[0]; |
| 816 | int cardState = ndefinfo[1]; |
| 817 | byte[] buff = readNdef(); |
nxpandroid | 6fd9cdb | 2017-07-12 18:25:41 +0530 | [diff] [blame] | 818 | if (buff != null && buff.length > 0) { |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 819 | try { |
| 820 | ndefMsg = new NdefMessage(buff); |
| 821 | addNdefTechnology(ndefMsg, |
| 822 | getConnectedHandle(), |
| 823 | getConnectedLibNfcType(), |
| 824 | getConnectedTechnology(), |
| 825 | supportedNdefLength, cardState); |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 826 | reconnect(); |
| 827 | } catch (FormatException e) { |
| 828 | // Create an intent anyway, without NDEF messages |
| 829 | generateEmptyNdef = true; |
| 830 | } |
nxpandroid | 6fd9cdb | 2017-07-12 18:25:41 +0530 | [diff] [blame] | 831 | } else if(buff != null){ |
| 832 | // Empty buffer, unformatted tags fall into this case |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 833 | generateEmptyNdef = true; |
| 834 | } |
| 835 | |
| 836 | if (generateEmptyNdef) { |
| 837 | ndefMsg = null; |
| 838 | addNdefTechnology(null, |
nxpandroid | 6fd9cdb | 2017-07-12 18:25:41 +0530 | [diff] [blame] | 839 | getConnectedHandle(), |
| 840 | getConnectedLibNfcType(), |
| 841 | getConnectedTechnology(), |
| 842 | supportedNdefLength, cardState); |
nxpandroid | 5d64ce9 | 2016-11-18 19:48:53 +0530 | [diff] [blame] | 843 | foundFormattable = false; |
nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 844 | reconnect(); |
| 845 | } |
| 846 | break; |
| 847 | } |
| 848 | |
| 849 | if (ndefMsg == null && foundFormattable) { |
| 850 | // Tag is not NDEF yet, and found a formattable target, |
| 851 | // so add formattable tech to tech list. |
| 852 | addNdefFormatableTechnology( |
| 853 | formattableHandle, |
| 854 | formattableLibNfcType); |
| 855 | } |
| 856 | |
| 857 | return ndefMsg; |
| 858 | } |
| 859 | } |