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