Commit 26515da0 authored by Martijn Coenen's avatar Martijn Coenen

NFC payment settings.

First version, pending final UX.

Change-Id: I357e900c3f2012b35814ae197c49a8c9b97b7148
parent 8a181dd0
......@@ -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=""
......
......@@ -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 {
......
<?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>
......@@ -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] -->
......
<?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>
......@@ -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"
......
......@@ -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 */ }
}
/*
* 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
/*
* 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;
}
}
/*
* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment