• 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.Activity;
20 import android.app.ActivityManager;
21 import android.app.AlertDialog;
22 import android.app.PendingIntent;
23 import android.app.PendingIntent.CanceledException;
24 import android.bluetooth.BluetoothAdapter;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.ContentResolver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.pm.ResolveInfo;
34 import android.content.res.Resources.NotFoundException;
35 import android.net.Uri;
36 import android.nfc.NdefMessage;
37 import android.nfc.NdefRecord;
38 import android.nfc.NfcAdapter;
39 import android.nfc.Tag;
40 import android.nfc.tech.Ndef;
41 import android.nfc.tech.NfcBarcode;
42 import android.os.Binder;
43 import android.os.Handler;
44 import android.os.Message;
45 import android.os.Messenger;
46 import android.os.Process;
47 import android.os.SystemProperties;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.util.Log;
51 import android.util.proto.ProtoOutputStream;
52 import android.view.LayoutInflater;
53 import android.view.View;
54 import android.view.WindowManager;
55 import android.widget.TextView;
56 
57 import com.android.nfc.RegisteredComponentCache.ComponentInfo;
58 import com.android.nfc.handover.HandoverDataParser;
59 import com.android.nfc.handover.PeripheralHandoverService;
60 
61 import java.io.FileDescriptor;
62 import java.io.PrintWriter;
63 import java.nio.charset.StandardCharsets;
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.LinkedList;
67 import java.util.List;
68 import java.util.StringJoiner;
69 import java.util.concurrent.atomic.AtomicBoolean;
70 
71 /**
72  * Dispatch of NFC events to start activities
73  */
74 class NfcDispatcher {
75     private static final boolean DBG =
76             SystemProperties.getBoolean("persist.nfc.debug_enabled", false);
77     private static final String TAG = "NfcDispatcher";
78 
79     static final int DISPATCH_SUCCESS = 1;
80     static final int DISPATCH_FAIL = 2;
81     static final int DISPATCH_UNLOCK = 3;
82 
83     private final Context mContext;
84     private final RegisteredComponentCache mTechListFilters;
85     private final ContentResolver mContentResolver;
86     private final HandoverDataParser mHandoverDataParser;
87     private final String[] mProvisioningMimes;
88     private final ScreenStateHelper mScreenStateHelper;
89     private final NfcUnlockManager mNfcUnlockManager;
90     private final boolean mDeviceSupportsBluetooth;
91     private final Handler mMessageHandler = new MessageHandler();
92     private final Messenger mMessenger = new Messenger(mMessageHandler);
93     private AtomicBoolean mBluetoothEnabledByNfc = new AtomicBoolean();
94 
95     // Locked on this
96     private PendingIntent mOverrideIntent;
97     private IntentFilter[] mOverrideFilters;
98     private String[][] mOverrideTechLists;
99     private int mForegroundUid;
100     private ForegroundUtils mForegroundUtils;
101     private boolean mProvisioningOnly;
102 
NfcDispatcher(Context context, HandoverDataParser handoverDataParser, boolean provisionOnly)103     NfcDispatcher(Context context,
104                   HandoverDataParser handoverDataParser,
105                   boolean provisionOnly) {
106         mContext = context;
107         mTechListFilters = new RegisteredComponentCache(mContext,
108                 NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED);
109         mContentResolver = context.getContentResolver();
110         mHandoverDataParser = handoverDataParser;
111         mScreenStateHelper = new ScreenStateHelper(context);
112         mNfcUnlockManager = NfcUnlockManager.getInstance();
113         mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null;
114         mForegroundUid = Process.INVALID_UID;
115         mForegroundUtils = ForegroundUtils.getInstance(
116                 context.getSystemService(ActivityManager.class));
117         synchronized (this) {
118             mProvisioningOnly = provisionOnly;
119         }
120         String[] provisionMimes = null;
121         if (provisionOnly) {
122             try {
123                 // Get accepted mime-types
124                 provisionMimes = context.getResources().
125                         getStringArray(R.array.provisioning_mime_types);
126             } catch (NotFoundException e) {
127                provisionMimes = null;
128             }
129         }
130         mProvisioningMimes = provisionMimes;
131 
132         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
133         mContext.registerReceiver(mBluetoothStatusReceiver, filter);
134     }
135 
136     @Override
finalize()137     protected void finalize() throws Throwable {
138         mContext.unregisterReceiver(mBluetoothStatusReceiver);
139         super.finalize();
140     }
141 
setForegroundDispatch(PendingIntent intent, IntentFilter[] filters, String[][] techLists)142     public synchronized void setForegroundDispatch(PendingIntent intent,
143             IntentFilter[] filters, String[][] techLists) {
144         if (DBG) Log.d(TAG, "Set Foreground Dispatch");
145         mOverrideIntent = intent;
146         mOverrideFilters = filters;
147         mOverrideTechLists = techLists;
148 
149         if (mOverrideIntent != null) {
150             int callingUid = Binder.getCallingUid();
151             if (mForegroundUid != callingUid) {
152                 mForegroundUtils.registerUidToBackgroundCallback(mForegroundCallback, callingUid);
153                 mForegroundUid = callingUid;
154             }
155         }
156     }
157 
158     final ForegroundUtils.Callback mForegroundCallback = new ForegroundCallbackImpl();
159 
160     class ForegroundCallbackImpl implements ForegroundUtils.Callback {
161         @Override
onUidToBackground(int uid)162         public void onUidToBackground(int uid) {
163             synchronized (NfcDispatcher.this) {
164                 if (mForegroundUid == uid) {
165                     if (DBG) Log.d(TAG, "Uid " + uid + " switch to background.");
166                     mForegroundUid = Process.INVALID_UID;
167                     setForegroundDispatch(null, null, null);
168                 }
169             }
170         }
171     }
172 
disableProvisioningMode()173     public synchronized void disableProvisioningMode() {
174        mProvisioningOnly = false;
175     }
176 
177     /**
178      * Helper for re-used objects and methods during a single tag dispatch.
179      */
180     static class DispatchInfo {
181         public final Intent intent;
182 
183         final Intent rootIntent;
184         final Uri ndefUri;
185         final String ndefMimeType;
186         final PackageManager packageManager;
187         final Context context;
188 
DispatchInfo(Context context, Tag tag, NdefMessage message)189         public DispatchInfo(Context context, Tag tag, NdefMessage message) {
190             intent = new Intent();
191             intent.putExtra(NfcAdapter.EXTRA_TAG, tag);
192             intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId());
193             if (message != null) {
194                 intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message});
195                 ndefUri = message.getRecords()[0].toUri();
196                 ndefMimeType = message.getRecords()[0].toMimeType();
197             } else {
198                 ndefUri = null;
199                 ndefMimeType = null;
200             }
201 
202             rootIntent = new Intent(context, NfcRootActivity.class);
203             rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent);
204             rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
205 
206             this.context = context;
207             packageManager = context.getPackageManager();
208         }
209 
setNdefIntent()210         public Intent setNdefIntent() {
211             intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED);
212             if (ndefUri != null) {
213                 intent.setData(ndefUri);
214                 return intent;
215             } else if (ndefMimeType != null) {
216                 intent.setType(ndefMimeType);
217                 return intent;
218             }
219             return null;
220         }
221 
setTechIntent()222         public Intent setTechIntent() {
223             intent.setData(null);
224             intent.setType(null);
225             intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED);
226             return intent;
227         }
228 
setTagIntent()229         public Intent setTagIntent() {
230             intent.setData(null);
231             intent.setType(null);
232             intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED);
233             return intent;
234         }
235 
hasIntentReceiver()236         public boolean hasIntentReceiver() {
237             boolean status = false;
238             List<UserHandle> luh = getCurrentActiveUserHandles();
239             for (UserHandle uh : luh) {
240                 if (packageManager.queryIntentActivitiesAsUser(intent, 0,
241                         uh).size() > 0) {
242                     status = true;
243                 }
244             }
245             return status;
246         }
247 
isWebIntent()248         public boolean isWebIntent() {
249             return ndefUri != null && ndefUri.normalizeScheme().getScheme() != null &&
250                 ndefUri.normalizeScheme().getScheme().startsWith("http");
251         }
252 
getUri()253         public String getUri() {
254             return ndefUri.toString();
255         }
256 
257         /**
258          * Launch the activity via a (single) NFC root task, so that it
259          * creates a new task stack instead of interfering with any existing
260          * task stack for that activity.
261          * NfcRootActivity acts as the task root, it immediately calls
262          * start activity on the intent it is passed.
263          */
tryStartActivity()264         boolean tryStartActivity() {
265             // Ideally we'd have used startActivityForResult() to determine whether the
266             // NfcRootActivity was able to launch the intent, but startActivityForResult()
267             // is not available on Context. Instead, we query the PackageManager beforehand
268             // to determine if there is an Activity to handle this intent, and base the
269             // result of off that.
270             // try current user if there is an Activity to handle this intent
271             List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(intent, 0,
272                     ActivityManager.getCurrentUser());
273             if (activities.size() > 0) {
274                 context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
275                 NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED,
276                         NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
277                 return true;
278             }
279             // try other users when there is no Activity in current user to handle this intent
280             List<UserHandle> userHandles = getCurrentActiveUserHandles();
281             for (UserHandle uh : userHandles) {
282                 activities = packageManager.queryIntentActivitiesAsUser(intent, 0, uh);
283                 if (activities.size() > 0) {
284                     rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT_USER_HANDLE, uh);
285                     context.startActivityAsUser(rootIntent, uh);
286                     NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED,
287                             NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
288                     return true;
289                 }
290             }
291             return false;
292         }
293 
tryStartActivity(Intent intentToStart)294         boolean tryStartActivity(Intent intentToStart) {
295             // try current user if there is an Activity to handle this intent
296             List<ResolveInfo> activities = packageManager.queryIntentActivitiesAsUser(
297                     intentToStart, 0, ActivityManager.getCurrentUser());
298             if (activities.size() > 0) {
299                 rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart);
300                 context.startActivityAsUser(rootIntent, UserHandle.CURRENT);
301                 NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED,
302                         NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
303                 return true;
304             }
305             // try other users when there is no Activity in current user to handle this intent
306             List<UserHandle> userHandles = getCurrentActiveUserHandles();
307             for (UserHandle uh : userHandles) {
308                 activities = packageManager.queryIntentActivitiesAsUser(intentToStart, 0, uh);
309                 if (activities.size() > 0) {
310                     rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart);
311                     rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT_USER_HANDLE, uh);
312                     context.startActivityAsUser(rootIntent, uh);
313                     NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED,
314                             NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
315                     return true;
316                 }
317             }
318             return false;
319         }
320 
getCurrentActiveUserHandles()321         List<UserHandle> getCurrentActiveUserHandles() {
322             UserManager um = context.createContextAsUser(
323                     UserHandle.of(ActivityManager.getCurrentUser()), /*flags=*/0)
324                     .getSystemService(UserManager.class);
325             List<UserHandle> luh = um.getEnabledProfiles();
326             List<UserHandle> rluh = new ArrayList<UserHandle>();
327             for (UserHandle uh : luh) {
328                 if (um.isQuietModeEnabled(uh)) {
329                     rluh.add(uh);
330                 }
331             }
332             luh.removeAll(rluh);
333             return luh;
334         }
335     }
336 
337     /** Returns:
338      * <ul>
339      *  <li /> DISPATCH_SUCCESS if dispatched to an activity,
340      *  <li /> DISPATCH_FAIL if no activities were found to dispatch to,
341      *  <li /> DISPATCH_UNLOCK if the tag was used to unlock the device
342      * </ul>
343      */
dispatchTag(Tag tag)344     public int dispatchTag(Tag tag) {
345         PendingIntent overrideIntent;
346         IntentFilter[] overrideFilters;
347         String[][] overrideTechLists;
348         String[] provisioningMimes;
349         boolean provisioningOnly;
350         NdefMessage message = null;
351         Ndef ndef = Ndef.get(tag);
352 
353         synchronized (this) {
354             overrideFilters = mOverrideFilters;
355             overrideIntent = mOverrideIntent;
356             overrideTechLists = mOverrideTechLists;
357             provisioningOnly = mProvisioningOnly;
358             provisioningMimes = mProvisioningMimes;
359         }
360 
361         boolean screenUnlocked = false;
362         if (!provisioningOnly &&
363                 mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) {
364             screenUnlocked = handleNfcUnlock(tag);
365             if (!screenUnlocked)
366                 return DISPATCH_FAIL;
367         }
368 
369         if (ndef != null) {
370             message = ndef.getCachedNdefMessage();
371         } else {
372             NfcBarcode nfcBarcode = NfcBarcode.get(tag);
373             if (nfcBarcode != null && nfcBarcode.getType() == NfcBarcode.TYPE_KOVIO) {
374                 message = decodeNfcBarcodeUri(nfcBarcode);
375             }
376         }
377 
378         if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message);
379 
380         DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
381 
382         resumeAppSwitches();
383 
384         if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters,
385                 overrideTechLists)) {
386             NfcStatsLog.write(
387                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__APP_LAUNCH);
388             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
389         }
390 
391         if (tryPeripheralHandover(message)) {
392             if (DBG) Log.i(TAG, "matched BT HANDOVER");
393             NfcStatsLog.write(
394                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__BT_PAIRING);
395             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
396         }
397 
398         if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) {
399             if (DBG) Log.i(TAG, "matched NFC WPS TOKEN");
400             NfcStatsLog.write(
401                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__WIFI_CONNECT);
402             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
403         }
404 
405         if (provisioningOnly) {
406             NfcStatsLog.write(
407                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__PROVISION);
408             if (message == null) {
409                 // We only allow NDEF-message dispatch in provisioning mode
410                 return DISPATCH_FAIL;
411             }
412             // Restrict to mime-types in allowlist.
413             String ndefMimeType = message.getRecords()[0].toMimeType();
414             if (provisioningMimes == null ||
415                     !(Arrays.asList(provisioningMimes).contains(ndefMimeType))) {
416                 Log.e(TAG, "Dropping NFC intent in provisioning mode.");
417                 return DISPATCH_FAIL;
418             }
419         }
420 
421         if (tryNdef(dispatch, message)) {
422             return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS;
423         }
424 
425         if (screenUnlocked) {
426             // We only allow NDEF-based mimeType matching in case of an unlock
427             return DISPATCH_UNLOCK;
428         }
429 
430         // Only allow NDEF-based mimeType matching for unlock tags
431         if (tryTech(dispatch, tag)) {
432             return DISPATCH_SUCCESS;
433         }
434 
435         dispatch.setTagIntent();
436         if (dispatch.tryStartActivity()) {
437             if (DBG) Log.i(TAG, "matched TAG");
438             return DISPATCH_SUCCESS;
439         }
440 
441         if (DBG) Log.i(TAG, "no match");
442         NfcStatsLog.write(NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__OTHERS);
443         return DISPATCH_FAIL;
444     }
445 
handleNfcUnlock(Tag tag)446     private boolean handleNfcUnlock(Tag tag) {
447         return mNfcUnlockManager.tryUnlock(tag);
448     }
449 
450     /**
451      * Checks for the presence of a URL stored in a tag with tech NfcBarcode.
452      * If found, decodes URL and returns NdefMessage message containing an
453      * NdefRecord containing the decoded URL. If not found, returns null.
454      *
455      * URLs are decoded as follows:
456      *
457      * Ignore first byte (which is 0x80 ORd with a manufacturer ID, corresponding
458      * to ISO/IEC 7816-6).
459      * The second byte describes the payload data format. There are four defined data
460      * format values that identify URL data. Depending on the data format value, the
461      * associated prefix is appended to the URL data:
462      *
463      * 0x01: URL with "http://www." prefix
464      * 0x02: URL with "https://www." prefix
465      * 0x03: URL with "http://" prefix
466      * 0x04: URL with "https://" prefix
467      *
468      * Other data format values do not identify URL data and are not handled by this function.
469      * URL payload is encoded in US-ASCII, following the limitations defined in RFC3987.
470      * see http://www.ietf.org/rfc/rfc3987.txt
471      *
472      * The final two bytes of a tag with tech NfcBarcode are always reserved for CRC data,
473      * and are therefore not part of the payload. They are ignored in the decoding of a URL.
474      *
475      * The default assumption is that the URL occupies the entire payload of the NfcBarcode
476      * ID and all bytes of the NfcBarcode payload are decoded until the CRC (final two bytes)
477      * is reached. However, the OPTIONAL early terminator byte 0xfe can be used to signal
478      * an early end of the URL. Once this function reaches an early terminator byte 0xfe,
479      * URL decoding stops and the NdefMessage is created and returned. Any payload data after
480      * the first early terminator byte is ignored for the purposes of URL decoding.
481      */
decodeNfcBarcodeUri(NfcBarcode nfcBarcode)482     private NdefMessage decodeNfcBarcodeUri(NfcBarcode nfcBarcode) {
483         final byte URI_PREFIX_HTTP_WWW  = (byte) 0x01; // "http://www."
484         final byte URI_PREFIX_HTTPS_WWW = (byte) 0x02; // "https://www."
485         final byte URI_PREFIX_HTTP      = (byte) 0x03; // "http://"
486         final byte URI_PREFIX_HTTPS     = (byte) 0x04; // "https://"
487 
488         NdefMessage message = null;
489         byte[] tagId = nfcBarcode.getTag().getId();
490         // All tags of NfcBarcode technology and Kovio type have lengths of a multiple of 16 bytes
491         if (tagId.length >= 4
492                 && (tagId[1] == URI_PREFIX_HTTP_WWW || tagId[1] == URI_PREFIX_HTTPS_WWW
493                     || tagId[1] == URI_PREFIX_HTTP || tagId[1] == URI_PREFIX_HTTPS)) {
494             // Look for optional URI terminator (0xfe), used to indicate the end of a URI prior to
495             // the end of the full NfcBarcode payload. No terminator means that the URI occupies the
496             // entire length of the payload field. Exclude checking the CRC in the final two bytes
497             // of the NfcBarcode tagId.
498             int end = 2;
499             for (; end < tagId.length - 2; end++) {
500                 if (tagId[end] == (byte) 0xfe) {
501                     break;
502                 }
503             }
504             byte[] payload = new byte[end - 1]; // Skip also first byte (manufacturer ID)
505             System.arraycopy(tagId, 1, payload, 0, payload.length);
506             NdefRecord uriRecord = new NdefRecord(
507                     NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, tagId, payload);
508             message = new NdefMessage(uriRecord);
509         }
510         return message;
511     }
512 
tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent, IntentFilter[] overrideFilters, String[][] overrideTechLists)513     boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent,
514             IntentFilter[] overrideFilters, String[][] overrideTechLists) {
515         if (overrideIntent == null) {
516             return false;
517         }
518         Intent intent;
519 
520         // NDEF
521         if (message != null) {
522             intent = dispatch.setNdefIntent();
523             if (intent != null &&
524                     isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
525                 try {
526                     overrideIntent.send(mContext, Activity.RESULT_OK, intent);
527                     if (DBG) Log.i(TAG, "matched NDEF override");
528                     return true;
529                 } catch (CanceledException e) {
530                     return false;
531                 }
532             }
533         }
534 
535         // TECH
536         intent = dispatch.setTechIntent();
537         if (isTechMatch(tag, overrideTechLists)) {
538             try {
539                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
540                 if (DBG) Log.i(TAG, "matched TECH override");
541                 return true;
542             } catch (CanceledException e) {
543                 return false;
544             }
545         }
546 
547         // TAG
548         intent = dispatch.setTagIntent();
549         if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) {
550             try {
551                 overrideIntent.send(mContext, Activity.RESULT_OK, intent);
552                 if (DBG) Log.i(TAG, "matched TAG override");
553                 return true;
554             } catch (CanceledException e) {
555                 return false;
556             }
557         }
558         return false;
559     }
560 
isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter)561     boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) {
562         if (filters != null) {
563             for (IntentFilter filter : filters) {
564                 if (filter.match(mContentResolver, intent, false, TAG) >= 0) {
565                     return true;
566                 }
567             }
568         } else if (!hasTechFilter) {
569             return true;  // always match if both filters and techlists are null
570         }
571         return false;
572     }
573 
isTechMatch(Tag tag, String[][] techLists)574     boolean isTechMatch(Tag tag, String[][] techLists) {
575         if (techLists == null) {
576             return false;
577         }
578 
579         String[] tagTechs = tag.getTechList();
580         Arrays.sort(tagTechs);
581         for (String[] filterTechs : techLists) {
582             if (filterMatch(tagTechs, filterTechs)) {
583                 return true;
584             }
585         }
586         return false;
587     }
588 
tryNdef(DispatchInfo dispatch, NdefMessage message)589     boolean tryNdef(DispatchInfo dispatch, NdefMessage message) {
590         if (message == null) {
591             return false;
592         }
593         Intent intent = dispatch.setNdefIntent();
594 
595         // Bail out if the intent does not contain filterable NDEF data
596         if (intent == null) return false;
597 
598         // Try to start AAR activity with matching filter
599         List<String> aarPackages = extractAarPackages(message);
600         for (String pkg : aarPackages) {
601             dispatch.intent.setPackage(pkg);
602             if (dispatch.tryStartActivity()) {
603                 if (DBG) Log.i(TAG, "matched AAR to NDEF");
604                 return true;
605             }
606         }
607 
608         List<UserHandle> luh = dispatch.getCurrentActiveUserHandles();
609         // Try to perform regular launch of the first AAR
610         if (aarPackages.size() > 0) {
611             String firstPackage = aarPackages.get(0);
612             PackageManager pm;
613             for (UserHandle uh : luh) {
614                 try {
615                     pm = mContext.createPackageContextAsUser("android", 0,
616                             uh).getPackageManager();
617                 } catch (NameNotFoundException e) {
618                     Log.e(TAG, "Could not create user package context");
619                     return false;
620                 }
621                 Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage);
622                 if (appLaunchIntent != null) {
623                     ResolveInfo ri = pm.resolveActivity(appLaunchIntent, 0);
624                     if (ri != null && ri.activityInfo != null && ri.activityInfo.exported
625                             && dispatch.tryStartActivity(appLaunchIntent)) {
626                         if (DBG) Log.i(TAG, "matched AAR to application launch");
627                         return true;
628                     }
629                 }
630             }
631             // Find the package in Market:
632             Intent marketIntent = getAppSearchIntent(firstPackage);
633             if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) {
634                 if (DBG) Log.i(TAG, "matched AAR to market launch");
635                 return true;
636             }
637         }
638 
639         // regular launch
640         dispatch.intent.setPackage(null);
641 
642         if (dispatch.isWebIntent() && dispatch.hasIntentReceiver()) {
643             if (DBG) Log.i(TAG, "matched Web link - prompting user");
644             showWebLinkConfirmation(dispatch);
645             NfcStatsLog.write(
646                     NfcStatsLog.NFC_TAG_OCCURRED, NfcStatsLog.NFC_TAG_OCCURRED__TYPE__URL);
647             return true;
648         }
649 
650         for (UserHandle uh : luh) {
651             try {
652                 PackageManager pm = mContext.createPackageContextAsUser("android", 0,
653                         uh).getPackageManager();
654                 ResolveInfo ri = pm.resolveActivity(intent, 0);
655 
656                 if (ri != null && ri.activityInfo != null && ri.activityInfo.exported
657                         && dispatch.tryStartActivity()) {
658                     if (DBG) Log.i(TAG, "matched NDEF");
659                     return true;
660                 }
661             } catch (NameNotFoundException ignore) {
662                 Log.e(TAG, "Could not create user package context");
663             }
664         }
665         return false;
666     }
667 
extractAarPackages(NdefMessage message)668     static List<String> extractAarPackages(NdefMessage message) {
669         List<String> aarPackages = new LinkedList<String>();
670         for (NdefRecord record : message.getRecords()) {
671             String pkg = checkForAar(record);
672             if (pkg != null) {
673                 aarPackages.add(pkg);
674             }
675         }
676         return aarPackages;
677     }
678 
tryTech(DispatchInfo dispatch, Tag tag)679     boolean tryTech(DispatchInfo dispatch, Tag tag) {
680         dispatch.setTechIntent();
681 
682         String[] tagTechs = tag.getTechList();
683         Arrays.sort(tagTechs);
684 
685         // Standard tech dispatch path
686         ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
687         List<ComponentInfo> registered = mTechListFilters.getComponents();
688 
689         PackageManager pm;
690         List<UserHandle> luh = dispatch.getCurrentActiveUserHandles();
691 
692         for (UserHandle uh : luh) {
693             try {
694                 pm = mContext.createPackageContextAsUser("android", 0,
695                         uh).getPackageManager();
696             } catch (NameNotFoundException e) {
697                 Log.e(TAG, "Could not create user package context");
698                 return false;
699             }
700             // Check each registered activity to see if it matches
701             for (ComponentInfo info : registered) {
702                 // Don't allow wild card matching
703                 if (filterMatch(tagTechs, info.techs)
704                         && isComponentEnabled(pm, info.resolveInfo)) {
705                     // Add the activity as a match if it's not already in the list
706                     // Check if exported flag is not explicitly set to false to prevent
707                     // SecurityExceptions.
708                     if (!matches.contains(info.resolveInfo)
709                             && info.resolveInfo.activityInfo.exported) {
710                         matches.add(info.resolveInfo);
711                     }
712                 }
713             }
714         }
715 
716         if (matches.size() == 1) {
717             // Single match, launch directly
718             ResolveInfo info = matches.get(0);
719             dispatch.intent.setClassName(info.activityInfo.packageName, info.activityInfo.name);
720             if (dispatch.tryStartActivity()) {
721                 if (DBG) Log.i(TAG, "matched single TECH");
722                 return true;
723             }
724             dispatch.intent.setComponent(null);
725         } else if (matches.size() > 1) {
726             // Multiple matches, show a custom activity chooser dialog
727             Intent intent = new Intent(mContext, TechListChooserActivity.class);
728             intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent);
729             intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS,
730                     matches);
731             if (dispatch.tryStartActivity(intent)) {
732                 if (DBG) Log.i(TAG, "matched multiple TECH");
733                 return true;
734             }
735         }
736         return false;
737     }
738 
tryPeripheralHandover(NdefMessage m)739     public boolean tryPeripheralHandover(NdefMessage m) {
740         if (m == null || !mDeviceSupportsBluetooth) return false;
741 
742         if (DBG) Log.d(TAG, "tryHandover(): " + m.toString());
743 
744         HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m);
745         if (handover == null || !handover.valid) return false;
746         if (UserManager.get(mContext).hasUserRestriction(
747                 UserManager.DISALLOW_CONFIG_BLUETOOTH,
748                 // hasUserRestriction does not support UserHandle.CURRENT
749                 UserHandle.of(ActivityManager.getCurrentUser()))) {
750             return false;
751         }
752 
753         Intent intent = new Intent(mContext, PeripheralHandoverService.class);
754         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device);
755         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name);
756         intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport);
757         if (handover.oobData != null) {
758             intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_OOB_DATA, handover.oobData);
759         }
760         if (handover.uuids != null) {
761             intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_UUIDS, handover.uuids);
762         }
763         if (handover.btClass != null) {
764             intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_CLASS, handover.btClass);
765         }
766         intent.putExtra(PeripheralHandoverService.EXTRA_BT_ENABLED, mBluetoothEnabledByNfc.get());
767         intent.putExtra(PeripheralHandoverService.EXTRA_CLIENT, mMessenger);
768         mContext.startServiceAsUser(intent, UserHandle.CURRENT);
769 
770         return true;
771     }
772 
773 
774     /**
775      * Tells the ActivityManager to resume allowing app switches.
776      *
777      * If the current app called stopAppSwitches() then our startActivity() can
778      * be delayed for several seconds. This happens with the default home
779      * screen.  As a system service we can override this behavior with
780      * resumeAppSwitches().
781     */
resumeAppSwitches()782     void resumeAppSwitches() {
783         //// Should be auto resumed after S
784         // try {
785         //     mIActivityManager.resumeAppSwitches();
786         // } catch (RemoteException e) { }
787     }
788 
789     /** Returns true if the tech list filter matches the techs on the tag */
filterMatch(String[] tagTechs, String[] filterTechs)790     boolean filterMatch(String[] tagTechs, String[] filterTechs) {
791         if (filterTechs == null || filterTechs.length == 0) return false;
792 
793         for (String tech : filterTechs) {
794             if (Arrays.binarySearch(tagTechs, tech) < 0) {
795                 return false;
796             }
797         }
798         return true;
799     }
800 
checkForAar(NdefRecord record)801     static String checkForAar(NdefRecord record) {
802         if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE &&
803                 Arrays.equals(record.getType(), NdefRecord.RTD_ANDROID_APP)) {
804             return new String(record.getPayload(), StandardCharsets.US_ASCII);
805         }
806         return null;
807     }
808 
809     /**
810      * Returns an intent that can be used to find an application not currently
811      * installed on the device.
812      */
getAppSearchIntent(String pkg)813     static Intent getAppSearchIntent(String pkg) {
814         Intent market = new Intent(Intent.ACTION_VIEW);
815         market.setData(Uri.parse("market://details?id=" + pkg));
816         return market;
817     }
818 
isComponentEnabled(PackageManager pm, ResolveInfo info)819     static boolean isComponentEnabled(PackageManager pm, ResolveInfo info) {
820         boolean enabled = false;
821         ComponentName compname = new ComponentName(
822                 info.activityInfo.packageName, info.activityInfo.name);
823         try {
824             // Note that getActivityInfo() will internally call
825             // isEnabledLP() to determine whether the component
826             // enabled. If it's not, null is returned.
827             if (pm.getActivityInfo(compname,0) != null) {
828                 enabled = true;
829             }
830         } catch (PackageManager.NameNotFoundException e) {
831             enabled = false;
832         }
833         if (!enabled) {
834             Log.d(TAG, "Component not enabled: " + compname);
835         }
836         return enabled;
837     }
838 
showWebLinkConfirmation(DispatchInfo dispatch)839     void showWebLinkConfirmation(DispatchInfo dispatch) {
840         if (!mContext.getResources().getBoolean(R.bool.enable_nfc_url_open_dialog)) {
841             dispatch.tryStartActivity();
842             return;
843         }
844         AlertDialog.Builder builder = new AlertDialog.Builder(
845                 mContext.getApplicationContext(),
846                 R.style.DialogAlertDayNight);
847         builder.setTitle(R.string.title_confirm_url_open);
848         LayoutInflater inflater = LayoutInflater.from(mContext);
849         View view = inflater.inflate(R.layout.url_open_confirmation, null);
850         if (view != null) {
851             TextView url = view.findViewById(R.id.url_open_confirmation_link);
852             if (url != null) {
853                 url.setText(dispatch.getUri());
854             }
855             builder.setView(view);
856         }
857         builder.setNegativeButton(R.string.cancel, (dialog, which) -> {});
858         builder.setPositiveButton(R.string.action_confirm_url_open, (dialog, which) -> {
859             dispatch.tryStartActivity();
860         });
861         AlertDialog dialog = builder.create();
862         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
863         dialog.show();
864     }
865 
dump(FileDescriptor fd, PrintWriter pw, String[] args)866     void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
867         synchronized (this) {
868             pw.println("mOverrideIntent=" + mOverrideIntent);
869             pw.println("mOverrideFilters=" + mOverrideFilters);
870             pw.println("mOverrideTechLists=" + mOverrideTechLists);
871         }
872     }
873 
dumpDebug(ProtoOutputStream proto)874     void dumpDebug(ProtoOutputStream proto) {
875         proto.write(NfcDispatcherProto.DEVICE_SUPPORTS_BLUETOOTH, mDeviceSupportsBluetooth);
876         proto.write(NfcDispatcherProto.BLUETOOTH_ENABLED_BY_NFC, mBluetoothEnabledByNfc.get());
877 
878         synchronized (this) {
879             proto.write(NfcDispatcherProto.PROVISIONING_ONLY, mProvisioningOnly);
880             if (mOverrideTechLists != null) {
881                 StringJoiner techListsJoiner = new StringJoiner(System.lineSeparator());
882                 for (String[] list : mOverrideTechLists) {
883                     techListsJoiner.add(Arrays.toString(list));
884                 }
885                 proto.write(NfcDispatcherProto.OVERRIDE_TECH_LISTS, techListsJoiner.toString());
886             }
887             if (mOverrideIntent != null) {
888                 mOverrideIntent.dumpDebug(proto, NfcDispatcherProto.OVERRIDE_INTENT);
889             }
890             if (mOverrideFilters != null) {
891                 for (IntentFilter filter : mOverrideFilters) {
892                     filter.dumpDebug(proto, NfcDispatcherProto.OVERRIDE_FILTERS);
893                 }
894             }
895         }
896     }
897 
898     private class MessageHandler extends Handler {
899         @Override
handleMessage(Message msg)900         public void handleMessage(Message msg) {
901             if (DBG) Log.d(TAG, "handleMessage: msg=" + msg);
902 
903             switch (msg.what) {
904                 case PeripheralHandoverService.MSG_HEADSET_CONNECTED:
905                 case PeripheralHandoverService.MSG_HEADSET_NOT_CONNECTED:
906                     mBluetoothEnabledByNfc.set(msg.arg1 != 0);
907                     break;
908                 default:
909                     break;
910             }
911         }
912     }
913 
914     final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
915         @Override
916         public void onReceive(Context context, Intent intent) {
917             String action = intent.getAction();
918             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
919                 handleBluetoothStateChanged(intent);
920             }
921         }
922 
923         private void handleBluetoothStateChanged(Intent intent) {
924             int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
925                     BluetoothAdapter.ERROR);
926             if (state == BluetoothAdapter.STATE_OFF) {
927                 mBluetoothEnabledByNfc.set(false);
928             }
929         }
930     };
931 }
932