Home | History | Annotate | Download | only in nfc
      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 
    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
    150     protected void finalize() throws Throwable {
    151         mContext.unregisterReceiver(mBluetoothStatusReceiver);
    152         super.finalize();
    153     }
    154 
    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 
    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 
    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 
    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 
    212         public Intent setTechIntent() {
    213             intent.setData(null);
    214             intent.setType(null);
    215             intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);
    216             return intent;
    217         }
    218 
    219         public Intent setTagIntent() {
    220             intent.setData(null);
    221             intent.setType(null);
    222             intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED);
    223             return intent;
    224         }
    225 
    226         public boolean isWebIntent() {
    227             return ndefUri != null && ndefUri.normalizeScheme().getScheme() != null &&
    228                 ndefUri.normalizeScheme().getScheme().startsWith("http");
    229         }
    230 
    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          */
    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 
    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      */
    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 
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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     */
    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 */
    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 
    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      */
    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 
    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 
    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 
    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
    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