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