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