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