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