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