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