1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 package com.android.settings.nfc; 15 16 import android.app.Dialog; 17 import android.content.ActivityNotFoundException; 18 import android.content.Context; 19 import android.content.DialogInterface; 20 import android.content.Intent; 21 import android.content.pm.PackageManager; 22 import android.nfc.NfcAdapter; 23 import android.os.UserManager; 24 import android.util.Log; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.widget.BaseAdapter; 29 import android.widget.CompoundButton; 30 import android.widget.ImageView; 31 import android.widget.RadioButton; 32 33 import androidx.appcompat.app.AlertDialog.Builder; 34 import androidx.preference.Preference; 35 import androidx.preference.PreferenceScreen; 36 import androidx.preference.PreferenceViewHolder; 37 38 import com.android.settings.R; 39 import com.android.settings.core.BasePreferenceController; 40 import com.android.settings.nfc.PaymentBackend.PaymentAppInfo; 41 import com.android.settingslib.core.lifecycle.LifecycleObserver; 42 import com.android.settingslib.core.lifecycle.events.OnStart; 43 import com.android.settingslib.core.lifecycle.events.OnStop; 44 45 import java.util.List; 46 47 public class NfcPaymentPreferenceController extends BasePreferenceController implements 48 PaymentBackend.Callback, View.OnClickListener, NfcPaymentPreference.Listener, 49 LifecycleObserver, OnStart, OnStop { 50 51 private static final String TAG = "NfcPaymentController"; 52 53 private final NfcPaymentAdapter mAdapter; 54 private PaymentBackend mPaymentBackend; 55 private NfcPaymentPreference mPreference; 56 private ImageView mSettingsButtonView; 57 NfcPaymentPreferenceController(Context context, String key)58 public NfcPaymentPreferenceController(Context context, String key) { 59 super(context, key); 60 mAdapter = new NfcPaymentAdapter(context); 61 } 62 setPaymentBackend(PaymentBackend backend)63 public void setPaymentBackend(PaymentBackend backend) { 64 mPaymentBackend = backend; 65 } 66 67 @Override onStart()68 public void onStart() { 69 if (mPaymentBackend != null) { 70 mPaymentBackend.registerCallback(this); 71 } 72 } 73 74 @Override onStop()75 public void onStop() { 76 if (mPaymentBackend != null) { 77 mPaymentBackend.unregisterCallback(this); 78 } 79 } 80 81 @Override getAvailabilityStatus()82 public int getAvailabilityStatus() { 83 final PackageManager pm = mContext.getPackageManager(); 84 if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC)) { 85 return UNSUPPORTED_ON_DEVICE; 86 } 87 if (NfcAdapter.getDefaultAdapter(mContext) == null) { 88 return UNSUPPORTED_ON_DEVICE; 89 } 90 if (mPaymentBackend == null) { 91 mPaymentBackend = new PaymentBackend(mContext); 92 } 93 final List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos(); 94 return (appInfos != null && !appInfos.isEmpty()) 95 ? AVAILABLE 96 : UNSUPPORTED_ON_DEVICE; 97 } 98 99 @Override displayPreference(PreferenceScreen screen)100 public void displayPreference(PreferenceScreen screen) { 101 super.displayPreference(screen); 102 mPreference = screen.findPreference(getPreferenceKey()); 103 if (mPreference != null) { 104 mPreference.initialize(this); 105 } 106 } 107 108 @Override onBindViewHolder(PreferenceViewHolder view)109 public void onBindViewHolder(PreferenceViewHolder view) { 110 mSettingsButtonView = (ImageView) view.findViewById(R.id.settings_button); 111 mSettingsButtonView.setOnClickListener(this); 112 113 updateSettingsVisibility(); 114 } 115 116 @Override updateState(Preference preference)117 public void updateState(Preference preference) { 118 final List<PaymentAppInfo> appInfos = mPaymentBackend.getPaymentAppInfos(); 119 if (appInfos != null) { 120 final PaymentAppInfo[] apps = appInfos.toArray(new PaymentAppInfo[appInfos.size()]); 121 mAdapter.updateApps(apps); 122 } 123 super.updateState(preference); 124 updateSettingsVisibility(); 125 } 126 127 @Override getSummary()128 public CharSequence getSummary() { 129 final PaymentAppInfo defaultApp = mPaymentBackend.getDefaultApp(); 130 if (defaultApp != null) { 131 UserManager um = mContext.createContextAsUser( 132 defaultApp.userHandle, /*flags=*/0).getSystemService(UserManager.class); 133 134 return defaultApp.label + " (" + um.getUserName() + ")"; 135 } else { 136 return mContext.getText(R.string.nfc_payment_default_not_set); 137 } 138 } 139 140 @Override onPrepareDialogBuilder(Builder builder, DialogInterface.OnClickListener listener)141 public void onPrepareDialogBuilder(Builder builder, 142 DialogInterface.OnClickListener listener) { 143 builder.setSingleChoiceItems(mAdapter, 0, listener); 144 } 145 146 @Override onPaymentAppsChanged()147 public void onPaymentAppsChanged() { 148 updateState(mPreference); 149 } 150 151 @Override onClick(View view)152 public void onClick(View view) { 153 final PaymentAppInfo defaultAppInfo = mPaymentBackend.getDefaultApp(); 154 if (defaultAppInfo != null && defaultAppInfo.settingsComponent != null) { 155 final Intent settingsIntent = new Intent(Intent.ACTION_MAIN); 156 settingsIntent.setComponent(defaultAppInfo.settingsComponent); 157 settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 158 try { 159 mContext.startActivity(settingsIntent); 160 } catch (ActivityNotFoundException e) { 161 Log.e(TAG, "Settings activity not found."); 162 } 163 } 164 } 165 updateSettingsVisibility()166 private void updateSettingsVisibility() { 167 if (mSettingsButtonView != null) { 168 final PaymentAppInfo defaultApp = mPaymentBackend.getDefaultApp(); 169 if (defaultApp == null || defaultApp.settingsComponent == null) { 170 mSettingsButtonView.setVisibility(View.GONE); 171 } else { 172 mSettingsButtonView.setVisibility(View.VISIBLE); 173 } 174 } 175 } 176 177 private class NfcPaymentAdapter extends BaseAdapter implements 178 CompoundButton.OnCheckedChangeListener, View.OnClickListener { 179 private final LayoutInflater mLayoutInflater; 180 181 // Only modified on UI thread 182 private PaymentAppInfo[] appInfos; 183 NfcPaymentAdapter(Context context)184 public NfcPaymentAdapter(Context context) { 185 mLayoutInflater = (LayoutInflater) context.getSystemService( 186 Context.LAYOUT_INFLATER_SERVICE); 187 } 188 updateApps(PaymentAppInfo[] appInfos)189 public void updateApps(PaymentAppInfo[] appInfos) { 190 // Clone app infos, only add an application label 191 this.appInfos = appInfos; 192 notifyDataSetChanged(); 193 } 194 195 @Override getCount()196 public int getCount() { 197 return (appInfos != null) ? appInfos.length : 0; 198 } 199 200 @Override getItem(int i)201 public PaymentAppInfo getItem(int i) { 202 return appInfos[i]; 203 } 204 205 @Override getItemId(int i)206 public long getItemId(int i) { 207 return appInfos[i].componentName.hashCode(); 208 } 209 210 @Override getView(int position, View convertView, ViewGroup parent)211 public View getView(int position, View convertView, ViewGroup parent) { 212 ViewHolder holder; 213 final PaymentAppInfo appInfo = appInfos[position]; 214 if (convertView == null) { 215 convertView = mLayoutInflater.inflate( 216 R.layout.nfc_payment_option, parent, false); 217 holder = new ViewHolder(); 218 holder.radioButton = convertView.findViewById(R.id.button); 219 convertView.setTag(holder); 220 } else { 221 holder = (ViewHolder) convertView.getTag(); 222 } 223 224 // Prevent checked callback getting called on recycled views 225 UserManager um = mContext.createContextAsUser( 226 appInfo.userHandle, /*flags=*/0).getSystemService(UserManager.class); 227 228 holder.radioButton.setOnCheckedChangeListener(null); 229 holder.radioButton.setChecked(appInfo.isDefault); 230 holder.radioButton.setContentDescription(appInfo.label + " (" + um.getUserName() + ")"); 231 holder.radioButton.setOnCheckedChangeListener(this); 232 holder.radioButton.setTag(appInfo); 233 holder.radioButton.setText(appInfo.label + " (" + um.getUserName() + ")"); 234 return convertView; 235 } 236 237 private class ViewHolder { 238 public RadioButton radioButton; 239 } 240 241 @Override onCheckedChanged(CompoundButton compoundButton, boolean b)242 public void onCheckedChanged(CompoundButton compoundButton, boolean b) { 243 PaymentAppInfo appInfo = (PaymentAppInfo) compoundButton.getTag(); 244 makeDefault(appInfo); 245 } 246 247 @Override onClick(View view)248 public void onClick(View view) { 249 PaymentAppInfo appInfo = (PaymentAppInfo) view.getTag(); 250 makeDefault(appInfo); 251 } 252 makeDefault(PaymentAppInfo appInfo)253 private void makeDefault(PaymentAppInfo appInfo) { 254 if (!appInfo.isDefault) { 255 mPaymentBackend.setDefaultPaymentApp(appInfo.componentName, 256 appInfo.userHandle.getIdentifier()); 257 } 258 final Dialog dialog = mPreference.getDialog(); 259 if (dialog != null) { 260 dialog.dismiss(); 261 } 262 } 263 } 264 } 265