NFC payment settings.

First version, pending final UX.

Change-Id: I357e900c3f2012b35814ae197c49a8c9b97b7148
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 4221059..c327f4a 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -143,7 +143,8 @@
             R.id.date_time_settings,
             R.id.about_settings,
             R.id.accessibility_settings,
-            R.id.print_settings
+            R.id.print_settings,
+            R.id.nfc_payment_settings
     };
 
     private SharedPreferences mDevelopmentPreferences;
@@ -551,6 +552,10 @@
                         || Utils.isMonkeyRunning()) {
                     target.remove(i);
                 }
+            } else if (id == R.id.nfc_payment_settings) {
+                if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC_HCE)) {
+                    target.remove(i);
+                }
             } else if (id == R.id.development_settings) {
                 if (!showDev) {
                     target.remove(i);
@@ -945,4 +950,5 @@
     public static class UserSettingsActivity extends Settings { /* empty */ }
     public static class NotificationAccessSettingsActivity extends Settings { /* empty */ }
     public static class UsbSettingsActivity extends Settings { /* empty */ }
+    public static class NfcPaymentActivity extends Settings { /* empty */ }
 }
diff --git a/src/com/android/settings/nfc/PaymentBackend.java b/src/com/android/settings/nfc/PaymentBackend.java
new file mode 100644
index 0000000..fc0f4a3
--- /dev/null
+++ b/src/com/android/settings/nfc/PaymentBackend.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package com.android.settings.nfc;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.ApduServiceInfo;
+import android.nfc.cardemulation.CardEmulationManager;
+import android.provider.Settings;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PaymentBackend {
+    public static final String TAG = "Settings.PaymentBackend";
+
+    public static class PaymentAppInfo {
+        CharSequence caption;
+        Drawable icon;
+        boolean isDefault;
+        public ComponentName componentName;
+    }
+
+    private final Context mContext;
+    private final NfcAdapter mAdapter;
+    private final CardEmulationManager mCardEmuManager;
+
+    public PaymentBackend(Context context) {
+        mContext = context;
+
+        mAdapter = NfcAdapter.getDefaultAdapter(context);
+        mCardEmuManager = CardEmulationManager.getInstance(mAdapter);
+    }
+
+    public List<PaymentAppInfo> getPaymentAppInfos() {
+        PackageManager pm = mContext.getPackageManager();
+        List<ApduServiceInfo> serviceInfos =
+                mCardEmuManager.getServices(CardEmulationManager.CATEGORY_PAYMENT);
+        List<PaymentAppInfo> appInfos = new ArrayList<PaymentAppInfo>();
+
+        if (serviceInfos == null) return appInfos;
+
+        ComponentName defaultApp = getDefaultPaymentApp();
+
+        for (ApduServiceInfo service : serviceInfos) {
+            PaymentAppInfo appInfo = new PaymentAppInfo();
+            appInfo.caption = service.loadLabel(pm);
+            appInfo.icon = service.loadIcon(pm);
+            appInfo.isDefault = service.getComponent().equals(defaultApp);
+            appInfo.componentName = service.getComponent();
+            appInfos.add(appInfo);
+        }
+
+        return appInfos;
+    }
+
+    ComponentName getDefaultPaymentApp() {
+        String componentString = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+        if (componentString != null) {
+            return ComponentName.unflattenFromString(componentString);
+        } else {
+            return null;
+        }
+    }
+
+    public void setDefaultPaymentApp(ComponentName app) {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+                app != null ? app.flattenToString() : null);
+    }
+
+    public boolean isAutoPaymentMode() {
+        String mode = Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.NFC_PAYMENT_MODE);
+        return (!CardEmulationManager.PAYMENT_MODE_MANUAL.equals(mode));
+    }
+
+    public void setAutoPaymentMode(boolean enable) {
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.NFC_PAYMENT_MODE,
+                enable ? CardEmulationManager.PAYMENT_MODE_AUTO
+                       : CardEmulationManager.PAYMENT_MODE_MANUAL);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/nfc/PaymentDefaultDialog.java b/src/com/android/settings/nfc/PaymentDefaultDialog.java
new file mode 100644
index 0000000..f3217dd
--- /dev/null
+++ b/src/com/android/settings/nfc/PaymentDefaultDialog.java
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+package com.android.settings.nfc;
+
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.nfc.cardemulation.CardEmulationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.app.AlertActivity;
+import com.android.internal.app.AlertController;
+import com.android.settings.R;
+import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
+
+import java.util.List;
+
+public final class PaymentDefaultDialog extends AlertActivity implements
+        DialogInterface.OnClickListener {
+
+    public static final String TAG = "PaymentDefaultDialog";
+
+    private PaymentBackend mBackend;
+    private ComponentName mNewDefault;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mBackend = new PaymentBackend(this);
+        Intent intent = getIntent();
+        ComponentName component = intent.getParcelableExtra(
+                CardEmulationManager.EXTRA_SERVICE_COMPONENT);
+        String category = intent.getStringExtra(CardEmulationManager.EXTRA_CATEGORY);
+
+        if (!buildDialog(component, category)) {
+            finish();
+        }
+
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        switch (which) {
+            case BUTTON_POSITIVE:
+                mBackend.setDefaultPaymentApp(mNewDefault);
+                mBackend.setAutoPaymentMode(true);
+                break;
+            case BUTTON_NEGATIVE:
+                break;
+        }
+    }
+
+    private boolean buildDialog(ComponentName component, String category) {
+        if (component == null || category == null) {
+            Log.e(TAG, "Component or category are null");
+            return false;
+        }
+
+        if (!CardEmulationManager.CATEGORY_PAYMENT.equals(category)) {
+            Log.e(TAG, "Don't support defaults for category " + category);
+            return false;
+        }
+
+        // Check if passed in service exists
+        boolean found = false;
+
+        List<PaymentAppInfo> services = mBackend.getPaymentAppInfos();
+        for (PaymentAppInfo service : services) {
+            if (component.equals(service.componentName)) {
+                found = true;
+                break;
+            }
+        }
+
+        if (!found) {
+            Log.e(TAG, "Component " + component + " is not a registered payment service.");
+            return false;
+        }
+
+        // Get current mode and default component
+        boolean isAuto = mBackend.isAutoPaymentMode();
+        ComponentName defaultComponent = mBackend.getDefaultPaymentApp();
+        if (defaultComponent != null && defaultComponent.equals(component)) {
+            Log.e(TAG, "Component " + component + " is already default.");
+            return false;
+        }
+
+        PackageManager pm = getPackageManager();
+        ApplicationInfo newAppInfo;
+        try {
+            newAppInfo = pm.getApplicationInfo(component.getPackageName(), 0);
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "PM could not load app info for " + component);
+            return false;
+        }
+        ApplicationInfo defaultAppInfo = null;
+        try {
+            if (defaultComponent != null) {
+                defaultAppInfo = pm.getApplicationInfo(defaultComponent.getPackageName(), 0);
+            }
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "PM could not load app info for " + defaultComponent);
+            // Continue intentionally
+        }
+
+        mNewDefault = component;
+
+        // Compose dialog; get
+        final AlertController.AlertParams p = mAlertParams;
+        p.mTitle = getString(R.string.nfc_payment_set_default);
+        if (defaultAppInfo == null || !isAuto) {
+            p.mMessage = "Always use " + newAppInfo.loadLabel(pm) + " when you tap and pay?";
+        } else {
+            p.mMessage = "Always use " + newAppInfo.loadLabel(pm) + " instead of " +
+                    defaultAppInfo.loadLabel(pm) + " when you tap and pay?";
+        }
+        p.mPositiveButtonText = getString(R.string.yes);
+        p.mNegativeButtonText = getString(R.string.no);
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonListener = this;
+        setupAlert();
+
+        return true;
+    }
+
+}
diff --git a/src/com/android/settings/nfc/PaymentSettings.java b/src/com/android/settings/nfc/PaymentSettings.java
new file mode 100644
index 0000000..a1ed883
--- /dev/null
+++ b/src/com/android/settings/nfc/PaymentSettings.java
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+package com.android.settings.nfc;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.RadioButton;
+
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
+import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
+
+import java.util.List;
+
+public class PaymentSettings extends SettingsPreferenceFragment implements
+        OnClickListener {
+    public static final String TAG = "PaymentSettings";
+    private PaymentBackend mPaymentBackend;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setHasOptionsMenu(false);
+        mPaymentBackend = new PaymentBackend(getActivity());
+    }
+
+    public void refresh() {
+        PreferenceManager manager = getPreferenceManager();
+        PreferenceScreen screen = manager.createPreferenceScreen(getActivity());
+
+        boolean isAuto = mPaymentBackend.isAutoPaymentMode();
+
+        // Get all payment services
+        List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos();
+        if (appInfos != null && appInfos.size() > 0) {
+            // Add all payment apps
+            for (PaymentAppInfo appInfo : appInfos) {
+                PaymentAppPreference preference =
+                        new PaymentAppPreference(getActivity(), appInfo, this);
+                // If for some reason isAuto gets out of sync, clear out app default
+                appInfo.isDefault &= isAuto;
+                preference.setIcon(appInfo.icon);
+                preference.setTitle(appInfo.caption);
+                screen.addPreference(preference);
+            }
+            if (appInfos.size() > 1) {
+                PaymentAppInfo appInfo = new PaymentAppInfo();
+                appInfo.icon = null;
+                appInfo.componentName = null;
+                appInfo.isDefault = !isAuto;
+                // Add "Ask every time" option
+                PaymentAppPreference preference =
+                        new PaymentAppPreference(getActivity(), appInfo, this);
+                preference.setIcon(null);
+                preference.setTitle(R.string.nfc_payment_ask);
+                screen.addPreference(preference);
+            }
+        }
+        setPreferenceScreen(screen);
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getTag() instanceof PaymentAppInfo) {
+            PaymentAppInfo appInfo = (PaymentAppInfo) v.getTag();
+            if (appInfo.componentName != null) {
+                mPaymentBackend.setDefaultPaymentApp(appInfo.componentName);
+                mPaymentBackend.setAutoPaymentMode(true);
+            } else {
+                mPaymentBackend.setDefaultPaymentApp(null);
+                mPaymentBackend.setAutoPaymentMode(false);
+            }
+            refresh();
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refresh();
+    }
+
+    public static class PaymentAppPreference extends Preference {
+        private final OnClickListener listener;
+        private final PaymentAppInfo appInfo;
+
+        public PaymentAppPreference(Context context, PaymentAppInfo appInfo,
+                OnClickListener listener) {
+            super(context);
+            setLayoutResource(R.layout.nfc_payment_option);
+            this.appInfo = appInfo;
+            this.listener = listener;
+        }
+
+        @Override
+        protected void onBindView(View view) {
+            super.onBindView(view);
+
+            view.setOnClickListener(listener);
+            view.setTag(appInfo);
+
+            RadioButton radioButton = (RadioButton) view.findViewById(android.R.id.button1);
+            radioButton.setChecked(appInfo.isDefault);
+        }
+    }
+}
\ No newline at end of file