1 /* 2 * Copyright (C) 2011 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 */ 16 17 package com.android.nfc; 18 19 import com.android.nfc.echoserver.EchoServer; 20 import com.android.nfc.handover.HandoverClient; 21 import com.android.nfc.handover.HandoverManager; 22 import com.android.nfc.handover.HandoverServer; 23 import com.android.nfc.ndefpush.NdefPushClient; 24 import com.android.nfc.ndefpush.NdefPushServer; 25 import com.android.nfc.snep.SnepClient; 26 import com.android.nfc.snep.SnepMessage; 27 import com.android.nfc.snep.SnepServer; 28 29 import android.app.ActivityManager; 30 import android.app.ActivityManager.RunningTaskInfo; 31 import android.content.Context; 32 import android.content.SharedPreferences; 33 import android.content.pm.ApplicationInfo; 34 import android.content.pm.PackageManager; 35 import android.content.pm.PackageManager.NameNotFoundException; 36 import android.net.Uri; 37 import android.nfc.INdefPushCallback; 38 import android.nfc.NdefMessage; 39 import android.nfc.NdefRecord; 40 import android.os.AsyncTask; 41 import android.os.Bundle; 42 import android.os.Handler; 43 import android.os.Message; 44 import android.os.RemoteException; 45 import android.os.SystemClock; 46 import android.provider.ContactsContract.Contacts; 47 import android.provider.ContactsContract.Profile; 48 import android.util.Log; 49 50 import java.io.FileDescriptor; 51 import java.io.IOException; 52 import java.io.PrintWriter; 53 import java.nio.charset.Charsets; 54 import java.util.Arrays; 55 import java.util.List; 56 57 /** 58 * Interface to listen for P2P events. 59 * All callbacks are made from the UI thread. 60 */ 61 interface P2pEventListener { 62 /** 63 * Indicates a P2P device is in range. 64 * <p>onP2pInRange() and onP2pOutOfRange() will always be called 65 * alternately. 66 * <p>All other callbacks will only occur while a P2P device is in range. 67 */ onP2pInRange()68 public void onP2pInRange(); 69 70 /** 71 * Called when a NDEF payload is prepared to send, and confirmation is 72 * required. Call Callback.onP2pSendConfirmed() to make the confirmation. 73 */ onP2pSendConfirmationRequested()74 public void onP2pSendConfirmationRequested(); 75 76 /** 77 * Called to indicate a send was successful. 78 */ onP2pSendComplete()79 public void onP2pSendComplete(); 80 81 /** 82 * Called to indicate the remote device does not support connection handover 83 */ onP2pHandoverNotSupported()84 public void onP2pHandoverNotSupported(); 85 86 /** 87 * Called to indicate a receive was successful. 88 */ onP2pReceiveComplete(boolean playSound)89 public void onP2pReceiveComplete(boolean playSound); 90 91 /** 92 * Indicates the P2P device went out of range. 93 */ onP2pOutOfRange()94 public void onP2pOutOfRange(); 95 96 public interface Callback { onP2pSendConfirmed()97 public void onP2pSendConfirmed(); 98 } 99 } 100 101 /** 102 * Manages sending and receiving NDEF message over LLCP link. 103 * Does simple debouncing of the LLCP link - so that even if the link 104 * drops and returns the user does not know. 105 */ 106 public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { 107 static final String TAG = "NfcP2pLinkManager"; 108 static final boolean DBG = true; 109 110 /** Include this constant as a meta-data entry in the manifest 111 * of an application to disable beaming the market/AAR link, like this: 112 * <pre>{@code 113 * <application ...> 114 * <meta-data android:name="android.nfc.disable_beam_default" 115 * android:value="true" /> 116 * </application> 117 * }</pre> 118 */ 119 static final String DISABLE_BEAM_DEFAULT = "android.nfc.disable_beam_default"; 120 121 /** Enables the LLCP EchoServer, which can be used to test the android 122 * LLCP stack against nfcpy. 123 */ 124 static final boolean ECHOSERVER_ENABLED = false; 125 126 // TODO dynamically assign SAP values 127 static final int NDEFPUSH_SAP = 0x10; 128 static final int HANDOVER_SAP = 0x14; 129 130 static final int LINK_DEBOUNCE_MS = 750; 131 132 static final int MSG_DEBOUNCE_TIMEOUT = 1; 133 static final int MSG_RECEIVE_COMPLETE = 2; 134 static final int MSG_RECEIVE_HANDOVER = 3; 135 static final int MSG_SEND_COMPLETE = 4; 136 static final int MSG_START_ECHOSERVER = 5; 137 static final int MSG_STOP_ECHOSERVER = 6; 138 static final int MSG_HANDOVER_NOT_SUPPORTED = 7; 139 140 // values for mLinkState 141 static final int LINK_STATE_DOWN = 1; 142 static final int LINK_STATE_UP = 2; 143 static final int LINK_STATE_DEBOUNCE =3; 144 145 // values for mSendState 146 static final int SEND_STATE_NOTHING_TO_SEND = 1; 147 static final int SEND_STATE_NEED_CONFIRMATION = 2; 148 static final int SEND_STATE_SENDING = 3; 149 150 // return values for doSnepProtocol 151 static final int SNEP_SUCCESS = 0; 152 static final int SNEP_FAILURE = 1; 153 static final int SNEP_HANDOVER_UNSUPPORTED = 2; 154 155 static final Uri PROFILE_URI = Profile.CONTENT_VCARD_URI.buildUpon(). 156 appendQueryParameter(Contacts.QUERY_PARAMETER_VCARD_NO_PHOTO, "true"). 157 build(); 158 159 final NdefPushServer mNdefPushServer; 160 final SnepServer mDefaultSnepServer; 161 final HandoverServer mHandoverServer; 162 final EchoServer mEchoServer; 163 final ActivityManager mActivityManager; 164 final PackageManager mPackageManager; 165 final Context mContext; 166 final P2pEventListener mEventListener; 167 final Handler mHandler; 168 final HandoverManager mHandoverManager; 169 170 final int mDefaultMiu; 171 final int mDefaultRwSize; 172 173 // Locked on NdefP2pManager.this 174 int mLinkState; 175 int mSendState; // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE 176 boolean mIsSendEnabled; 177 boolean mIsReceiveEnabled; 178 NdefMessage mMessageToSend; // valid during SEND_STATE_NEED_CONFIRMATION or SEND_STATE_SENDING 179 Uri[] mUrisToSend; // valid during SEND_STATE_NEED_CONFIRMATION or SEND_STATE_SENDING 180 INdefPushCallback mCallbackNdef; 181 SendTask mSendTask; 182 SharedPreferences mPrefs; 183 boolean mFirstBeam; 184 P2pLinkManager(Context context, HandoverManager handoverManager, int defaultMiu, int defaultRwSize)185 public P2pLinkManager(Context context, HandoverManager handoverManager, int defaultMiu, 186 int defaultRwSize) { 187 mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback); 188 mDefaultSnepServer = new SnepServer(mDefaultSnepCallback, defaultMiu, defaultRwSize); 189 mHandoverServer = new HandoverServer(HANDOVER_SAP, handoverManager, mHandoverCallback); 190 191 if (ECHOSERVER_ENABLED) { 192 mEchoServer = new EchoServer(); 193 } else { 194 mEchoServer = null; 195 } 196 mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 197 mPackageManager = context.getPackageManager(); 198 mContext = context; 199 mEventListener = new P2pEventManager(context, this); 200 mHandler = new Handler(this); 201 mLinkState = LINK_STATE_DOWN; 202 mSendState = SEND_STATE_NOTHING_TO_SEND; 203 mIsSendEnabled = false; 204 mIsReceiveEnabled = false; 205 mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE); 206 mFirstBeam = mPrefs.getBoolean(NfcService.PREF_FIRST_BEAM, true); 207 mHandoverManager = handoverManager; 208 mDefaultMiu = defaultMiu; 209 mDefaultRwSize = defaultRwSize; 210 } 211 212 /** 213 * May be called from any thread. 214 * Assumes that NFC is already on if any parameter is true. 215 */ enableDisable(boolean sendEnable, boolean receiveEnable)216 public void enableDisable(boolean sendEnable, boolean receiveEnable) { 217 synchronized (this) { 218 if (!mIsReceiveEnabled && receiveEnable) { 219 mDefaultSnepServer.start(); 220 mNdefPushServer.start(); 221 mHandoverServer.start(); 222 if (mEchoServer != null) { 223 mHandler.sendEmptyMessage(MSG_START_ECHOSERVER); 224 } 225 } else if (mIsReceiveEnabled && !receiveEnable) { 226 mDefaultSnepServer.stop(); 227 mNdefPushServer.stop(); 228 mHandoverServer.stop(); 229 if (mEchoServer != null) { 230 mHandler.sendEmptyMessage(MSG_STOP_ECHOSERVER); 231 } 232 } 233 mIsSendEnabled = sendEnable; 234 mIsReceiveEnabled = receiveEnable; 235 } 236 } 237 238 /** 239 * Set NDEF callback for sending. 240 * May be called from any thread. 241 * NDEF callbacks may be set at any time (even if NFC is 242 * currently off or P2P send is currently off). They will become 243 * active as soon as P2P send is enabled. 244 */ setNdefCallback(INdefPushCallback callbackNdef)245 public void setNdefCallback(INdefPushCallback callbackNdef) { 246 synchronized (this) { 247 mCallbackNdef = callbackNdef; 248 } 249 } 250 251 /** 252 * Must be called on UI Thread. 253 */ onLlcpActivated()254 public void onLlcpActivated() { 255 Log.i(TAG, "LLCP activated"); 256 257 synchronized (P2pLinkManager.this) { 258 if (mEchoServer != null) { 259 mEchoServer.onLlcpActivated(); 260 } 261 262 switch (mLinkState) { 263 case LINK_STATE_DOWN: 264 mLinkState = LINK_STATE_UP; 265 mSendState = SEND_STATE_NOTHING_TO_SEND; 266 if (DBG) Log.d(TAG, "onP2pInRange()"); 267 mEventListener.onP2pInRange(); 268 269 prepareMessageToSend(); 270 if (mMessageToSend != null || 271 (mUrisToSend != null && mHandoverManager.isHandoverSupported())) { 272 mSendState = SEND_STATE_NEED_CONFIRMATION; 273 if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()"); 274 mEventListener.onP2pSendConfirmationRequested(); 275 } 276 break; 277 case LINK_STATE_UP: 278 if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()"); 279 return; 280 case LINK_STATE_DEBOUNCE: 281 mLinkState = LINK_STATE_UP; 282 mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT); 283 284 if (mSendState == SEND_STATE_SENDING) { 285 Log.i(TAG, "Retry send..."); 286 sendNdefMessage(); 287 } 288 break; 289 } 290 } 291 } 292 prepareMessageToSend()293 void prepareMessageToSend() { 294 synchronized (P2pLinkManager.this) { 295 if (!mIsSendEnabled) { 296 mMessageToSend = null; 297 mUrisToSend = null; 298 return; 299 } 300 301 // Try application callback first 302 //TODO: Check that mCallbackNdef refers to the foreground activity 303 if (mCallbackNdef != null) { 304 try { 305 mMessageToSend = mCallbackNdef.createMessage(); 306 mUrisToSend = mCallbackNdef.getUris(); 307 return; 308 } catch (RemoteException e) { 309 // Ignore 310 } 311 } 312 313 // fall back to default NDEF for this activity, unless the 314 // application disabled this explicitly in their manifest. 315 List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1); 316 if (tasks.size() > 0) { 317 String pkg = tasks.get(0).baseActivity.getPackageName(); 318 if (beamDefaultDisabled(pkg)) { 319 Log.d(TAG, "Disabling default Beam behavior"); 320 mMessageToSend = null; 321 } else { 322 mMessageToSend = createDefaultNdef(pkg); 323 } 324 } else { 325 mMessageToSend = null; 326 } 327 if (DBG) Log.d(TAG, "mMessageToSend = " + mMessageToSend); 328 if (DBG) Log.d(TAG, "mUrisToSend = " + mUrisToSend); 329 } 330 } 331 beamDefaultDisabled(String pkgName)332 boolean beamDefaultDisabled(String pkgName) { 333 try { 334 ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgName, 335 PackageManager.GET_META_DATA); 336 if (ai == null || ai.metaData == null) { 337 return false; 338 } 339 return ai.metaData.getBoolean(DISABLE_BEAM_DEFAULT); 340 } catch (NameNotFoundException e) { 341 return false; 342 } 343 } 344 createDefaultNdef(String pkgName)345 NdefMessage createDefaultNdef(String pkgName) { 346 NdefRecord appUri = NdefRecord.createUri(Uri.parse( 347 "http://play.google.com/store/apps/details?id=" + pkgName + "&feature=beam")); 348 NdefRecord appRecord = NdefRecord.createApplicationRecord(pkgName); 349 return new NdefMessage(new NdefRecord[] { appUri, appRecord }); 350 } 351 352 /** 353 * Must be called on UI Thread. 354 */ onLlcpDeactivated()355 public void onLlcpDeactivated() { 356 Log.i(TAG, "LLCP deactivated."); 357 synchronized (this) { 358 if (mEchoServer != null) { 359 mEchoServer.onLlcpDeactivated(); 360 } 361 362 switch (mLinkState) { 363 case LINK_STATE_DOWN: 364 case LINK_STATE_DEBOUNCE: 365 Log.i(TAG, "Duplicate onLlcpDectivated()"); 366 break; 367 case LINK_STATE_UP: 368 // Debounce 369 mLinkState = LINK_STATE_DEBOUNCE; 370 mHandler.sendEmptyMessageDelayed(MSG_DEBOUNCE_TIMEOUT, LINK_DEBOUNCE_MS); 371 cancelSendNdefMessage(); 372 break; 373 } 374 } 375 } 376 onHandoverUnsupported()377 void onHandoverUnsupported() { 378 mHandler.sendEmptyMessage(MSG_HANDOVER_NOT_SUPPORTED); 379 } 380 onSendComplete(NdefMessage msg, long elapsedRealtime)381 void onSendComplete(NdefMessage msg, long elapsedRealtime) { 382 if (mFirstBeam) { 383 EventLogTags.writeNfcFirstShare(); 384 mPrefs.edit().putBoolean(NfcService.PREF_FIRST_BEAM, false).apply(); 385 mFirstBeam = false; 386 } 387 EventLogTags.writeNfcShare(getMessageSize(msg), getMessageTnf(msg), getMessageType(msg), 388 getMessageAarPresent(msg), (int) elapsedRealtime); 389 // Make callbacks on UI thread 390 mHandler.sendEmptyMessage(MSG_SEND_COMPLETE); 391 } 392 sendNdefMessage()393 void sendNdefMessage() { 394 synchronized (this) { 395 cancelSendNdefMessage(); 396 mSendTask = new SendTask(); 397 mSendTask.execute(); 398 } 399 } 400 cancelSendNdefMessage()401 void cancelSendNdefMessage() { 402 synchronized (P2pLinkManager.this) { 403 if (mSendTask != null) { 404 mSendTask.cancel(true); 405 } 406 } 407 } 408 409 final class SendTask extends AsyncTask<Void, Void, Void> { 410 @Override doInBackground(Void... args)411 public Void doInBackground(Void... args) { 412 NdefMessage m; 413 Uri[] uris; 414 boolean result; 415 416 synchronized (P2pLinkManager.this) { 417 if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) { 418 return null; 419 } 420 m = mMessageToSend; 421 uris = mUrisToSend; 422 } 423 424 long time = SystemClock.elapsedRealtime(); 425 426 try { 427 if (DBG) Log.d(TAG, "Sending ndef via SNEP"); 428 429 int snepResult = doSnepProtocol(mHandoverManager, m, uris, 430 mDefaultMiu, mDefaultRwSize); 431 432 switch (snepResult) { 433 case SNEP_HANDOVER_UNSUPPORTED: 434 onHandoverUnsupported(); 435 return null; 436 case SNEP_SUCCESS: 437 result = true; 438 break; 439 case SNEP_FAILURE: 440 result = false; 441 break; 442 default: 443 result = false; 444 } 445 } catch (IOException e) { 446 Log.i(TAG, "Failed to connect over SNEP, trying NPP"); 447 448 if (isCancelled()) { 449 return null; 450 } 451 452 if (m != null) { 453 result = new NdefPushClient().push(m); 454 } else { 455 result = false; 456 } 457 } 458 time = SystemClock.elapsedRealtime() - time; 459 460 if (DBG) Log.d(TAG, "SendTask result=" + result + ", time ms=" + time); 461 462 if (result) { 463 onSendComplete(m, time); 464 } 465 return null; 466 } 467 } 468 doSnepProtocol(HandoverManager handoverManager, NdefMessage msg, Uri[] uris, int miu, int rwSize)469 static int doSnepProtocol(HandoverManager handoverManager, 470 NdefMessage msg, Uri[] uris, int miu, int rwSize) throws IOException { 471 SnepClient snepClient = new SnepClient(miu, rwSize); 472 try { 473 snepClient.connect(); 474 } catch (IOException e) { 475 // Throw exception to fall back to NPP. 476 snepClient.close(); 477 throw new IOException("SNEP not available.", e); 478 } 479 480 try { 481 if (uris != null) { 482 HandoverClient handoverClient = new HandoverClient(); 483 484 NdefMessage response = null; 485 NdefMessage request = handoverManager.createHandoverRequestMessage(); 486 if (request != null) { 487 response = handoverClient.sendHandoverRequest(request); 488 489 if (response == null) { 490 // Remote device may not support handover service, 491 // try the (deprecated) SNEP GET implementation 492 // for devices running Android 4.1 493 SnepMessage snepResponse = snepClient.get(request); 494 response = snepResponse.getNdefMessage(); 495 } 496 } // else, handover not supported 497 if (response != null) { 498 handoverManager.doHandoverUri(uris, response); 499 } else if (msg != null) { 500 // For backwards-compatibility to pre-J devices, 501 // try to push an NDEF message (if any) if the handover GET 502 // does not work. 503 snepClient.put(msg); 504 } else { 505 // We had a failed handover and no alternate message to 506 // send; indicate remote device doesn't support handover. 507 return SNEP_HANDOVER_UNSUPPORTED; 508 } 509 } else if (msg != null) { 510 snepClient.put(msg); 511 } 512 return SNEP_SUCCESS; 513 } catch (IOException e) { 514 // SNEP available but had errors, don't fall back to NPP. 515 } finally { 516 snepClient.close(); 517 } 518 return SNEP_FAILURE; 519 } 520 521 final HandoverServer.Callback mHandoverCallback = new HandoverServer.Callback() { 522 @Override 523 public void onHandoverRequestReceived() { 524 onReceiveHandover(); 525 } 526 }; 527 528 final NdefPushServer.Callback mNppCallback = new NdefPushServer.Callback() { 529 @Override 530 public void onMessageReceived(NdefMessage msg) { 531 onReceiveComplete(msg); 532 } 533 }; 534 535 final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() { 536 @Override 537 public SnepMessage doPut(NdefMessage msg) { 538 onReceiveComplete(msg); 539 return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS); 540 } 541 542 @Override 543 public SnepMessage doGet(int acceptableLength, NdefMessage msg) { 544 // The NFC Forum Default SNEP server is not allowed to respond to 545 // SNEP GET requests - see SNEP 1.0 TS section 6.1. However, 546 // since Android 4.1 used the NFC Forum default server to 547 // implement connection handover, we will support this 548 // until we can deprecate it. 549 NdefMessage response = mHandoverManager.tryHandoverRequest(msg); 550 if (response != null) { 551 onReceiveHandover(); 552 return SnepMessage.getSuccessResponse(response); 553 } else { 554 return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_IMPLEMENTED); 555 } 556 } 557 }; 558 onReceiveHandover()559 void onReceiveHandover() { 560 mHandler.obtainMessage(MSG_RECEIVE_HANDOVER).sendToTarget(); 561 } 562 onReceiveComplete(NdefMessage msg)563 void onReceiveComplete(NdefMessage msg) { 564 EventLogTags.writeNfcNdefReceived(getMessageSize(msg), getMessageTnf(msg), 565 getMessageType(msg), getMessageAarPresent(msg)); 566 // Make callbacks on UI thread 567 mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget(); 568 } 569 570 @Override handleMessage(Message msg)571 public boolean handleMessage(Message msg) { 572 switch (msg.what) { 573 case MSG_START_ECHOSERVER: 574 synchronized (this) { 575 mEchoServer.start(); 576 break; 577 } 578 case MSG_STOP_ECHOSERVER: 579 synchronized (this) { 580 mEchoServer.stop(); 581 break; 582 } 583 case MSG_DEBOUNCE_TIMEOUT: 584 synchronized (this) { 585 if (mLinkState != LINK_STATE_DEBOUNCE) { 586 break; 587 } 588 if (mSendState == SEND_STATE_SENDING) { 589 EventLogTags.writeNfcShareFail(getMessageSize(mMessageToSend), 590 getMessageTnf(mMessageToSend), getMessageType(mMessageToSend), 591 getMessageAarPresent(mMessageToSend)); 592 } 593 if (DBG) Log.d(TAG, "Debounce timeout"); 594 mLinkState = LINK_STATE_DOWN; 595 mSendState = SEND_STATE_NOTHING_TO_SEND; 596 mMessageToSend = null; 597 mUrisToSend = null; 598 if (DBG) Log.d(TAG, "onP2pOutOfRange()"); 599 mEventListener.onP2pOutOfRange(); 600 } 601 break; 602 case MSG_RECEIVE_HANDOVER: 603 // We're going to do a handover request 604 synchronized (this) { 605 if (mLinkState == LINK_STATE_DOWN) { 606 break; 607 } 608 if (mSendState == SEND_STATE_SENDING) { 609 cancelSendNdefMessage(); 610 } 611 mSendState = SEND_STATE_NOTHING_TO_SEND; 612 if (DBG) Log.d(TAG, "onP2pReceiveComplete()"); 613 mEventListener.onP2pReceiveComplete(false); 614 } 615 break; 616 case MSG_RECEIVE_COMPLETE: 617 NdefMessage m = (NdefMessage) msg.obj; 618 synchronized (this) { 619 if (mLinkState == LINK_STATE_DOWN) { 620 break; 621 } 622 if (mSendState == SEND_STATE_SENDING) { 623 cancelSendNdefMessage(); 624 } 625 mSendState = SEND_STATE_NOTHING_TO_SEND; 626 if (DBG) Log.d(TAG, "onP2pReceiveComplete()"); 627 mEventListener.onP2pReceiveComplete(true); 628 NfcService.getInstance().sendMockNdefTag(m); 629 } 630 break; 631 case MSG_HANDOVER_NOT_SUPPORTED: 632 synchronized (P2pLinkManager.this) { 633 mSendTask = null; 634 635 if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) { 636 break; 637 } 638 mSendState = SEND_STATE_NOTHING_TO_SEND; 639 if (DBG) Log.d(TAG, "onP2pHandoverNotSupported()"); 640 mEventListener.onP2pHandoverNotSupported(); 641 } 642 break; 643 case MSG_SEND_COMPLETE: 644 synchronized (P2pLinkManager.this) { 645 mSendTask = null; 646 647 if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) { 648 break; 649 } 650 mSendState = SEND_STATE_NOTHING_TO_SEND; 651 if (DBG) Log.d(TAG, "onP2pSendComplete()"); 652 mEventListener.onP2pSendComplete(); 653 if (mCallbackNdef != null) { 654 try { 655 mCallbackNdef.onNdefPushComplete(); 656 } catch (RemoteException e) { } 657 } 658 } 659 break; 660 } 661 return true; 662 } 663 getMessageSize(NdefMessage msg)664 int getMessageSize(NdefMessage msg) { 665 if (msg != null) { 666 return msg.toByteArray().length; 667 } else { 668 return 0; 669 } 670 } 671 getMessageTnf(NdefMessage msg)672 int getMessageTnf(NdefMessage msg) { 673 if (msg == null) { 674 return NdefRecord.TNF_EMPTY; 675 } 676 NdefRecord records[] = msg.getRecords(); 677 if (records == null || records.length == 0) { 678 return NdefRecord.TNF_EMPTY; 679 } 680 return records[0].getTnf(); 681 } 682 getMessageType(NdefMessage msg)683 String getMessageType(NdefMessage msg) { 684 if (msg == null) { 685 return "null"; 686 } 687 NdefRecord records[] = msg.getRecords(); 688 if (records == null || records.length == 0) { 689 return "null"; 690 } 691 NdefRecord record = records[0]; 692 switch (record.getTnf()) { 693 case NdefRecord.TNF_ABSOLUTE_URI: 694 // The actual URI is in the type field, don't log it 695 return "uri"; 696 case NdefRecord.TNF_EXTERNAL_TYPE: 697 case NdefRecord.TNF_MIME_MEDIA: 698 case NdefRecord.TNF_WELL_KNOWN: 699 return new String(record.getType(), Charsets.UTF_8); 700 default: 701 return "unknown"; 702 } 703 } 704 getMessageAarPresent(NdefMessage msg)705 int getMessageAarPresent(NdefMessage msg) { 706 if (msg == null) { 707 return 0; 708 } 709 NdefRecord records[] = msg.getRecords(); 710 if (records == null) { 711 return 0; 712 } 713 for (NdefRecord record : records) { 714 if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE && 715 Arrays.equals(NdefRecord.RTD_ANDROID_APP, record.getType())) { 716 return 1; 717 } 718 } 719 return 0; 720 } 721 722 @Override onP2pSendConfirmed()723 public void onP2pSendConfirmed() { 724 if (DBG) Log.d(TAG, "onP2pSendConfirmed()"); 725 synchronized (this) { 726 if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_NEED_CONFIRMATION) { 727 return; 728 } 729 mSendState = SEND_STATE_SENDING; 730 if (mLinkState == LINK_STATE_UP) { 731 sendNdefMessage(); 732 } 733 } 734 } 735 sendStateToString(int state)736 static String sendStateToString(int state) { 737 switch (state) { 738 case SEND_STATE_NOTHING_TO_SEND: 739 return "SEND_STATE_NOTHING_TO_SEND"; 740 case SEND_STATE_NEED_CONFIRMATION: 741 return "SEND_STATE_NEED_CONFIRMATION"; 742 case SEND_STATE_SENDING: 743 return "SEND_STATE_SENDING"; 744 default: 745 return "<error>"; 746 } 747 } 748 linkStateToString(int state)749 static String linkStateToString(int state) { 750 switch (state) { 751 case LINK_STATE_DOWN: 752 return "LINK_STATE_DOWN"; 753 case LINK_STATE_DEBOUNCE: 754 return "LINK_STATE_DEBOUNCE"; 755 case LINK_STATE_UP: 756 return "LINK_STATE_UP"; 757 default: 758 return "<error>"; 759 } 760 } 761 dump(FileDescriptor fd, PrintWriter pw, String[] args)762 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 763 synchronized (this) { 764 pw.println("mIsSendEnabled=" + mIsSendEnabled); 765 pw.println("mIsReceiveEnabled=" + mIsReceiveEnabled); 766 pw.println("mLinkState=" + linkStateToString(mLinkState)); 767 pw.println("mSendState=" + sendStateToString(mSendState)); 768 769 pw.println("mCallbackNdef=" + mCallbackNdef); 770 pw.println("mMessageToSend=" + mMessageToSend); 771 pw.println("mUrisToSend=" + mUrisToSend); 772 } 773 } 774 } 775