1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.nfc.cardemulation;
18 
19 import com.android.internal.R;
20 import com.android.internal.app.AlertActivity;
21 import com.android.internal.app.AlertController;
22 
23 import java.util.ArrayList;
24 import java.util.List;
25 
26 import android.app.ActivityManager;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.graphics.drawable.Drawable;
36 import android.nfc.NfcAdapter;
37 import android.nfc.cardemulation.ApduServiceInfo;
38 import android.nfc.cardemulation.CardEmulation;
39 import android.os.Bundle;
40 import android.util.Log;
41 import android.view.LayoutInflater;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.Window;
45 import android.view.WindowManager;
46 import android.widget.AdapterView;
47 import android.widget.BaseAdapter;
48 import android.widget.ImageView;
49 import android.widget.ListView;
50 import android.widget.TextView;
51 
52 public class AppChooserActivity extends AlertActivity
53         implements AdapterView.OnItemClickListener {
54 
55     static final String TAG = "AppChooserActivity";
56 
57     public static final String EXTRA_APDU_SERVICES = "services";
58     public static final String EXTRA_CATEGORY = "category";
59     public static final String EXTRA_FAILED_COMPONENT = "failed_component";
60 
61     private int mIconSize;
62     private ListView mListView;
63     private ListAdapter mListAdapter;
64     private CardEmulation mCardEmuManager;
65     private String mCategory;
66 
67     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
68         @Override
69         public void onReceive(Context context, Intent intent) {
70             finish();
71         }
72     };
73 
74     @Override
onDestroy()75     protected void onDestroy() {
76         super.onDestroy();
77         unregisterReceiver(mReceiver);
78     }
79 
onCreate(Bundle savedInstanceState, String category, ArrayList<ApduServiceInfo> options, ComponentName failedComponent)80     protected void onCreate(Bundle savedInstanceState, String category,
81             ArrayList<ApduServiceInfo> options, ComponentName failedComponent) {
82         super.onCreate(savedInstanceState);
83         setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert);
84 
85         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
86         registerReceiver(mReceiver, filter);
87 
88         if ((options == null || options.size() == 0) && failedComponent == null) {
89             Log.e(TAG, "No components passed in.");
90             finish();
91             return;
92         }
93 
94         mCategory = category;
95         boolean isPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
96 
97         final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
98         mCardEmuManager = CardEmulation.getInstance(adapter);
99         AlertController.AlertParams ap = mAlertParams;
100 
101         final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
102         mIconSize = am.getLauncherLargeIconSize();
103 
104         // Three cases:
105         // 1. Failed component and no alternatives: just an OK box
106         // 2. Failed component and alternatives: pick alternative
107         // 3. No failed component and alternatives: pick alternative
108         PackageManager pm = getPackageManager();
109 
110         CharSequence applicationLabel = "unknown";
111         if (failedComponent != null) {
112             try {
113                 ApplicationInfo info = pm.getApplicationInfo(failedComponent.getPackageName(), 0);
114                 applicationLabel = info.loadLabel(pm);
115             } catch (NameNotFoundException e) {
116             }
117 
118         }
119         if (options.size() == 0 && failedComponent != null) {
120             String formatString = getString(com.android.nfc.R.string.transaction_failure);
121             ap.mTitle = "";
122             ap.mMessage = String.format(formatString, applicationLabel);
123             ap.mPositiveButtonText = getString(R.string.ok);
124             setupAlert();
125         } else {
126             mListAdapter = new ListAdapter(this, options);
127             if (failedComponent != null) {
128                 String formatString = getString(com.android.nfc.R.string.could_not_use_app);
129                 ap.mTitle = String.format(formatString, applicationLabel);
130                 ap.mNegativeButtonText = getString(R.string.cancel);
131             } else {
132                 if (CardEmulation.CATEGORY_PAYMENT.equals(category)) {
133                     ap.mTitle = getString(com.android.nfc.R.string.pay_with);
134                 } else {
135                     ap.mTitle = getString(com.android.nfc.R.string.complete_with);
136                 }
137             }
138             ap.mView = getLayoutInflater().inflate(com.android.nfc.R.layout.cardemu_resolver, null);
139 
140             mListView = (ListView) ap.mView.findViewById(com.android.nfc.R.id.resolver_list);
141             if (isPayment) {
142                 mListView.setDivider(getResources().getDrawable(android.R.color.transparent));
143                 int height = (int) (getResources().getDisplayMetrics().density * 16);
144                 mListView.setDividerHeight(height);
145             } else {
146                 mListView.setPadding(0, 0, 0, 0);
147             }
148             mListView.setAdapter(mListAdapter);
149             mListView.setOnItemClickListener(this);
150 
151             setupAlert();
152         }
153         Window window = getWindow();
154         window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
155     }
156 
157     @Override
onCreate(Bundle savedInstanceState)158     protected void onCreate(Bundle savedInstanceState) {
159         Intent intent = getIntent();
160         ArrayList<ApduServiceInfo> services = intent.getParcelableArrayListExtra(EXTRA_APDU_SERVICES);
161         String category = intent.getStringExtra(EXTRA_CATEGORY);
162         ComponentName failedComponent = intent.getParcelableExtra(EXTRA_FAILED_COMPONENT);
163         onCreate(savedInstanceState, category, services, failedComponent);
164     }
165 
166     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)167     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
168         DisplayAppInfo info = (DisplayAppInfo) mListAdapter.getItem(position);
169         mCardEmuManager.setDefaultForNextTap(info.serviceInfo.getComponent());
170         Intent dialogIntent = new Intent(this, TapAgainDialog.class);
171         dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, mCategory);
172         dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, info.serviceInfo);
173         startActivity(dialogIntent);
174         finish();
175     }
176 
177     final class DisplayAppInfo {
178         ApduServiceInfo serviceInfo;
179         CharSequence displayLabel;
180         Drawable displayIcon;
181         Drawable displayBanner;
182 
DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon, Drawable banner)183         public DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon,
184                 Drawable banner) {
185             this.serviceInfo = serviceInfo;
186             displayIcon = icon;
187             displayLabel = label;
188             displayBanner = banner;
189         }
190     }
191 
192     final class ListAdapter extends BaseAdapter {
193         private final LayoutInflater mInflater;
194         private final boolean mIsPayment;
195         private List<DisplayAppInfo> mList;
196 
ListAdapter(Context context, ArrayList<ApduServiceInfo> services)197         public ListAdapter(Context context, ArrayList<ApduServiceInfo> services) {
198             mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
199             // For each component, get the corresponding app name and icon
200             PackageManager pm = getPackageManager();
201             mList = new ArrayList<DisplayAppInfo>();
202             mIsPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
203             for (ApduServiceInfo service : services) {
204                 CharSequence label = service.getDescription();
205                 if (label == null) label = service.loadLabel(pm);
206                 Drawable icon = service.loadIcon(pm);
207                 Drawable banner = null;
208                 if (mIsPayment) {
209                     banner = service.loadBanner(pm);
210                     if (banner == null) {
211                         Log.e(TAG, "Not showing " + label + " because no banner specified.");
212                         continue;
213                     }
214                 }
215                 DisplayAppInfo info = new DisplayAppInfo(service, label, icon, banner);
216                 mList.add(info);
217             }
218         }
219 
220         @Override
getCount()221         public int getCount() {
222             return mList.size();
223         }
224 
225         @Override
getItem(int position)226         public Object getItem(int position) {
227             return mList.get(position);
228         }
229 
230         @Override
getItemId(int position)231         public long getItemId(int position) {
232             return position;
233         }
234 
235         @Override
getView(int position, View convertView, ViewGroup parent)236         public View getView(int position, View convertView, ViewGroup parent) {
237             View view;
238             if (convertView == null) {
239                 if (mIsPayment) {
240                     view = mInflater.inflate(
241                             com.android.nfc.R.layout.cardemu_payment_item, parent, false);
242                 } else {
243                     view = mInflater.inflate(
244                             com.android.nfc.R.layout.cardemu_item, parent, false);
245                 }
246                 final ViewHolder holder = new ViewHolder(view);
247                 view.setTag(holder);
248 
249             } else {
250                 view = convertView;
251             }
252 
253             final ViewHolder holder = (ViewHolder) view.getTag();
254             DisplayAppInfo appInfo = mList.get(position);
255             if (mIsPayment) {
256                 holder.banner.setImageDrawable(appInfo.displayBanner);
257             } else {
258                 ViewGroup.LayoutParams lp = holder.icon.getLayoutParams();
259                 lp.width = lp.height = mIconSize;
260                 holder.icon.setImageDrawable(appInfo.displayIcon);
261                 holder.text.setText(appInfo.displayLabel);
262             }
263             return view;
264         }
265     }
266 
267     static class ViewHolder {
268         public TextView text;
269         public ImageView icon;
270         public ImageView banner;
ViewHolder(View view)271         public ViewHolder(View view) {
272             text = (TextView) view.findViewById(com.android.nfc.R.id.applabel);
273             icon = (ImageView) view.findViewById(com.android.nfc.R.id.appicon);
274             banner = (ImageView) view.findViewById(com.android.nfc.R.id.banner);
275         }
276     }
277 }