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