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 
NfcDispatcher(Context context, HandoverDataParser handoverDataParser, boolean provisionOnly)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 
setForegroundDispatch(PendingIntent intent, IntentFilter[] filters, String[][] techLists)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 
disableProvisioningMode()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 
DispatchInfo(Context context, Tag tag, NdefMessage message)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 
setNdefIntent()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 
setTechIntent()174         public Intent setTechIntent() {
175             intent.setData(null);
176             intent.setType(null);
177             intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);
178             return intent;
179         }
180 
setTagIntent()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          */
tryStartActivity()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 
tryStartActivity(Intent intentToStart)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      */
dispatchTag(Tag tag)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 
handleNfcUnlock(Tag tag)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      */
decodeNfcBarcodeUri(NfcBarcode nfcBarcode)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 
tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent, IntentFilter[] overrideFilters, String[][] overrideTechLists)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 
isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter)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 
isTechMatch(Tag tag, String[][] techLists)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 
tryNdef(DispatchInfo dispatch, NdefMessage message)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 
extractAarPackages(NdefMessage message)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 
tryTech(DispatchInfo dispatch, Tag tag)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 
tryPeripheralHandover(NdefMessage m)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     */
resumeAppSwitches()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 */
filterMatch(String[] tagTechs, String[] filterTechs)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 
checkForAar(NdefRecord record)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      */
getAppSearchIntent(String pkg)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 
isComponentEnabled(PackageManager pm, ResolveInfo info)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 
dump(FileDescriptor fd, PrintWriter pw, String[] args)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