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