Allow incoming provisioning intents while in setup wizard.

When the device is in setup wizard, the keyguard state
registers as locked, hence in this state NFC was not
enabled.

This change adds some resource settings to enable
NFC in setup wizard, and allow a set of mime-types
to be received while in that mode.

Bug: 8275527
Change-Id: Iab6cd8438fa77764b0cc9c96cbf1a36e95d79524
diff --git a/res/values/provisioning.xml b/res/values/provisioning.xml
new file mode 100644
index 0000000..74e4418
--- /dev/null
+++ b/res/values/provisioning.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- NFC resources that may need to be customized
+     for different hardware or product builds. -->
+<resources>
+    <!-- Whether the device can receive NFC data in setup wizard -->
+    <bool name="enable_nfc_provisioning">false</bool>
+
+    <!-- The accepted mime-types when NFC is enabled in setup wizard.
+         Mime-types must be lower case, wildcards are *not* accepted. -->
+    <string-array name="provisioning_mime_types">
+    </string-array>
+</resources>
diff --git a/src/com/android/nfc/NfcDispatcher.java b/src/com/android/nfc/NfcDispatcher.java
index 1721d1a..90316ba 100644
--- a/src/com/android/nfc/NfcDispatcher.java
+++ b/src/com/android/nfc/NfcDispatcher.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
+import android.content.res.Resources.NotFoundException;
 import android.net.Uri;
 import android.nfc.NdefMessage;
 import android.nfc.NdefRecord;
@@ -63,19 +64,35 @@
     final RegisteredComponentCache mTechListFilters;
     final ContentResolver mContentResolver;
     final HandoverManager mHandoverManager;
+    final String[] mProvisioningMimes;
 
     // Locked on this
     PendingIntent mOverrideIntent;
     IntentFilter[] mOverrideFilters;
     String[][] mOverrideTechLists;
+    boolean mProvisioningOnly;
 
-    public NfcDispatcher(Context context, HandoverManager handoverManager) {
+    public NfcDispatcher(Context context, HandoverManager handoverManager, boolean provisionOnly) {
         mContext = context;
         mIActivityManager = ActivityManagerNative.getDefault();
         mTechListFilters = new RegisteredComponentCache(mContext,
                 NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED);
         mContentResolver = context.getContentResolver();
         mHandoverManager = handoverManager;
+        synchronized (this) {
+            mProvisioningOnly = provisionOnly;
+        }
+        String[] provisionMimes = null;
+        if (provisionOnly) {
+            try {
+                // Get accepted mime-types
+                provisionMimes = context.getResources().
+                        getStringArray(R.array.provisioning_mime_types);
+            } catch (NotFoundException e) {
+               provisionMimes = null;
+            }
+        }
+        mProvisioningMimes = provisionMimes;
     }
 
     public synchronized void setForegroundDispatch(PendingIntent intent,
@@ -86,6 +103,10 @@
         mOverrideTechLists = techLists;
     }
 
+    public synchronized void disableProvisioningMode() {
+       mProvisioningOnly = false;
+    }
+
     /**
      * Helper for re-used objects and methods during a single tag dispatch.
      */
@@ -191,12 +212,14 @@
         PendingIntent overrideIntent;
         IntentFilter[] overrideFilters;
         String[][] overrideTechLists;
+        boolean provisioningOnly;
 
         DispatchInfo dispatch = new DispatchInfo(mContext, tag, message);
         synchronized (this) {
             overrideFilters = mOverrideFilters;
             overrideIntent = mOverrideIntent;
             overrideTechLists = mOverrideTechLists;
+            provisioningOnly = mProvisioningOnly;
         }
 
         resumeAppSwitches();
@@ -205,15 +228,20 @@
             return true;
         }
 
-        if (mHandoverManager.tryHandover(message)) {
+        if (!provisioningOnly && mHandoverManager.tryHandover(message)) {
             if (DBG) Log.i(TAG, "matched BT HANDOVER");
             return true;
         }
 
-        if (tryNdef(dispatch, message)) {
+        if (tryNdef(dispatch, message, provisioningOnly)) {
             return true;
         }
 
+        if (provisioningOnly) {
+            // We only allow NDEF-based mimeType matching
+            return false;
+        }
+
         if (tryTech(dispatch, tag)) {
             return true;
         }
@@ -304,7 +332,7 @@
         return false;
     }
 
-    boolean tryNdef(DispatchInfo dispatch, NdefMessage message) {
+    boolean tryNdef(DispatchInfo dispatch, NdefMessage message, boolean provisioningOnly) {
         if (message == null) {
             return false;
         }
@@ -313,6 +341,14 @@
         // Bail out if the intent does not contain filterable NDEF data
         if (intent == null) return false;
 
+        if (provisioningOnly) {
+            if (mProvisioningMimes == null ||
+                    !(Arrays.asList(mProvisioningMimes).contains(intent.getType()))) {
+                Log.e(TAG, "Dropping NFC intent in provisioning mode.");
+                return false;
+            }
+        }
+
         // Try to start AAR activity with matching filter
         List<String> aarPackages = extractAarPackages(message);
         for (String pkg : aarPackages) {
@@ -408,7 +444,7 @@
                 if (DBG) Log.i(TAG, "matched single TECH");
                 return true;
             }
-            dispatch.intent.setClassName((String)null, null);
+            dispatch.intent.setComponent(null);
         } else if (matches.size() > 1) {
             // Multiple matches, show a custom activity chooser dialog
             Intent intent = new Intent(mContext, TechListChooserActivity.class);
diff --git a/src/com/android/nfc/NfcService.java b/src/com/android/nfc/NfcService.java
index 4436927..d2f89b2 100755
--- a/src/com/android/nfc/NfcService.java
+++ b/src/com/android/nfc/NfcService.java
@@ -37,6 +37,7 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.res.Resources.NotFoundException;
 import android.media.AudioManager;
 import android.media.SoundPool;
 import android.net.Uri;
@@ -199,6 +200,7 @@
     // as SE access is not granted for non-owner users.
     HashSet<String> mSePackages = new HashSet<String>();
     int mScreenState;
+    boolean mInProvisionMode; // whether we're in setup wizard and enabled NFC provisioning
     boolean mIsNdefPushEnabled;
     boolean mNfceeRouteEnabled;  // current Device Host state of NFC-EE routing
     boolean mNfcPollingEnabled;  // current Device Host state of NFC-C polling
@@ -232,6 +234,8 @@
     private NfcDispatcher mNfcDispatcher;
     private PowerManager mPowerManager;
     private KeyguardManager mKeyguard;
+    private HandoverManager mHandoverManager;
+    private ContentResolver mContentResolver;
 
     private static NfcService sService;
 
@@ -340,12 +344,27 @@
         sService = this;
 
         mContext = nfcApplication;
+        mContentResolver = mContext.getContentResolver();
         mDeviceHost = new NativeNfcManager(mContext, this);
 
-        HandoverManager handoverManager = new HandoverManager(mContext);
-        mNfcDispatcher = new NfcDispatcher(mContext, handoverManager);
+        mHandoverManager = new HandoverManager(mContext);
+        boolean isNfcProvisioningEnabled = false;
+        try {
+            isNfcProvisioningEnabled = mContext.getResources().getBoolean(
+                    R.bool.enable_nfc_provisioning);
+        } catch (NotFoundException e) {
+        }
 
-        mP2pLinkManager = new P2pLinkManager(mContext, handoverManager,
+        if (isNfcProvisioningEnabled) {
+            mInProvisionMode = Settings.Secure.getInt(mContentResolver,
+                    Settings.Global.DEVICE_PROVISIONED, 0) == 0;
+        } else {
+            mInProvisionMode = false;
+        }
+
+        mHandoverManager.setEnabled(!mInProvisionMode);
+        mNfcDispatcher = new NfcDispatcher(mContext, mHandoverManager, mInProvisionMode);
+        mP2pLinkManager = new P2pLinkManager(mContext, mHandoverManager,
                 mDeviceHost.getDefaultLlcpMiu(), mDeviceHost.getDefaultLlcpRwSize());
 
         mSecureElement = new NativeNfcSecureElement(mContext);
@@ -421,16 +440,15 @@
     }
 
     void registerForAirplaneMode(IntentFilter filter) {
-        final ContentResolver resolver = mContext.getContentResolver();
-        final String airplaneModeRadios = Settings.System.getString(resolver,
-                Settings.System.AIRPLANE_MODE_RADIOS);
-        final String toggleableRadios = Settings.System.getString(resolver,
-                Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+        final String airplaneModeRadios = Settings.System.getString(mContentResolver,
+                Settings.Global.AIRPLANE_MODE_RADIOS);
+        final String toggleableRadios = Settings.System.getString(mContentResolver,
+                Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
 
         mIsAirplaneSensitive = airplaneModeRadios == null ? true :
-                airplaneModeRadios.contains(Settings.System.RADIO_NFC);
+                airplaneModeRadios.contains(Settings.Global.RADIO_NFC);
         mIsAirplaneToggleable = toggleableRadios == null ? false :
-            toggleableRadios.contains(Settings.System.RADIO_NFC);
+            toggleableRadios.contains(Settings.Global.RADIO_NFC);
 
         if (mIsAirplaneSensitive) {
             filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
@@ -1536,7 +1554,16 @@
                 return;
             }
             WatchDogThread watchDog = new WatchDogThread("applyRouting", ROUTING_WATCHDOG_MS);
-
+            if (mInProvisionMode) {
+                mInProvisionMode = Settings.Secure.getInt(mContentResolver,
+                        Settings.Global.DEVICE_PROVISIONED, 0) == 0;
+                if (!mInProvisionMode) {
+                    // Notify dispatcher it's fine to dispatch to any package now
+                    // and allow handover transfers.
+                    mNfcDispatcher.disableProvisioningMode();
+                    mHandoverManager.setEnabled(true);
+                }
+            }
             try {
                 watchDog.start();
 
@@ -1589,6 +1616,13 @@
                         mNfcPollingEnabled = true;
                         mDeviceHost.enableDiscovery();
                     }
+                } else if (mInProvisionMode && mScreenState >= SCREEN_STATE_ON_LOCKED) {
+                    // Special case for setup provisioning
+                    if (!mNfcPollingEnabled) {
+                        Log.d(TAG, "NFC-C ON");
+                        mNfcPollingEnabled = true;
+                        mDeviceHost.enableDiscovery();
+                    }
                 } else {
                     if (force || mNfcPollingEnabled) {
                         Log.d(TAG, "NFC-C OFF");
@@ -2052,8 +2086,8 @@
 
     /** Returns true if airplane mode is currently on */
     boolean isAirplaneModeOn() {
-        return Settings.System.getInt(mContext.getContentResolver(),
-                Settings.System.AIRPLANE_MODE_ON, 0) == 1;
+        return Settings.System.getInt(mContentResolver,
+                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
     }
 
     /** for debugging only - no i18n */
diff --git a/src/com/android/nfc/handover/HandoverManager.java b/src/com/android/nfc/handover/HandoverManager.java
index af684cb..9628301 100644
--- a/src/com/android/nfc/handover/HandoverManager.java
+++ b/src/com/android/nfc/handover/HandoverManager.java
@@ -81,6 +81,7 @@
     Messenger mService = null;
     boolean mBound;
     String mLocalBluetoothAddress;
+    boolean mEnabled;
 
     static class BluetoothHandoverData {
         public boolean valid = false;
@@ -162,6 +163,7 @@
 
         mContext.bindServiceAsUser(new Intent(mContext, HandoverService.class), mConnection,
                 Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+        mEnabled = true;
     }
 
     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -214,6 +216,11 @@
         return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload);
     }
 
+    public void setEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mEnabled = enabled;
+        }
+    }
     public boolean isHandoverSupported() {
         return (mBluetoothAdapter != null);
     }
@@ -293,6 +300,8 @@
         // while waiting to receive a picture.
         boolean bluetoothActivating = !mBluetoothAdapter.isEnabled();
         synchronized (mLock) {
+            if (!mEnabled) return null;
+
             if (!mBound) {
                 Log.e(TAG, "Could not connect to handover service");
                 return null;
@@ -332,6 +341,8 @@
         if (!handover.valid) return true;
 
         synchronized (mLock) {
+            if (!mEnabled) return false;
+
             if (mBluetoothAdapter == null) {
                 if (DBG) Log.d(TAG, "BT handover, but BT not available");
                 return true;