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