Merge "New UX for the NFC default payment settings"

This commit is contained in:
TreeHugger Robot
2022-01-21 12:30:06 +00:00
committed by Android (Google) Code Review
11 changed files with 540 additions and 79 deletions

View File

@@ -0,0 +1,278 @@
/*
* Copyright (C) 2022 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.settings.SettingsEnums;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.Layout;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AlignmentSpan;
import android.text.style.BulletSpan;
import android.text.style.ClickableSpan;
import android.text.style.RelativeSizeSpan;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.applications.defaultapps.DefaultAppPickerFragment;
import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
import com.android.settingslib.widget.CandidateInfo;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* DefaultPaymentSettings handles the NFC default payment app selection.
*/
public class DefaultPaymentSettings extends DefaultAppPickerFragment {
public static final String TAG = "DefaultPaymentSettings";
private PaymentBackend mPaymentBackend;
private List<PaymentAppInfo> mAppInfos;
private Preference mFooterPreference;
@Override
public int getMetricsCategory() {
return SettingsEnums.NFC_PAYMENT;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.nfc_default_payment_settings;
}
@Override
protected String getDefaultKey() {
PaymentAppInfo defaultAppInfo = mPaymentBackend.getDefaultApp();
if (defaultAppInfo != null) {
return defaultAppInfo.componentName.flattenToString() + " "
+ defaultAppInfo.userHandle.getIdentifier();
}
return null;
}
@Override
protected boolean setDefaultKey(String key) {
String[] keys = key.split(" ");
if (keys.length >= 2) {
mPaymentBackend.setDefaultPaymentApp(ComponentName.unflattenFromString(keys[0]),
Integer.parseInt(keys[1]));
}
return true;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mPaymentBackend = new PaymentBackend(getActivity());
mAppInfos = mPaymentBackend.getPaymentAppInfos();
}
@Override
protected void addStaticPreferences(PreferenceScreen screen) {
if (mFooterPreference == null) {
setupFooterPreference();
}
screen.addPreference(mFooterPreference);
}
@Override
public void onResume() {
super.onResume();
mPaymentBackend.onResume();
}
@Override
public void onPause() {
super.onPause();
mPaymentBackend.onPause();
}
/**
* Comparator for NfcPaymentCandidateInfo.
*/
public class NfcPaymentCandidateInfoComparator implements Comparator<NfcPaymentCandidateInfo> {
/**
* Compare the NfcPaymentCandidateInfo by the label string.
*/
public int compare(NfcPaymentCandidateInfo obj1, NfcPaymentCandidateInfo obj2) {
if (obj1.loadLabel() == obj2.loadLabel()) {
return 0;
}
if (obj1.loadLabel() == null) {
return -1;
}
if (obj2.loadLabel() == null) {
return 1;
}
return obj1.loadLabel().toString().compareTo(obj2.loadLabel().toString());
}
}
@Override
public void bindPreferenceExtra(SelectorWithWidgetPreference pref, String key,
CandidateInfo info, String defaultKey, String systemDefaultKey) {
final NfcPaymentCandidateInfo candidateInfo = (NfcPaymentCandidateInfo) info;
if (candidateInfo.isManagedProfile()) {
pref.setSummary("Work");
}
}
@Override
protected List<? extends CandidateInfo> getCandidates() {
final List<NfcPaymentCandidateInfo> candidates = new ArrayList<>();
for (PaymentAppInfo appInfo: mAppInfos) {
UserManager um = getContext().createContextAsUser(
appInfo.userHandle, /*flags=*/0).getSystemService(UserManager.class);
boolean isManagedProfile = um.isManagedProfile(appInfo.userHandle.getIdentifier());
CharSequence label;
label = appInfo.label;
candidates.add(new NfcPaymentCandidateInfo(
appInfo.componentName.flattenToString(),
label,
appInfo.icon,
appInfo.userHandle.getIdentifier(),
isManagedProfile));
}
Collections.sort(candidates, new NfcPaymentCandidateInfoComparator());
return candidates;
}
@VisibleForTesting
class NfcPaymentCandidateInfo extends CandidateInfo {
private final String mKey;
private final CharSequence mLabel;
private final Drawable mDrawable;
private final int mUserId;
private final boolean mIsManagedProfile;
NfcPaymentCandidateInfo(String key, CharSequence label, Drawable drawable, int userId,
boolean isManagedProfile) {
super(true /* enabled */);
mKey = key;
mLabel = label;
mDrawable = drawable;
mUserId = userId;
mIsManagedProfile = isManagedProfile;
}
@Override
public CharSequence loadLabel() {
return mLabel;
}
@Override
public Drawable loadIcon() {
return mDrawable;
}
@Override
public String getKey() {
return mKey + " " + mUserId;
}
public boolean isManagedProfile() {
return mIsManagedProfile;
}
}
@Override
protected CharSequence getConfirmationMessage(CandidateInfo appInfo) {
if (appInfo == null) {
return null;
}
NfcPaymentCandidateInfo paymentInfo = (NfcPaymentCandidateInfo) appInfo;
UserManager um = getContext().createContextAsUser(UserHandle.of(paymentInfo.mUserId),
/*flags=*/0).getSystemService(UserManager.class);
boolean isManagedProfile = um.isManagedProfile(paymentInfo.mUserId);
if (!isManagedProfile) {
return null;
}
final String title = getContext().getString(
R.string.nfc_default_payment_workapp_confirmation_title);
final String messageTitle = getContext().getString(
R.string.nfc_default_payment_workapp_confirmation_message_title);
final String messageOne = getContext().getString(
R.string.nfc_default_payment_workapp_confirmation_message_1);
final String messageTwo = getContext().getString(
R.string.nfc_default_payment_workapp_confirmation_message_2);
final SpannableString titleString = new SpannableString(title);
final SpannableString messageString = new SpannableString(messageTitle);
final SpannableString oneString = new SpannableString(messageOne);
final SpannableString twoString = new SpannableString(messageTwo);
titleString.setSpan(new RelativeSizeSpan(1.5f), 0, title.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
titleString.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 0,
title.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
messageString.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER), 0,
messageTitle.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
oneString.setSpan(new BulletSpan(20), 0, messageOne.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
twoString.setSpan(new BulletSpan(20), 0, messageTwo.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return TextUtils.concat(titleString, "\n\n", messageString, "\n\n", oneString, "\n",
twoString);
}
private void setupFooterPreference() {
final String textNfcDefaultPaymentFooter = getResources().getString(
R.string.nfc_default_payment_footer);
final String textMoreDetails = getResources().getString(R.string.nfc_more_details);
final SpannableString spannableString = new SpannableString(
textNfcDefaultPaymentFooter + System.lineSeparator()
+ System.lineSeparator() + textMoreDetails);
final ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
Intent howItWorksIntent = new Intent(getActivity(), HowItWorks.class);
startActivity(howItWorksIntent);
}
};
if (textNfcDefaultPaymentFooter != null && textMoreDetails != null) {
spannableString.setSpan(clickableSpan, textNfcDefaultPaymentFooter.length() + 1,
textNfcDefaultPaymentFooter.length() + textMoreDetails.length() + 2,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
mFooterPreference = new FooterPreference(getContext());
mFooterPreference.setLayoutResource(R.layout.preference_footer);
mFooterPreference.setTitle(spannableString);
mFooterPreference.setSelectable(false);
mFooterPreference.setIcon(R.drawable.ic_info_outline_24dp);
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2022 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.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.nfc.NfcAdapter;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.applications.defaultapps.DefaultAppPreferenceController;
import com.android.settings.nfc.PaymentBackend.PaymentAppInfo;
import com.android.settingslib.applications.DefaultAppInfo;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
import java.util.List;
/**
* NfcDefaultPaymentPreferenceController shows an app icon and text summary for current selected
* default payment, and links to the nfc default payment selection page.
*/
public class NfcDefaultPaymentPreferenceController extends DefaultAppPreferenceController implements
PaymentBackend.Callback, LifecycleObserver, OnResume, OnPause {
private static final String TAG = "NfcDefaultPaymentController";
private static final String KEY = "nfc_payment_app";
private PaymentBackend mPaymentBackend;
private Preference mPreference;
private Context mContext;
public NfcDefaultPaymentPreferenceController(Context context, Lifecycle lifecycle) {
super(context);
mContext = context;
mPaymentBackend = new PaymentBackend(context);
if (lifecycle != null) {
lifecycle.addObserver(this);
}
}
@Override
public boolean isAvailable() {
final PackageManager pm = mContext.getPackageManager();
if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC)) {
return false;
}
if (NfcAdapter.getDefaultAdapter(mContext) == null) {
return false;
}
if (mPaymentBackend == null) {
mPaymentBackend = new PaymentBackend(mContext);
}
final List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos();
return (appInfos != null && !appInfos.isEmpty())
? true
: false;
}
@Override
public String getPreferenceKey() {
return KEY;
}
@Override
public void onResume() {
if (mPaymentBackend != null) {
mPaymentBackend.registerCallback(this);
mPaymentBackend.onResume();
}
}
@Override
public void onPause() {
if (mPaymentBackend != null) {
mPaymentBackend.unregisterCallback(this);
mPaymentBackend.onPause();
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPreference = screen.findPreference(getPreferenceKey());
super.displayPreference(screen);
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
preference.setIconSpaceReserved(true);
}
@Override
public void onPaymentAppsChanged() {
updateState(mPreference);
}
/**
* PaymentDefaultAppInfo is used to store the default payment app info.
*/
public static class PaymentDefaultAppInfo extends DefaultAppInfo {
public PaymentAppInfo mInfo;
public PaymentDefaultAppInfo(Context context, PackageManager pm, int userId,
PaymentAppInfo info) {
super(context, pm, userId, info.componentName);
mInfo = info;
}
@Override
public Drawable loadIcon() {
return mInfo.icon;
}
}
@Override
protected DefaultAppInfo getDefaultAppInfo() {
if (mPaymentBackend == null) {
return null;
}
final PaymentAppInfo defaultApp = mPaymentBackend.getDefaultApp();
if (defaultApp != null) {
return new PaymentDefaultAppInfo(mContext, mPackageManager,
defaultApp.userHandle.getIdentifier(), defaultApp);
}
return null;
}
}

View File

@@ -16,11 +16,9 @@ package com.android.settings.nfc;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import androidx.preference.DropDownPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
@@ -36,13 +34,18 @@ public class NfcForegroundPreferenceController extends BasePreferenceController
PaymentBackend.Callback, Preference.OnPreferenceChangeListener,
LifecycleObserver, OnStart, OnStop {
private DropDownPreference mPreference;
private ListPreference mPreference;
private PaymentBackend mPaymentBackend;
private MetricsFeatureProvider mMetricsFeatureProvider;
private final String[] mListValues;
private final String[] mListEntries;
public NfcForegroundPreferenceController(Context context, String key) {
super(context, key);
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
mListValues = context.getResources().getStringArray(R.array.nfc_payment_favor_values);
mListEntries = context.getResources().getStringArray(R.array.nfc_payment_favor);
}
public void setPaymentBackend(PaymentBackend backend) {
@@ -78,21 +81,6 @@ public class NfcForegroundPreferenceController extends BasePreferenceController
: UNSUPPORTED_ON_DEVICE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
if (mPreference == null) {
return;
}
mPreference.setEntries(new CharSequence[]{
mContext.getText(R.string.nfc_payment_favor_open),
mContext.getText(R.string.nfc_payment_favor_default)
});
mPreference.setEntryValues(new CharSequence[]{"1", "0"});
}
@Override
public void onPaymentAppsChanged() {
updateState(mPreference);
@@ -100,26 +88,29 @@ public class NfcForegroundPreferenceController extends BasePreferenceController
@Override
public void updateState(Preference preference) {
if (preference instanceof DropDownPreference) {
((DropDownPreference) preference).setValue(
mPaymentBackend.isForegroundMode() ? "1" : "0");
}
super.updateState(preference);
if (!(preference instanceof ListPreference)) {
return;
}
final ListPreference listPreference = (ListPreference) preference;
listPreference.setIconSpaceReserved(true);
listPreference.setValue(mListValues[mPaymentBackend.isForegroundMode() ? 1 : 0]);
}
@Override
public CharSequence getSummary() {
return mPreference.getEntry();
return mListEntries[mPaymentBackend.isForegroundMode() ? 1 : 0];
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if (!(preference instanceof DropDownPreference)) {
if (!(preference instanceof ListPreference)) {
return false;
}
final DropDownPreference pref = (DropDownPreference) preference;
final ListPreference listPreference = (ListPreference) preference;
final String newValueString = (String) newValue;
pref.setSummary(pref.getEntries()[pref.findIndexOfValue(newValueString)]);
listPreference.setSummary(mListEntries[listPreference.findIndexOfValue(newValueString)]);
final boolean foregroundMode = Integer.parseInt(newValueString) != 0;
mPaymentBackend.setForegroundMode(foregroundMode);
mMetricsFeatureProvider.action(mContext,
@@ -127,12 +118,4 @@ public class NfcForegroundPreferenceController extends BasePreferenceController
: SettingsEnums.ACTION_NFC_PAYMENT_ALWAYS_SETTING);
return true;
}
@Override
public void updateNonIndexableKeys(List<String> keys) {
final String key = getPreferenceKey();
if (!TextUtils.isEmpty(key)) {
keys.add(key);
}
}
}
}

View File

@@ -20,6 +20,7 @@ import android.app.ActivityManager;
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.CardEmulation;
@@ -50,6 +51,7 @@ public class PaymentBackend {
public ComponentName componentName;
public ComponentName settingsComponent;
public UserHandle userHandle;
public Drawable icon;
}
/**
@@ -131,6 +133,7 @@ public class PaymentBackend {
appInfo.settingsComponent = null;
}
appInfo.description = service.getDescription();
appInfo.icon = pm.getUserBadgedIcon(service.loadIcon(pm), appInfo.userHandle);
appInfos.add(appInfo);
}

View File

@@ -18,15 +18,11 @@ package com.android.settings.nfc;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@@ -37,8 +33,12 @@ import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.List;
@SearchIndexable
public class PaymentSettings extends DashboardFragment {
@@ -61,13 +61,24 @@ public class PaymentSettings extends DashboardFragment {
return R.xml.nfc_payment_settings;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
return buildPreferenceControllers(context, getSettingsLifecycle());
}
private static List<AbstractPreferenceController> buildPreferenceControllers(Context context,
Lifecycle lifecycle) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new NfcDefaultPaymentPreferenceController(context, lifecycle));
return controllers;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mPaymentBackend = new PaymentBackend(getActivity());
setHasOptionsMenu(true);
use(NfcPaymentPreferenceController.class).setPaymentBackend(mPaymentBackend);
use(NfcForegroundPreferenceController.class).setPaymentBackend(mPaymentBackend);
}
@@ -93,15 +104,6 @@ public class PaymentSettings extends DashboardFragment {
mPaymentBackend.onPause();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
MenuItem menuItem = menu.add(R.string.nfc_payment_how_it_works);
Intent howItWorksIntent = new Intent(getActivity(), HowItWorks.class);
menuItem.setIntent(howItWorksIntent);
menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER);
}
@VisibleForTesting
boolean isShowEmptyImage(PreferenceScreen screen) {
for (int i = 0; i < screen.getPreferenceCount(); i++) {
@@ -127,4 +129,4 @@ public class PaymentSettings extends DashboardFragment {
return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
}
};
}
}