New Tap & Pay UX.

Change-Id: Icbffe0f58d2c37d5691357c13a14ab9a40e53249
This commit is contained in:
Martijn Coenen
2015-04-21 13:47:55 +02:00
parent 79670bbe68
commit fe58b534f6
14 changed files with 646 additions and 278 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -1,45 +1,62 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <?xml version="1.0" encoding="utf-8"?>
android:layout_width="match_parent" <!-- Copyright (C) 2015 The Android Open Source Project
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical" >
<ImageView
android:id="@+id/nfc_payment_tap_image"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
android:src="@drawable/nfc_payment_empty_state"/>
<TextView
android:id="@+id/nfc_payment_empty_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24sp"
android:visibility="gone"
android:paddingTop="16dp"
android:text="@string/nfc_payment_no_apps"/>
<TextView
android:id="@+id/nfc_payment_learn_more"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:clickable="true"
android:textSize="20sp"
android:textStyle="italic"
android:visibility="gone"
android:textColor="@android:color/holo_blue_light"
android:paddingTop="16dp"
android:text="@string/nfc_payment_learn_more"/>
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="5dp" />
</FrameLayout> 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:orientation="vertical">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/nfc_payment_tap_image"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:visibility="gone"
android:src="@drawable/tapandpay_emptystate"/>
<TextView
android:id="@+id/nfc_payment_empty_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="24sp"
android:visibility="gone"
android:paddingTop="32dp"
android:text="@string/nfc_payment_no_apps"/>
</LinearLayout>
<ListView android:id="@android:id/list"
android:drawSelectorOnTop="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbarStyle="@integer/preference_scrollbar_style" />
<!--
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:scrollbarStyle="@integer/preference_scrollbar_style" />
-->
</FrameLayout>
</LinearLayout>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2015 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_gravity="center"
android:gravity="center"
android:orientation="vertical">
<ImageView
android:id="@+id/nfc_payment_tap_image"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:src="@drawable/tapandpay_emptystate"/>
<TextView
android:id="@+id/nfc_payment_empty_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorSecondary"
android:paddingTop="32dp"
android:text="@string/nfc_payment_no_apps"/>
</LinearLayout>

View File

@@ -13,40 +13,28 @@
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:id="@+id/nfc_payment_pref" android:id="@+id/nfc_payment_pref"
android:focusable="true" android:focusable="true"
android:clickable="false" android:clickable="false"
android:gravity="center_vertical" android:orientation="horizontal"
android:paddingTop="10dp" android:paddingLeft="24dip"
android:paddingBottom="10dp"
android:minHeight="?android:attr/listPreferredItemHeight" android:minHeight="?android:attr/listPreferredItemHeight"
android:background="?android:attr/selectableItemBackground"> android:background="?android:attr/selectableItemBackground">
<FrameLayout <RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="fill_parent"
android:gravity="center" android:checkMark="?android:attr/listChoiceIndicatorSingle"
android:minWidth="@*android:dimen/preference_icon_minWidth" />
android:orientation="horizontal"> <ImageView
<ImageView android:id="@+id/banner"
android:id="@+id/banner"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="96dp"
android:scaleType="centerInside"
android:clickable="true"
/>
</FrameLayout>
<RadioButton
android:id="@android:id/button1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="64dp"
android:layout_alignParentEnd="true" android:scaleType="centerInside"
android:layout_centerVertical="true"
android:duplicateParentState="true"
android:clickable="true" android:clickable="true"
android:focusable="false" /> />
</RelativeLayout> </LinearLayout>

View File

@@ -0,0 +1,33 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingEnd="?android:attr/scrollbarSize">
<!-- Settings button -->
<ImageView
android:id="@+id/settings_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dip"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_sysbar_quicksettings"
android:contentDescription="@string/settings_button" />
</LinearLayout>

View File

@@ -5501,24 +5501,35 @@
<!-- NFC payment settings --><skip/> <!-- NFC payment settings --><skip/>
<string name="nfc_payment_settings_title">Tap &amp; pay</string> <string name="nfc_payment_settings_title">Tap &amp; pay</string>
<!-- Google Wallet label. [CHAR LIMIT=40] --><skip/> <!-- Caption for button linking to a page explaining how Tap and Pay works-->
<string name="google_wallet">Google Wallet</string> <string name="nfc_payment_how_it_works">How it works</string>
<!-- String shown when there are no NFC payment applications installed --> <!-- String shown when there are no NFC payment applications installed -->
<string name="nfc_payment_no_apps">Pay with just a tap</string> <string name="nfc_payment_no_apps">Use Tap &amp; pay to make in-store purchases</string>
<!-- String shown before a checkbox, allowing the user to indicate that he wants foreground apps <!-- Header text that can be clicked on to change the default payment app -->
to be able to override the configured default app --> <string name="nfc_payment_default">Payment default</string>
<string name="nfc_payment_favor_foreground">Favor foreground app</string> <!-- Summary text that is shown when no default app is set -->
<!-- String shown when there are no NFC payment applications installed, clickable, pointing to <string name="nfc_payment_default_not_set">Not set</string>
a website to learn more--> <!-- String indicating the label of the default payment app and a description of its state; eg Google Wallet - MasterCard 1234 -->
<string name="nfc_payment_learn_more">Learn more</string> <string name="nfc_payment_app_and_desc"><xliff:g id="app">%1$s</xliff:g> - <xliff:g id="description">%2$s</xliff:g></string>
<!-- Header for action to choose when the open app supports TapPay -->
<string name="nfc_payment_open_app">If open app supports Tap &amp; pay</string>
<!-- If open app supports TapPay, use that app instead of the default -->
<string name="nfc_payment_favor_open">Use that app instead of <xliff:g id="app">%1$s</xliff:g></string>
<!-- If open app supports TapPay, use that app instead of the default (name of default app unknown) -->
<string name="nfc_payment_favor_open_default_unknown">Use that app instead</string>
<!-- If open app supports TapPay, still use the default app -->
<string name="nfc_payment_favor_default">Still use <xliff:g id="app">%1$s</xliff:g></string>
<!-- If open app supports TapPay, still use the default app (name of default app unknown) -->
<string name="nfc_payment_favor_default_default_unknown">Still use default</string>
<!-- Header for a dialog asking the user which payment app to use -->
<string name="nfc_payment_pay_with">At a Tap &amp; pay terminal, pay with:</string>
<!-- NFC More... title. [CHAR LIMIT=40] --> <!-- NFC More... title. [CHAR LIMIT=40] -->
<string name="nfc_more_title">More...</string> <string name="nfc_more_title">More...</string>
<string name="nfc_payment_menu_item_add_service">Find apps</string>
<!-- Label for the dialog that is shown when the user is asked to set a <!-- Label for the dialog that is shown when the user is asked to set a
preferred payment application --> preferred payment application -->
<string name="nfc_payment_set_default_label">Set as your preference?</string> <string name="nfc_payment_set_default_label">Set as your preference?</string>
<string name="nfc_payment_set_default">Always use <xliff:g id="app">%1$s</xliff:g> when you tap &amp; pay?</string> <string name="nfc_payment_set_default">Always use <xliff:g id="app">%1$s</xliff:g> when you Tap &amp; pay?</string>
<string name="nfc_payment_set_default_instead_of">Always use <xliff:g id="app">%1$s</xliff:g> instead of <xliff:g id="app">%2$s</xliff:g> when you tap &amp; pay?</string> <string name="nfc_payment_set_default_instead_of">Always use <xliff:g id="app">%1$s</xliff:g> instead of <xliff:g id="app">%2$s</xliff:g> when you Tap &amp; pay?</string>
<!-- Restrictions settings --><skip/> <!-- Restrictions settings --><skip/>
<!-- Restriction settings title [CHAR LIMIT=35] --> <!-- Restriction settings title [CHAR LIMIT=35] -->

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2015 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 com.android.settings.DropDownPreference;
import com.android.settings.R;
public class NfcForegroundPreference extends DropDownPreference implements
DropDownPreference.Callback, PaymentBackend.Callback {
private final PaymentBackend mPaymentBackend;
public NfcForegroundPreference(Context context, PaymentBackend backend) {
super(context);
mPaymentBackend = backend;
mPaymentBackend.registerCallback(this);
setCallback(this);
refresh();
}
@Override
public void onPaymentAppsChanged() {
refresh();
}
void refresh() {
PaymentBackend.PaymentAppInfo defaultApp = mPaymentBackend.getDefaultApp();
boolean foregroundMode = mPaymentBackend.isForegroundMode();
setPersistent(false);
setTitle(getContext().getString(R.string.nfc_payment_open_app));
CharSequence favorOpen;
CharSequence favorDefault;
clearItems();
if (defaultApp == null) {
favorOpen = getContext().getString(R.string.nfc_payment_favor_open_default_unknown);
favorDefault = getContext().getString(R.string.nfc_payment_favor_default_default_unknown);
} else {
favorOpen = getContext().getString(R.string.nfc_payment_favor_open, defaultApp.label);
favorDefault = getContext().getString(R.string.nfc_payment_favor_default, defaultApp.label);
}
addItem(favorOpen.toString(), true);
addItem(favorDefault.toString(), false);
if (foregroundMode) {
setSelectedValue(true);
} else {
setSelectedValue(false);
}
}
@Override
public boolean onItemSelected(int pos, Object value) {
mPaymentBackend.setForegroundMode((Boolean) value);
return true;
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright (C) 2015 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.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.preference.DialogPreference;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.RadioButton;
import com.android.settings.R;
import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
import java.util.List;
public class NfcPaymentPreference extends DialogPreference implements
DialogInterface.OnClickListener, PaymentBackend.Callback, View.OnClickListener {
private static final String TAG = "NfcPaymentPreference";
private final NfcPaymentAdapter mAdapter;
private final Context mContext;
private final LayoutInflater mLayoutInflater;
private final PaymentBackend mPaymentBackend;
// Fields below only modified on UI thread
private ImageView mSettingsButtonView;
public NfcPaymentPreference(Context context, PaymentBackend backend) {
super(context, null);
mPaymentBackend = backend;
mContext = context;
backend.registerCallback(this);
mAdapter = new NfcPaymentAdapter();
setDialogTitle(context.getString(R.string.nfc_payment_pay_with));
mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
setWidgetLayoutResource(R.layout.preference_widget_settings);
refresh();
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
mSettingsButtonView = (ImageView) view.findViewById(R.id.settings_button);
mSettingsButtonView.setOnClickListener(this);
updateSettingsVisibility();
}
/**
* MUST be called on UI thread.
*/
public void refresh() {
List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos();
PaymentAppInfo defaultApp = mPaymentBackend.getDefaultApp();
if (appInfos != null) {
PaymentAppInfo[] apps = appInfos.toArray(new PaymentAppInfo[appInfos.size()]);
mAdapter.updateApps(apps, defaultApp);
}
setTitle(R.string.nfc_payment_default);
if (defaultApp != null) {
setSummary(mContext.getString(R.string.nfc_payment_app_and_desc,
defaultApp.label, defaultApp.description));
} else {
setSummary(mContext.getString(R.string.nfc_payment_default_not_set));
}
updateSettingsVisibility();
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setSingleChoiceItems(mAdapter, 0, this);
}
@Override
public void onPaymentAppsChanged() {
refresh();
}
@Override
public void onClick(View view) {
PaymentAppInfo defaultAppInfo = mPaymentBackend.getDefaultApp();
if (defaultAppInfo != null && defaultAppInfo.settingsComponent != null) {
Intent settingsIntent = new Intent(Intent.ACTION_MAIN);
settingsIntent.setComponent(defaultAppInfo.settingsComponent);
settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mContext.startActivity(settingsIntent);
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Settings activity not found.");
}
}
}
void updateSettingsVisibility() {
if (mSettingsButtonView != null) {
PaymentAppInfo defaultApp = mPaymentBackend.getDefaultApp();
if (defaultApp == null || defaultApp.settingsComponent == null) {
mSettingsButtonView.setVisibility(View.GONE);
} else {
mSettingsButtonView.setVisibility(View.VISIBLE);
}
}
}
class NfcPaymentAdapter extends BaseAdapter implements CompoundButton.OnCheckedChangeListener,
View.OnClickListener {
// Only modified on UI thread
private PaymentAppInfo[] appInfos;
public NfcPaymentAdapter() {
}
public void updateApps(PaymentAppInfo[] appInfos, PaymentAppInfo currentDefault) {
// Clone app infos, only add those with a banner
this.appInfos = appInfos;
notifyDataSetChanged();
}
@Override
public int getCount() {
return appInfos.length;
}
@Override
public PaymentAppInfo getItem(int i) {
return appInfos[i];
}
@Override
public long getItemId(int i) {
return appInfos[i].componentName.hashCode();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
PaymentAppInfo appInfo = appInfos[position];
if (convertView == null) {
convertView = mLayoutInflater.inflate(
R.layout.nfc_payment_option, parent, false);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView.findViewById(R.id.banner);
holder.radioButton = (RadioButton) convertView.findViewById(R.id.button);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.imageView.setImageDrawable(appInfo.banner);
holder.imageView.setTag(appInfo);
holder.imageView.setOnClickListener(this);
// Prevent checked callback getting called on recycled views
holder.radioButton.setOnCheckedChangeListener(null);
holder.radioButton.setChecked(appInfo.isDefault);
holder.radioButton.setOnCheckedChangeListener(this);
holder.radioButton.setTag(appInfo);
return convertView;
}
public class ViewHolder {
public ImageView imageView;
public RadioButton radioButton;
}
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
PaymentAppInfo appInfo = (PaymentAppInfo) compoundButton.getTag();
makeDefault(appInfo);
}
@Override
public void onClick(View view) {
PaymentAppInfo appInfo = (PaymentAppInfo) view.getTag();
makeDefault(appInfo);
}
void makeDefault(PaymentAppInfo appInfo) {
if (!appInfo.isDefault) {
mPaymentBackend.setDefaultPaymentApp(appInfo.componentName);
}
getDialog().dismiss();
}
}
}

View File

@@ -16,15 +16,22 @@
package com.android.settings.nfc; package com.android.settings.nfc;
import android.content.ComponentName; import android.app.Activity;
import android.content.Context; import android.content.*;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.nfc.NfcAdapter; import android.nfc.NfcAdapter;
import android.nfc.cardemulation.ApduServiceInfo; import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation; import android.nfc.cardemulation.CardEmulation;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException; import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
import com.android.internal.content.PackageMonitor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -32,47 +39,135 @@ import java.util.List;
public class PaymentBackend { public class PaymentBackend {
public static final String TAG = "Settings.PaymentBackend"; public static final String TAG = "Settings.PaymentBackend";
public interface Callback {
void onPaymentAppsChanged();
}
public static class PaymentAppInfo { public static class PaymentAppInfo {
CharSequence caption; CharSequence label;
CharSequence description;
Drawable banner; Drawable banner;
boolean isDefault; boolean isDefault;
public ComponentName componentName; public ComponentName componentName;
public ComponentName settingsComponent;
} }
private final Context mContext; private final Context mContext;
private final NfcAdapter mAdapter; private final NfcAdapter mAdapter;
private final CardEmulation mCardEmuManager; private final CardEmulation mCardEmuManager;
private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
// Fields below only modified on UI thread
private ArrayList<PaymentAppInfo> mAppInfos;
private PaymentAppInfo mDefaultAppInfo;
private ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
public PaymentBackend(Context context) { public PaymentBackend(Context context) {
mContext = context; mContext = context;
mAdapter = NfcAdapter.getDefaultAdapter(context); mAdapter = NfcAdapter.getDefaultAdapter(context);
mCardEmuManager = CardEmulation.getInstance(mAdapter); mCardEmuManager = CardEmulation.getInstance(mAdapter);
refresh();
} }
public List<PaymentAppInfo> getPaymentAppInfos() { public void onPause() {
mSettingsPackageMonitor.unregister();
mContext.unregisterReceiver(mReceiver);
}
public void onResume() {
mSettingsPackageMonitor.register(mContext, mContext.getMainLooper(), false);
// Register broadcast receiver for dynamic resource updates
IntentFilter filter = new IntentFilter(CardEmulation.ACTION_REQUEST_SERVICE_RESOURCES);
mContext.registerReceiver(mReceiver, filter);
}
public void refresh() {
PackageManager pm = mContext.getPackageManager(); PackageManager pm = mContext.getPackageManager();
List<ApduServiceInfo> serviceInfos = List<ApduServiceInfo> serviceInfos =
mCardEmuManager.getServices(CardEmulation.CATEGORY_PAYMENT); mCardEmuManager.getServices(CardEmulation.CATEGORY_PAYMENT);
List<PaymentAppInfo> appInfos = new ArrayList<PaymentAppInfo>(); ArrayList<PaymentAppInfo> appInfos = new ArrayList<PaymentAppInfo>();
if (serviceInfos == null) return appInfos; if (serviceInfos == null) {
makeCallbacks();
ComponentName defaultApp = getDefaultPaymentApp(); return;
for (ApduServiceInfo service : serviceInfos) {
PaymentAppInfo appInfo = new PaymentAppInfo();
appInfo.banner = service.loadBanner(pm);
appInfo.caption = service.getDescription();
if (appInfo.caption == null) {
appInfo.caption = service.loadLabel(pm);
}
appInfo.isDefault = service.getComponent().equals(defaultApp);
appInfo.componentName = service.getComponent();
appInfos.add(appInfo);
} }
return appInfos; ComponentName defaultAppName = getDefaultPaymentApp();
PaymentAppInfo foundDefaultApp = null;
for (ApduServiceInfo service : serviceInfos) {
PaymentAppInfo appInfo = new PaymentAppInfo();
appInfo.label = service.loadLabel(pm);
if (appInfo.label == null) {
appInfo.label = service.loadAppLabel(pm);
}
appInfo.isDefault = service.getComponent().equals(defaultAppName);
if (appInfo.isDefault) {
foundDefaultApp = appInfo;
}
appInfo.componentName = service.getComponent();
String settingsActivity = service.getSettingsActivityName();
if (settingsActivity != null) {
appInfo.settingsComponent = new ComponentName(appInfo.componentName.getPackageName(),
settingsActivity);
} else {
appInfo.settingsComponent = null;
}
if (service.hasDynamicResources()) {
appInfo.description = "";
appInfo.banner = null;
sendBroadcastForResources(appInfo);
} else {
appInfo.description = service.getDescription();
appInfo.banner = service.loadBanner(pm);
}
appInfos.add(appInfo);
}
mAppInfos = appInfos;
mDefaultAppInfo = foundDefaultApp;
makeCallbacks();
}
public void registerCallback(Callback callback) {
mCallbacks.add(callback);
}
public void unregisterCallback(Callback callback) {
mCallbacks.remove(callback);
}
public List<PaymentAppInfo> getPaymentAppInfos() {
return mAppInfos;
}
public PaymentAppInfo getDefaultApp() {
return mDefaultAppInfo;
}
void makeCallbacks() {
for (Callback callback : mCallbacks) {
callback.onPaymentAppsChanged();
}
}
Drawable loadDrawableForPackage(String pkgName, int drawableResId) {
PackageManager pm = mContext.getPackageManager();
try {
Resources res = pm.getResourcesForApplication(pkgName);
Drawable banner = res.getDrawable(drawableResId);
return banner;
} catch (Resources.NotFoundException e) {
return null;
} catch (PackageManager.NameNotFoundException e) {
return null;
}
}
void sendBroadcastForResources(PaymentAppInfo appInfo) {
Intent broadcastIntent = new Intent(CardEmulation.ACTION_REQUEST_SERVICE_RESOURCES);
broadcastIntent.setPackage(appInfo.componentName.getPackageName());
broadcastIntent.putExtra(CardEmulation.EXTRA_SERVICE_COMPONENT, appInfo.componentName);
mContext.sendOrderedBroadcastAsUser(broadcastIntent, UserHandle.CURRENT,
null, mReceiver, null, Activity.RESULT_OK, null, null);
} }
boolean isForegroundMode() { boolean isForegroundMode() {
@@ -103,5 +198,66 @@ public class PaymentBackend {
Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.putString(mContext.getContentResolver(),
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
app != null ? app.flattenToString() : null); app != null ? app.flattenToString() : null);
refresh();
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Bundle results = getResultExtras(false);
if (results != null) {
String desc = results.getString(CardEmulation.EXTRA_DESCRIPTION);
int resId = results.getInt(CardEmulation.EXTRA_BANNER_RES_ID, -1);
// Find corresponding component
PaymentAppInfo matchingAppInfo = null;
for (PaymentAppInfo appInfo : mAppInfos) {
if (appInfo.componentName.equals(
intent.getParcelableExtra(CardEmulation.EXTRA_SERVICE_COMPONENT))) {
matchingAppInfo = appInfo;
}
}
if (matchingAppInfo != null && (desc != null || resId != -1)) {
if (desc != null) {
matchingAppInfo.description = desc;
}
if (resId != -1) {
matchingAppInfo.banner = loadDrawableForPackage(
matchingAppInfo.componentName.getPackageName(), resId);
}
makeCallbacks();
}
} else {
Log.e(TAG, "Didn't find results extra.");
}
}
};
private final Handler mHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
refresh();
}
};
private class SettingsPackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
mHandler.obtainMessage().sendToTarget();
}
@Override
public void onPackageAppeared(String packageName, int reason) {
mHandler.obtainMessage().sendToTarget();
}
@Override
public void onPackageDisappeared(String packageName, int reason) {
mHandler.obtainMessage().sendToTarget();
}
@Override
public void onPackageRemoved(String packageName, int uid) {
mHandler.obtainMessage().sendToTarget();
}
} }
} }

View File

@@ -111,13 +111,13 @@ public final class PaymentDefaultDialog extends AlertActivity implements
if (defaultPaymentApp == null) { if (defaultPaymentApp == null) {
String formatString = getString(R.string.nfc_payment_set_default); String formatString = getString(R.string.nfc_payment_set_default);
String msg = String.format(formatString, String msg = String.format(formatString,
sanitizePaymentAppCaption(requestedPaymentApp.caption.toString())); sanitizePaymentAppCaption(requestedPaymentApp.label.toString()));
p.mMessage = msg; p.mMessage = msg;
} else { } else {
String formatString = getString(R.string.nfc_payment_set_default_instead_of); String formatString = getString(R.string.nfc_payment_set_default_instead_of);
String msg = String.format(formatString, String msg = String.format(formatString,
sanitizePaymentAppCaption(requestedPaymentApp.caption.toString()), sanitizePaymentAppCaption(requestedPaymentApp.label.toString()),
sanitizePaymentAppCaption(defaultPaymentApp.caption.toString())); sanitizePaymentAppCaption(defaultPaymentApp.label.toString()));
p.mMessage = msg; p.mMessage = msg;
} }
p.mPositiveButtonText = getString(R.string.yes); p.mPositiveButtonText = getString(R.string.yes);

View File

@@ -16,47 +16,24 @@
package com.android.settings.nfc; package com.android.settings.nfc;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.TextView;
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger;
import com.android.settings.HelpUtils;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment; import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.nfc.PaymentBackend.PaymentAppInfo; import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
import java.util.List; import java.util.List;
public class PaymentSettings extends SettingsPreferenceFragment implements public class PaymentSettings extends SettingsPreferenceFragment {
OnClickListener, OnPreferenceChangeListener {
public static final String TAG = "PaymentSettings"; public static final String TAG = "PaymentSettings";
private LayoutInflater mInflater;
private PaymentBackend mPaymentBackend; private PaymentBackend mPaymentBackend;
private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
@Override @Override
protected int getMetricsCategory() { protected int getMetricsCategory() {
@@ -68,180 +45,50 @@ public class PaymentSettings extends SettingsPreferenceFragment implements
super.onCreate(icicle); super.onCreate(icicle);
mPaymentBackend = new PaymentBackend(getActivity()); mPaymentBackend = new PaymentBackend(getActivity());
mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }
public void refresh() { @Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ViewGroup contentRoot = (ViewGroup) getListView().getParent();
View emptyView = getActivity().getLayoutInflater().inflate(
R.layout.nfc_payment_empty, contentRoot, false);
contentRoot.addView(emptyView);
getListView().setEmptyView(emptyView);
PreferenceManager manager = getPreferenceManager(); PreferenceManager manager = getPreferenceManager();
PreferenceScreen screen = manager.createPreferenceScreen(getActivity()); PreferenceScreen screen = manager.createPreferenceScreen(getActivity());
// Get all payment services
List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos(); List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos();
if (appInfos != null && appInfos.size() > 0) { if (appInfos != null && appInfos.size() > 0) {
// Add all payment apps NfcPaymentPreference preference =
for (PaymentAppInfo appInfo : appInfos) { new NfcPaymentPreference(getActivity(), mPaymentBackend);
PaymentAppPreference preference = screen.addPreference(preference);
new PaymentAppPreference(getActivity(), appInfo, this); NfcForegroundPreference foreground = new NfcForegroundPreference(getActivity(),
preference.setTitle(appInfo.caption); mPaymentBackend);
if (appInfo.banner != null) {
screen.addPreference(preference);
} else {
// Ignore, no banner
Log.e(TAG, "Couldn't load banner drawable of service " + appInfo.componentName);
}
}
}
TextView emptyText = (TextView) getView().findViewById(R.id.nfc_payment_empty_text);
TextView learnMore = (TextView) getView().findViewById(R.id.nfc_payment_learn_more);
ImageView emptyImage = (ImageView) getView().findViewById(R.id.nfc_payment_tap_image);
if (screen.getPreferenceCount() == 0) {
emptyText.setVisibility(View.VISIBLE);
learnMore.setVisibility(View.VISIBLE);
emptyImage.setVisibility(View.VISIBLE);
getListView().setVisibility(View.GONE);
} else {
SwitchPreference foreground = new SwitchPreference(getActivity());
boolean foregroundMode = mPaymentBackend.isForegroundMode();
foreground.setPersistent(false);
foreground.setTitle(getString(R.string.nfc_payment_favor_foreground));
foreground.setChecked(foregroundMode);
foreground.setOnPreferenceChangeListener(this);
screen.addPreference(foreground); screen.addPreference(foreground);
emptyText.setVisibility(View.GONE);
learnMore.setVisibility(View.GONE);
emptyImage.setVisibility(View.GONE);
getListView().setVisibility(View.VISIBLE);
} }
setPreferenceScreen(screen); setPreferenceScreen(screen);
} }
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View v = mInflater.inflate(R.layout.nfc_payment, container, false);
TextView learnMore = (TextView) v.findViewById(R.id.nfc_payment_learn_more);
learnMore.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String helpUrl;
if (!TextUtils.isEmpty(helpUrl = getResources().getString(
R.string.help_url_nfc_payment))) {
final Uri fullUri = HelpUtils.uriWithAddedParameters(
PaymentSettings.this.getActivity(), Uri.parse(helpUrl));
Intent intent = new Intent(Intent.ACTION_VIEW, fullUri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(intent);
} else {
Log.e(TAG, "Help url not set.");
}
}
});
return v;
}
@Override
public void onClick(View v) {
if (v.getTag() instanceof PaymentAppInfo) {
PaymentAppInfo appInfo = (PaymentAppInfo) v.getTag();
if (appInfo.componentName != null) {
mPaymentBackend.setDefaultPaymentApp(appInfo.componentName);
}
refresh();
}
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false); mPaymentBackend.onResume();
refresh();
} }
@Override @Override
public void onPause() { public void onPause() {
mSettingsPackageMonitor.unregister();
super.onPause(); super.onPause();
mPaymentBackend.onPause();
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
String searchUri = Settings.Secure.getString(getContentResolver(), MenuItem menuItem = menu.add(R.string.nfc_payment_how_it_works);
Settings.Secure.PAYMENT_SERVICE_SEARCH_URI); menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_ALWAYS);
if (!TextUtils.isEmpty(searchUri)) { // TODO link to tutorial screen
MenuItem menuItem = menu.add(R.string.nfc_payment_menu_item_add_service);
menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menuItem.setIntent(new Intent(Intent.ACTION_VIEW,Uri.parse(searchUri)));
}
}
private final Handler mHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
refresh();
}
};
private class SettingsPackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
mHandler.obtainMessage().sendToTarget();
}
@Override
public void onPackageAppeared(String packageName, int reason) {
mHandler.obtainMessage().sendToTarget();
}
@Override
public void onPackageDisappeared(String packageName, int reason) {
mHandler.obtainMessage().sendToTarget();
}
@Override
public void onPackageRemoved(String packageName, int uid) {
mHandler.obtainMessage().sendToTarget();
}
}
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);
RadioButton radioButton = (RadioButton) view.findViewById(android.R.id.button1);
radioButton.setChecked(appInfo.isDefault);
radioButton.setOnClickListener(listener);
radioButton.setTag(appInfo);
ImageView banner = (ImageView) view.findViewById(R.id.banner);
banner.setImageDrawable(appInfo.banner);
banner.setOnClickListener(listener);
banner.setTag(appInfo);
}
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (preference instanceof SwitchPreference) {
mPaymentBackend.setForegroundMode(((Boolean) newValue).booleanValue());
return true;
} else {
return false;
}
} }
} }