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