diff options
-rw-r--r-- | AndroidManifest.xml | 25 | ||||
-rw-r--r-- | proguard.flags | 1 | ||||
-rw-r--r-- | res/drawable-xhdpi/ic_settings_nfc_payment.png | bin | 0 -> 7154 bytes | |||
-rw-r--r-- | res/layout/nfc_payment_option.xml | 80 | ||||
-rw-r--r-- | res/values/strings.xml | 9 | ||||
-rw-r--r-- | res/xml/nfc_payment_settings.xml | 18 | ||||
-rw-r--r-- | res/xml/settings_headers.xml | 7 | ||||
-rw-r--r-- | src/com/android/settings/Settings.java | 8 | ||||
-rw-r--r-- | src/com/android/settings/nfc/PaymentBackend.java | 102 | ||||
-rw-r--r-- | src/com/android/settings/nfc/PaymentDefaultDialog.java | 144 | ||||
-rw-r--r-- | src/com/android/settings/nfc/PaymentSettings.java | 126 |
11 files changed, 519 insertions, 1 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 10c66d4..5769305 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1571,6 +1571,31 @@ android:resource="@id/user_settings" /> </activity> + <activity android:name="Settings$PaymentSettingsActivity" + android:uiOptions="splitActionBarWhenNarrow" + android:label="@string/nfc_payment_settings_title" + android:taskAffinity="" + android:excludeFromRecents="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <action android:name="android.settings.NFC_PAYMENT_SETTINGS" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + <meta-data android:name="com.android.settings.FRAGMENT_CLASS" + android:value="com.android.settings.nfc.PaymentSettings" /> + <meta-data android:name="com.android.settings.TOP_LEVEL_HEADER_ID" + android:resource="@id/nfc_payment_settings" /> + </activity> + <activity android:name=".nfc.PaymentDefaultDialog" + android:label="@string/nfc_payment_set_default" + android:excludeFromRecents="true" + android:theme="@*android:style/Theme.Holo.Light.Dialog.Alert"> + <intent-filter> + <action android:name="android.nfc.cardemulation.ACTION_CHANGE_DEFAULT" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + <activity android:name="Settings$NotificationAccessSettingsActivity" android:label="@string/manage_notification_access" android:taskAffinity="" diff --git a/proguard.flags b/proguard.flags index 8a6c2cb..211a5a4 100644 --- a/proguard.flags +++ b/proguard.flags @@ -13,6 +13,7 @@ -keep class com.android.settings.fuelgauge.* -keep class com.android.settings.users.* -keep class com.android.settings.NotificationStation +-keep class com.android.settings.nfc.* # Keep click responders -keepclassmembers class com.android.settings.inputmethod.UserDictionaryAddWordActivity { diff --git a/res/drawable-xhdpi/ic_settings_nfc_payment.png b/res/drawable-xhdpi/ic_settings_nfc_payment.png Binary files differnew file mode 100644 index 0000000..3590a15 --- /dev/null +++ b/res/drawable-xhdpi/ic_settings_nfc_payment.png diff --git a/res/layout/nfc_payment_option.xml b/res/layout/nfc_payment_option.xml new file mode 100644 index 0000000..122e041 --- /dev/null +++ b/res/layout/nfc_payment_option.xml @@ -0,0 +1,80 @@ +<?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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="1" + android:id="@+id/nfc_payment_pref" + android:focusable="true" + android:clickable="true" + android:gravity="center_vertical" + android:minHeight="?android:attr/listPreferredItemHeight" + android:background="?android:attr/selectableItemBackground"> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center" + android:minWidth="@*android:dimen/preference_icon_minWidth" + android:orientation="horizontal"> + <ImageView + android:id="@+android:id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="center" + android:minWidth="48dp" + android:scaleType="centerInside" + android:layout_marginEnd="@*android:dimen/preference_item_padding_inner" + /> + </LinearLayout> + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="6dip" + android:layout_marginTop="6dip" + android:layout_marginBottom="6dip" + android:layout_weight="1"> + <TextView + android:id="@+android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium" + android:ellipsize="marquee" + android:fadingEdge="horizontal"/> + <TextView + android:id="@android:id/summary" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@android:id/title" + android:layout_alignStart="@android:id/title" + android:paddingBottom="3dip" + android:visibility="gone" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textSize="13sp" + android:textColor="?android:attr/textColorSecondary" + android:focusable="false" + android:maxLines="4" /> + </RelativeLayout> + <RadioButton + android:id="@android:id/button1" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:duplicateParentState="true" + android:clickable="false" + android:focusable="false" /> +</LinearLayout> diff --git a/res/values/strings.xml b/res/values/strings.xml index a4a3aec..9d20fab 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4589,6 +4589,15 @@ <!-- Warning message title for global font change [CHAR LIMIT=40] --> <string name="global_font_change_title">Change font size</string> + <!-- NFC payment settings --><skip/> + <string name="nfc_payment_settings_title">Tap and Pay</string> + <!-- Option to tell Android to ask the user which payment app to use every time + a payment terminal is tapped --> + <string name="nfc_payment_ask">Ask every time</string> + <!-- Label for the dialog that is shown when the user is asked to set a + preferred payment application --> + <string name="nfc_payment_set_default">Set as your preference?</string> + <!-- Restrictions settings --><skip/> <!-- Restriction settings title [CHAR LIMIT=35] --> diff --git a/res/xml/nfc_payment_settings.xml b/res/xml/nfc_payment_settings.xml new file mode 100644 index 0000000..9b47dda --- /dev/null +++ b/res/xml/nfc_payment_settings.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 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. +--> + +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> +</PreferenceScreen> diff --git a/res/xml/settings_headers.xml b/res/xml/settings_headers.xml index 867fc19..65c42ef 100644 --- a/res/xml/settings_headers.xml +++ b/res/xml/settings_headers.xml @@ -104,6 +104,13 @@ android:title="@string/user_settings_title" android:id="@+id/user_settings" /> + <!-- Manage NFC payment apps --> + <header + android:fragment="com.android.settings.nfc.PaymentSettings" + android:icon="@drawable/ic_settings_nfc_payment" + android:title="@string/nfc_payment_settings_title" + android:id="@+id/nfc_payment_settings" /> + <!-- Manufacturer hook --> <header android:fragment="com.android.settings.WirelessSettings" 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 @@ public class Settings extends PreferenceActivity 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 @@ public class Settings extends PreferenceActivity || 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 class Settings extends PreferenceActivity 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 |