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.ndefpush.NdefPushClient; 20 import com.android.nfc.ndefpush.NdefPushServer; 21 import com.android.nfc.snep.SnepClient; 22 import com.android.nfc.snep.SnepMessage; 23 import com.android.nfc.snep.SnepServer; 24 25 import android.app.ActivityManager; 26 import android.app.ActivityManager.RunningTaskInfo; 27 import android.content.Context; 28 import android.content.SharedPreferences; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.content.pm.PackageManager.NameNotFoundException; 32 import android.net.Uri; 33 import android.nfc.INdefPushCallback; 34 import android.nfc.NdefMessage; 35 import android.nfc.NdefRecord; 36 import android.os.AsyncTask; 37 import android.os.Handler; 38 import android.os.Message; 39 import android.os.RemoteException; 40 import android.os.SystemClock; 41 import android.provider.ContactsContract.Contacts; 42 import android.provider.ContactsContract.Profile; 43 import android.util.Log; 44 45 import java.io.FileDescriptor; 46 import java.io.IOException; 47 import java.io.PrintWriter; 48 import java.nio.charset.Charsets; 49 import java.util.Arrays; 50 import java.util.List; 51 52 /** 53 * Interface to listen for P2P events. 54 * All callbacks are made from the UI thread. 55 */ 56 interface P2pEventListener { 57 /** 58 * Indicates a P2P device is in range. 59 * <p>onP2pInRange() and onP2pOutOfRange() will always be called 60 * alternately. 61 * <p>All other callbacks will only occur while a P2P device is in range. 62 */ 63 public void onP2pInRange(); 64 65 /** 66 * Called when a NDEF payload is prepared to send, and confirmation is 67 * required. Call Callback.onP2pSendConfirmed() to make the confirmation. 68 */ 69 public void onP2pSendConfirmationRequested(); 70 71 /** 72 * Called to indicate a send was successful. 73 */ 74 public void onP2pSendComplete(); 75 76 /** 77 * Called to indicate a receive was successful. 78 */ 79 public void onP2pReceiveComplete(); 80 81 /** 82 * Indicates the P2P device went out of range. 83 */ 84 public void onP2pOutOfRange(); 85 86 public interface Callback { 87 public void onP2pSendConfirmed(); 88 } 89 } 90 91 /** 92 * Manages sending and receiving NDEF message over LLCP link. 93 * Does simple debouncing of the LLCP link - so that even if the link 94 * drops and returns the user does not know. 95 */ 96 public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { 97 static final String TAG = "NfcP2pLinkManager"; 98 static final boolean DBG = true; 99 100 // TODO dynamically assign SAP values 101 static final int NDEFPUSH_SAP = 0x10; 102 103 static final int LINK_DEBOUNCE_MS = 750; 104 105 static final int MSG_DEBOUNCE_TIMEOUT = 1; 106 static final int MSG_RECEIVE_COMPLETE = 2; 107 static final int MSG_SEND_COMPLETE = 3; 108 109 // values for mLinkState 110 static final int LINK_STATE_DOWN = 1; 111 static final int LINK_STATE_UP = 2; 112 static final int LINK_STATE_DEBOUNCE =3; 113 114 // values for mSendState 115 static final int SEND_STATE_NOTHING_TO_SEND = 1; 116 static final int SEND_STATE_NEED_CONFIRMATION = 2; 117 static final int SEND_STATE_SENDING = 3; 118 119 120 static final Uri PROFILE_URI = Profile.CONTENT_VCARD_URI.buildUpon(). 121 appendQueryParameter(Contacts.QUERY_PARAMETER_VCARD_NO_PHOTO, "true"). 122 build(); 123 124 final NdefPushServer mNdefPushServer; 125 final SnepServer mDefaultSnepServer; 126 final ActivityManager mActivityManager; 127 final PackageManager mPackageManager; 128 final Context mContext; 129 final P2pEventListener mEventListener; 130 final Handler mHandler; 131 132 // Locked on NdefP2pManager.this 133 int mLinkState; 134 int mSendState; // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE 135 boolean mIsSendEnabled; 136 boolean mIsReceiveEnabled; 137 NdefMessage mMessageToSend; // valid during SEND_STATE_NEED_CONFIRMATION or SEND_STATE_SENDING 138 NdefMessage mStaticNdef; 139 INdefPushCallback mCallbackNdef; 140 SendTask mSendTask; 141 SharedPreferences mPrefs; 142 boolean mFirstBeam; 143 144 public P2pLinkManager(Context context) { 145 mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback); 146 mDefaultSnepServer = new SnepServer(mDefaultSnepCallback); 147 mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 148 mPackageManager = context.getPackageManager(); 149 mContext = context; 150 mEventListener = new P2pEventManager(context, this); 151 mHandler = new Handler(this); 152 mLinkState = LINK_STATE_DOWN; 153 mSendState = SEND_STATE_NOTHING_TO_SEND; 154 mIsSendEnabled = false; 155 mIsReceiveEnabled = false; 156 mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE); 157 mFirstBeam = mPrefs.getBoolean(NfcService.PREF_FIRST_BEAM, true); 158 } 159 160 /** 161 * May be called from any thread. 162 * Assumes that NFC is already on if any parameter is true. 163 */ 164 public void enableDisable(boolean sendEnable, boolean receiveEnable) { 165 synchronized (this) { 166 if (!mIsReceiveEnabled && receiveEnable) { 167 mDefaultSnepServer.start(); 168 mNdefPushServer.start(); 169 } else if (mIsReceiveEnabled && !receiveEnable) { 170 mDefaultSnepServer.stop(); 171 mNdefPushServer.stop(); 172 } 173 mIsSendEnabled = sendEnable; 174 mIsReceiveEnabled = receiveEnable; 175 } 176 } 177 178 /** 179 * Set NDEF message or callback for sending. 180 * May be called from any thread. 181 * NDEF messages or callbacks may be set at any time (even if NFC is 182 * currently off or P2P send is currently off). They will become 183 * active as soon as P2P send is enabled. 184 */ 185 public void setNdefToSend(NdefMessage staticNdef, INdefPushCallback callbackNdef) { 186 synchronized (this) { 187 mStaticNdef = staticNdef; 188 mCallbackNdef = callbackNdef; 189 } 190 } 191 192 /** 193 * Must be called on UI Thread. 194 */ 195 public void onLlcpActivated() { 196 Log.i(TAG, "LLCP activated"); 197 198 synchronized (P2pLinkManager.this) { 199 switch (mLinkState) { 200 case LINK_STATE_DOWN: 201 mLinkState = LINK_STATE_UP; 202 mSendState = SEND_STATE_NOTHING_TO_SEND; 203 if (DBG) Log.d(TAG, "onP2pInRange()"); 204 mEventListener.onP2pInRange(); 205 206 prepareMessageToSend(); 207 if (mMessageToSend != null) { 208 mSendState = SEND_STATE_NEED_CONFIRMATION; 209 if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()"); 210 mEventListener.onP2pSendConfirmationRequested(); 211 } 212 break; 213 case LINK_STATE_UP: 214 if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()"); 215 return; 216 case LINK_STATE_DEBOUNCE: 217 mLinkState = LINK_STATE_UP; 218 mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT); 219 220 if (mSendState == SEND_STATE_SENDING) { 221 Log.i(TAG, "Retry send..."); 222 sendNdefMessage(); 223 } 224 break; 225 } 226 } 227 } 228 229 void prepareMessageToSend() { 230 synchronized (P2pLinkManager.this) { 231 if (!mIsSendEnabled) { 232 mMessageToSend = null; 233 return; 234 } 235 236 NdefMessage messageToSend = mStaticNdef; 237 INdefPushCallback callback = mCallbackNdef; 238 239 if (callback != null) { 240 try { 241 messageToSend = callback.createMessage(); 242 } catch (RemoteException e) { 243 // Ignore 244 } 245 } 246 247 if (messageToSend == null) { 248 messageToSend = createDefaultNdef(); 249 } 250 mMessageToSend = messageToSend; 251 } 252 } 253 254 NdefMessage createDefaultNdef() { 255 List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1); 256 if (tasks.size() > 0) { 257 String pkg = tasks.get(0).baseActivity.getPackageName(); 258 try { 259 ApplicationInfo appInfo = mPackageManager.getApplicationInfo(pkg, 0); 260 if (0 == (appInfo.flags & ApplicationInfo.FLAG_SYSTEM)) { 261 NdefRecord appUri = NdefRecord.createUri( 262 Uri.parse("http://market.android.com/search?q=pname:" + pkg)); 263 NdefRecord appRecord = NdefRecord.createApplicationRecord(pkg); 264 return new NdefMessage(new NdefRecord[] { appUri, appRecord }); 265 } 266 } catch (NameNotFoundException e) { 267 Log.e(TAG, "Bad package returned from ActivityManager: " + pkg); 268 } 269 } else { 270 Log.d(TAG, "no foreground activity"); 271 } 272 return null; 273 } 274 275 /** 276 * Must be called on UI Thread. 277 */ 278 public void onLlcpDeactivated() { 279 Log.i(TAG, "LLCP deactivated."); 280 synchronized (this) { 281 switch (mLinkState) { 282 case LINK_STATE_DOWN: 283 case LINK_STATE_DEBOUNCE: 284 Log.i(TAG, "Duplicate onLlcpDectivated()"); 285 break; 286 case LINK_STATE_UP: 287 // Debounce 288 mLinkState = LINK_STATE_DEBOUNCE; 289 mHandler.sendEmptyMessageDelayed(MSG_DEBOUNCE_TIMEOUT, LINK_DEBOUNCE_MS); 290 cancelSendNdefMessage(); 291 break; 292 } 293 } 294 } 295 296 void onSendComplete(NdefMessage msg, long elapsedRealtime) { 297 if (mFirstBeam) { 298 EventLogTags.writeNfcFirstShare(); 299 mPrefs.edit().putBoolean(NfcService.PREF_FIRST_BEAM, false).apply(); 300 mFirstBeam = false; 301 } 302 EventLogTags.writeNfcShare(getMessageSize(msg), getMessageTnf(msg), getMessageType(msg), 303 getMessageAarPresent(msg), (int) elapsedRealtime); 304 // Make callbacks on UI thread 305 mHandler.sendEmptyMessage(MSG_SEND_COMPLETE); 306 } 307 308 void sendNdefMessage() { 309 synchronized (this) { 310 cancelSendNdefMessage(); 311 mSendTask = new SendTask(); 312 mSendTask.execute(); 313 } 314 } 315 316 void cancelSendNdefMessage() { 317 synchronized (P2pLinkManager.this) { 318 if (mSendTask != null) { 319 mSendTask.cancel(true); 320 } 321 } 322 } 323 324 final class SendTask extends AsyncTask<Void, Void, Void> { 325 @Override 326 public Void doInBackground(Void... args) { 327 NdefMessage m; 328 boolean result; 329 330 synchronized (P2pLinkManager.this) { 331 if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) { 332 return null; 333 } 334 m = mMessageToSend; 335 } 336 337 long time = SystemClock.elapsedRealtime(); 338 try { 339 if (DBG) Log.d(TAG, "Sending ndef via SNEP"); 340 result = doSnepProtocol(m); 341 } catch (IOException e) { 342 Log.i(TAG, "Failed to connect over SNEP, trying NPP"); 343 344 if (isCancelled()) { 345 return null; 346 } 347 348 result = new NdefPushClient().push(m); 349 } 350 time = SystemClock.elapsedRealtime() - time; 351 352 if (DBG) Log.d(TAG, "SendTask result=" + result + ", time ms=" + time); 353 354 if (result) { 355 onSendComplete(m, time); 356 } 357 return null; 358 } 359 } 360 361 static boolean doSnepProtocol(NdefMessage msg) throws IOException { 362 SnepClient snepClient = new SnepClient(); 363 try { 364 snepClient.connect(); 365 } catch (IOException e) { 366 // Throw exception to fall back to NPP. 367 snepClient.close(); 368 throw new IOException("SNEP not available.", e); 369 } 370 371 try { 372 snepClient.put(msg); 373 return true; 374 } catch (IOException e) { 375 // SNEP available but had errors, don't fall back to NPP. 376 } finally { 377 snepClient.close(); 378 } 379 return false; 380 } 381 382 final NdefPushServer.Callback mNppCallback = new NdefPushServer.Callback() { 383 @Override 384 public void onMessageReceived(NdefMessage msg) { 385 onReceiveComplete(msg); 386 } 387 }; 388 389 final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() { 390 @Override 391 public SnepMessage doPut(NdefMessage msg) { 392 onReceiveComplete(msg); 393 return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS); 394 } 395 396 @Override 397 public SnepMessage doGet(int acceptableLength, NdefMessage msg) { 398 if (DBG) Log.d(TAG, "GET not supported."); 399 return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_IMPLEMENTED); 400 } 401 }; 402 403 void onReceiveComplete(NdefMessage msg) { 404 EventLogTags.writeNfcNdefReceived(getMessageSize(msg), getMessageTnf(msg), 405 getMessageType(msg), getMessageAarPresent(msg)); 406 // Make callbacks on UI thread 407 mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget(); 408 } 409 410 @Override 411 public boolean handleMessage(Message msg) { 412 switch (msg.what) { 413 case MSG_DEBOUNCE_TIMEOUT: 414 synchronized (this) { 415 if (mLinkState != LINK_STATE_DEBOUNCE) { 416 break; 417 } 418 if (mSendState == SEND_STATE_SENDING) { 419 EventLogTags.writeNfcShareFail(getMessageSize(mMessageToSend), 420 getMessageTnf(mMessageToSend), getMessageType(mMessageToSend), 421 getMessageAarPresent(mMessageToSend)); 422 } 423 if (DBG) Log.d(TAG, "Debounce timeout"); 424 mLinkState = LINK_STATE_DOWN; 425 mSendState = SEND_STATE_NOTHING_TO_SEND; 426 mMessageToSend = null; 427 if (DBG) Log.d(TAG, "onP2pOutOfRange()"); 428 mEventListener.onP2pOutOfRange(); 429 } 430 break; 431 case MSG_RECEIVE_COMPLETE: 432 NdefMessage m = (NdefMessage) msg.obj; 433 synchronized (this) { 434 if (mLinkState == LINK_STATE_DOWN) { 435 break; 436 } 437 if (mSendState == SEND_STATE_SENDING) { 438 cancelSendNdefMessage(); 439 } 440 mSendState = SEND_STATE_NOTHING_TO_SEND; 441 if (DBG) Log.d(TAG, "onP2pReceiveComplete()"); 442 mEventListener.onP2pReceiveComplete(); 443 NfcService.getInstance().sendMockNdefTag(m); 444 } 445 break; 446 case MSG_SEND_COMPLETE: 447 synchronized (P2pLinkManager.this) { 448 mSendTask = null; 449 450 if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) { 451 break; 452 } 453 mSendState = SEND_STATE_NOTHING_TO_SEND; 454 if (DBG) Log.d(TAG, "onP2pSendComplete()"); 455 mEventListener.onP2pSendComplete(); 456 if (mCallbackNdef != null) { 457 try { 458 mCallbackNdef.onNdefPushComplete(); 459 } catch (RemoteException e) { } 460 } 461 mSendTask = null; 462 } 463 break; 464 } 465 return true; 466 } 467 468 int getMessageSize(NdefMessage msg) { 469 if (msg != null) { 470 return msg.toByteArray().length; 471 } else { 472 return 0; 473 } 474 } 475 476 int getMessageTnf(NdefMessage msg) { 477 if (msg == null) { 478 return NdefRecord.TNF_EMPTY; 479 } 480 NdefRecord records[] = msg.getRecords(); 481 if (records == null || records.length == 0) { 482 return NdefRecord.TNF_EMPTY; 483 } 484 return records[0].getTnf(); 485 } 486 487 String getMessageType(NdefMessage msg) { 488 if (msg == null) { 489 return "null"; 490 } 491 NdefRecord records[] = msg.getRecords(); 492 if (records == null || records.length == 0) { 493 return "null"; 494 } 495 NdefRecord record = records[0]; 496 switch (record.getTnf()) { 497 case NdefRecord.TNF_ABSOLUTE_URI: 498 // The actual URI is in the type field, don't log it 499 return "uri"; 500 case NdefRecord.TNF_EXTERNAL_TYPE: 501 case NdefRecord.TNF_MIME_MEDIA: 502 case NdefRecord.TNF_WELL_KNOWN: 503 return new String(record.getType(), Charsets.UTF_8); 504 default: 505 return "unknown"; 506 } 507 } 508 509 int getMessageAarPresent(NdefMessage msg) { 510 if (msg == null) { 511 return 0; 512 } 513 NdefRecord records[] = msg.getRecords(); 514 if (records == null) { 515 return 0; 516 } 517 for (NdefRecord record : records) { 518 if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE && 519 Arrays.equals(NdefRecord.RTD_ANDROID_APP, record.getType())) { 520 return 1; 521 } 522 } 523 return 0; 524 } 525 526 @Override 527 public void onP2pSendConfirmed() { 528 if (DBG) Log.d(TAG, "onP2pSendConfirmed()"); 529 synchronized (this) { 530 if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_NEED_CONFIRMATION) { 531 return; 532 } 533 mSendState = SEND_STATE_SENDING; 534 if (mLinkState == LINK_STATE_UP) { 535 sendNdefMessage(); 536 } 537 } 538 } 539 540 static String sendStateToString(int state) { 541 switch (state) { 542 case SEND_STATE_NOTHING_TO_SEND: 543 return "SEND_STATE_NOTHING_TO_SEND"; 544 case SEND_STATE_NEED_CONFIRMATION: 545 return "SEND_STATE_NEED_CONFIRMATION"; 546 case SEND_STATE_SENDING: 547 return "SEND_STATE_SENDING"; 548 default: 549 return "<error>"; 550 } 551 } 552 553 static String linkStateToString(int state) { 554 switch (state) { 555 case LINK_STATE_DOWN: 556 return "LINK_STATE_DOWN"; 557 case LINK_STATE_DEBOUNCE: 558 return "LINK_STATE_DEBOUNCE"; 559 case LINK_STATE_UP: 560 return "LINK_STATE_UP"; 561 default: 562 return "<error>"; 563 } 564 } 565 566 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 567 synchronized (this) { 568 pw.println("mIsSendEnabled=" + mIsSendEnabled); 569 pw.println("mIsReceiveEnabled=" + mIsReceiveEnabled); 570 pw.println("mLinkState=" + linkStateToString(mLinkState)); 571 pw.println("mSendState=" + sendStateToString(mSendState)); 572 573 pw.println("mStaticNdef=" + mStaticNdef); 574 pw.println("mCallbackNdef=" + mCallbackNdef); 575 pw.println("mMessageToSend=" + mMessageToSend); 576 } 577 } 578 } 579