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.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.NfcAdapter; | |
14 import android.nfc.NfcAdapter.ReaderCallback; | |
15 import android.nfc.NfcManager; | |
16 import android.nfc.Tag; | |
17 import android.nfc.TagLostException; | |
18 import android.os.Build; | |
19 import android.os.Process; | |
20 import android.support.annotation.Nullable; | |
21 | |
22 import org.chromium.base.Log; | |
23 import org.chromium.mojo.bindings.Callbacks; | |
24 import org.chromium.mojo.system.MojoException; | |
25 import org.chromium.mojom.device.nfc.Nfc; | |
26 import org.chromium.mojom.device.nfc.NfcClient; | |
27 import org.chromium.mojom.device.nfc.NfcError; | |
28 import org.chromium.mojom.device.nfc.NfcErrorType; | |
29 import org.chromium.mojom.device.nfc.NfcMessage; | |
30 import org.chromium.mojom.device.nfc.NfcPushOptions; | |
31 import org.chromium.mojom.device.nfc.NfcPushTarget; | |
32 import org.chromium.mojom.device.nfc.NfcWatchOptions; | |
33 | |
34 import java.io.IOException; | |
35 | |
36 /** | |
37 * Android implementation of the NFC mojo service defined in | |
38 * device/nfc/nfc.mojom. | |
39 */ | |
40 public class NfcImpl implements Nfc { | |
41 private static final String TAG = "NfcImpl"; | |
42 | |
43 /** | |
44 * Used to get instance of NFC adapter, @see android.nfc.NfcManager | |
45 */ | |
46 private final NfcManager mNfcManager; | |
47 | |
48 /** | |
49 * NFC adapter. @see android.nfc.NfcAdapter | |
50 */ | |
51 private final NfcAdapter mNfcAdapter; | |
52 | |
53 /** | |
54 * Activity object that is requred to enable / disable NFC reader mode opera tions. | |
55 */ | |
56 private final Activity mActivity; | |
Ted C
2016/05/12 18:23:20
this has the same problem I mentioned before. the
Yusuf
2016/05/12 22:06:25
Another solution would be to construct the impl cl
| |
57 | |
58 /** | |
59 * Flag that indicates whether NFC permission is granted. | |
60 */ | |
61 private final boolean mHasPermission; | |
62 | |
63 /** | |
64 * Implementation of android.nfc.NfcAdapter.ReaderCallback. @see ReaderCallb ackHandler | |
65 */ | |
66 private ReaderCallbackHandler mReaderCallbackHandler; | |
67 | |
68 /** | |
69 * Object that contains data that was passed to method | |
70 * #push(NfcMessage message, NfcPushOptions options, PushResponse callback) | |
71 * @see PendingPushOperation | |
72 */ | |
73 private PendingPushOperation mPendingPushOperation; | |
74 | |
75 /** | |
76 * Utility that provides I/O operations for a Tag. Created on demand when Ta g is found. | |
77 * @see NfcTagHandler | |
78 */ | |
79 private NfcTagHandler mTagHandler; | |
80 | |
81 public NfcImpl(Activity activity) { | |
82 mActivity = activity; | |
83 | |
84 if (mActivity != null) { | |
85 int permission = mActivity.checkPermission( | |
86 Manifest.permission.NFC, Process.myPid(), Process.myUid()); | |
87 mHasPermission = permission == PackageManager.PERMISSION_GRANTED; | |
88 } else { | |
89 mHasPermission = false; | |
90 } | |
91 | |
92 if (mHasPermission && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKA T) { | |
93 mNfcManager = (NfcManager) mActivity.getSystemService(Context.NFC_SE RVICE); | |
94 if (mNfcManager != null) { | |
95 mNfcAdapter = mNfcManager.getDefaultAdapter(); | |
96 } else { | |
97 Log.w(TAG, "NFC is not supported."); | |
98 mNfcAdapter = null; | |
99 } | |
100 } else { | |
101 Log.w(TAG, "NFC operations are not permitted."); | |
102 mNfcAdapter = null; | |
103 mNfcManager = null; | |
104 } | |
105 } | |
106 | |
107 /** | |
108 * Sets NfcClient. NfcClient interface is used to notify mojo NFC service cl ient when NFC | |
109 * device is in proximity and has NfcMessage that matches NfcWatchOptions cr iteria. | |
110 * @see Nfc#watch(NfcWatchOptions options, WatchResponse callback) | |
111 * | |
112 * @param client @see NfcClient | |
113 */ | |
114 @Override | |
115 public void setClient(NfcClient client) { | |
116 // TODO(shalamov): Should be implemented when watch() is implemented. | |
117 } | |
118 | |
119 /** | |
120 * Pushes NfcMessage to Tag or Peer, whenever NFC device is in proximity. At the moment, only | |
121 * passive NFC devices are supported (NfcPushTarget.TAG). | |
122 * | |
123 * @param message that should be pushed to NFC device. | |
124 * @param options that contain information about timeout and target device t ype. | |
125 * @param callback that is used to notify when push operation is completed. | |
126 */ | |
127 @Override | |
128 public void push(NfcMessage message, NfcPushOptions options, PushResponse ca llback) { | |
129 if (!checkIfReady(callback)) return; | |
130 | |
131 if (options.target == NfcPushTarget.PEER) { | |
132 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); | |
133 return; | |
134 } | |
135 | |
136 // If previous pending push operation is not completed, subsequent call | |
137 // should cancel pending operation. | |
138 if (mPendingPushOperation != null) { | |
139 mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CA NCELLED)); | |
140 } | |
141 | |
142 mPendingPushOperation = new PendingPushOperation(message, options, callb ack); | |
143 enableReaderMode(); | |
144 processPendingPushOperation(); | |
145 } | |
146 | |
147 /** | |
148 * Cancels pending push operation. | |
149 * At the moment, only passive NFC devices are supported (NfcPushTarget.TAG) . | |
150 * | |
151 * @param target @see NfcPushTarget | |
152 * @param callback that is used to notify caller when cancelPush() is comple ted. | |
153 */ | |
154 @Override | |
155 public void cancelPush(int target, CancelPushResponse callback) { | |
156 if (!checkIfReady(callback)) return; | |
157 | |
158 if (target == NfcPushTarget.PEER) { | |
159 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); | |
160 return; | |
161 } | |
162 | |
163 if (mPendingPushOperation != null) { | |
164 mPendingPushOperation.complete(createError(NfcErrorType.OPERATION_CA NCELLED)); | |
165 mPendingPushOperation = null; | |
166 callback.call(null); | |
167 disableReaderMode(); | |
168 } else { | |
169 callback.call(createError(NfcErrorType.NOT_FOUND)); | |
170 } | |
171 } | |
172 | |
173 /** | |
174 * Watch method allows to set filtering criteria for NfcMessages that are fo und when NFC device | |
175 * is within proximity. On success, watch ID is returned to caller through W atchResponse | |
176 * callback. When NfcMessage that matches NfcWatchOptions is found, it is pa ssed to NfcClient | |
177 * interface together with corresponding watch ID. | |
178 * @see NfcClient#onWatch(int[] id, NfcMessage message) | |
179 * | |
180 * @param options used to filter NfcMessages, @see NfcWatchOptions. | |
181 * @param callback that is used to notify caller when watch() is completed a nd return watch ID. | |
182 */ | |
183 @Override | |
184 public void watch(NfcWatchOptions options, WatchResponse callback) { | |
185 if (!checkIfReady(callback)) return; | |
186 // TODO(shalamov): Not implemented. | |
187 callback.call(0, createError(NfcErrorType.NOT_SUPPORTED)); | |
188 } | |
189 | |
190 /** | |
191 * Cancels NFC watch operation. | |
192 * | |
193 * @param id of watch operation. | |
194 * @param callback that is used to notify caller when cancelWatch() is compl eted. | |
195 */ | |
196 @Override | |
197 public void cancelWatch(int id, CancelWatchResponse callback) { | |
198 if (!checkIfReady(callback)) return; | |
199 // TODO(shalamov): Not implemented. | |
200 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); | |
201 } | |
202 | |
203 /** | |
204 * Cancels all NFC watch operations. | |
205 * | |
206 * @param callback that is used to notify caller when cancelAllWatches() is completed. | |
207 */ | |
208 @Override | |
209 public void cancelAllWatches(CancelAllWatchesResponse callback) { | |
210 if (!checkIfReady(callback)) return; | |
211 // TODO(shalamov): Not implemented. | |
212 callback.call(createError(NfcErrorType.NOT_SUPPORTED)); | |
213 } | |
214 | |
215 /** | |
216 * Suspends all pending operations. Should be called when web page visibilit y is lost. | |
217 */ | |
218 @Override | |
219 public void suspendNfcOperations() { | |
220 disableReaderMode(); | |
221 } | |
222 | |
223 /** | |
224 * Resumes all pending watch / push operations. Should be called when web pa ge becomes visible. | |
225 */ | |
226 @Override | |
227 public void resumeNfcOperations() { | |
228 if (mPendingPushOperation != null) enableReaderMode(); | |
229 // TODO(shalamov): check if there are active watchers. | |
230 } | |
231 | |
232 @Override | |
233 public void close() { | |
234 disableReaderMode(); | |
235 } | |
236 | |
237 @Override | |
238 public void onConnectionError(MojoException e) { | |
239 close(); | |
240 } | |
241 | |
242 /** | |
243 * Holds information about pending push operation. | |
244 */ | |
245 private static class PendingPushOperation { | |
246 public final NfcMessage nfcMessage; | |
247 public final NfcPushOptions nfcPushOptions; | |
248 private final PushResponse mPushResponseCallback; | |
249 | |
250 public PendingPushOperation( | |
251 NfcMessage message, NfcPushOptions options, PushResponse callbac k) { | |
252 nfcMessage = message; | |
253 nfcPushOptions = options; | |
254 mPushResponseCallback = callback; | |
255 } | |
256 | |
257 /** | |
258 * Completes pending push operation. | |
259 * | |
260 * @param error should be null when operation is completed successfully, otherwise, | |
261 * error object with corresponding NfcErrorType should be provided. | |
262 */ | |
263 public void complete(NfcError error) { | |
264 if (mPushResponseCallback != null) mPushResponseCallback.call(error) ; | |
265 } | |
266 } | |
267 | |
268 /** | |
269 * Helper method that creates NfcError object from NfcErrorType. | |
270 */ | |
271 private NfcError createError(int errorType) { | |
272 NfcError error = new NfcError(); | |
273 error.errorType = errorType; | |
274 return error; | |
275 } | |
276 | |
277 /** | |
278 * Checks if NFC funcionality can be used by the mojo service. If permission to use NFC is | |
279 * granted and hardware is enabled, returns null. | |
280 */ | |
281 @Nullable | |
282 private NfcError checkIfReady() { | |
283 if (!mHasPermission) { | |
284 return createError(NfcErrorType.SECURITY); | |
285 } else if (mNfcManager == null || mNfcAdapter == null) { | |
286 return createError(NfcErrorType.NOT_SUPPORTED); | |
287 } else if (!mNfcAdapter.isEnabled()) { | |
288 return createError(NfcErrorType.DEVICE_DISABLED); | |
289 } | |
290 return null; | |
291 } | |
292 | |
293 /** | |
294 * Uses checkIfReady() method and if NFC cannot be used, calls mojo callback with NfcError. | |
295 * | |
296 * @param WatchResponse Callback that is provided to watch() method. | |
297 * @return boolean true if NFC functionality can be used, false otherwise. | |
298 */ | |
299 private boolean checkIfReady(WatchResponse callback) { | |
300 NfcError error = checkIfReady(); | |
301 if (error == null) return true; | |
302 callback.call(0, error); | |
303 return false; | |
304 } | |
305 | |
306 /** | |
307 * Uses checkIfReady() method and if NFC cannot be used, calls mojo callback with NfcError. | |
308 * | |
309 * @param callback Generic callback that is provided to push(), cancelPush() , | |
310 * cancelWatch() and cancelAllWatches() methods. | |
311 * @return boolean true if NFC functionality can be used, false otherwise. | |
312 */ | |
313 private boolean checkIfReady(Callbacks.Callback1<NfcError> callback) { | |
314 NfcError error = checkIfReady(); | |
315 if (error == null) return true; | |
316 callback.call(error); | |
317 return false; | |
318 } | |
319 | |
320 /** | |
321 * Implementation of android.nfc.NfcAdapter.ReaderCallback. Callback is call ed when NFC tag is | |
322 * discovered, Tag object is delegated to mojo service implementation method | |
323 * NfcImpl.onTagDiscovered(). | |
324 */ | |
325 @TargetApi(Build.VERSION_CODES.KITKAT) | |
326 private static class ReaderCallbackHandler implements ReaderCallback { | |
327 private final NfcImpl mNfcImpl; | |
328 | |
329 public ReaderCallbackHandler(NfcImpl impl) { | |
330 mNfcImpl = impl; | |
331 } | |
332 | |
333 @Override | |
334 public void onTagDiscovered(Tag tag) { | |
335 mNfcImpl.onTagDiscovered(tag); | |
336 } | |
337 } | |
338 | |
339 /** | |
340 * Enables reader mode, allowing NFC device to read / write NFC tags. | |
341 * @see android.nfc.NfcAdapter#enableReaderMode | |
342 */ | |
343 private void enableReaderMode() { | |
344 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; | |
345 if (mReaderCallbackHandler != null || mActivity == null) return; | |
346 | |
347 mReaderCallbackHandler = new ReaderCallbackHandler(this); | |
348 mNfcAdapter.enableReaderMode(mActivity, mReaderCallbackHandler, | |
349 NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_NFC_B | |
350 | NfcAdapter.FLAG_READER_NFC_F | NfcAdapter.FLAG_READER_ NFC_V, | |
351 null); | |
352 } | |
353 | |
354 /** | |
355 * Disables reader mode. | |
356 * @see android.nfc.NfcAdapter#disableReaderMode | |
357 */ | |
358 private void disableReaderMode() { | |
359 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; | |
360 | |
361 mReaderCallbackHandler = null; | |
362 if (mActivity != null) { | |
363 mNfcAdapter.disableReaderMode(mActivity); | |
364 } | |
365 } | |
366 | |
367 /** | |
368 * Completes pending push operation. On error, invalidates #mTagHandler. | |
369 */ | |
370 private void pendingPushOperationCompleted(NfcError error) { | |
371 if (mPendingPushOperation != null) { | |
372 mPendingPushOperation.complete(error); | |
373 mPendingPushOperation = null; | |
374 | |
375 // TODO(shalamov): When nfc.watch is implemented, disable reader mod e | |
376 // only when there are no active watch operations. | |
377 disableReaderMode(); | |
378 } | |
379 | |
380 if (error != null) mTagHandler = null; | |
381 } | |
382 | |
383 /** | |
384 * Checks whether there is a #mPendingPushOperation and writes data to NFC t ag. In case of | |
385 * exception calls pendingPushOperationCompleted() with appropriate error ob ject. | |
386 */ | |
387 private void processPendingPushOperation() { | |
388 if (mTagHandler == null || mPendingPushOperation == null) return; | |
389 | |
390 if (mTagHandler.isTagOutOfRange()) { | |
391 mTagHandler = null; | |
392 return; | |
393 } | |
394 | |
395 try { | |
396 mTagHandler.connect(); | |
397 mTagHandler.write(NfcTypeConverter.toNdefMessage(mPendingPushOperati on.nfcMessage)); | |
398 pendingPushOperationCompleted(null); | |
399 mTagHandler.close(); | |
400 } catch (InvalidNfcMessageException e) { | |
401 Log.w(TAG, "Cannot write data to NFC tag. Invalid NfcMessage."); | |
402 pendingPushOperationCompleted(createError(NfcErrorType.INVALID_MESSA GE)); | |
403 } catch (TagLostException e) { | |
404 Log.w(TAG, "Cannot write data to NFC tag. Tag is lost."); | |
405 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); | |
406 } catch (FormatException | IOException e) { | |
407 Log.w(TAG, "Cannot write data to NFC tag. IO_ERROR."); | |
408 pendingPushOperationCompleted(createError(NfcErrorType.IO_ERROR)); | |
409 } | |
410 } | |
411 | |
412 /** | |
413 * Called by ReaderCallbackHandler when NFC tag is in proximity. | |
414 */ | |
415 public void onTagDiscovered(Tag tag) { | |
416 mTagHandler = NfcTagHandler.create(tag); | |
417 processPendingPushOperation(); | |
418 } | |
419 } | |
OLD | NEW |