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 android.Manifest; 20 import android.app.ActivityManager; 21 import android.bluetooth.BluetoothAdapter; 22 import android.os.UserManager; 23 24 import com.android.nfc.RegisteredComponentCache.ComponentInfo; 25 import com.android.nfc.handover.HandoverDataParser; 26 import com.android.nfc.handover.PeripheralHandoverService; 27 28 import android.app.Activity; 29 import android.app.ActivityManager; 30 import android.app.AlertDialog; 31 import android.app.IActivityManager; 32 import android.app.PendingIntent; 33 import android.app.PendingIntent.CanceledException; 34 import android.content.BroadcastReceiver; 35 import android.content.ComponentName; 36 import android.content.ContentResolver; 37 import android.content.Context; 38 import android.content.DialogInterface; 39 import android.content.Intent; 40 import android.content.IntentFilter; 41 import android.content.pm.PackageManager; 42 import android.content.pm.PackageManager.NameNotFoundException; 43 import android.content.pm.ResolveInfo; 44 import android.content.res.Resources.NotFoundException; 45 import android.net.Uri; 46 import android.nfc.NdefMessage; 47 import android.nfc.NdefRecord; 48 import android.nfc.NfcAdapter; 49 import android.nfc.Tag; 50 import android.nfc.tech.Ndef; 51 import android.nfc.tech.NfcBarcode; 52 import android.os.Handler; 53 import android.os.Message; 54 import android.os.Messenger; 55 import android.os.RemoteException; 56 import android.os.UserHandle; 57 import android.util.Log; 58 import android.view.LayoutInflater; 59 import android.view.View; 60 import android.view.WindowManager; 61 import android.widget.TextView; 62 63 import java.io.FileDescriptor; 64 import java.io.PrintWriter; 65 import java.nio.charset.StandardCharsets; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.concurrent.atomic.AtomicBoolean; 69 import java.util.LinkedList; 70 import java.util.List; 71 import java.util.Locale; 72 73 import android.util.StatsLog; 74 /** 75 * Dispatch of NFC events to start activities 76 */ 77 class NfcDispatcher { 78 private static final boolean DBG = false; 79 private static final String TAG = "NfcDispatcher"; 80 81 static final int DISPATCH_SUCCESS = 1; 82 static final int DISPATCH_FAIL = 2; 83 static final int DISPATCH_UNLOCK = 3; 84 85 private final Context mContext; 86 private final IActivityManager mIActivityManager; 87 private final RegisteredComponentCache mTechListFilters; 88 private final ContentResolver mContentResolver; 89 private final HandoverDataParser mHandoverDataParser; 90 private final String[] mProvisioningMimes; 91 private final ScreenStateHelper mScreenStateHelper; 92 private final NfcUnlockManager mNfcUnlockManager; 93 private final boolean mDeviceSupportsBluetooth; 94 private final Handler mMessageHandler = new MessageHandler(); 95 private final Messenger mMessenger = new Messenger(mMessageHandler); 96 private AtomicBoolean mBluetoothEnabledByNfc = new AtomicBoolean(); 97 98 // Locked on this 99 private PendingIntent mOverrideIntent; 100 private IntentFilter[] mOverrideFilters; 101 private String[][] mOverrideTechLists; 102 private boolean mProvisioningOnly; 103 NfcDispatcher(Context context, HandoverDataParser handoverDataParser, boolean provisionOnly)104 NfcDispatcher(Context context, 105 HandoverDataParser handoverDataParser, 106 boolean provisionOnly) { 107 mContext = context; 108 mIActivityManager = ActivityManager.getService(); 109 mTechListFilters = new RegisteredComponentCache(mContext, 110 NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED); 111 mContentResolver = context.getContentResolver(); 112 mHandoverDataParser = handoverDataParser; 113 mScreenStateHelper = new ScreenStateHelper(context); 114 mNfcUnlockManager = NfcUnlockManager.getInstance(); 115 mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null; 116 117 synchronized (this) { 118 mProvisioningOnly = provisionOnly; 119 } 120 String[] provisionMimes = null; 121 if (provisionOnly) { 122 try { 123 // Get accepted mime-types 124 provisionMimes = context.getResources(). 125 getStringArray(R.array.provisioning_mime_types); 126 } catch (NotFoundException e) { 127 provisionMimes = null; 128 } 129 } 130 mProvisioningMimes = provisionMimes; 131 132 IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 133 mContext.registerReceiver(mBluetoothStatusReceiver, filter); 134 } 135 136 @Override finalize()137 protected void finalize() throws Throwable { 138 mContext.unregisterReceiver(mBluetoothStatusReceiver); 139 super.finalize(); 140 } 141 setForegroundDispatch(PendingIntent intent, IntentFilter[] filters, String[][] techLists)142 public synchronized void setForegroundDispatch(PendingIntent intent, 143 IntentFilter[] filters, String[][] techLists) { 144 if (DBG) Log.d(TAG, "Set Foreground Dispatch"); 145 mOverrideIntent = intent; 146 mOverrideFilters = filters; 147 mOverrideTechLists = techLists; 148 } 149 disableProvisioningMode()150 public synchronized void disableProvisioningMode() { 151 mProvisioningOnly = false; 152 } 153 154 /** 155 * Helper for re-used objects and methods during a single tag dispatch. 156 */ 157 static class DispatchInfo { 158 public final Intent intent; 159 160 final Intent rootIntent; 161 final Uri ndefUri; 162 final String ndefMimeType; 163 final PackageManager packageManager; 164 final Context context; 165 DispatchInfo(Context context, Tag tag, NdefMessage message)166 public DispatchInfo(Context context, Tag tag, NdefMessage message) { 167 intent = new Intent(); 168 intent.putExtra(NfcAdapter.EXTRA_TAG, tag); 169 intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId()); 170 if (message != null) { 171 intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message}); 172 ndefUri = message.getRecords()[0].toUri(); 173 ndefMimeType = message.getRecords()[0].toMimeType(); 174 } else { 175 ndefUri = null; 176 ndefMimeType = null; 177 } 178 179 rootIntent = new Intent(context, NfcRootActivity.class); 180 rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent); 181 rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 182 183 this.context = context; 184 packageManager = context.getPackageManager(); 185 } 186 setNdefIntent()187 public Intent setNdefIntent() { 188 intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED); 189 if (ndefUri != null) { 190 intent.setData(ndefUri); 191 return intent; 192 } else if (ndefMimeType != null) { 193 intent.setType(ndefMimeType); 194 return intent; 195 } 196 return null; 197 } 198 setTechIntent()199 public Intent setTechIntent() { 200 intent.setData(null); 201 intent.setType(null); 202 intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED); 203 return intent; 204 } 205 setTagIntent()206 public Intent setTagIntent() { 207 intent.setData(null); 208 intent.setType(null); 209 intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED); 210 return intent; 211 } 212 hasIntentReceiver()213 public boolean hasIntentReceiver() { 214 return packageManager.queryIntentActivitiesAsUser(intent, 0, 215 ActivityManager.getCurrentUser()).size() > 0; 216 } 217 isWebIntent()218 public boolean isWebIntent() { 219 return ndefUri != null && ndefUri.normalizeScheme().getScheme() != null && 220 ndefUri.normalizeScheme().getScheme().startsWith("http"); 221 } 222 getUri()223 public String getUri() { 224 return ndefUri.toString(); 225 } 226 227 /** 228 * Launch the activity via a (single) NFC root task, so that it 229 * creates a new task stack instead of interfering with any existing 230 * task stack for that activity. 231 * NfcRootActivity acts as the task root, it immediately calls 232 * start activity on the intent it is passed. 233 */ tryStartActivity()234 boolean tryStartActivity() { 235 // Ideally we'd have used startActivityForResult() to determine whether the 236 // NfcRootActivity was able to launch the intent, but startActivityForResult() 237 // is not available on Context. Instead, we query the PackageManager beforehand 238 // to determine if there is an Activity to handle this intent, and base the 239 // result of off that. 240 List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(intent, 0, 241 ActivityManager.getCurrentUser()); 242 if (activities.size() > 0) { 243 context.startActivityAsUser(rootIntent, UserHandle.CURRENT); 244 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH); 245 return true; 246 } 247 return false; 248 } 249 tryStartActivity(Intent intentToStart)250 boolean tryStartActivity(Intent intentToStart) { 251 List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser( 252 intentToStart, 0, ActivityManager.getCurrentUser()); 253 if (activities.size() > 0) { 254 rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart); 255 context.startActivityAsUser(rootIntent, UserHandle.CURRENT); 256 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH); 257 return true; 258 } 259 return false; 260 } 261 } 262 263 /** Returns: 264 * <ul> 265 * <li /> DISPATCH_SUCCESS if dispatched to an activity, 266 * <li /> DISPATCH_FAIL if no activities were found to dispatch to, 267 * <li /> DISPATCH_UNLOCK if the tag was used to unlock the device 268 * </ul> 269 */ dispatchTag(Tag tag)270 public int dispatchTag(Tag tag) { 271 PendingIntent overrideIntent; 272 IntentFilter[] overrideFilters; 273 String[][] overrideTechLists; 274 String[] provisioningMimes; 275 boolean provisioningOnly; 276 NdefMessage message = null; 277 Ndef ndef = Ndef.get(tag); 278 279 synchronized (this) { 280 overrideFilters = mOverrideFilters; 281 overrideIntent = mOverrideIntent; 282 overrideTechLists = mOverrideTechLists; 283 provisioningOnly = mProvisioningOnly; 284 provisioningMimes = mProvisioningMimes; 285 } 286 287 boolean screenUnlocked = false; 288 if (!provisioningOnly && 289 mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) { 290 screenUnlocked = handleNfcUnlock(tag); 291 if (!screenUnlocked) 292 return DISPATCH_FAIL; 293 } 294 295 if (ndef != null) { 296 message = ndef.getCachedNdefMessage(); 297 } else { 298 NfcBarcode nfcBarcode = NfcBarcode.get(tag); 299 if (nfcBarcode != null && nfcBarcode.getType() == NfcBarcode.TYPE_KOVIO) { 300 message = decodeNfcBarcodeUri(nfcBarcode); 301 } 302 } 303 304 if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message); 305 306 DispatchInfo dispatch = new DispatchInfo(mContext, tag, message); 307 308 resumeAppSwitches(); 309 310 if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters, 311 overrideTechLists)) { 312 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH); 313 return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; 314 } 315 316 if (tryPeripheralHandover(message)) { 317 if (DBG) Log.i(TAG, "matched BT HANDOVER"); 318 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__BT_PAIRING); 319 return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; 320 } 321 322 if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) { 323 if (DBG) Log.i(TAG, "matched NFC WPS TOKEN"); 324 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__WIFI_CONNECT); 325 return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; 326 } 327 328 if (provisioningOnly) { 329 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__PROVISION); 330 if (message == null) { 331 // We only allow NDEF-message dispatch in provisioning mode 332 return DISPATCH_FAIL; 333 } 334 // Restrict to mime-types in whitelist. 335 String ndefMimeType = message.getRecords()[0].toMimeType(); 336 if (provisioningMimes == null || 337 !(Arrays.asList(provisioningMimes).contains(ndefMimeType))) { 338 Log.e(TAG, "Dropping NFC intent in provisioning mode."); 339 return DISPATCH_FAIL; 340 } 341 } 342 343 if (tryNdef(dispatch, message)) { 344 return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; 345 } 346 347 if (screenUnlocked) { 348 // We only allow NDEF-based mimeType matching in case of an unlock 349 return DISPATCH_UNLOCK; 350 } 351 352 // Only allow NDEF-based mimeType matching for unlock tags 353 if (tryTech(dispatch, tag)) { 354 return DISPATCH_SUCCESS; 355 } 356 357 dispatch.setTagIntent(); 358 if (dispatch.tryStartActivity()) { 359 if (DBG) Log.i(TAG, "matched TAG"); 360 return DISPATCH_SUCCESS; 361 } 362 363 if (DBG) Log.i(TAG, "no match"); 364 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__OTHERS); 365 return DISPATCH_FAIL; 366 } 367 handleNfcUnlock(Tag tag)368 private boolean handleNfcUnlock(Tag tag) { 369 return mNfcUnlockManager.tryUnlock(tag); 370 } 371 372 /** 373 * Checks for the presence of a URL stored in a tag with tech NfcBarcode. 374 * If found, decodes URL and returns NdefMessage message containing an 375 * NdefRecord containing the decoded URL. If not found, returns null. 376 * 377 * URLs are decoded as follows: 378 * 379 * Ignore first byte (which is 0x80 ORd with a manufacturer ID, corresponding 380 * to ISO/IEC 7816-6). 381 * The second byte describes the payload data format. There are four defined data 382 * format values that identify URL data. Depending on the data format value, the 383 * associated prefix is appended to the URL data: 384 * 385 * 0x01: URL with "http://www." prefix 386 * 0x02: URL with "https://www." prefix 387 * 0x03: URL with "http://" prefix 388 * 0x04: URL with "https://" prefix 389 * 390 * Other data format values do not identify URL data and are not handled by this function. 391 * URL payload is encoded in US-ASCII, following the limitations defined in RFC3987. 392 * see http://www.ietf.org/rfc/rfc3987.txt 393 * 394 * The final two bytes of a tag with tech NfcBarcode are always reserved for CRC data, 395 * and are therefore not part of the payload. They are ignored in the decoding of a URL. 396 * 397 * The default assumption is that the URL occupies the entire payload of the NfcBarcode 398 * ID and all bytes of the NfcBarcode payload are decoded until the CRC (final two bytes) 399 * is reached. However, the OPTIONAL early terminator byte 0xfe can be used to signal 400 * an early end of the URL. Once this function reaches an early terminator byte 0xfe, 401 * URL decoding stops and the NdefMessage is created and returned. Any payload data after 402 * the first early terminator byte is ignored for the purposes of URL decoding. 403 */ decodeNfcBarcodeUri(NfcBarcode nfcBarcode)404 private NdefMessage decodeNfcBarcodeUri(NfcBarcode nfcBarcode) { 405 final byte URI_PREFIX_HTTP_WWW = (byte) 0x01; // "http://www." 406 final byte URI_PREFIX_HTTPS_WWW = (byte) 0x02; // "https://www." 407 final byte URI_PREFIX_HTTP = (byte) 0x03; // "http://" 408 final byte URI_PREFIX_HTTPS = (byte) 0x04; // "https://" 409 410 NdefMessage message = null; 411 byte[] tagId = nfcBarcode.getTag().getId(); 412 // All tags of NfcBarcode technology and Kovio type have lengths of a multiple of 16 bytes 413 if (tagId.length >= 4 414 && (tagId[1] == URI_PREFIX_HTTP_WWW || tagId[1] == URI_PREFIX_HTTPS_WWW 415 || tagId[1] == URI_PREFIX_HTTP || tagId[1] == URI_PREFIX_HTTPS)) { 416 // Look for optional URI terminator (0xfe), used to indicate the end of a URI prior to 417 // the end of the full NfcBarcode payload. No terminator means that the URI occupies the 418 // entire length of the payload field. Exclude checking the CRC in the final two bytes 419 // of the NfcBarcode tagId. 420 int end = 2; 421 for (; end < tagId.length - 2; end++) { 422 if (tagId[end] == (byte) 0xfe) { 423 break; 424 } 425 } 426 byte[] payload = new byte[end - 1]; // Skip also first byte (manufacturer ID) 427 System.arraycopy(tagId, 1, payload, 0, payload.length); 428 NdefRecord uriRecord = new NdefRecord( 429 NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, tagId, payload); 430 message = new NdefMessage(uriRecord); 431 } 432 return message; 433 } 434 tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent, IntentFilter[] overrideFilters, String[][] overrideTechLists)435 boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent, 436 IntentFilter[] overrideFilters, String[][] overrideTechLists) { 437 if (overrideIntent == null) { 438 return false; 439 } 440 Intent intent; 441 442 // NDEF 443 if (message != null) { 444 intent = dispatch.setNdefIntent(); 445 if (intent != null && 446 isFilterMatch(intent, overrideFilters, overrideTechLists != null)) { 447 try { 448 overrideIntent.send(mContext, Activity.RESULT_OK, intent); 449 if (DBG) Log.i(TAG, "matched NDEF override"); 450 return true; 451 } catch (CanceledException e) { 452 return false; 453 } 454 } 455 } 456 457 // TECH 458 intent = dispatch.setTechIntent(); 459 if (isTechMatch(tag, overrideTechLists)) { 460 try { 461 overrideIntent.send(mContext, Activity.RESULT_OK, intent); 462 if (DBG) Log.i(TAG, "matched TECH override"); 463 return true; 464 } catch (CanceledException e) { 465 return false; 466 } 467 } 468 469 // TAG 470 intent = dispatch.setTagIntent(); 471 if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) { 472 try { 473 overrideIntent.send(mContext, Activity.RESULT_OK, intent); 474 if (DBG) Log.i(TAG, "matched TAG override"); 475 return true; 476 } catch (CanceledException e) { 477 return false; 478 } 479 } 480 return false; 481 } 482 isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter)483 boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) { 484 if (filters != null) { 485 for (IntentFilter filter : filters) { 486 if (filter.match(mContentResolver, intent, false, TAG) >= 0) { 487 return true; 488 } 489 } 490 } else if (!hasTechFilter) { 491 return true; // always match if both filters and techlists are null 492 } 493 return false; 494 } 495 isTechMatch(Tag tag, String[][] techLists)496 boolean isTechMatch(Tag tag, String[][] techLists) { 497 if (techLists == null) { 498 return false; 499 } 500 501 String[] tagTechs = tag.getTechList(); 502 Arrays.sort(tagTechs); 503 for (String[] filterTechs : techLists) { 504 if (filterMatch(tagTechs, filterTechs)) { 505 return true; 506 } 507 } 508 return false; 509 } 510 tryNdef(DispatchInfo dispatch, NdefMessage message)511 boolean tryNdef(DispatchInfo dispatch, NdefMessage message) { 512 if (message == null) { 513 return false; 514 } 515 Intent intent = dispatch.setNdefIntent(); 516 517 // Bail out if the intent does not contain filterable NDEF data 518 if (intent == null) return false; 519 520 // Try to start AAR activity with matching filter 521 List<String> aarPackages = extractAarPackages(message); 522 for (String pkg : aarPackages) { 523 dispatch.intent.setPackage(pkg); 524 if (dispatch.tryStartActivity()) { 525 if (DBG) Log.i(TAG, "matched AAR to NDEF"); 526 return true; 527 } 528 } 529 530 // Try to perform regular launch of the first AAR 531 if (aarPackages.size() > 0) { 532 String firstPackage = aarPackages.get(0); 533 PackageManager pm; 534 try { 535 UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser()); 536 pm = mContext.createPackageContextAsUser("android", 0, 537 currentUser).getPackageManager(); 538 } catch (NameNotFoundException e) { 539 Log.e(TAG, "Could not create user package context"); 540 return false; 541 } 542 Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage); 543 if (appLaunchIntent != null) { 544 ResolveInfo ri = pm.resolveActivity(appLaunchIntent, 0); 545 if (ri != null && ri.activityInfo != null && ri.activityInfo.exported && 546 dispatch.tryStartActivity(appLaunchIntent)) { 547 if (DBG) Log.i(TAG, "matched AAR to application launch"); 548 return true; 549 } 550 } 551 // Find the package in Market: 552 Intent marketIntent = getAppSearchIntent(firstPackage); 553 if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) { 554 if (DBG) Log.i(TAG, "matched AAR to market launch"); 555 return true; 556 } 557 } 558 559 // regular launch 560 dispatch.intent.setPackage(null); 561 562 if (dispatch.isWebIntent() && dispatch.hasIntentReceiver()) { 563 if (DBG) Log.i(TAG, "matched Web link - prompting user"); 564 showWebLinkConfirmation(dispatch); 565 StatsLog.write(StatsLog.NFC_TAG_OCCURRED, StatsLog.NFC_TAG_OCCURRED__TYPE__URL); 566 return true; 567 } 568 569 try { 570 UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser()); 571 PackageManager pm = mContext.createPackageContextAsUser("android", 0, 572 currentUser).getPackageManager(); 573 ResolveInfo ri = pm.resolveActivity(intent, 0); 574 575 if (ri != null && ri.activityInfo != null && ri.activityInfo.exported && dispatch.tryStartActivity()) { 576 if (DBG) Log.i(TAG, "matched NDEF"); 577 return true; 578 } 579 } catch (NameNotFoundException ignore) { 580 Log.e(TAG, "Could not create user package context"); 581 } 582 583 return false; 584 } 585 extractAarPackages(NdefMessage message)586 static List<String> extractAarPackages(NdefMessage message) { 587 List<String> aarPackages = new LinkedList<String>(); 588 for (NdefRecord record : message.getRecords()) { 589 String pkg = checkForAar(record); 590 if (pkg != null) { 591 aarPackages.add(pkg); 592 } 593 } 594 return aarPackages; 595 } 596 tryTech(DispatchInfo dispatch, Tag tag)597 boolean tryTech(DispatchInfo dispatch, Tag tag) { 598 dispatch.setTechIntent(); 599 600 String[] tagTechs = tag.getTechList(); 601 Arrays.sort(tagTechs); 602 603 // Standard tech dispatch path 604 ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>(); 605 List<ComponentInfo> registered = mTechListFilters.getComponents(); 606 607 PackageManager pm; 608 try { 609 UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser()); 610 pm = mContext.createPackageContextAsUser("android", 0, 611 currentUser).getPackageManager(); 612 } catch (NameNotFoundException e) { 613 Log.e(TAG, "Could not create user package context"); 614 return false; 615 } 616 // Check each registered activity to see if it matches 617 for (ComponentInfo info : registered) { 618 // Don't allow wild card matching 619 if (filterMatch(tagTechs, info.techs) && 620 isComponentEnabled(pm, info.resolveInfo)) { 621 // Add the activity as a match if it's not already in the list 622 // Check if exported flag is not explicitly set to false to prevent 623 // SecurityExceptions. 624 if (!matches.contains(info.resolveInfo) && info.resolveInfo.activityInfo.exported) { 625 matches.add(info.resolveInfo); 626 } 627 } 628 } 629 630 if (matches.size() == 1) { 631 // Single match, launch directly 632 ResolveInfo info = matches.get(0); 633 dispatch.intent.setClassName(info.activityInfo.packageName, info.activityInfo.name); 634 if (dispatch.tryStartActivity()) { 635 if (DBG) Log.i(TAG, "matched single TECH"); 636 return true; 637 } 638 dispatch.intent.setComponent(null); 639 } else if (matches.size() > 1) { 640 // Multiple matches, show a custom activity chooser dialog 641 Intent intent = new Intent(mContext, TechListChooserActivity.class); 642 intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent); 643 intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS, 644 matches); 645 if (dispatch.tryStartActivity(intent)) { 646 if (DBG) Log.i(TAG, "matched multiple TECH"); 647 return true; 648 } 649 } 650 return false; 651 } 652 tryPeripheralHandover(NdefMessage m)653 public boolean tryPeripheralHandover(NdefMessage m) { 654 if (m == null || !mDeviceSupportsBluetooth) return false; 655 656 if (DBG) Log.d(TAG, "tryHandover(): " + m.toString()); 657 658 HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m); 659 if (handover == null || !handover.valid) return false; 660 if (UserManager.get(mContext).hasUserRestriction( 661 UserManager.DISALLOW_CONFIG_BLUETOOTH, 662 // hasUserRestriction does not support UserHandle.CURRENT 663 UserHandle.of(ActivityManager.getCurrentUser()))) { 664 return false; 665 } 666 667 Intent intent = new Intent(mContext, PeripheralHandoverService.class); 668 intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device); 669 intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name); 670 intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport); 671 if (handover.oobData != null) { 672 intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_OOB_DATA, handover.oobData); 673 } 674 if (handover.uuids != null) { 675 intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_UUIDS, handover.uuids); 676 } 677 if (handover.btClass != null) { 678 intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_CLASS, handover.btClass); 679 } 680 intent.putExtra(PeripheralHandoverService.EXTRA_BT_ENABLED, mBluetoothEnabledByNfc.get()); 681 intent.putExtra(PeripheralHandoverService.EXTRA_CLIENT, mMessenger); 682 mContext.startServiceAsUser(intent, UserHandle.CURRENT); 683 684 return true; 685 } 686 687 688 /** 689 * Tells the ActivityManager to resume allowing app switches. 690 * 691 * If the current app called stopAppSwitches() then our startActivity() can 692 * be delayed for several seconds. This happens with the default home 693 * screen. As a system service we can override this behavior with 694 * resumeAppSwitches(). 695 */ resumeAppSwitches()696 void resumeAppSwitches() { 697 try { 698 mIActivityManager.resumeAppSwitches(); 699 } catch (RemoteException e) { } 700 } 701 702 /** Returns true if the tech list filter matches the techs on the tag */ filterMatch(String[] tagTechs, String[] filterTechs)703 boolean filterMatch(String[] tagTechs, String[] filterTechs) { 704 if (filterTechs == null || filterTechs.length == 0) return false; 705 706 for (String tech : filterTechs) { 707 if (Arrays.binarySearch(tagTechs, tech) < 0) { 708 return false; 709 } 710 } 711 return true; 712 } 713 checkForAar(NdefRecord record)714 static String checkForAar(NdefRecord record) { 715 if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE && 716 Arrays.equals(record.getType(), NdefRecord.RTD_ANDROID_APP)) { 717 return new String(record.getPayload(), StandardCharsets.US_ASCII); 718 } 719 return null; 720 } 721 722 /** 723 * Returns an intent that can be used to find an application not currently 724 * installed on the device. 725 */ getAppSearchIntent(String pkg)726 static Intent getAppSearchIntent(String pkg) { 727 Intent market = new Intent(Intent.ACTION_VIEW); 728 market.setData(Uri.parse("market://details?id=" + pkg)); 729 return market; 730 } 731 isComponentEnabled(PackageManager pm, ResolveInfo info)732 static boolean isComponentEnabled(PackageManager pm, ResolveInfo info) { 733 boolean enabled = false; 734 ComponentName compname = new ComponentName( 735 info.activityInfo.packageName, info.activityInfo.name); 736 try { 737 // Note that getActivityInfo() will internally call 738 // isEnabledLP() to determine whether the component 739 // enabled. If it's not, null is returned. 740 if (pm.getActivityInfo(compname,0) != null) { 741 enabled = true; 742 } 743 } catch (PackageManager.NameNotFoundException e) { 744 enabled = false; 745 } 746 if (!enabled) { 747 Log.d(TAG, "Component not enabled: " + compname); 748 } 749 return enabled; 750 } 751 showWebLinkConfirmation(DispatchInfo dispatch)752 void showWebLinkConfirmation(DispatchInfo dispatch) { 753 if (!mContext.getResources().getBoolean(R.bool.enable_nfc_url_open_dialog)) { 754 dispatch.tryStartActivity(); 755 return; 756 } 757 AlertDialog.Builder builder = new AlertDialog.Builder( 758 mContext.getApplicationContext(), 759 android.R.style.Theme_DeviceDefault_Light_Dialog_Alert); 760 builder.setTitle(R.string.title_confirm_url_open); 761 LayoutInflater inflater = LayoutInflater.from(mContext); 762 View view = inflater.inflate(R.layout.url_open_confirmation, null); 763 if (view != null) { 764 TextView url = view.findViewById(R.id.url_open_confirmation_link); 765 if (url != null) { 766 url.setText(dispatch.getUri()); 767 } 768 builder.setView(view); 769 } 770 builder.setNegativeButton(R.string.cancel, (dialog, which) -> {}); 771 builder.setPositiveButton(R.string.action_confirm_url_open, (dialog, which) -> { 772 dispatch.tryStartActivity(); 773 }); 774 AlertDialog dialog = builder.create(); 775 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 776 dialog.show(); 777 } 778 dump(FileDescriptor fd, PrintWriter pw, String[] args)779 void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 780 synchronized (this) { 781 pw.println("mOverrideIntent=" + mOverrideIntent); 782 pw.println("mOverrideFilters=" + mOverrideFilters); 783 pw.println("mOverrideTechLists=" + mOverrideTechLists); 784 } 785 } 786 787 private class MessageHandler extends Handler { 788 @Override handleMessage(Message msg)789 public void handleMessage(Message msg) { 790 if (DBG) Log.d(TAG, "handleMessage: msg=" + msg); 791 792 switch (msg.what) { 793 case PeripheralHandoverService.MSG_HEADSET_CONNECTED: 794 case PeripheralHandoverService.MSG_HEADSET_NOT_CONNECTED: 795 mBluetoothEnabledByNfc.set(msg.arg1 != 0); 796 break; 797 default: 798 break; 799 } 800 } 801 } 802 803 final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() { 804 @Override 805 public void onReceive(Context context, Intent intent) { 806 String action = intent.getAction(); 807 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 808 handleBluetoothStateChanged(intent); 809 } 810 } 811 812 private void handleBluetoothStateChanged(Intent intent) { 813 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 814 BluetoothAdapter.ERROR); 815 if (state == BluetoothAdapter.STATE_OFF) { 816 mBluetoothEnabledByNfc.set(false); 817 } 818 } 819 }; 820 } 821