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