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