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