1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.nfc;
18 
19 import android.Manifest;
20 import android.app.ActivityManager;
21 import android.bluetooth.BluetoothAdapter;
22 import android.os.UserManager;
23 
24 import com.android.nfc.RegisteredComponentCache.ComponentInfo;
25 import com.android.nfc.handover.HandoverDataParser;
26 import com.android.nfc.handover.PeripheralHandoverService;
27 
28 import android.app.Activity;
29 import android.app.ActivityManager;
30 import android.app.AlertDialog;
31 import android.app.IActivityManager;
32 import android.app.PendingIntent;
33 import android.app.PendingIntent.CanceledException;
34 import android.content.BroadcastReceiver;
35 import android.content.ComponentName;
36 import android.content.ContentResolver;
37 import android.content.Context;
38 import android.content.DialogInterface;
39 import android.content.Intent;
40 import android.content.IntentFilter;
41 import android.content.pm.PackageManager;
42 import android.content.pm.PackageManager.NameNotFoundException;
43 import android.content.pm.ResolveInfo;
44 import android.content.res.Resources.NotFoundException;
45 import android.net.Uri;
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.Handler;
53 import android.os.Message;
54 import android.os.Messenger;
55 import android.os.RemoteException;
56 import android.os.UserHandle;
57 import android.util.Log;
58 import android.util.proto.ProtoOutputStream;
59 import android.view.LayoutInflater;
60 import android.view.View;
61 import android.view.WindowManager;
62 import android.widget.TextView;
63 
64 import java.io.FileDescriptor;
65 import java.io.PrintWriter;
66 import java.nio.charset.StandardCharsets;
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.StringJoiner;
70 import java.util.concurrent.atomic.AtomicBoolean;
71 import java.util.LinkedList;
72 import java.util.List;
73 import java.util.Locale;
74 
75 /**
76  * Dispatch of NFC events to start activities
77  */
78 class NfcDispatcher {
79     private static final boolean DBG = false;
80     private static final String TAG = "NfcDispatcher";
81 
82     static final int DISPATCH_SUCCESS = 1;
83     static final int DISPATCH_FAIL = 2;
84     static final int DISPATCH_UNLOCK = 3;
85 
86     private final Context mContext;
87     private final IActivityManager mIActivityManager;
88     private final RegisteredComponentCache mTechListFilters;
89     private final ContentResolver mContentResolver;
90     private final HandoverDataParser mHandoverDataParser;
91     private final String[] mProvisioningMimes;
92     private final ScreenStateHelper mScreenStateHelper;
93     private final NfcUnlockManager mNfcUnlockManager;
94     private final boolean mDeviceSupportsBluetooth;
95     private final Handler mMessageHandler = new MessageHandler();
96     private final Messenger mMessenger = new Messenger(mMessageHandler);
97     private AtomicBoolean mBluetoothEnabledByNfc = new AtomicBoolean();
98 
99     // Locked on this
100     private PendingIntent mOverrideIntent;
101     private IntentFilter[] mOverrideFilters;
102     private String[][] mOverrideTechLists;
103     private boolean mProvisioningOnly;
104 
NfcDispatcher(Context context, HandoverDataParser handoverDataParser, boolean provisionOnly)105     NfcDispatcher(Context context,
106                   HandoverDataParser handoverDataParser,
107                   boolean provisionOnly) {
108         mContext = context;
109         mIActivityManager = ActivityManager.getService();
110         mTechListFilters = new RegisteredComponentCache(mContext,
111                 NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED);
112         mContentResolver = context.getContentResolver();
113         mHandoverDataParser = handoverDataParser;
114         mScreenStateHelper = new ScreenStateHelper(context);
115         mNfcUnlockManager = NfcUnlockManager.getInstance();
116         mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null;
117 
118         synchronized (this) {
119             mProvisioningOnly = provisionOnly;
120         }
121         String[] provisionMimes = null;
122         if (provisionOnly) {
123             try {
124                 // Get accepted mime-types
125                 provisionMimes = context.getResources().
126                         getStringArray(R.array.provisioning_mime_types);
127             } catch (NotFoundException e) {
128                provisionMimes = null;
129             }
130         }
131         mProvisioningMimes = provisionMimes;
132 
133         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
134         mContext.registerReceiver(mBluetoothStatusReceiver, filter);
135     }
136 
137     @Override
finalize()138     protected void finalize() throws Throwable {
139         mContext.unregisterReceiver(mBluetoothStatusReceiver);
140         super.finalize();
141     }
142 
setForegroundDispatch(PendingIntent intent, IntentFilter[] filters, String[][] techLists)143     public synchronized void setForegroundDispatch(PendingIntent intent,
144             IntentFilter[] filters, String[][] techLists) {
145         if (DBG) Log.d(TAG, "Set Foreground Dispatch");
146         mOverrideIntent = intent;
147         mOverrideFilters = filters;
148         mOverrideTechLists = techLists;
149     }
150 
disableProvisioningMode()151     public synchronized void disableProvisioningMode() {
152        mProvisioningOnly = false;
153     }
154 
155     /**
156      * Helper for re-used objects and methods during a single tag dispatch.
157      */
158     static class DispatchInfo {
159         public final Intent intent;
160 
161         final Intent rootIntent;
162         final Uri ndefUri;
163         final String ndefMimeType;
164         final PackageManager packageManager;
165         final Context context;
166 
DispatchInfo(Context context, Tag tag, NdefMessage message)167         public DispatchInfo(Context context, Tag tag, NdefMessage message) {
168             intent = new Intent();
169             intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
170             intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
171             if (message != null) {
172                 intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message});
173                 ndefUri = message.getRecords()[0].toUri();
174                 ndefMimeType = message.getRecords()[0].toMimeType();
175             } else {
176                 ndefUri = null;
177                 ndefMimeType = null;
178             }
179 
180             rootIntent = new Intent(context, NfcRootActivity.class);
181             rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent);
182             rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
183 
184             this.context = context;
185             packageManager = context.getPackageManager();
186         }
187 
setNdefIntent()188         public Intent setNdefIntent() {
189             intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
190             if (ndefUri != null) {
191                 intent.setData(ndefUri);
192                 return intent;
193             } else if (ndefMimeType != null) {
194                 intent.setType(ndefMimeType);
195                 return intent;
196             }
197             return null;
198         }
199 
setTechIntent()200         public Intent setTechIntent() {
201             intent.setData(null);
202             intent.setType(null);
203             intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);
204             return intent;
205         }
206 
setTagIntent()207         public Intent setTagIntent() {
208             intent.setData(null);
209             intent.setType(null);
210             intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED);
211             return intent;
212         }
213 
hasIntentReceiver()214         public boolean hasIntentReceiver() {
215             return packageManager.queryIntentActivitiesAsUser(intent, 0,
216                     ActivityManager.getCurrentUser()).size() > 0;
217         }
218 
isWebIntent()219         public boolean isWebIntent() {
220             return ndefUri != null && ndefUri.normalizeScheme().getScheme() != null &&
221                 ndefUri.normalizeScheme().getScheme().startsWith("http");
222         }
223 
getUri()224         public String getUri() {
225             return ndefUri.toString();
226         }
227 
228         /**
229          * Launch the activity via a (single) NFC root task, so that it
230          * creates a new task stack instead of interfering with any existing
231          * task stack for that activity.
232          * NfcRootActivity acts as the task root, it immediately calls
233          * start activity on the intent it is passed.
234          */
tryStartActivity()235         boolean tryStartActivity() {
236             // Ideally we'd have used startActivityForResult() to determine whether the
237             // NfcRootActivity was able to launch the intent, but startActivityForResult()
238             // is not available on Context. Instead, we query the PackageManager beforehand
239             // to determine if there is an Activity to handle this intent, and base the
240             // result of off that.
241             List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(intent, 0,
242                     ActivityManager.getCurrentUser());
243             if (activities.size() > 0) {
244                 context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
245                 NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED,
246                         NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
247                 return true;
248             }
249             return false;
250         }
251 
tryStartActivity(Intent intentToStart)252         boolean tryStartActivity(Intent intentToStart) {
253             List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(
254                     intentToStart, 0, ActivityManager.getCurrentUser());
255             if (activities.size() > 0) {
256                 rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart);
257                 context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
258                 NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED,
259                         NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
260                 return true;
261             }
262             return false;
263         }
264     }
265 
266     /** Returns:
267      * <ul>
268      *  <li /> DISPATCH_SUCCESS if dispatched to an activity,
269      *  <li /> DISPATCH_FAIL if no activities were found to dispatch to,
270      *  <li /> DISPATCH_UNLOCK if the tag was used to unlock the device
271      * </ul>
272      */
dispatchTag(Tag tag)273     public int dispatchTag(Tag tag) {
274         PendingIntent overrideIntent;
275         IntentFilter[] overrideFilters;
276         String[][] overrideTechLists;
277         String[] provisioningMimes;
278         boolean provisioningOnly;
279         NdefMessage message = null;
280         Ndef ndef = Ndef.get(tag);
281 
282         synchronized (this) {
283             overrideFilters = mOverrideFilters;
284             overrideIntent = mOverrideIntent;
285             overrideTechLists = mOverrideTechLists;
286             provisioningOnly = mProvisioningOnly;
287             provisioningMimes = mProvisioningMimes;
288         }
289 
290         boolean screenUnlocked = false;
291         if (!provisioningOnly &&
292                 mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) {
293             screenUnlocked = handleNfcUnlock(tag);
294             if (!screenUnlocked)
295                 return DISPATCH_FAIL;
296         }
297 
298         if (ndef != null) {
299             message = ndef.getCachedNdefMessage();
300         } else {
301             NfcBarcode nfcBarcode = NfcBarcode.get(tag);
302             if (nfcBarcode != null && nfcBarcode.getType() == NfcBarcode.TYPE_KOVIO) {
303                 message = decodeNfcBarcodeUri(nfcBarcode);
304             }
305         }
306 
307         if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);
308 
309         DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
310 
311         resumeAppSwitches();
312 
313         if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters,
314                 overrideTechLists)) {
315             NfcStatsLog.write(
316                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
317             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
318         }
319 
320         if (tryPeripheralHandover(message)) {
321             if (DBG) Log.i(TAG, "matched BT HANDOVER");
322             NfcStatsLog.write(
323                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__BT_PAIRING);
324             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
325         }
326 
327         if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) {
328             if (DBG) Log.i(TAG, "matched NFC WPS TOKEN");
329             NfcStatsLog.write(
330                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__WIFI_CONNECT);
331             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
332         }
333 
334         if (provisioningOnly) {
335             NfcStatsLog.write(
336                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__PROVISION);
337             if (message == null) {
338                 // We only allow NDEF-message dispatch in provisioning mode
339                 return DISPATCH_FAIL;
340             }
341             // Restrict to mime-types in whitelist.
342             String ndefMimeType = message.getRecords()[0].toMimeType();
343             if (provisioningMimes == null ||
344                     !(Arrays.asList(provisioningMimes).contains(ndefMimeType))) {
345                 Log.e(TAG, "Dropping NFC intent in provisioning mode.");
346                 return DISPATCH_FAIL;
347             }
348         }
349 
350         if (tryNdef(dispatch, message)) {
351             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
352         }
353 
354         if (screenUnlocked) {
355             // We only allow NDEF-based mimeType matching in case of an unlock
356             return DISPATCH_UNLOCK;
357         }
358 
359         // Only allow NDEF-based mimeType matching for unlock tags
360         if (tryTech(dispatch, tag)) {
361             return DISPATCH_SUCCESS;
362         }
363 
364         dispatch.setTagIntent();
365         if (dispatch.tryStartActivity()) {
366             if (DBG) Log.i(TAG, "matched TAG");
367             return DISPATCH_SUCCESS;
368         }
369 
370         if (DBG) Log.i(TAG, "no match");
371         NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__OTHERS);
372         return DISPATCH_FAIL;
373     }
374 
handleNfcUnlock(Tag tag)375     private boolean handleNfcUnlock(Tag tag) {
376         return mNfcUnlockManager.tryUnlock(tag);
377     }
378 
379     /**
380      * Checks for the presence of a URL stored in a tag with tech NfcBarcode.
381      * If found, decodes URL and returns NdefMessage message containing an
382      * NdefRecord containing the decoded URL. If not found, returns null.
383      *
384      * URLs are decoded as follows:
385      *
386      * Ignore first byte (which is 0x80 ORd with a manufacturer ID, corresponding
387      * to ISO/IEC 7816-6).
388      * The second byte describes the payload data format. There are four defined data
389      * format values that identify URL data. Depending on the data format value, the
390      * associated prefix is appended to the URL data:
391      *
392      * 0x01: URL with "http://www." prefix
393      * 0x02: URL with "https://www." prefix
394      * 0x03: URL with "http://" prefix
395      * 0x04: URL with "https://" prefix
396      *
397      * Other data format values do not identify URL data and are not handled by this function.
398      * URL payload is encoded in US-ASCII, following the limitations defined in RFC3987.
399      * see http://www.ietf.org/rfc/rfc3987.txt
400      *
401      * The final two bytes of a tag with tech NfcBarcode are always reserved for CRC data,
402      * and are therefore not part of the payload. They are ignored in the decoding of a URL.
403      *
404      * The default assumption is that the URL occupies the entire payload of the NfcBarcode
405      * ID and all bytes of the NfcBarcode payload are decoded until the CRC (final two bytes)
406      * is reached. However, the OPTIONAL early terminator byte 0xfe can be used to signal
407      * an early end of the URL. Once this function reaches an early terminator byte 0xfe,
408      * URL decoding stops and the NdefMessage is created and returned. Any payload data after
409      * the first early terminator byte is ignored for the purposes of URL decoding.
410      */
decodeNfcBarcodeUri(NfcBarcode nfcBarcode)411     private NdefMessage decodeNfcBarcodeUri(NfcBarcode nfcBarcode) {
412         final byte URI_PREFIX_HTTP_WWW  = (byte) 0x01; // "http://www."
413         final byte URI_PREFIX_HTTPS_WWW = (byte) 0x02; // "https://www."
414         final byte URI_PREFIX_HTTP      = (byte) 0x03; // "http://"
415         final byte URI_PREFIX_HTTPS     = (byte) 0x04; // "https://"
416 
417         NdefMessage message = null;
418         byte[] tagId = nfcBarcode.getTag().getId();
419         // All tags of NfcBarcode technology and Kovio type have lengths of a multiple of 16 bytes
420         if (tagId.length >= 4
421                 && (tagId[1] == URI_PREFIX_HTTP_WWW || tagId[1] == URI_PREFIX_HTTPS_WWW
422                     || tagId[1] == URI_PREFIX_HTTP || tagId[1] == URI_PREFIX_HTTPS)) {
423             // Look for optional URI terminator (0xfe), used to indicate the end of a URI prior to
424             // the end of the full NfcBarcode payload. No terminator means that the URI occupies the
425             // entire length of the payload field. Exclude checking the CRC in the final two bytes
426             // of the NfcBarcode tagId.
427             int end = 2;
428             for (; end < tagId.length - 2; end++) {
429                 if (tagId[end] == (byte) 0xfe) {
430                     break;
431                 }
432             }
433             byte[] payload = new byte[end - 1]; // Skip also first byte (manufacturer ID)
434             System.arraycopy(tagId, 1, payload, 0, payload.length);
435             NdefRecord uriRecord = new NdefRecord(
436                     NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, tagId, payload);
437             message = new NdefMessage(uriRecord);
438         }
439         return message;
440     }
441 
tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent, IntentFilter[] overrideFilters, String[][] overrideTechLists)442     boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent,
443             IntentFilter[] overrideFilters, String[][] overrideTechLists) {
444         if (overrideIntent == null) {
445             return false;
446         }
447         Intent intent;
448 
449         // NDEF
450         if (message != null) {
451             intent = dispatch.setNdefIntent();
452             if (intent != null &&
453                     isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
454                 try {
455                     overrideIntent.send(mContext, Activity.RESULT_OK, intent);
456                     if (DBG) Log.i(TAG, "matched NDEF override");
457                     return true;
458                 } catch (CanceledException e) {
459                     return false;
460                 }
461             }
462         }
463 
464         // TECH
465         intent = dispatch.setTechIntent();
466         if (isTechMatch(tag, overrideTechLists)) {
467             try {
468                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
469                 if (DBG) Log.i(TAG, "matched TECH override");
470                 return true;
471             } catch (CanceledException e) {
472                 return false;
473             }
474         }
475 
476         // TAG
477         intent = dispatch.setTagIntent();
478         if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
479             try {
480                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
481                 if (DBG) Log.i(TAG, "matched TAG override");
482                 return true;
483             } catch (CanceledException e) {
484                 return false;
485             }
486         }
487         return false;
488     }
489 
isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter)490     boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) {
491         if (filters != null) {
492             for (IntentFilter filter : filters) {
493                 if (filter.match(mContentResolver, intent, false, TAG) >= 0) {
494                     return true;
495                 }
496             }
497         } else if (!hasTechFilter) {
498             return true;  // always match if both filters and techlists are null
499         }
500         return false;
501     }
502 
isTechMatch(Tag tag, String[][] techLists)503     boolean isTechMatch(Tag tag, String[][] techLists) {
504         if (techLists == null) {
505             return false;
506         }
507 
508         String[] tagTechs = tag.getTechList();
509         Arrays.sort(tagTechs);
510         for (String[] filterTechs : techLists) {
511             if (filterMatch(tagTechs, filterTechs)) {
512                 return true;
513             }
514         }
515         return false;
516     }
517 
tryNdef(DispatchInfo dispatch, NdefMessage message)518     boolean tryNdef(DispatchInfo dispatch, NdefMessage message) {
519         if (message == null) {
520             return false;
521         }
522         Intent intent = dispatch.setNdefIntent();
523 
524         // Bail out if the intent does not contain filterable NDEF data
525         if (intent == null) return false;
526 
527         // Try to start AAR activity with matching filter
528         List<String> aarPackages = extractAarPackages(message);
529         for (String pkg : aarPackages) {
530             dispatch.intent.setPackage(pkg);
531             if (dispatch.tryStartActivity()) {
532                 if (DBG) Log.i(TAG, "matched AAR to NDEF");
533                 return true;
534             }
535         }
536 
537         // Try to perform regular launch of the first AAR
538         if (aarPackages.size() > 0) {
539             String firstPackage = aarPackages.get(0);
540             PackageManager pm;
541             try {
542                 UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
543                 pm = mContext.createPackageContextAsUser("android", 0,
544                         currentUser).getPackageManager();
545             } catch (NameNotFoundException e) {
546                 Log.e(TAG, "Could not create user package context");
547                 return false;
548             }
549             Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage);
550             if (appLaunchIntent != null) {
551                 ResolveInfo ri = pm.resolveActivity(appLaunchIntent, 0);
552                 if (ri != null && ri.activityInfo != null && ri.activityInfo.exported &&
553                         dispatch.tryStartActivity(appLaunchIntent)) {
554                     if (DBG) Log.i(TAG, "matched AAR to application launch");
555                     return true;
556                 }
557             }
558             // Find the package in Market:
559             Intent marketIntent = getAppSearchIntent(firstPackage);
560             if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) {
561                 if (DBG) Log.i(TAG, "matched AAR to market launch");
562                 return true;
563             }
564         }
565 
566         // regular launch
567         dispatch.intent.setPackage(null);
568 
569         if (dispatch.isWebIntent() && dispatch.hasIntentReceiver()) {
570             if (DBG) Log.i(TAG, "matched Web link - prompting user");
571             showWebLinkConfirmation(dispatch);
572             NfcStatsLog.write(
573                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__URL);
574             return true;
575         }
576 
577         try {
578             UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
579             PackageManager pm = mContext.createPackageContextAsUser("android", 0,
580                         currentUser).getPackageManager();
581             ResolveInfo ri = pm.resolveActivity(intent, 0);
582 
583             if (ri != null && ri.activityInfo != null && ri.activityInfo.exported && dispatch.tryStartActivity()) {
584                 if (DBG) Log.i(TAG, "matched NDEF");
585                 return true;
586             }
587         } catch (NameNotFoundException ignore) {
588             Log.e(TAG, "Could not create user package context");
589         }
590 
591         return false;
592     }
593 
extractAarPackages(NdefMessage message)594     static List<String> extractAarPackages(NdefMessage message) {
595         List<String> aarPackages = new LinkedList<String>();
596         for (NdefRecord record : message.getRecords()) {
597             String pkg = checkForAar(record);
598             if (pkg != null) {
599                 aarPackages.add(pkg);
600             }
601         }
602         return aarPackages;
603     }
604 
tryTech(DispatchInfo dispatch, Tag tag)605     boolean tryTech(DispatchInfo dispatch, Tag tag) {
606         dispatch.setTechIntent();
607 
608         String[] tagTechs = tag.getTechList();
609         Arrays.sort(tagTechs);
610 
611         // Standard tech dispatch path
612         ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
613         List<ComponentInfo> registered = mTechListFilters.getComponents();
614 
615         PackageManager pm;
616         try {
617             UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
618             pm = mContext.createPackageContextAsUser("android", 0,
619                     currentUser).getPackageManager();
620         } catch (NameNotFoundException e) {
621             Log.e(TAG, "Could not create user package context");
622             return false;
623         }
624         // Check each registered activity to see if it matches
625         for (ComponentInfo info : registered) {
626             // Don't allow wild card matching
627             if (filterMatch(tagTechs, info.techs) &&
628                     isComponentEnabled(pm, info.resolveInfo)) {
629                 // Add the activity as a match if it's not already in the list
630                 // Check if exported flag is not explicitly set to false to prevent
631                 // SecurityExceptions.
632                 if (!matches.contains(info.resolveInfo) && info.resolveInfo.activityInfo.exported) {
633                     matches.add(info.resolveInfo);
634                 }
635             }
636         }
637 
638         if (matches.size() == 1) {
639             // Single match, launch directly
640             ResolveInfo info = matches.get(0);
641             dispatch.intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
642             if (dispatch.tryStartActivity()) {
643                 if (DBG) Log.i(TAG, "matched single TECH");
644                 return true;
645             }
646             dispatch.intent.setComponent(null);
647         } else if (matches.size() > 1) {
648             // Multiple matches, show a custom activity chooser dialog
649             Intent intent = new Intent(mContext, TechListChooserActivity.class);
650             intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent);
651             intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
652                     matches);
653             if (dispatch.tryStartActivity(intent)) {
654                 if (DBG) Log.i(TAG, "matched multiple TECH");
655                 return true;
656             }
657         }
658         return false;
659     }
660 
tryPeripheralHandover(NdefMessage m)661     public boolean tryPeripheralHandover(NdefMessage m) {
662         if (m == null || !mDeviceSupportsBluetooth) return false;
663 
664         if (DBG) Log.d(TAG, "tryHandover(): " + m.toString());
665 
666         HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m);
667         if (handover == null || !handover.valid) return false;
668         if (UserManager.get(mContext).hasUserRestriction(
669                 UserManager.DISALLOW_CONFIG_BLUETOOTH,
670                 // hasUserRestriction does not support UserHandle.CURRENT
671                 UserHandle.of(ActivityManager.getCurrentUser()))) {
672             return false;
673         }
674 
675         Intent intent = new Intent(mContext, PeripheralHandoverService.class);
676         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device);
677         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name);
678         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport);
679         if (handover.oobData != null) {
680             intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_OOB_DATA, handover.oobData);
681         }
682         if (handover.uuids != null) {
683             intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_UUIDS, handover.uuids);
684         }
685         if (handover.btClass != null) {
686             intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_CLASS, handover.btClass);
687         }
688         intent.putExtra(PeripheralHandoverService.EXTRA_BT_ENABLED, mBluetoothEnabledByNfc.get());
689         intent.putExtra(PeripheralHandoverService.EXTRA_CLIENT, mMessenger);
690         mContext.startServiceAsUser(intent, UserHandle.CURRENT);
691 
692         return true;
693     }
694 
695 
696     /**
697      * Tells the ActivityManager to resume allowing app switches.
698      *
699      * If the current app called stopAppSwitches() then our startActivity() can
700      * be delayed for several seconds. This happens with the default home
701      * screen.  As a system service we can override this behavior with
702      * resumeAppSwitches().
703     */
resumeAppSwitches()704     void resumeAppSwitches() {
705         try {
706             mIActivityManager.resumeAppSwitches();
707         } catch (RemoteException e) { }
708     }
709 
710     /** Returns true if the tech list filter matches the techs on the tag */
filterMatch(String[] tagTechs, String[] filterTechs)711     boolean filterMatch(String[] tagTechs, String[] filterTechs) {
712         if (filterTechs == null || filterTechs.length == 0) return false;
713 
714         for (String tech : filterTechs) {
715             if (Arrays.binarySearch(tagTechs, tech) < 0) {
716                 return false;
717             }
718         }
719         return true;
720     }
721 
checkForAar(NdefRecord record)722     static String checkForAar(NdefRecord record) {
723         if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE &&
724                 Arrays.equals(record.getType(), NdefRecord.RTD_ANDROID_APP)) {
725             return new String(record.getPayload(), StandardCharsets.US_ASCII);
726         }
727         return null;
728     }
729 
730     /**
731      * Returns an intent that can be used to find an application not currently
732      * installed on the device.
733      */
getAppSearchIntent(String pkg)734     static Intent getAppSearchIntent(String pkg) {
735         Intent market = new Intent(Intent.ACTION_VIEW);
736         market.setData(Uri.parse("market://details?id=" + pkg));
737         return market;
738     }
739 
isComponentEnabled(PackageManager pm, ResolveInfo info)740     static boolean isComponentEnabled(PackageManager pm, ResolveInfo info) {
741         boolean enabled = false;
742         ComponentName compname = new ComponentName(
743                 info.activityInfo.packageName, info.activityInfo.name);
744         try {
745             // Note that getActivityInfo() will internally call
746             // isEnabledLP() to determine whether the component
747             // enabled. If it's not, null is returned.
748             if (pm.getActivityInfo(compname,0) != null) {
749                 enabled = true;
750             }
751         } catch (PackageManager.NameNotFoundException e) {
752             enabled = false;
753         }
754         if (!enabled) {
755             Log.d(TAG, "Component not enabled: " + compname);
756         }
757         return enabled;
758     }
759 
showWebLinkConfirmation(DispatchInfo dispatch)760     void showWebLinkConfirmation(DispatchInfo dispatch) {
761         if (!mContext.getResources().getBoolean(R.bool.enable_nfc_url_open_dialog)) {
762             dispatch.tryStartActivity();
763             return;
764         }
765         AlertDialog.Builder builder = new AlertDialog.Builder(
766                 mContext.getApplicationContext(),
767                 R.style.DialogAlertDayNight);
768         builder.setTitle(R.string.title_confirm_url_open);
769         LayoutInflater inflater = LayoutInflater.from(mContext);
770         View view = inflater.inflate(R.layout.url_open_confirmation, null);
771         if (view != null) {
772             TextView url = view.findViewById(R.id.url_open_confirmation_link);
773             if (url != null) {
774                 url.setText(dispatch.getUri());
775             }
776             builder.setView(view);
777         }
778         builder.setNegativeButton(R.string.cancel, (dialog, which) -> {});
779         builder.setPositiveButton(R.string.action_confirm_url_open, (dialog, which) -> {
780             dispatch.tryStartActivity();
781         });
782         AlertDialog dialog = builder.create();
783         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
784         dialog.show();
785     }
786 
dump(FileDescriptor fd, PrintWriter pw, String[] args)787     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
788         synchronized (this) {
789             pw.println("mOverrideIntent=" + mOverrideIntent);
790             pw.println("mOverrideFilters=" + mOverrideFilters);
791             pw.println("mOverrideTechLists=" + mOverrideTechLists);
792         }
793     }
794 
dumpDebug(ProtoOutputStream proto)795     void dumpDebug(ProtoOutputStream proto) {
796         proto.write(NfcDispatcherProto.DEVICE_SUPPORTS_BLUETOOTH, mDeviceSupportsBluetooth);
797         proto.write(NfcDispatcherProto.BLUETOOTH_ENABLED_BY_NFC, mBluetoothEnabledByNfc.get());
798 
799         synchronized (this) {
800             proto.write(NfcDispatcherProto.PROVISIONING_ONLY, mProvisioningOnly);
801             if (mOverrideTechLists != null) {
802                 StringJoiner techListsJoiner = new StringJoiner(System.lineSeparator());
803                 for (String[] list : mOverrideTechLists) {
804                     techListsJoiner.add(Arrays.toString(list));
805                 }
806                 proto.write(NfcDispatcherProto.OVERRIDE_TECH_LISTS, techListsJoiner.toString());
807             }
808             if (mOverrideIntent != null) {
809                 mOverrideIntent.dumpDebug(proto, NfcDispatcherProto.OVERRIDE_INTENT);
810             }
811             if (mOverrideFilters != null) {
812                 for (IntentFilter filter : mOverrideFilters) {
813                     filter.dumpDebug(proto, NfcDispatcherProto.OVERRIDE_FILTERS);
814                 }
815             }
816         }
817     }
818 
819     private class MessageHandler extends Handler {
820         @Override
handleMessage(Message msg)821         public void handleMessage(Message msg) {
822             if (DBG) Log.d(TAG, "handleMessage: msg=" + msg);
823 
824             switch (msg.what) {
825                 case PeripheralHandoverService.MSG_HEADSET_CONNECTED:
826                 case PeripheralHandoverService.MSG_HEADSET_NOT_CONNECTED:
827                     mBluetoothEnabledByNfc.set(msg.arg1 != 0);
828                     break;
829                 default:
830                     break;
831             }
832         }
833     }
834 
835     final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
836         @Override
837         public void onReceive(Context context, Intent intent) {
838             String action = intent.getAction();
839             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
840                 handleBluetoothStateChanged(intent);
841             }
842         }
843 
844         private void handleBluetoothStateChanged(Intent intent) {
845             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
846                     BluetoothAdapter.ERROR);
847             if (state == BluetoothAdapter.STATE_OFF) {
848                 mBluetoothEnabledByNfc.set(false);
849             }
850         }
851     };
852 }
853