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