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