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