• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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