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.bluetooth.BluetoothAdapter;
     20 
     21 import com.android.nfc.RegisteredComponentCache.ComponentInfo;
     22 import com.android.nfc.handover.HandoverDataParser;
     23 import com.android.nfc.handover.PeripheralHandoverService;
     24 
     25 import android.app.Activity;
     26 import android.app.ActivityManager;
     27 import android.app.ActivityManagerNative;
     28 import android.app.IActivityManager;
     29 import android.app.PendingIntent;
     30 import android.app.PendingIntent.CanceledException;
     31 import android.content.ComponentName;
     32 import android.content.ContentResolver;
     33 import android.content.Context;
     34 import android.content.Intent;
     35 import android.content.IntentFilter;
     36 import android.content.pm.PackageManager;
     37 import android.content.pm.PackageManager.NameNotFoundException;
     38 import android.content.pm.ResolveInfo;
     39 import android.content.res.Resources.NotFoundException;
     40 import android.net.Uri;
     41 import android.nfc.NdefMessage;
     42 import android.nfc.NdefRecord;
     43 import android.nfc.NfcAdapter;
     44 import android.nfc.Tag;
     45 import android.nfc.tech.Ndef;
     46 import android.nfc.tech.NfcBarcode;
     47 import android.os.RemoteException;
     48 import android.os.UserHandle;
     49 import android.util.Log;
     50 
     51 import java.io.FileDescriptor;
     52 import java.io.PrintWriter;
     53 import java.nio.charset.StandardCharsets;
     54 import java.util.ArrayList;
     55 import java.util.Arrays;
     56 import java.util.LinkedList;
     57 import java.util.List;
     58 
     59 /**
     60  * Dispatch of NFC events to start activities
     61  */
     62 class NfcDispatcher {
     63     private static final boolean DBG = false;
     64     private static final String TAG = "NfcDispatcher";
     65 
     66     static final int DISPATCH_SUCCESS = 1;
     67     static final int DISPATCH_FAIL = 2;
     68     static final int DISPATCH_UNLOCK = 3;
     69 
     70     private final Context mContext;
     71     private final IActivityManager mIActivityManager;
     72     private final RegisteredComponentCache mTechListFilters;
     73     private final ContentResolver mContentResolver;
     74     private final HandoverDataParser mHandoverDataParser;
     75     private final String[] mProvisioningMimes;
     76     private final ScreenStateHelper mScreenStateHelper;
     77     private final NfcUnlockManager mNfcUnlockManager;
     78     private final boolean mDeviceSupportsBluetooth;
     79 
     80     // Locked on this
     81     private PendingIntent mOverrideIntent;
     82     private IntentFilter[] mOverrideFilters;
     83     private String[][] mOverrideTechLists;
     84     private boolean mProvisioningOnly;
     85 
     86     NfcDispatcher(Context context,
     87                   HandoverDataParser handoverDataParser,
     88                   boolean provisionOnly) {
     89         mContext = context;
     90         mIActivityManager = ActivityManagerNative.getDefault();
     91         mTechListFilters = new RegisteredComponentCache(mContext,
     92                 NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED);
     93         mContentResolver = context.getContentResolver();
     94         mHandoverDataParser = handoverDataParser;
     95         mScreenStateHelper = new ScreenStateHelper(context);
     96         mNfcUnlockManager = NfcUnlockManager.getInstance();
     97         mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null;
     98 
     99         synchronized (this) {
    100             mProvisioningOnly = provisionOnly;
    101         }
    102         String[] provisionMimes = null;
    103         if (provisionOnly) {
    104             try {
    105                 // Get accepted mime-types
    106                 provisionMimes = context.getResources().
    107                         getStringArray(R.array.provisioning_mime_types);
    108             } catch (NotFoundException e) {
    109                provisionMimes = null;
    110             }
    111         }
    112         mProvisioningMimes = provisionMimes;
    113     }
    114 
    115     public synchronized void setForegroundDispatch(PendingIntent intent,
    116             IntentFilter[] filters, String[][] techLists) {
    117         if (DBG) Log.d(TAG, "Set Foreground Dispatch");
    118         mOverrideIntent = intent;
    119         mOverrideFilters = filters;
    120         mOverrideTechLists = techLists;
    121     }
    122 
    123     public synchronized void disableProvisioningMode() {
    124        mProvisioningOnly = false;
    125     }
    126 
    127     /**
    128      * Helper for re-used objects and methods during a single tag dispatch.
    129      */
    130     static class DispatchInfo {
    131         public final Intent intent;
    132 
    133         final Intent rootIntent;
    134         final Uri ndefUri;
    135         final String ndefMimeType;
    136         final PackageManager packageManager;
    137         final Context context;
    138 
    139         public DispatchInfo(Context context, Tag tag, NdefMessage message) {
    140             intent = new Intent();
    141             intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
    142             intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
    143             if (message != null) {
    144                 intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message});
    145                 ndefUri = message.getRecords()[0].toUri();
    146                 ndefMimeType = message.getRecords()[0].toMimeType();
    147             } else {
    148                 ndefUri = null;
    149                 ndefMimeType = null;
    150             }
    151 
    152             rootIntent = new Intent(context, NfcRootActivity.class);
    153             rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent);
    154             rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    155 
    156             this.context = context;
    157             packageManager = context.getPackageManager();
    158         }
    159 
    160         public Intent setNdefIntent() {
    161             intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
    162             if (ndefUri != null) {
    163                 intent.setData(ndefUri);
    164                 return intent;
    165             } else if (ndefMimeType != null) {
    166                 intent.setType(ndefMimeType);
    167                 return intent;
    168             }
    169             return null;
    170         }
    171 
    172         public Intent setTechIntent() {
    173             intent.setData(null);
    174             intent.setType(null);
    175             intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);
    176             return intent;
    177         }
    178 
    179         public Intent setTagIntent() {
    180             intent.setData(null);
    181             intent.setType(null);
    182             intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED);
    183             return intent;
    184         }
    185 
    186         /**
    187          * Launch the activity via a (single) NFC root task, so that it
    188          * creates a new task stack instead of interfering with any existing
    189          * task stack for that activity.
    190          * NfcRootActivity acts as the task root, it immediately calls
    191          * start activity on the intent it is passed.
    192          */
    193         boolean tryStartActivity() {
    194             // Ideally we'd have used startActivityForResult() to determine whether the
    195             // NfcRootActivity was able to launch the intent, but startActivityForResult()
    196             // is not available on Context. Instead, we query the PackageManager beforehand
    197             // to determine if there is an Activity to handle this intent, and base the
    198             // result of off that.
    199             List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(intent, 0,
    200                     ActivityManager.getCurrentUser());
    201             if (activities.size() > 0) {
    202                 context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
    203                 return true;
    204             }
    205             return false;
    206         }
    207 
    208         boolean tryStartActivity(Intent intentToStart) {
    209             List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(
    210                     intentToStart, 0, ActivityManager.getCurrentUser());
    211             if (activities.size() > 0) {
    212                 rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart);
    213                 context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
    214                 return true;
    215             }
    216             return false;
    217         }
    218     }
    219 
    220     /** Returns:
    221      * <ul>
    222      *  <li /> DISPATCH_SUCCESS if dispatched to an activity,
    223      *  <li /> DISPATCH_FAIL if no activities were found to dispatch to,
    224      *  <li /> DISPATCH_UNLOCK if the tag was used to unlock the device
    225      * </ul>
    226      */
    227     public int dispatchTag(Tag tag) {
    228         PendingIntent overrideIntent;
    229         IntentFilter[] overrideFilters;
    230         String[][] overrideTechLists;
    231         boolean provisioningOnly;
    232 
    233         synchronized (this) {
    234             overrideFilters = mOverrideFilters;
    235             overrideIntent = mOverrideIntent;
    236             overrideTechLists = mOverrideTechLists;
    237             provisioningOnly = mProvisioningOnly;
    238         }
    239 
    240         boolean screenUnlocked = false;
    241         if (!provisioningOnly &&
    242                 mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) {
    243             screenUnlocked = handleNfcUnlock(tag);
    244             if (!screenUnlocked) {
    245                 return DISPATCH_FAIL;
    246             }
    247         }
    248 
    249         NdefMessage message = null;
    250         Ndef ndef = Ndef.get(tag);
    251         if (ndef != null) {
    252             message = ndef.getCachedNdefMessage();
    253         } else {
    254             NfcBarcode nfcBarcode = NfcBarcode.get(tag);
    255             if (nfcBarcode != null && nfcBarcode.getType() == NfcBarcode.TYPE_KOVIO) {
    256                 message = decodeNfcBarcodeUri(nfcBarcode);
    257             }
    258         }
    259 
    260         if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);
    261 
    262         DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
    263 
    264         resumeAppSwitches();
    265 
    266         if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters,
    267                 overrideTechLists)) {
    268             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
    269         }
    270 
    271         if (tryPeripheralHandover(message)) {
    272             if (DBG) Log.i(TAG, "matched BT HANDOVER");
    273             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
    274         }
    275 
    276         if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) {
    277             if (DBG) Log.i(TAG, "matched NFC WPS TOKEN");
    278             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
    279         }
    280 
    281         if (tryNdef(dispatch, message, provisioningOnly)) {
    282             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
    283         }
    284 
    285         if (screenUnlocked) {
    286             // We only allow NDEF-based mimeType matching in case of an unlock
    287             return DISPATCH_UNLOCK;
    288         }
    289 
    290         if (provisioningOnly) {
    291             // We only allow NDEF-based mimeType matching
    292             return DISPATCH_FAIL;
    293         }
    294 
    295         // Only allow NDEF-based mimeType matching for unlock tags
    296         if (tryTech(dispatch, tag)) {
    297             return DISPATCH_SUCCESS;
    298         }
    299 
    300         dispatch.setTagIntent();
    301         if (dispatch.tryStartActivity()) {
    302             if (DBG) Log.i(TAG, "matched TAG");
    303             return DISPATCH_SUCCESS;
    304         }
    305 
    306         if (DBG) Log.i(TAG, "no match");
    307         return DISPATCH_FAIL;
    308     }
    309 
    310     private boolean handleNfcUnlock(Tag tag) {
    311         return mNfcUnlockManager.tryUnlock(tag);
    312     }
    313 
    314     /**
    315      * Checks for the presence of a URL stored in a tag with tech NfcBarcode.
    316      * If found, decodes URL and returns NdefMessage message containing an
    317      * NdefRecord containing the decoded URL. If not found, returns null.
    318      *
    319      * URLs are decoded as follows:
    320      *
    321      * Ignore first byte (which is 0x80 ORd with a manufacturer ID, corresponding
    322      * to ISO/IEC 7816-6).
    323      * The second byte describes the payload data format. There are four defined data
    324      * format values that identify URL data. Depending on the data format value, the
    325      * associated prefix is appended to the URL data:
    326      *
    327      * 0x01: URL with "http://www." prefix
    328      * 0x02: URL with "https://www." prefix
    329      * 0x03: URL with "http://" prefix
    330      * 0x04: URL with "https://" prefix
    331      *
    332      * Other data format values do not identify URL data and are not handled by this function.
    333      * URL payload is encoded in US-ASCII, following the limitations defined in RFC3987.
    334      * see http://www.ietf.org/rfc/rfc3987.txt
    335      *
    336      * The final two bytes of a tag with tech NfcBarcode are always reserved for CRC data,
    337      * and are therefore not part of the payload. They are ignored in the decoding of a URL.
    338      *
    339      * The default assumption is that the URL occupies the entire payload of the NfcBarcode
    340      * ID and all bytes of the NfcBarcode payload are decoded until the CRC (final two bytes)
    341      * is reached. However, the OPTIONAL early terminator byte 0xfe can be used to signal
    342      * an early end of the URL. Once this function reaches an early terminator byte 0xfe,
    343      * URL decoding stops and the NdefMessage is created and returned. Any payload data after
    344      * the first early terminator byte is ignored for the purposes of URL decoding.
    345      */
    346     private NdefMessage decodeNfcBarcodeUri(NfcBarcode nfcBarcode) {
    347         final byte URI_PREFIX_HTTP_WWW  = (byte) 0x01; // "http://www."
    348         final byte URI_PREFIX_HTTPS_WWW = (byte) 0x02; // "https://www."
    349         final byte URI_PREFIX_HTTP      = (byte) 0x03; // "http://"
    350         final byte URI_PREFIX_HTTPS     = (byte) 0x04; // "https://"
    351 
    352         NdefMessage message = null;
    353         byte[] tagId = nfcBarcode.getTag().getId();
    354         // All tags of NfcBarcode technology and Kovio type have lengths of a multiple of 16 bytes
    355         if (tagId.length >= 4
    356                 && (tagId[1] == URI_PREFIX_HTTP_WWW || tagId[1] == URI_PREFIX_HTTPS_WWW
    357                     || tagId[1] == URI_PREFIX_HTTP || tagId[1] == URI_PREFIX_HTTPS)) {
    358             // Look for optional URI terminator (0xfe), used to indicate the end of a URI prior to
    359             // the end of the full NfcBarcode payload. No terminator means that the URI occupies the
    360             // entire length of the payload field. Exclude checking the CRC in the final two bytes
    361             // of the NfcBarcode tagId.
    362             int end = 2;
    363             for (; end < tagId.length - 2; end++) {
    364                 if (tagId[end] == (byte) 0xfe) {
    365                     break;
    366                 }
    367             }
    368             byte[] payload = new byte[end - 1]; // Skip also first byte (manufacturer ID)
    369             System.arraycopy(tagId, 1, payload, 0, payload.length);
    370             NdefRecord uriRecord = new NdefRecord(
    371                     NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, tagId, payload);
    372             message = new NdefMessage(uriRecord);
    373         }
    374         return message;
    375     }
    376 
    377     boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent,
    378             IntentFilter[] overrideFilters, String[][] overrideTechLists) {
    379         if (overrideIntent == null) {
    380             return false;
    381         }
    382         Intent intent;
    383 
    384         // NDEF
    385         if (message != null) {
    386             intent = dispatch.setNdefIntent();
    387             if (intent != null &&
    388                     isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
    389                 try {
    390                     overrideIntent.send(mContext, Activity.RESULT_OK, intent);
    391                     if (DBG) Log.i(TAG, "matched NDEF override");
    392                     return true;
    393                 } catch (CanceledException e) {
    394                     return false;
    395                 }
    396             }
    397         }
    398 
    399         // TECH
    400         intent = dispatch.setTechIntent();
    401         if (isTechMatch(tag, overrideTechLists)) {
    402             try {
    403                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
    404                 if (DBG) Log.i(TAG, "matched TECH override");
    405                 return true;
    406             } catch (CanceledException e) {
    407                 return false;
    408             }
    409         }
    410 
    411         // TAG
    412         intent = dispatch.setTagIntent();
    413         if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
    414             try {
    415                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
    416                 if (DBG) Log.i(TAG, "matched TAG override");
    417                 return true;
    418             } catch (CanceledException e) {
    419                 return false;
    420             }
    421         }
    422         return false;
    423     }
    424 
    425     boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) {
    426         if (filters != null) {
    427             for (IntentFilter filter : filters) {
    428                 if (filter.match(mContentResolver, intent, false, TAG) >= 0) {
    429                     return true;
    430                 }
    431             }
    432         } else if (!hasTechFilter) {
    433             return true;  // always match if both filters and techlists are null
    434         }
    435         return false;
    436     }
    437 
    438     boolean isTechMatch(Tag tag, String[][] techLists) {
    439         if (techLists == null) {
    440             return false;
    441         }
    442 
    443         String[] tagTechs = tag.getTechList();
    444         Arrays.sort(tagTechs);
    445         for (String[] filterTechs : techLists) {
    446             if (filterMatch(tagTechs, filterTechs)) {
    447                 return true;
    448             }
    449         }
    450         return false;
    451     }
    452 
    453     boolean tryNdef(DispatchInfo dispatch, NdefMessage message, boolean provisioningOnly) {
    454         if (message == null) {
    455             return false;
    456         }
    457         Intent intent = dispatch.setNdefIntent();
    458 
    459         // Bail out if the intent does not contain filterable NDEF data
    460         if (intent == null) return false;
    461 
    462         if (provisioningOnly) {
    463             if (mProvisioningMimes == null ||
    464                     !(Arrays.asList(mProvisioningMimes).contains(intent.getType()))) {
    465                 Log.e(TAG, "Dropping NFC intent in provisioning mode.");
    466                 return false;
    467             }
    468         }
    469 
    470         // Try to start AAR activity with matching filter
    471         List<String> aarPackages = extractAarPackages(message);
    472         for (String pkg : aarPackages) {
    473             dispatch.intent.setPackage(pkg);
    474             if (dispatch.tryStartActivity()) {
    475                 if (DBG) Log.i(TAG, "matched AAR to NDEF");
    476                 return true;
    477             }
    478         }
    479 
    480         // Try to perform regular launch of the first AAR
    481         if (aarPackages.size() > 0) {
    482             String firstPackage = aarPackages.get(0);
    483             PackageManager pm;
    484             try {
    485                 UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
    486                 pm = mContext.createPackageContextAsUser("android", 0,
    487                         currentUser).getPackageManager();
    488             } catch (NameNotFoundException e) {
    489                 Log.e(TAG, "Could not create user package context");
    490                 return false;
    491             }
    492             Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage);
    493             if (appLaunchIntent != null && dispatch.tryStartActivity(appLaunchIntent)) {
    494                 if (DBG) Log.i(TAG, "matched AAR to application launch");
    495                 return true;
    496             }
    497             // Find the package in Market:
    498             Intent marketIntent = getAppSearchIntent(firstPackage);
    499             if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) {
    500                 if (DBG) Log.i(TAG, "matched AAR to market launch");
    501                 return true;
    502             }
    503         }
    504 
    505         // regular launch
    506         dispatch.intent.setPackage(null);
    507         if (dispatch.tryStartActivity()) {
    508             if (DBG) Log.i(TAG, "matched NDEF");
    509             return true;
    510         }
    511 
    512         return false;
    513     }
    514 
    515     static List<String> extractAarPackages(NdefMessage message) {
    516         List<String> aarPackages = new LinkedList<String>();
    517         for (NdefRecord record : message.getRecords()) {
    518             String pkg = checkForAar(record);
    519             if (pkg != null) {
    520                 aarPackages.add(pkg);
    521             }
    522         }
    523         return aarPackages;
    524     }
    525 
    526     boolean tryTech(DispatchInfo dispatch, Tag tag) {
    527         dispatch.setTechIntent();
    528 
    529         String[] tagTechs = tag.getTechList();
    530         Arrays.sort(tagTechs);
    531 
    532         // Standard tech dispatch path
    533         ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
    534         List<ComponentInfo> registered = mTechListFilters.getComponents();
    535 
    536         PackageManager pm;
    537         try {
    538             UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser());
    539             pm = mContext.createPackageContextAsUser("android", 0,
    540                     currentUser).getPackageManager();
    541         } catch (NameNotFoundException e) {
    542             Log.e(TAG, "Could not create user package context");
    543             return false;
    544         }
    545         // Check each registered activity to see if it matches
    546         for (ComponentInfo info : registered) {
    547             // Don't allow wild card matching
    548             if (filterMatch(tagTechs, info.techs) &&
    549                     isComponentEnabled(pm, info.resolveInfo)) {
    550                 // Add the activity as a match if it's not already in the list
    551                 if (!matches.contains(info.resolveInfo)) {
    552                     matches.add(info.resolveInfo);
    553                 }
    554             }
    555         }
    556 
    557         if (matches.size() == 1) {
    558             // Single match, launch directly
    559             ResolveInfo info = matches.get(0);
    560             dispatch.intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
    561             if (dispatch.tryStartActivity()) {
    562                 if (DBG) Log.i(TAG, "matched single TECH");
    563                 return true;
    564             }
    565             dispatch.intent.setComponent(null);
    566         } else if (matches.size() > 1) {
    567             // Multiple matches, show a custom activity chooser dialog
    568             Intent intent = new Intent(mContext, TechListChooserActivity.class);
    569             intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent);
    570             intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
    571                     matches);
    572             if (dispatch.tryStartActivity(intent)) {
    573                 if (DBG) Log.i(TAG, "matched multiple TECH");
    574                 return true;
    575             }
    576         }
    577         return false;
    578     }
    579 
    580     public boolean tryPeripheralHandover(NdefMessage m) {
    581         if (m == null || !mDeviceSupportsBluetooth) return false;
    582 
    583         if (DBG) Log.d(TAG, "tryHandover(): " + m.toString());
    584 
    585         HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m);
    586         if (handover == null || !handover.valid) return false;
    587 
    588         Intent intent = new Intent(mContext, PeripheralHandoverService.class);
    589         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device);
    590         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name);
    591         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport);
    592         mContext.startServiceAsUser(intent, UserHandle.CURRENT);
    593 
    594         return true;
    595     }
    596 
    597 
    598     /**
    599      * Tells the ActivityManager to resume allowing app switches.
    600      *
    601      * If the current app called stopAppSwitches() then our startActivity() can
    602      * be delayed for several seconds. This happens with the default home
    603      * screen.  As a system service we can override this behavior with
    604      * resumeAppSwitches().
    605     */
    606     void resumeAppSwitches() {
    607         try {
    608             mIActivityManager.resumeAppSwitches();
    609         } catch (RemoteException e) { }
    610     }
    611 
    612     /** Returns true if the tech list filter matches the techs on the tag */
    613     boolean filterMatch(String[] tagTechs, String[] filterTechs) {
    614         if (filterTechs == null || filterTechs.length == 0) return false;
    615 
    616         for (String tech : filterTechs) {
    617             if (Arrays.binarySearch(tagTechs, tech) < 0) {
    618                 return false;
    619             }
    620         }
    621         return true;
    622     }
    623 
    624     static String checkForAar(NdefRecord record) {
    625         if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE &&
    626                 Arrays.equals(record.getType(), NdefRecord.RTD_ANDROID_APP)) {
    627             return new String(record.getPayload(), StandardCharsets.US_ASCII);
    628         }
    629         return null;
    630     }
    631 
    632     /**
    633      * Returns an intent that can be used to find an application not currently
    634      * installed on the device.
    635      */
    636     static Intent getAppSearchIntent(String pkg) {
    637         Intent market = new Intent(Intent.ACTION_VIEW);
    638         market.setData(Uri.parse("market://details?id=" + pkg));
    639         return market;
    640     }
    641 
    642     static boolean isComponentEnabled(PackageManager pm, ResolveInfo info) {
    643         boolean enabled = false;
    644         ComponentName compname = new ComponentName(
    645                 info.activityInfo.packageName, info.activityInfo.name);
    646         try {
    647             // Note that getActivityInfo() will internally call
    648             // isEnabledLP() to determine whether the component
    649             // enabled. If it's not, null is returned.
    650             if (pm.getActivityInfo(compname,0) != null) {
    651                 enabled = true;
    652             }
    653         } catch (PackageManager.NameNotFoundException e) {
    654             enabled = false;
    655         }
    656         if (!enabled) {
    657             Log.d(TAG, "Component not enabled: " + compname);
    658         }
    659         return enabled;
    660     }
    661 
    662     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    663         synchronized (this) {
    664             pw.println("mOverrideIntent=" + mOverrideIntent);
    665             pw.println("mOverrideFilters=" + mOverrideFilters);
    666             pw.println("mOverrideTechLists=" + mOverrideTechLists);
    667         }
    668     }
    669 }
    670