Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(242)

Side by Side Diff: device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java

Issue 2894373002: [DeviceService] Move //device/nfc to be part of the internal impl of Device Service (Closed)
Patch Set: Modify code comment Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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.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.NfcAdapter;
15 import android.nfc.NfcAdapter.ReaderCallback;
16 import android.nfc.NfcManager;
17 import android.nfc.Tag;
18 import android.nfc.TagLostException;
19 import android.os.Build;
20 import android.os.Handler;
21 import android.os.Process;
22 import android.util.SparseArray;
23
24 import org.chromium.base.Callback;
25 import org.chromium.base.Log;
26 import org.chromium.device.nfc.mojom.Nfc;
27 import org.chromium.device.nfc.mojom.NfcClient;
28 import org.chromium.device.nfc.mojom.NfcError;
29 import org.chromium.device.nfc.mojom.NfcErrorType;
30 import org.chromium.device.nfc.mojom.NfcMessage;
31 import org.chromium.device.nfc.mojom.NfcPushOptions;
32 import org.chromium.device.nfc.mojom.NfcPushTarget;
33 import org.chromium.device.nfc.mojom.NfcWatchMode;
34 import org.chromium.device.nfc.mojom.NfcWatchOptions;
35 import org.chromium.mojo.bindings.Callbacks;
36 import org.chromium.mojo.system.MojoException;
37
38 import java.io.IOException;
39 import java.io.UnsupportedEncodingException;
40 import java.util.ArrayList;
41 import java.util.List;
42
43 /** Android implementation of the NFC mojo service defined in device/nfc/nfc.moj om.
44 */
45 public class NfcImpl implements Nfc {
46 private static final String TAG = "NfcImpl";
47
48 private final int mHostId;
49
50 private final NfcDelegate mDelegate;
51
52 /**
53 * Used to get instance of NFC adapter, @see android.nfc.NfcManager
54 */
55 private final NfcManager mNfcManager;
56
57 /**
58 * NFC adapter. @see android.nfc.NfcAdapter
59 */
60 private final NfcAdapter mNfcAdapter;
61
62 /**
63 * Activity that is in foreground and is used to enable / disable NFC reader mode operations.
64 * Can be updated when activity associated with web page is changed. @see #s etActivity
65 */
66 private Activity mActivity;
67
68 /**
69 * Flag that indicates whether NFC permission is granted.
70 */
71 private final boolean mHasPermission;
72
73 /**
74 * Implementation of android.nfc.NfcAdapter.ReaderCallback. @see ReaderCallb ackHandler
75 */
76 private ReaderCallbackHandler mReaderCallbackHandler;
77
78 /**
79 * Object that contains data that was passed to method
80 * #push(NfcMessage message, NfcPushOptions options, PushResponse callback)
81 * @see PendingPushOperation
82 */
83 private PendingPushOperation mPendingPushOperation;
84
85 /**
86 * Utility that provides I/O operations for a Tag. Created on demand when Ta g is found.
87 * @see NfcTagHandler
88 */
89 private NfcTagHandler mTagHandler;
90
91 /**
92 * Client interface used to deliver NFCMessages for registered watch operati ons.
93 * @see #watch
94 */
95 private NfcClient mClient;
96
97 /**
98 * Watcher id that is incremented for each #watch call.
99 */
100 private int mWatcherId;
101
102 /**
103 * Map of watchId <-> NfcWatchOptions. All NfcWatchOptions are matched again st tag that is in
104 * proximity, when match algorithm (@see #matchesWatchOptions) returns true, watcher with
105 * corresponding ID would be notified using NfcClient interface.
106 * @see NfcClient#onWatch(int[] id, NfcMessage message)
107 */
108 private final SparseArray<NfcWatchOptions> mWatchers = new SparseArray<>();
109
110 /**
111 * Handler that runs delayed push timeout task.
112 */
113 private final Handler mPushTimeoutHandler = new Handler();
114
115 /**
116 * Runnable responsible for cancelling push operation after specified timeou t.
117 */
118 private Runnable mPushTimeoutRunnable;
119
120 public NfcImpl(Context context, int hostId, NfcDelegate delegate) {
121 mHostId = hostId;
122 mDelegate = delegate;
123 int permission =
124 context.checkPermission(Manifest.permission.NFC, Process.myPid() , Process.myUid());
125 mHasPermission = permission == PackageManager.PERMISSION_GRANTED;
126 Callback<Activity> onActivityUpdatedCallback = new Callback<Activity>() {
127 @Override
128 public void onResult(Activity activity) {
129 setActivity(activity);
130 }
131 };
132
133 mDelegate.trackActivityForHost(mHostId, onActivityUpdatedCallback);
134
135 if (!mHasPermission || Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKA T) {
136 Log.w(TAG, "NFC operations are not permitted.");
137 mNfcAdapter = null;
138 mNfcManager = null;
139 } else {
140 mNfcManager = (NfcManager) context.getSystemService(Context.NFC_SERV ICE);
141 if (mNfcManager == null) {
142 Log.w(TAG, "NFC is not supported.");
143 mNfcAdapter = null;
144 } else {
145 mNfcAdapter = mNfcManager.getDefaultAdapter();
146 }
147 }
148 }
149
150 /**
151 * Sets Activity that is used to enable / disable NFC reader mode. When Acti vity is set,
152 * reader mode is disabled for old Activity and enabled for the new Activity .
153 */
154 protected void setActivity(Activity activity) {
155 disableReaderMode();
156 mActivity = activity;
157 enableReaderModeIfNeeded();
158 }
159
160 /**
161 * Sets NfcClient. NfcClient interface is used to notify mojo NFC service cl ient when NFC
162 * device is in proximity and has NfcMessage that matches NfcWatchOptions cr iteria.
163 * @see Nfc#watch(NfcWatchOptions options, WatchResponse callback)
164 *
165 * @param client @see NfcClient
166 */
167 @Override
168 public void setClient(NfcClient client) {
169 mClient = client;
170 }
171
172 /**
173 * Pushes NfcMessage to Tag or Peer, whenever NFC device is in proximity. At the moment, only
174 * passive NFC devices are supported (NfcPushTarget.TAG).
175 *
176 * @param message that should be pushed to NFC device.
177 * @param options that contain information about timeout and target device t ype.
178 * @param callback that is used to notify when push operation is completed.
179 */
180 @Override
181 public void push(NfcMessage message, NfcPushOptions options, PushResponse ca llback) {
182 if (!checkIfReady(callback)) return;
183
184 if (!NfcMessageValidator.isValid(message)) {
185 callback.call(createError(NfcErrorType.INVALID_MESSAGE));
186 return;
187 }
188
189 // Check NfcPushOptions that are not supported by Android platform.
190 if (options.target == NfcPushTarget.PEER || options.timeout < 0
191 || (options.timeout > Long.MAX_VALUE && !Double.isInfinite(optio ns.timeout))) {
192 callback.call(createError(NfcErrorType.NOT_SUPPORTED));
193 return;
194 }
195
196 // If previous pending push operation is not completed, cancel it.
197 if (mPendingPushOperation != null) {
198 mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CA NCELLED));
199 cancelPushTimeoutTask();
200 }
201
202 mPendingPushOperation = new PendingPushOperation(message, options, callb ack);
203
204 // Schedule push timeout task for new #mPendingPushOperation.
205 schedulePushTimeoutTask(options);
206 enableReaderModeIfNeeded();
207 processPendingPushOperation();
208 }
209
210 /**
211 * Cancels pending push operation.
212 * At the moment, only passive NFC devices are supported (NfcPushTarget.TAG) .
213 *
214 * @param target @see NfcPushTarget
215 * @param callback that is used to notify caller when cancelPush() is comple ted.
216 */
217 @Override
218 public void cancelPush(int target, CancelPushResponse callback) {
219 if (!checkIfReady(callback)) return;
220
221 if (target == NfcPushTarget.PEER) {
222 callback.call(createError(NfcErrorType.NOT_SUPPORTED));
223 return;
224 }
225
226 if (mPendingPushOperation == null) {
227 callback.call(createError(NfcErrorType.NOT_FOUND));
228 } else {
229 completePendingPushOperation(createError(NfcErrorType.OPERATION_CANC ELLED));
230 callback.call(null);
231 }
232 }
233
234 /**
235 * Watch method allows to set filtering criteria for NfcMessages that are fo und when NFC device
236 * is within proximity. On success, watch ID is returned to caller through W atchResponse
237 * callback. When NfcMessage that matches NfcWatchOptions is found, it is pa ssed to NfcClient
238 * interface together with corresponding watch ID.
239 * @see NfcClient#onWatch(int[] id, NfcMessage message)
240 *
241 * @param options used to filter NfcMessages, @see NfcWatchOptions.
242 * @param callback that is used to notify caller when watch() is completed a nd return watch ID.
243 */
244 @Override
245 public void watch(NfcWatchOptions options, WatchResponse callback) {
246 if (!checkIfReady(callback)) return;
247 int watcherId = ++mWatcherId;
248 mWatchers.put(watcherId, options);
249 callback.call(watcherId, null);
250 enableReaderModeIfNeeded();
251 processPendingWatchOperations();
252 }
253
254 /**
255 * Cancels NFC watch operation.
256 *
257 * @param id of watch operation.
258 * @param callback that is used to notify caller when cancelWatch() is compl eted.
259 */
260 @Override
261 public void cancelWatch(int id, CancelWatchResponse callback) {
262 if (!checkIfReady(callback)) return;
263
264 if (mWatchers.indexOfKey(id) < 0) {
265 callback.call(createError(NfcErrorType.NOT_FOUND));
266 } else {
267 mWatchers.remove(id);
268 callback.call(null);
269 disableReaderModeIfNeeded();
270 }
271 }
272
273 /**
274 * Cancels all NFC watch operations.
275 *
276 * @param callback that is used to notify caller when cancelAllWatches() is completed.
277 */
278 @Override
279 public void cancelAllWatches(CancelAllWatchesResponse callback) {
280 if (!checkIfReady(callback)) return;
281
282 if (mWatchers.size() == 0) {
283 callback.call(createError(NfcErrorType.NOT_FOUND));
284 } else {
285 mWatchers.clear();
286 callback.call(null);
287 disableReaderModeIfNeeded();
288 }
289 }
290
291 /**
292 * Suspends all pending operations. Should be called when web page visibilit y is lost.
293 */
294 @Override
295 public void suspendNfcOperations() {
296 disableReaderMode();
297 }
298
299 /**
300 * Resumes all pending watch / push operations. Should be called when web pa ge becomes visible.
301 */
302 @Override
303 public void resumeNfcOperations() {
304 enableReaderModeIfNeeded();
305 }
306
307 @Override
308 public void close() {
309 mDelegate.stopTrackingActivityForHost(mHostId);
310 disableReaderMode();
311 }
312
313 @Override
314 public void onConnectionError(MojoException e) {
315 close();
316 }
317
318 /**
319 * Holds information about pending push operation.
320 */
321 private static class PendingPushOperation {
322 public final NfcMessage nfcMessage;
323 public final NfcPushOptions nfcPushOptions;
324 private final PushResponse mPushResponseCallback;
325
326 public PendingPushOperation(
327 NfcMessage message, NfcPushOptions options, PushResponse callbac k) {
328 nfcMessage = message;
329 nfcPushOptions = options;
330 mPushResponseCallback = callback;
331 }
332
333 /**
334 * Completes pending push operation.
335 *
336 * @param error should be null when operation is completed successfully, otherwise,
337 * error object with corresponding NfcErrorType should be provided.
338 */
339 public void complete(NfcError error) {
340 if (mPushResponseCallback != null) mPushResponseCallback.call(error) ;
341 }
342 }
343
344 /**
345 * Helper method that creates NfcError object from NfcErrorType.
346 */
347 private NfcError createError(int errorType) {
348 NfcError error = new NfcError();
349 error.errorType = errorType;
350 return error;
351 }
352
353 /**
354 * Checks if NFC funcionality can be used by the mojo service. If permission to use NFC is
355 * granted and hardware is enabled, returns null.
356 */
357 private NfcError checkIfReady() {
358 if (!mHasPermission || mActivity == null) {
359 return createError(NfcErrorType.SECURITY);
360 } else if (mNfcManager == null || mNfcAdapter == null) {
361 return createError(NfcErrorType.NOT_SUPPORTED);
362 } else if (!mNfcAdapter.isEnabled()) {
363 return createError(NfcErrorType.DEVICE_DISABLED);
364 }
365 return null;
366 }
367
368 /**
369 * Uses checkIfReady() method and if NFC cannot be used, calls mojo callback with NfcError.
370 *
371 * @param WatchResponse Callback that is provided to watch() method.
372 * @return boolean true if NFC functionality can be used, false otherwise.
373 */
374 private boolean checkIfReady(WatchResponse callback) {
375 NfcError error = checkIfReady();
376 if (error == null) return true;
377
378 callback.call(0, error);
379 return false;
380 }
381
382 /**
383 * Uses checkIfReady() method and if NFC cannot be used, calls mojo callback with NfcError.
384 *
385 * @param callback Generic callback that is provided to push(), cancelPush() ,
386 * cancelWatch() and cancelAllWatches() methods.
387 * @return boolean true if NFC functionality can be used, false otherwise.
388 */
389 private boolean checkIfReady(Callbacks.Callback1<NfcError> callback) {
390 NfcError error = checkIfReady();
391 if (error == null) return true;
392
393 callback.call(error);
394 return false;
395 }
396
397 /**
398 * Implementation of android.nfc.NfcAdapter.ReaderCallback. Callback is call ed when NFC tag is
399 * discovered, Tag object is delegated to mojo service implementation method
400 * NfcImpl.onTagDiscovered().
401 */
402 @TargetApi(Build.VERSION_CODES.KITKAT)
403 private static class ReaderCallbackHandler implements ReaderCallback {
404 private final NfcImpl mNfcImpl;
405
406 public ReaderCallbackHandler(NfcImpl impl) {
407 mNfcImpl = impl;
408 }
409
410 @Override
411 public void onTagDiscovered(Tag tag) {
412 mNfcImpl.onTagDiscovered(tag);
413 }
414 }
415
416 /**
417 * Enables reader mode, allowing NFC device to read / write NFC tags.
418 * @see android.nfc.NfcAdapter#enableReaderMode
419 */
420 private void enableReaderModeIfNeeded() {
421 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
422
423 if (mReaderCallbackHandler != null || mActivity == null || mNfcAdapter = = null) return;
424
425 // Do not enable reader mode, if there are no active push / watch operat ions.
426 if (mPendingPushOperation == null && mWatchers.size() == 0) return;
427
428 mReaderCallbackHandler = new ReaderCallbackHandler(this);
429 mNfcAdapter.enableReaderMode(mActivity, mReaderCallbackHandler,
430 NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B
431 | NfcAdapter.FLAG_READER_NFC_F | NfcAdapter.FLAG_READER_ NFC_V,
432 null);
433 }
434
435 /**
436 * Disables reader mode.
437 * @see android.nfc.NfcAdapter#disableReaderMode
438 */
439 @TargetApi(Build.VERSION_CODES.KITKAT)
440 private void disableReaderMode() {
441 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return;
442
443 // There is no API that could query whether reader mode is enabled for a dapter.
444 // If mReaderCallbackHandler is null, reader mode is not enabled.
445 if (mReaderCallbackHandler == null) return;
446
447 mReaderCallbackHandler = null;
448 if (mActivity != null && mNfcAdapter != null && !mActivity.isDestroyed() ) {
449 mNfcAdapter.disableReaderMode(mActivity);
450 }
451 }
452
453 /**
454 * Checks if there are pending push / watch operations and disables readre m ode
455 * whenever necessary.
456 */
457 private void disableReaderModeIfNeeded() {
458 if (mPendingPushOperation == null && mWatchers.size() == 0) {
459 disableReaderMode();
460 }
461 }
462
463 /**
464 * Handles completion of pending push operation, cancels timeout task and co mpletes push
465 * operation. On error, invalidates #mTagHandler.
466 */
467 private void pendingPushOperationCompleted(NfcError error) {
468 completePendingPushOperation(error);
469 if (error != null) mTagHandler = null;
470 }
471
472 /**
473 * Completes pending push operation and disables reader mode if needed.
474 */
475 private void completePendingPushOperation(NfcError error) {
476 if (mPendingPushOperation == null) return;
477
478 cancelPushTimeoutTask();
479 mPendingPushOperation.complete(error);
480 mPendingPushOperation = null;
481 disableReaderModeIfNeeded();
482 }
483
484 /**
485 * Checks whether there is a #mPendingPushOperation and writes data to NFC t ag. In case of
486 * exception calls pendingPushOperationCompleted() with appropriate error ob ject.
487 */
488 private void processPendingPushOperation() {
489 if (mTagHandler == null || mPendingPushOperation == null) return;
490
491 if (mTagHandler.isTagOutOfRange()) {
492 mTagHandler = null;
493 return;
494 }
495
496 try {
497 mTagHandler.connect();
498 mTagHandler.write(NfcTypeConverter.toNdefMessage(mPendingPushOperati on.nfcMessage));
499 pendingPushOperationCompleted(null);
500 } catch (InvalidNfcMessageException e) {
501 Log.w(TAG, "Cannot write data to NFC tag. Invalid NfcMessage.");
502 pendingPushOperationCompleted(createError(NfcErrorType.INVALID_MESSA GE));
503 } catch (TagLostException e) {
504 Log.w(TAG, "Cannot write data to NFC tag. Tag is lost.");
505 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR));
506 } catch (FormatException | IllegalStateException | IOException e) {
507 Log.w(TAG, "Cannot write data to NFC tag. IO_ERROR.");
508 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR));
509 }
510 }
511
512 /**
513 * Reads NfcMessage from a tag and forwards message to matching method.
514 */
515 private void processPendingWatchOperations() {
516 if (mTagHandler == null || mClient == null || mWatchers.size() == 0) ret urn;
517
518 // Skip reading if there is a pending push operation and ignoreRead flag is set.
519 if (mPendingPushOperation != null && mPendingPushOperation.nfcPushOption s.ignoreRead) {
520 return;
521 }
522
523 if (mTagHandler.isTagOutOfRange()) {
524 mTagHandler = null;
525 return;
526 }
527
528 NdefMessage message = null;
529
530 try {
531 mTagHandler.connect();
532 message = mTagHandler.read();
533 if (message.getByteArrayLength() > NfcMessage.MAX_SIZE) {
534 Log.w(TAG, "Cannot read data from NFC tag. NfcMessage exceeds al lowed size.");
535 return;
536 }
537 } catch (TagLostException e) {
538 Log.w(TAG, "Cannot read data from NFC tag. Tag is lost.");
539 } catch (FormatException | IllegalStateException | IOException e) {
540 Log.w(TAG, "Cannot read data from NFC tag. IO_ERROR.");
541 }
542
543 if (message != null) notifyMatchingWatchers(message);
544 }
545
546 /**
547 * Iterates through active watchers and if any of those match NfcWatchOption s criteria,
548 * delivers NfcMessage to the client.
549 */
550 private void notifyMatchingWatchers(NdefMessage message) {
551 try {
552 NfcMessage nfcMessage = NfcTypeConverter.toNfcMessage(message);
553 List<Integer> watchIds = new ArrayList<Integer>();
554 for (int i = 0; i < mWatchers.size(); i++) {
555 NfcWatchOptions options = mWatchers.valueAt(i);
556 if (matchesWatchOptions(nfcMessage, options)) watchIds.add(mWatc hers.keyAt(i));
557 }
558
559 if (watchIds.size() != 0) {
560 int[] ids = new int[watchIds.size()];
561 for (int i = 0; i < watchIds.size(); ++i) {
562 ids[i] = watchIds.get(i).intValue();
563 }
564 mClient.onWatch(ids, nfcMessage);
565 }
566 } catch (UnsupportedEncodingException e) {
567 Log.w(TAG, "Cannot convert NdefMessage to NfcMessage.");
568 }
569 }
570
571 /**
572 * Implements matching algorithm.
573 */
574 private boolean matchesWatchOptions(NfcMessage message, NfcWatchOptions opti ons) {
575 // Valid WebNFC message must have non-empty url.
576 if (options.mode == NfcWatchMode.WEBNFC_ONLY
577 && (message.url == null || message.url.isEmpty())) {
578 return false;
579 }
580
581 // Filter by NfcMessage.url
582 if (options.url != null && !options.url.isEmpty() && !options.url.equals (message.url)) {
583 return false;
584 }
585
586 // Matches any record / media type.
587 if ((options.mediaType == null || options.mediaType.isEmpty())
588 && options.recordFilter == null) {
589 return true;
590 }
591
592 // Filter by mediaType and recordType
593 for (int i = 0; i < message.data.length; i++) {
594 boolean matchedMediaType;
595 boolean matchedRecordType;
596
597 if (options.mediaType == null || options.mediaType.isEmpty()) {
598 // If media type for the watch options is empty, match all media types.
599 matchedMediaType = true;
600 } else {
601 matchedMediaType = options.mediaType.equals(message.data[i].medi aType);
602 }
603
604 if (options.recordFilter == null) {
605 // If record type filter for the watch options is null, match al l record types.
606 matchedRecordType = true;
607 } else {
608 matchedRecordType = options.recordFilter.recordType == message.d ata[i].recordType;
609 }
610
611 if (matchedMediaType && matchedRecordType) return true;
612 }
613
614 return false;
615 }
616
617 /**
618 * Called by ReaderCallbackHandler when NFC tag is in proximity.
619 */
620 public void onTagDiscovered(Tag tag) {
621 processPendingOperations(NfcTagHandler.create(tag));
622 }
623
624 /**
625 * Processes pending operation when NFC tag is in proximity.
626 */
627 protected void processPendingOperations(NfcTagHandler tagHandler) {
628 mTagHandler = tagHandler;
629 processPendingWatchOperations();
630 processPendingPushOperation();
631 if (mTagHandler != null && mTagHandler.isConnected()) {
632 try {
633 mTagHandler.close();
634 } catch (IOException e) {
635 Log.w(TAG, "Cannot close NFC tag connection.");
636 }
637 }
638 }
639
640 /**
641 * Schedules task that is executed after timeout and cancels pending push op eration.
642 */
643 private void schedulePushTimeoutTask(NfcPushOptions options) {
644 assert mPushTimeoutRunnable == null;
645 // Default timeout value.
646 if (Double.isInfinite(options.timeout)) return;
647
648 // Create and schedule timeout.
649 mPushTimeoutRunnable = new Runnable() {
650 @Override
651 public void run() {
652 completePendingPushOperation(createError(NfcErrorType.TIMER_EXPI RED));
653 }
654 };
655
656 mPushTimeoutHandler.postDelayed(mPushTimeoutRunnable, (long) options.tim eout);
657 }
658
659 /**
660 * Cancels push timeout task.
661 */
662 void cancelPushTimeoutTask() {
663 if (mPushTimeoutRunnable == null) return;
664
665 mPushTimeoutHandler.removeCallbacks(mPushTimeoutRunnable);
666 mPushTimeoutRunnable = null;
667 }
668 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698