NFC payment settings.

First version, pending final UX.

Change-Id: I357e900c3f2012b35814ae197c49a8c9b97b7148
This commit is contained in:
Martijn Coenen
2013-08-01 18:13:33 -07:00
parent 8a181dd0d1
commit 26515da087
11 changed files with 519 additions and 1 deletions

View File

@@ -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=""

View File

@@ -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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -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>

View File

@@ -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] -->

View File

@@ -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>

View File

@@ -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"

View File

@@ -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 */ }
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}