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