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