OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 package org.chromium.device.nfc; |
| 6 |
| 7 import android.Manifest; |
| 8 import android.app.Activity; |
| 9 import android.app.PendingIntent; |
| 10 import android.content.BroadcastReceiver; |
| 11 import android.content.Context; |
| 12 import android.content.Intent; |
| 13 import android.content.IntentFilter; |
| 14 import android.content.IntentFilter.MalformedMimeTypeException; |
| 15 import android.content.pm.PackageManager; |
| 16 import android.nfc.FormatException; |
| 17 import android.nfc.NdefMessage; |
| 18 import android.nfc.NdefRecord; |
| 19 import android.nfc.NfcAdapter; |
| 20 import android.nfc.NfcManager; |
| 21 import android.nfc.Tag; |
| 22 import android.nfc.TagLostException; |
| 23 import android.nfc.tech.Ndef; |
| 24 import android.nfc.tech.NdefFormatable; |
| 25 import android.nfc.tech.TagTechnology; |
| 26 import android.os.Build; |
| 27 import android.os.Process; |
| 28 import android.support.annotation.Nullable; |
| 29 import android.support.v4.content.LocalBroadcastManager; |
| 30 |
| 31 import org.chromium.base.Log; |
| 32 import org.chromium.mojo.bindings.Callbacks; |
| 33 import org.chromium.mojo.system.MojoException; |
| 34 import org.chromium.mojom.device.Nfc; |
| 35 import org.chromium.mojom.device.NfcError; |
| 36 import org.chromium.mojom.device.NfcErrorType; |
| 37 import org.chromium.mojom.device.NfcMessage; |
| 38 import org.chromium.mojom.device.NfcPushOptions; |
| 39 import org.chromium.mojom.device.NfcPushTarget; |
| 40 import org.chromium.mojom.device.NfcRecord; |
| 41 import org.chromium.mojom.device.NfcRecordType; |
| 42 import org.chromium.mojom.device.NfcWatchOptions; |
| 43 |
| 44 import java.io.IOException; |
| 45 import java.io.UnsupportedEncodingException; |
| 46 import java.util.ArrayList; |
| 47 import java.util.List; |
| 48 |
| 49 /** |
| 50 * Android implementation of the NFC mojo service defined in |
| 51 * device/nfc/nfc.mojom. |
| 52 */ |
| 53 public class NfcImpl extends BroadcastReceiver implements Nfc { |
| 54 private static final String TAG = "NfcImpl"; |
| 55 private static final String DOMAIN = "w3.org"; |
| 56 private static final String TYPE = "webnfc"; |
| 57 private static final String TEXT_MIME = "text/plain"; |
| 58 private static final String JSON_MIME = "application/json"; |
| 59 private static final String OPAQUE_MIME = "application/octet-stream"; |
| 60 private static final String CHARSET_UTF8 = ";charset=UTF-8"; |
| 61 private static final String CHARSET_UTF16 = ";charset=UTF-16"; |
| 62 |
| 63 /** |
| 64 * Used to get instance of NFC adapter, @see android.nfc.NfcManager |
| 65 */ |
| 66 private final NfcManager mNfcManager; |
| 67 |
| 68 /** |
| 69 * NFC adapter. @see android.nfc.NfcAdapter |
| 70 */ |
| 71 private final NfcAdapter mNfcAdapter; |
| 72 |
| 73 /** |
| 74 * Activity object that is requred to enable / disable NFC reader mode opera
tions. |
| 75 */ |
| 76 private final Activity mActivity; |
| 77 |
| 78 /** |
| 79 * Flag that indicates whether NFC permission is granted. |
| 80 */ |
| 81 private final boolean mHasPermission; |
| 82 |
| 83 /** |
| 84 * Object that contains data that was passed to method |
| 85 * #push(NfcMessage message, NfcPushOptions options, PushResponse callback) |
| 86 * @see PendingPushOperation |
| 87 */ |
| 88 private PendingPushOperation mPendingPushOperation; |
| 89 |
| 90 /** |
| 91 * Utility that provides I/O operations for a Tag. Created on demand when |
| 92 * Tag is found. @see NfcTagWriter |
| 93 */ |
| 94 private NfcTagWriter mTagWriter; |
| 95 |
| 96 /** |
| 97 * Pending intent that is used for foreground dispatch. |
| 98 * @see android.nfc.NfcAdapter.enableForegroundDispatch |
| 99 */ |
| 100 private PendingIntent mPendingIntent; |
| 101 |
| 102 public NfcImpl(Activity activity) { |
| 103 mActivity = activity; |
| 104 |
| 105 if (mActivity != null) { |
| 106 int permission = mActivity.checkPermission( |
| 107 Manifest.permission.NFC, Process.myPid(), Process.myUid()); |
| 108 mHasPermission = permission == PackageManager.PERMISSION_GRANTED; |
| 109 } else { |
| 110 mHasPermission = false; |
| 111 } |
| 112 |
| 113 if (mHasPermission) { |
| 114 mNfcManager = (NfcManager) mActivity.getSystemService(Context.NFC_SE
RVICE); |
| 115 if (mNfcManager != null) { |
| 116 mNfcAdapter = mNfcManager.getDefaultAdapter(); |
| 117 } else { |
| 118 Log.w(TAG, "NFC is not supported."); |
| 119 mNfcAdapter = null; |
| 120 } |
| 121 } else { |
| 122 Log.w(TAG, "NFC operations are not permitted."); |
| 123 mNfcAdapter = null; |
| 124 mNfcManager = null; |
| 125 } |
| 126 } |
| 127 |
| 128 /** |
| 129 * Pushes NfcMessage to Tag or Peer, whenever NFC device is in proximity. |
| 130 * At the moment, only passive NFC devices are supported (NfcPushTarget.TAG)
. |
| 131 * |
| 132 * @param message that should be pushed to NFC device. |
| 133 * @param options that contain information about timeout and target device t
ype. |
| 134 * @param callback that is used to notify when push operation is completed. |
| 135 */ |
| 136 @Override |
| 137 public void push(NfcMessage message, NfcPushOptions options, PushResponse ca
llback) { |
| 138 if (!checkIfReady(callback)) return; |
| 139 |
| 140 if (options.target == NfcPushTarget.PEER) { |
| 141 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
| 142 return; |
| 143 } |
| 144 |
| 145 // If previous pending push operation is not completed, subsequent call |
| 146 // should cancel pending operation. |
| 147 if (mPendingPushOperation != null) { |
| 148 mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CA
NCELLED)); |
| 149 } |
| 150 |
| 151 mPendingPushOperation = new PendingPushOperation(message, options, callb
ack); |
| 152 enableForegroundDispatch(); |
| 153 processPendingPushOperation(); |
| 154 } |
| 155 |
| 156 /** |
| 157 * Cancels pending push operation. |
| 158 * At the moment, only passive NFC devices are supported (NfcPushTarget.TAG)
. |
| 159 * |
| 160 * @param target @see NfcPushTarget |
| 161 * @param callback that is used to notify caller when cancelPush() is comple
ted. |
| 162 */ |
| 163 @Override |
| 164 public void cancelPush(int target, CancelPushResponse callback) { |
| 165 if (!checkIfReady(callback)) return; |
| 166 |
| 167 if (target == NfcPushTarget.PEER) { |
| 168 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
| 169 return; |
| 170 } |
| 171 |
| 172 if (mPendingPushOperation != null) { |
| 173 mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CA
NCELLED)); |
| 174 mPendingPushOperation = null; |
| 175 callback.call(null); |
| 176 disableForegroundDispatch(); |
| 177 } else { |
| 178 callback.call(createError(NfcErrorType.NOT_FOUND)); |
| 179 } |
| 180 } |
| 181 |
| 182 /** |
| 183 * Watch method allows to set filtering criteria for NfcMessages that are |
| 184 * found when NFC device is within proximity. On success, watch ID is |
| 185 * returned to caller through WatchResponse callback. When NfcMessage that |
| 186 * matches NfcWatchOptions is found, it is returned through |
| 187 * SubscribeToWatchEventsResponse callback interface. |
| 188 * @see SubscribeToWatchEventsResponse |
| 189 * |
| 190 * @param options used to filter NfcMessages, @see NfcWatchOptions. |
| 191 * @param callback that is used to notify caller when watch() is completed a
nd return watch ID. |
| 192 */ |
| 193 @Override |
| 194 public void watch(NfcWatchOptions options, WatchResponse callback) { |
| 195 if (!checkIfReady(callback)) return; |
| 196 // TODO(shalamov): Not implemented. |
| 197 callback.call(0, createError(NfcErrorType.NOT_SUPPORTED)); |
| 198 } |
| 199 |
| 200 /** |
| 201 * Cancels NFC watch operation. |
| 202 * |
| 203 * @param id of watch operation. |
| 204 * @param callback that is used to notify caller when cancelWatch() is compl
eted. |
| 205 */ |
| 206 @Override |
| 207 public void cancelWatch(int id, CancelWatchResponse callback) { |
| 208 if (!checkIfReady(callback)) return; |
| 209 // TODO(shalamov): Not implemented. |
| 210 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
| 211 } |
| 212 |
| 213 /** |
| 214 * Cancels all NFC watch operations. |
| 215 * |
| 216 * @param callback that is used to notify caller when cancelAllWatches() is
completed. |
| 217 */ |
| 218 @Override |
| 219 public void cancelAllWatches(CancelAllWatchesResponse callback) { |
| 220 if (!checkIfReady(callback)) return; |
| 221 // TODO(shalamov): Not implemented. |
| 222 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); |
| 223 } |
| 224 |
| 225 @Override |
| 226 public void subscribeToWatchEvents(SubscribeToWatchEventsResponse callback)
{ |
| 227 // TODO(shalamov): Not implemented. |
| 228 } |
| 229 |
| 230 /** |
| 231 * Suspends all pending watch / push operations. Should be called when web |
| 232 * page visibility is lost. |
| 233 */ |
| 234 @Override |
| 235 public void suspendNfcOperations() { |
| 236 disableForegroundDispatch(); |
| 237 } |
| 238 |
| 239 /** |
| 240 * Resumes all pending watch / push operations. Should be called when web |
| 241 * page becomes visible. |
| 242 */ |
| 243 @Override |
| 244 public void resumeNfcOperations() { |
| 245 if (mPendingPushOperation != null) enableForegroundDispatch(); |
| 246 } |
| 247 |
| 248 @Override |
| 249 public void close() { |
| 250 disableForegroundDispatch(); |
| 251 } |
| 252 |
| 253 @Override |
| 254 public void onConnectionError(MojoException e) { |
| 255 close(); |
| 256 } |
| 257 |
| 258 @Override |
| 259 public void onReceive(Context context, Intent intent) { |
| 260 Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); |
| 261 if (tag != null) onTagDiscovered(tag); |
| 262 } |
| 263 |
| 264 /** |
| 265 * Holds information about pending push operation. |
| 266 */ |
| 267 private static class PendingPushOperation { |
| 268 private final NfcMessage mNfcMessage; |
| 269 private final NfcPushOptions mNfcPushOptions; |
| 270 private final PushResponse mPushResponseCallback; |
| 271 |
| 272 public PendingPushOperation( |
| 273 NfcMessage message, NfcPushOptions options, PushResponse callbac
k) { |
| 274 mNfcMessage = message; |
| 275 mNfcPushOptions = options; |
| 276 mPushResponseCallback = callback; |
| 277 } |
| 278 |
| 279 /** |
| 280 * Completes pending push operation. |
| 281 * |
| 282 * @param error should be null when operation is completed successfully, |
| 283 * otherwise, error object with corresponding NfcErrorType must be provi
ded. |
| 284 */ |
| 285 public void complete(NfcError error) { |
| 286 if (mPushResponseCallback != null) mPushResponseCallback.call(error)
; |
| 287 } |
| 288 |
| 289 public NfcMessage message() { |
| 290 return mNfcMessage; |
| 291 } |
| 292 public NfcPushOptions pushOptions() { |
| 293 return mNfcPushOptions; |
| 294 } |
| 295 } |
| 296 |
| 297 /** |
| 298 * Helper method that creates NfcError object from NfcErrorType. |
| 299 * |
| 300 * @param errorType @see NfcErrorType. |
| 301 * @return NfcError |
| 302 * @see NfcError |
| 303 */ |
| 304 private NfcError createError(int errorType) { |
| 305 NfcError error = new NfcError(); |
| 306 error.errorType = errorType; |
| 307 return error; |
| 308 } |
| 309 |
| 310 /** |
| 311 * Checks if NFC funcionality can be used by the mojo service. |
| 312 * If permission to use NFC is granted and hardware is enabled, returns null
. |
| 313 * |
| 314 * @return NfcError |
| 315 */ |
| 316 @Nullable |
| 317 private NfcError checkIfReady() { |
| 318 if (!mHasPermission) { |
| 319 return createError(NfcErrorType.SECURITY); |
| 320 } else if (mNfcManager == null || mNfcAdapter == null |
| 321 || Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD_MR1)
{ |
| 322 return createError(NfcErrorType.NOT_SUPPORTED); |
| 323 } else if (!mNfcAdapter.isEnabled()) { |
| 324 return createError(NfcErrorType.DEVICE_DISABLED); |
| 325 } |
| 326 return null; |
| 327 } |
| 328 |
| 329 /** |
| 330 * Uses checkIfReady() method and if NFC functionality cannot be used, |
| 331 * calls mojo callback with NfcError. |
| 332 * |
| 333 * @param WatchResponse Callback that is provided to watch() method. |
| 334 * @return boolean true if NFC functionality can be used, false otherwise. |
| 335 */ |
| 336 private boolean checkIfReady(WatchResponse callback) { |
| 337 NfcError error = checkIfReady(); |
| 338 if (error == null) return true; |
| 339 callback.call(0, error); |
| 340 return false; |
| 341 } |
| 342 |
| 343 /** |
| 344 * Uses checkIfReady() method and if NFC functionality cannot be used, |
| 345 * calls mojo callback NfcError. |
| 346 * |
| 347 * @param callback Generic callback that is provided to push(), cancelPush()
, |
| 348 * cancelWatch() and cancelAllWatches() methods. |
| 349 * @return boolean true if NFC functionality can be used, false otherwise. |
| 350 */ |
| 351 private boolean checkIfReady(Callbacks.Callback1<NfcError> callback) { |
| 352 NfcError error = checkIfReady(); |
| 353 if (error == null) return true; |
| 354 callback.call(error); |
| 355 return false; |
| 356 } |
| 357 |
| 358 /** |
| 359 * Enables foreground dispatch. |
| 360 */ |
| 361 private void enableForegroundDispatch() { |
| 362 if (mPendingIntent == null) { |
| 363 mPendingIntent = PendingIntent.getActivity(mActivity, 0, |
| 364 new Intent(mActivity, mActivity.getClass()) |
| 365 .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), |
| 366 0); |
| 367 |
| 368 List<IntentFilter> filters = new ArrayList<IntentFilter>(); |
| 369 IntentFilter tag_filter = new IntentFilter(NfcAdapter.ACTION_TAG_DIS
COVERED); |
| 370 IntentFilter ndef_filter = new IntentFilter(NfcAdapter.ACTION_NDEF_D
ISCOVERED); |
| 371 filters.add(tag_filter); |
| 372 |
| 373 try { |
| 374 ndef_filter.addDataType(TEXT_MIME); |
| 375 ndef_filter.addDataType(JSON_MIME); |
| 376 ndef_filter.addDataType(OPAQUE_MIME); |
| 377 filters.add(ndef_filter); |
| 378 } catch (MalformedMimeTypeException e) { |
| 379 Log.w(TAG, "Invalid MIME type was provided to Intent filter."); |
| 380 ndef_filter = null; |
| 381 } |
| 382 |
| 383 IntentFilter[] filterArray = new IntentFilter[filters.size()]; |
| 384 filters.toArray(filterArray); |
| 385 |
| 386 String[][] techLists = new String[][] { |
| 387 new String[] {Ndef.class.getName(), NdefFormatable.class.get
Name()}}; |
| 388 |
| 389 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(mA
ctivity); |
| 390 manager.registerReceiver(this, tag_filter); |
| 391 if (ndef_filter != null) manager.registerReceiver(this, ndef_filter)
; |
| 392 mNfcAdapter.enableForegroundDispatch(mActivity, mPendingIntent, filt
erArray, techLists); |
| 393 } |
| 394 } |
| 395 |
| 396 /** |
| 397 * Disables foreground dispatch. |
| 398 */ |
| 399 private void disableForegroundDispatch() { |
| 400 if (mPendingIntent != null) { |
| 401 mNfcAdapter.disableForegroundDispatch(mActivity); |
| 402 LocalBroadcastManager manager = LocalBroadcastManager.getInstance(mA
ctivity); |
| 403 manager.unregisterReceiver(this); |
| 404 mPendingIntent = null; |
| 405 } |
| 406 } |
| 407 |
| 408 /** |
| 409 * NdefFormatable and Ndef interfaces have different signatures for writing |
| 410 * NdefMessage to a tag. This interface provides generic write method. |
| 411 */ |
| 412 private interface TagTechnologyWriter { |
| 413 public void write(NdefMessage message) |
| 414 throws IOException, TagLostException, FormatException; |
| 415 } |
| 416 |
| 417 /** |
| 418 * Implementation of TagTechnologyWriter that can write NdefMessage to NFC t
ag. |
| 419 */ |
| 420 private static class NdefWriter implements TagTechnologyWriter { |
| 421 private final Ndef mNdef; |
| 422 |
| 423 NdefWriter(Ndef ndef) { |
| 424 mNdef = ndef; |
| 425 } |
| 426 |
| 427 public void write(NdefMessage message) |
| 428 throws IOException, TagLostException, FormatException { |
| 429 mNdef.writeNdefMessage(message); |
| 430 } |
| 431 } |
| 432 |
| 433 /** |
| 434 * Implementation of TagTechnologyWriter that can format empty NFC tag |
| 435 * with provided NFCMessage. |
| 436 */ |
| 437 private static class NdefFormattableWriter implements TagTechnologyWriter { |
| 438 private final NdefFormatable mNdefFormattable; |
| 439 |
| 440 NdefFormattableWriter(NdefFormatable ndefFormattable) { |
| 441 mNdefFormattable = ndefFormattable; |
| 442 } |
| 443 |
| 444 public void write(NdefMessage message) |
| 445 throws IOException, TagLostException, FormatException { |
| 446 mNdefFormattable.format(message); |
| 447 } |
| 448 } |
| 449 |
| 450 /** |
| 451 * Utility class that holds TagTechnology and TagTechnologyWriter objects. |
| 452 * Provides connectivity and I/O related operations for NFC tag. |
| 453 */ |
| 454 private static class NfcTagWriter { |
| 455 private final TagTechnology mTech; |
| 456 private final TagTechnologyWriter mTechWriter; |
| 457 private boolean mWasConnected = false; |
| 458 |
| 459 /** |
| 460 * Factory method that creates NfcTagWriter with TagTechnologyWriter |
| 461 * appropriate for a given NFC Tag. |
| 462 * |
| 463 * @param tag @see android.nfc.Tag |
| 464 * @return NfcTagWriter or null when unsupported Tag is provided. |
| 465 */ |
| 466 public static NfcTagWriter create(Tag tag) { |
| 467 if (tag == null) return null; |
| 468 |
| 469 Ndef ndef = Ndef.get(tag); |
| 470 if (ndef != null) return new NfcTagWriter(ndef, new NdefWriter(ndef)
); |
| 471 |
| 472 NdefFormatable formattable = NdefFormatable.get(tag); |
| 473 if (formattable != null) { |
| 474 return new NfcTagWriter(formattable, new NdefFormattableWriter(f
ormattable)); |
| 475 } |
| 476 |
| 477 return null; |
| 478 } |
| 479 |
| 480 private NfcTagWriter(TagTechnology tech, TagTechnologyWriter writer) { |
| 481 mTech = tech; |
| 482 mTechWriter = writer; |
| 483 } |
| 484 |
| 485 /** |
| 486 * Connects to NFC tag. |
| 487 */ |
| 488 public void connect() throws IOException, TagLostException { |
| 489 if (!mTech.isConnected()) { |
| 490 mTech.connect(); |
| 491 mWasConnected = true; |
| 492 } |
| 493 } |
| 494 |
| 495 /** |
| 496 * Closes connection. |
| 497 */ |
| 498 public void close() throws IOException { |
| 499 mTech.close(); |
| 500 } |
| 501 |
| 502 /** |
| 503 * Writes NdefMessage to NFC tag. |
| 504 * |
| 505 * @param message @see android.nfc.NdefMessage |
| 506 */ |
| 507 public void write(NdefMessage message) |
| 508 throws IOException, TagLostException, FormatException { |
| 509 mTechWriter.write(message); |
| 510 } |
| 511 |
| 512 /** |
| 513 * If tag was previously connected and subsequent connection to the same |
| 514 * tag fails, consider tag to be out of ragne. |
| 515 */ |
| 516 public boolean isTagOutOfRange() { |
| 517 try { |
| 518 connect(); |
| 519 } catch (IOException e) { |
| 520 return mWasConnected; |
| 521 } |
| 522 return false; |
| 523 } |
| 524 } |
| 525 |
| 526 /** |
| 527 * Exception that is raised when mojo NfcMessage cannot be coverted to NdefM
essage. |
| 528 */ |
| 529 private static class InvalidMessageException extends Exception {} |
| 530 |
| 531 /** |
| 532 * Converts mojo NfcMessage to android.nfc.NdefMessage. |
| 533 * |
| 534 * @param message mojo NfcMessage |
| 535 * @return NdefMessage |
| 536 * @see android.nfc.NdefMessage |
| 537 */ |
| 538 private NdefMessage toNdefMessage(NfcMessage message) throws InvalidMessageE
xception { |
| 539 if (message == null || message.data.length == 0) throw new InvalidMessag
eException(); |
| 540 |
| 541 try { |
| 542 List<NdefRecord> records = new ArrayList<NdefRecord>(); |
| 543 for (NfcRecord record : message.data) { |
| 544 records.add(toNdefRecord(record)); |
| 545 } |
| 546 records.add(NdefRecord.createExternal(DOMAIN, TYPE, message.url.getB
ytes())); |
| 547 NdefRecord[] ndefRecords = new NdefRecord[records.size()]; |
| 548 records.toArray(ndefRecords); |
| 549 return new NdefMessage(ndefRecords); |
| 550 } catch (UnsupportedEncodingException | InvalidMessageException |
| 551 | IllegalArgumentException e) { |
| 552 throw new InvalidMessageException(); |
| 553 } |
| 554 } |
| 555 |
| 556 /** |
| 557 * Returns charset of mojo NfcRecord. Only applicable for URL and TEXT recor
ds. |
| 558 * If charset cannot be determined, UTF-8 charset is used by default. |
| 559 * |
| 560 * @param record |
| 561 * @return String |
| 562 */ |
| 563 private String getCharset(NfcRecord record) { |
| 564 if (record.mediaType.endsWith(CHARSET_UTF8)) return "UTF-8"; |
| 565 |
| 566 if (record.mediaType.endsWith(CHARSET_UTF16)) return "UTF-16LE"; |
| 567 |
| 568 Log.w(TAG, "Unknown charset, defaulting to UTF-8."); |
| 569 return "UTF-8"; |
| 570 } |
| 571 |
| 572 /** |
| 573 * Converts mojo NfcRecord to android.nfc.NdefRecord. |
| 574 * |
| 575 * @param record mojo NfcRecord |
| 576 * @return NdefRecord |
| 577 * @see android.nfc.NdefRecord |
| 578 */ |
| 579 private NdefRecord toNdefRecord(NfcRecord record) |
| 580 throws InvalidMessageException, IllegalArgumentException, Unsupporte
dEncodingException { |
| 581 switch (record.recordType) { |
| 582 case NfcRecordType.URL: |
| 583 return NdefRecord.createUri(new String(record.data, getCharset(r
ecord))); |
| 584 case NfcRecordType.TEXT: |
| 585 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
| 586 return NdefRecord.createTextRecord( |
| 587 "en-US", new String(record.data, getCharset(record))
); |
| 588 } else { |
| 589 return NdefRecord.createMime(TEXT_MIME, record.data); |
| 590 } |
| 591 case NfcRecordType.JSON: |
| 592 case NfcRecordType.OPAQUE_RECORD: |
| 593 return NdefRecord.createMime(record.mediaType, record.data); |
| 594 default: |
| 595 throw new InvalidMessageException(); |
| 596 } |
| 597 } |
| 598 |
| 599 /** |
| 600 * Completes pending push operation. On error, invalidates #mTagWriter. |
| 601 * |
| 602 * @param error |
| 603 */ |
| 604 private void pendingPushOperationCompleted(NfcError error) { |
| 605 if (mPendingPushOperation != null) { |
| 606 mPendingPushOperation.complete(error); |
| 607 mPendingPushOperation = null; |
| 608 } |
| 609 |
| 610 if (error != null) mTagWriter = null; |
| 611 } |
| 612 |
| 613 /** |
| 614 * Checks whether there is a #mPendingPushOperation and writes data to NFC t
ag. |
| 615 * In case of exception calls pendingPushOperationCompleted() with appropria
te |
| 616 * error object. |
| 617 */ |
| 618 private void processPendingPushOperation() { |
| 619 if (mTagWriter == null || mPendingPushOperation == null) return; |
| 620 |
| 621 if (mTagWriter.isTagOutOfRange()) { |
| 622 mTagWriter = null; |
| 623 return; |
| 624 } |
| 625 |
| 626 try { |
| 627 mTagWriter.connect(); |
| 628 mTagWriter.write(toNdefMessage(mPendingPushOperation.message())); |
| 629 pendingPushOperationCompleted(null); |
| 630 mTagWriter.close(); |
| 631 } catch (InvalidMessageException e) { |
| 632 Log.w(TAG, "Cannot write data to NFC tag. Invalid NfcMessage."); |
| 633 pendingPushOperationCompleted(createError(NfcErrorType.INVALID_MESSA
GE)); |
| 634 } catch (TagLostException e) { |
| 635 Log.w(TAG, "Cannot write data to NFC tag. Tag is lost."); |
| 636 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); |
| 637 } catch (FormatException | IOException e) { |
| 638 Log.w(TAG, "Cannot write data to NFC tag. IO_ERROR."); |
| 639 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); |
| 640 } |
| 641 } |
| 642 |
| 643 /** |
| 644 * Called by ReaderCallbackHandler when NFC tag is in proximity. |
| 645 * calls processPendingPushOperation() that will write data to a tag. |
| 646 * |
| 647 * @param tag |
| 648 */ |
| 649 private void onTagDiscovered(Tag tag) { |
| 650 mTagWriter = NfcTagWriter.create(tag); |
| 651 processPendingPushOperation(); |
| 652 } |
| 653 } |
OLD | NEW |