Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
|
timvolodine
2016/04/26 14:58:27
2015 -> 2016
shalamov
2016/04/26 21:56:52
Done.
| |
| 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.annotation.TargetApi; | |
| 9 import android.app.Activity; | |
| 10 import android.content.Context; | |
| 11 import android.content.pm.PackageManager; | |
| 12 import android.nfc.FormatException; | |
| 13 import android.nfc.NdefMessage; | |
| 14 import android.nfc.NdefRecord; | |
| 15 import android.nfc.NfcAdapter; | |
| 16 import android.nfc.NfcAdapter.ReaderCallback; | |
| 17 import android.nfc.NfcManager; | |
| 18 import android.nfc.Tag; | |
| 19 import android.nfc.TagLostException; | |
| 20 import android.nfc.tech.Ndef; | |
| 21 import android.nfc.tech.NdefFormatable; | |
| 22 import android.nfc.tech.TagTechnology; | |
| 23 import android.os.Build; | |
| 24 import android.os.Process; | |
| 25 import android.support.annotation.Nullable; | |
| 26 | |
| 27 import org.chromium.base.ApplicationStatus; | |
| 28 import org.chromium.base.Log; | |
| 29 import org.chromium.mojo.bindings.Callbacks; | |
| 30 import org.chromium.mojo.system.MojoException; | |
| 31 import org.chromium.mojom.device.Nfc; | |
| 32 import org.chromium.mojom.device.NfcClient; | |
| 33 import org.chromium.mojom.device.NfcError; | |
| 34 import org.chromium.mojom.device.NfcErrorType; | |
| 35 import org.chromium.mojom.device.NfcMessage; | |
| 36 import org.chromium.mojom.device.NfcPushOptions; | |
| 37 import org.chromium.mojom.device.NfcPushTarget; | |
| 38 import org.chromium.mojom.device.NfcRecord; | |
| 39 import org.chromium.mojom.device.NfcRecordType; | |
| 40 import org.chromium.mojom.device.NfcWatchOptions; | |
| 41 | |
| 42 import java.io.IOException; | |
| 43 import java.io.UnsupportedEncodingException; | |
| 44 import java.util.ArrayList; | |
| 45 import java.util.List; | |
| 46 | |
| 47 /** | |
| 48 * Android implementation of the NFC mojo service defined in | |
| 49 * device/nfc/nfc.mojom. | |
| 50 */ | |
| 51 public class NfcImpl implements Nfc { | |
| 52 private static final String TAG = "NfcImpl"; | |
| 53 private static final String DOMAIN = "w3.org"; | |
| 54 private static final String TYPE = "webnfc"; | |
| 55 private static final String TEXT_MIME = "text/plain"; | |
| 56 private static final String CHARSET_UTF8 = ";charset=UTF-8"; | |
| 57 private static final String CHARSET_UTF16 = ";charset=UTF-16"; | |
| 58 | |
| 59 private NfcManager mNfcManager; | |
|
timvolodine
2016/04/26 14:58:27
final?
shalamov
2016/04/26 21:56:53
Done.
| |
| 60 private NfcAdapter mNfcAdapter; | |
|
timvolodine
2016/04/26 14:58:27
final?
shalamov
2016/04/26 21:56:53
Done.
| |
| 61 private final Context mContext; | |
| 62 private Activity mActivity; | |
| 63 private boolean mHasPermission; | |
|
timvolodine
2016/04/26 14:58:26
final? same for mAcitivy above
shalamov
2016/04/26 21:56:53
Done.
| |
| 64 private ReaderCallbackHandler mReaderCallbackHandler; | |
| 65 private PendingPushOperation mPendingPushOperation; | |
| 66 private NfcTagWriter mTagWriter; | |
|
ncarter (slow)
2016/04/25 20:03:14
There are very few comments in this file overall.
timvolodine
2016/04/26 14:58:26
+1, bit more documentation would be great
shalamov
2016/04/26 21:56:53
Done.
shalamov
2016/04/26 21:56:53
Done.
| |
| 67 | |
| 68 public NfcImpl(Context context) { | |
| 69 mContext = context; | |
| 70 int permission = | |
| 71 context.checkPermission(Manifest.permission.NFC, Process.myPid() , Process.myUid()); | |
| 72 mHasPermission = permission == PackageManager.PERMISSION_GRANTED; | |
| 73 if (mHasPermission) { | |
| 74 mActivity = ApplicationStatus.getLastTrackedFocusedActivity(); | |
| 75 mNfcManager = (NfcManager) mContext.getSystemService(Context.NFC_SER VICE); | |
| 76 if (mNfcManager == null) { | |
| 77 Log.w(TAG, "NFC is not supported."); | |
| 78 } else { | |
| 79 mNfcAdapter = mNfcManager.getDefaultAdapter(); | |
| 80 } | |
| 81 } else { | |
| 82 Log.w(TAG, "NFC operations are not permitted."); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 // Public methods | |
|
ncarter (slow)
2016/04/25 20:03:14
I don't think this is necessary.
shalamov
2016/04/26 21:56:52
Done.
| |
| 87 @Override | |
| 88 public void setClient(NfcClient client) {} | |
| 89 | |
| 90 @Override | |
| 91 public void push(NfcMessage message, NfcPushOptions options, PushResponse ca llback) { | |
| 92 if (!checkIfReady(callback)) return; | |
| 93 | |
| 94 if (options.target == NfcPushTarget.PEER) { | |
| 95 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); | |
| 96 return; | |
| 97 } | |
| 98 | |
| 99 if (mPendingPushOperation != null) { | |
| 100 mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CA NCELLED)); | |
| 101 } | |
| 102 | |
| 103 mPendingPushOperation = new PendingPushOperation(message, options, callb ack); | |
| 104 enableReaderMode(); | |
| 105 processPendingPushOperation(); | |
| 106 } | |
| 107 | |
| 108 @Override | |
| 109 public void cancelPush(int target, CancelPushResponse callback) { | |
| 110 if (!checkIfReady(callback)) return; | |
| 111 | |
| 112 if (target == NfcPushTarget.PEER) { | |
| 113 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); | |
| 114 return; | |
| 115 } | |
| 116 | |
| 117 if (mPendingPushOperation != null) { | |
| 118 mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CA NCELLED)); | |
| 119 mPendingPushOperation = null; | |
| 120 callback.call(null); | |
| 121 } else { | |
| 122 callback.call(createError(NfcErrorType.NOT_FOUND)); | |
| 123 } | |
| 124 } | |
| 125 | |
| 126 @Override | |
| 127 public void watch(NfcWatchOptions options, WatchResponse callback) { | |
| 128 if (!checkIfReady(callback)) return; | |
|
timvolodine
2016/04/26 14:58:27
should this have a TODO? also below for cancelWatc
shalamov
2016/04/26 21:56:53
Done.
| |
| 129 } | |
| 130 | |
| 131 @Override | |
| 132 public void cancelWatch(int id, CancelWatchResponse callback) { | |
| 133 if (!checkIfReady(callback)) return; | |
| 134 } | |
| 135 | |
| 136 @Override | |
| 137 public void cancelAllWatches(CancelAllWatchesResponse callback) { | |
| 138 if (!checkIfReady(callback)) return; | |
| 139 } | |
| 140 | |
| 141 @Override | |
| 142 public void suspendNfcOperations() {} | |
|
timvolodine
2016/04/26 14:58:27
is this intentionally empty or is this a also TODO
shalamov
2016/04/26 21:56:52
Done.
| |
| 143 | |
| 144 @Override | |
| 145 public void resumeNfcOperations() {} | |
| 146 | |
| 147 @Override | |
| 148 public void close() {} | |
| 149 | |
| 150 @Override | |
| 151 public void onConnectionError(MojoException e) {} | |
| 152 | |
| 153 // Implementation | |
|
ncarter (slow)
2016/04/25 20:03:14
Class comment for PendingPushOperation? Can be a o
shalamov
2016/04/26 21:56:52
Done.
| |
| 154 private static class PendingPushOperation { | |
| 155 private final NfcMessage mNfcMessage; | |
| 156 private final NfcPushOptions mNfcPushOptions; | |
| 157 private final PushResponse mPushResponseCallback; | |
| 158 | |
| 159 public PendingPushOperation( | |
| 160 NfcMessage message, NfcPushOptions options, PushResponse callbac k) { | |
| 161 mNfcMessage = message; | |
| 162 mNfcPushOptions = options; | |
| 163 mPushResponseCallback = callback; | |
| 164 } | |
| 165 | |
| 166 public void complete(NfcError error) { | |
| 167 if (mPushResponseCallback != null) mPushResponseCallback.call(error) ; | |
| 168 } | |
| 169 | |
| 170 public NfcMessage message() { | |
| 171 return mNfcMessage; | |
| 172 } | |
| 173 public NfcPushOptions pushOptions() { | |
| 174 return mNfcPushOptions; | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 private NfcError createError(int errorType) { | |
| 179 NfcError error = new NfcError(); | |
| 180 error.errorType = errorType; | |
| 181 return error; | |
| 182 } | |
| 183 | |
| 184 @Nullable | |
| 185 private NfcError checkIfReady() { | |
| 186 if (!mHasPermission) { | |
| 187 return createError(NfcErrorType.SECURITY); | |
| 188 } else if (mNfcManager == null || mNfcAdapter == null | |
| 189 || Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { | |
| 190 return createError(NfcErrorType.NOT_SUPPORTED); | |
| 191 } else if (!mNfcAdapter.isEnabled()) { | |
| 192 return createError(NfcErrorType.DEVICE_DISABLED); | |
| 193 } | |
| 194 return null; | |
| 195 } | |
| 196 | |
| 197 private boolean checkIfReady(Callbacks.Callback2<Integer, NfcError> callback ) { | |
| 198 NfcError error = checkIfReady(); | |
| 199 if (error == null) return true; | |
| 200 callback.call(0, error); | |
| 201 return false; | |
| 202 } | |
| 203 | |
| 204 private boolean checkIfReady(Callbacks.Callback1<NfcError> callback) { | |
| 205 NfcError error = checkIfReady(); | |
| 206 if (error == null) return true; | |
| 207 callback.call(error); | |
| 208 return false; | |
| 209 } | |
| 210 | |
| 211 @TargetApi(Build.VERSION_CODES.KITKAT) | |
| 212 private static class ReaderCallbackHandler implements ReaderCallback { | |
| 213 private final NfcImpl mNfcImpl; | |
| 214 | |
| 215 public ReaderCallbackHandler(NfcImpl impl) { | |
| 216 mNfcImpl = impl; | |
| 217 } | |
| 218 | |
| 219 @Override | |
| 220 public void onTagDiscovered(Tag tag) { | |
| 221 mNfcImpl.onTagDiscovered(tag); | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 private void enableReaderMode() { | |
| 226 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; | |
| 227 if (mReaderCallbackHandler != null) return; | |
| 228 | |
| 229 mReaderCallbackHandler = new ReaderCallbackHandler(this); | |
| 230 mNfcAdapter.enableReaderMode(mActivity, mReaderCallbackHandler, | |
| 231 NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B | |
| 232 | NfcAdapter.FLAG_READER_NFC_F | NfcAdapter.FLAG_READER_ NFC_V, | |
| 233 null); | |
| 234 } | |
| 235 | |
| 236 private void disableReaderMode() { | |
| 237 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; | |
| 238 | |
| 239 mReaderCallbackHandler = null; | |
| 240 mNfcAdapter.disableReaderMode(mActivity); | |
| 241 } | |
| 242 | |
| 243 private interface TagTechnologyWriter { | |
| 244 public void write(NdefMessage message) | |
| 245 throws IOException, TagLostException, FormatException; | |
| 246 } | |
| 247 | |
| 248 private static class NdefWriter implements TagTechnologyWriter { | |
| 249 private final Ndef mNdef; | |
| 250 | |
| 251 NdefWriter(Ndef ndef) { | |
| 252 mNdef = ndef; | |
| 253 } | |
| 254 | |
| 255 public void write(NdefMessage message) | |
| 256 throws IOException, TagLostException, FormatException { | |
| 257 mNdef.writeNdefMessage(message); | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 private static class NdefFormattableWriter implements TagTechnologyWriter { | |
| 262 private final NdefFormatable mNdefFormattable; | |
| 263 | |
| 264 NdefFormattableWriter(NdefFormatable ndefFormattable) { | |
| 265 mNdefFormattable = ndefFormattable; | |
| 266 } | |
| 267 | |
| 268 public void write(NdefMessage message) | |
| 269 throws IOException, TagLostException, FormatException { | |
| 270 mNdefFormattable.format(message); | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 private static class NfcTagWriter { | |
| 275 private final TagTechnology mTech; | |
| 276 private final TagTechnologyWriter mTechWriter; | |
| 277 private boolean mWasConnected = false; | |
| 278 | |
| 279 public static NfcTagWriter create(Tag tag) { | |
| 280 if (tag == null) return null; | |
| 281 | |
| 282 Ndef ndef = Ndef.get(tag); | |
| 283 if (ndef != null) return new NfcTagWriter(ndef, new NdefWriter(ndef) ); | |
| 284 | |
| 285 NdefFormatable formattable = NdefFormatable.get(tag); | |
| 286 if (formattable != null) { | |
| 287 return new NfcTagWriter(formattable, new NdefFormattableWriter(f ormattable)); | |
| 288 } | |
| 289 | |
| 290 return null; | |
| 291 } | |
| 292 | |
| 293 private NfcTagWriter(TagTechnology tech, TagTechnologyWriter writer) { | |
| 294 mTech = tech; | |
| 295 mTechWriter = writer; | |
| 296 } | |
| 297 | |
| 298 public void connect() throws IOException, TagLostException { | |
| 299 if (!mTech.isConnected()) { | |
| 300 mTech.connect(); | |
| 301 mWasConnected = true; | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 public void close() throws IOException { | |
| 306 mTech.close(); | |
| 307 } | |
| 308 | |
| 309 public void write(NdefMessage message) | |
| 310 throws IOException, TagLostException, FormatException { | |
| 311 mTechWriter.write(message); | |
| 312 } | |
| 313 | |
| 314 public boolean isTagOutOfRange() { | |
| 315 try { | |
| 316 connect(); | |
| 317 } catch (IOException e) { | |
| 318 return mWasConnected; | |
| 319 } | |
| 320 return false; | |
| 321 } | |
| 322 } | |
| 323 | |
| 324 private static class InvalidMessageException extends Exception {} | |
| 325 | |
| 326 private NdefMessage toNdefMessage(NfcMessage message) throws InvalidMessageE xception { | |
| 327 if (message == null || message.data.length == 0) throw new InvalidMessag eException(); | |
| 328 | |
| 329 try { | |
| 330 List<NdefRecord> records = new ArrayList<NdefRecord>(); | |
| 331 for (NfcRecord record : message.data) { | |
| 332 records.add(toNdefRecord(record)); | |
| 333 } | |
| 334 records.add(NdefRecord.createExternal(DOMAIN, TYPE, message.url.getB ytes())); | |
| 335 NdefRecord[] ndefRecords = new NdefRecord[records.size()]; | |
| 336 records.toArray(ndefRecords); | |
| 337 return new NdefMessage(ndefRecords); | |
| 338 } catch (UnsupportedEncodingException | InvalidMessageException | |
| 339 | IllegalArgumentException e) { | |
| 340 throw new InvalidMessageException(); | |
| 341 } | |
| 342 } | |
| 343 | |
| 344 private String getCharset(NfcRecord record) { | |
| 345 if (record.mediaType.endsWith(CHARSET_UTF8)) return "UTF-8"; | |
| 346 | |
| 347 if (record.mediaType.endsWith(CHARSET_UTF16)) return "UTF-16LE"; | |
|
timvolodine
2016/04/26 14:58:27
why UTF-16LE? CHARSET_UTF16 doesn't specify LE.
shalamov
2016/04/26 21:56:53
When 16bit WTF::String data is converted to bytear
timvolodine
2016/04/28 17:06:34
yes utf-16be usually tends to be the default, mayb
shalamov
2016/05/11 14:09:57
Done.
| |
| 348 | |
| 349 Log.w(TAG, "Unknown charset, using UTF-8 by default."); | |
| 350 return "UTF-8"; | |
| 351 } | |
| 352 | |
| 353 private NdefRecord toNdefRecord(NfcRecord record) | |
| 354 throws InvalidMessageException, IllegalArgumentException, Unsupporte dEncodingException { | |
| 355 switch (record.recordType) { | |
| 356 case NfcRecordType.URL: | |
| 357 return NdefRecord.createUri(new String(record.data, getCharset(r ecord))); | |
| 358 case NfcRecordType.TEXT: | |
| 359 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { | |
| 360 return NdefRecord.createTextRecord( | |
| 361 "en-US", new String(record.data, getCharset(record)) ); | |
| 362 } else { | |
| 363 return NdefRecord.createMime(TEXT_MIME, record.data); | |
| 364 } | |
| 365 case NfcRecordType.JSON: | |
| 366 case NfcRecordType.OPAQUE_RECORD: | |
| 367 return NdefRecord.createMime(record.mediaType, record.data); | |
| 368 default: | |
| 369 throw new InvalidMessageException(); | |
| 370 } | |
| 371 } | |
| 372 | |
| 373 private void pendingPushOperationCompleted(NfcError error) { | |
| 374 if (mPendingPushOperation != null) { | |
| 375 mPendingPushOperation.complete(error); | |
| 376 mPendingPushOperation = null; | |
| 377 } | |
| 378 | |
| 379 if (error != null) mTagWriter = null; | |
| 380 } | |
| 381 | |
| 382 private void processPendingPushOperation() { | |
| 383 if (mTagWriter == null || mPendingPushOperation == null) return; | |
| 384 | |
| 385 if (mTagWriter.isTagOutOfRange()) { | |
| 386 mTagWriter = null; | |
| 387 return; | |
| 388 } | |
| 389 | |
| 390 try { | |
| 391 mTagWriter.connect(); | |
| 392 mTagWriter.write(toNdefMessage(mPendingPushOperation.message())); | |
| 393 pendingPushOperationCompleted(null); | |
| 394 mTagWriter.close(); | |
| 395 } catch (InvalidMessageException e) { | |
| 396 Log.w(TAG, "Cannot write data to NFC tag. Invalid NfcMessage."); | |
| 397 pendingPushOperationCompleted(createError(NfcErrorType.INVALID_MESSA GE)); | |
| 398 } catch (TagLostException e) { | |
| 399 Log.w(TAG, "Cannot write data to NFC tag. Tag is lost."); | |
| 400 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); | |
| 401 } catch (FormatException | IOException e) { | |
| 402 Log.w(TAG, "Cannot write data to NFC tag. IO_ERROR."); | |
| 403 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); | |
| 404 } | |
| 405 } | |
| 406 | |
| 407 public void onTagDiscovered(Tag tag) { | |
| 408 mTagWriter = NfcTagWriter.create(tag); | |
| 409 processPendingPushOperation(); | |
| 410 } | |
| 411 } | |
| OLD | NEW |