• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.app.ActivityManager;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.graphics.drawable.Drawable;
29 import android.nfc.NfcAdapter;
30 import android.nfc.cardemulation.ApduServiceInfo;
31 import android.nfc.cardemulation.CardEmulation;
32 import android.os.Bundle;
33 import android.os.UserHandle;
34 import android.util.Log;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.Window;
39 import android.view.WindowManager;
40 import android.widget.AdapterView;
41 import android.widget.BaseAdapter;
42 import android.widget.ImageView;
43 import android.widget.ListView;
44 import android.widget.TextView;
45 
46 import androidx.annotation.NonNull;
47 import androidx.appcompat.app.AppCompatActivity;
48 
49 import com.google.android.material.bottomsheet.BottomSheetBehavior;
50 
51 import java.util.ArrayList;
52 import java.util.List;
53 
54 public class AppChooserActivity extends AppCompatActivity
55         implements AdapterView.OnItemClickListener {
56 
57     static final String TAG = "AppChooserActivity";
58 
59     public static final String EXTRA_APDU_SERVICES = "services";
60     public static final String EXTRA_CATEGORY = "category";
61     public static final String EXTRA_FAILED_COMPONENT = "failed_component";
62 
63     private int mIconSize;
64     private TextView mTextView;
65     private ListView mListView;
66     private ListAdapter mListAdapter;
67     private CardEmulation mCardEmuManager;
68     private String mCategory;
69 
70     final BroadcastReceiver mReceiver = new BroadcastReceiver() {
71         @Override
72         public void onReceive(Context context, Intent intent) {
73             finish();
74         }
75     };
76 
77     @Override
onDestroy()78     protected void onDestroy() {
79         super.onDestroy();
80         unregisterReceiver(mReceiver);
81     }
82 
onCreate(Bundle savedInstanceState, String category, ArrayList<ApduServiceInfo> options, ComponentName failedComponent)83     protected void onCreate(Bundle savedInstanceState, String category,
84             ArrayList<ApduServiceInfo> options, ComponentName failedComponent) {
85         super.onCreate(savedInstanceState);
86 
87         IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
88         registerReceiver(mReceiver, filter);
89 
90         if ((options == null || options.size() == 0) && failedComponent == null) {
91             Log.e(TAG, "onCreate: No components passed in.");
92             finish();
93             return;
94         }
95 
96         mCategory = category;
97         boolean isPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
98 
99         final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
100         if (adapter == null) {
101             Log.e(TAG, "onCreate: adapter is null");
102             finish();
103             return;
104         }
105         mCardEmuManager = CardEmulation.getInstance(adapter);
106 
107         final ActivityManager am = getSystemService(ActivityManager.class);
108         mIconSize = am.getLauncherLargeIconSize();
109 
110         // Three cases:
111         // 1. Failed component and no alternatives: just an OK box
112         // 2. Failed component and alternatives: pick alternative
113         // 3. No failed component and alternatives: pick alternative
114         PackageManager pm = getPackageManager();
115 
116         setContentView(com.android.nfc.R.layout.cardemu_resolver_bottomsheet);
117 
118 
119         findViewById(com.android.nfc.R.id.touch_outside).setOnClickListener(v -> finish());
120         BottomSheetBehavior.from(findViewById(com.android.nfc.R.id.bottom_sheet))
121                 .setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
122                     @Override
123                     public void onStateChanged(@NonNull View bottomSheet, int newState) {
124                         switch (newState) {
125                             case BottomSheetBehavior.STATE_HIDDEN:
126                             finish();
127                             break;
128                         }
129                     }
130 
131                     @Override
132                     public void onSlide(@NonNull View bottomSheet, float slideOffset) {
133                     // no op
134                     }
135                });
136 
137         CharSequence applicationLabel = "unknown";
138         if (failedComponent != null) {
139             try {
140                 ApplicationInfo info = pm.getApplicationInfo(failedComponent.getPackageName(), 0);
141                 applicationLabel = info.loadLabel(pm);
142             } catch (NameNotFoundException e) {
143             }
144 
145         }
146         mTextView = (TextView) findViewById(com.android.nfc.R.id.appchooser_text);
147         if (options.size() == 0 && failedComponent != null) {
148             String formatString = getString(com.android.nfc.R.string.transaction_failure);
149             mTextView.setText(String.format(formatString, applicationLabel));
150         } else {
151             mListAdapter = new ListAdapter(this, options);
152             if (failedComponent != null) {
153                 String formatString = getString(com.android.nfc.R.string.could_not_use_app);
154                 mTextView.setText(String.format(formatString, applicationLabel));
155             }
156 
157             mListView = (ListView) findViewById(com.android.nfc.R.id.resolver_list);
158             mListView.setDivider(getResources().getDrawable(android.R.color.transparent));
159             if (isPayment) {
160                 int height = (int) (getResources().getDisplayMetrics().density * 16);
161                 mListView.setDividerHeight(height);
162             } else {
163                 mListView.setPadding(0, 0, 0, 0);
164             }
165             mListView.setAdapter(mListAdapter);
166             mListView.setOnItemClickListener(this);
167         }
168         Window window = getWindow();
169         window.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
170         window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
171     }
172 
173     @Override
onCreate(Bundle savedInstanceState)174     protected void onCreate(Bundle savedInstanceState) {
175         Intent intent = getIntent();
176         ArrayList<ApduServiceInfo> services = intent.getParcelableArrayListExtra(EXTRA_APDU_SERVICES);
177         String category = intent.getStringExtra(EXTRA_CATEGORY);
178         ComponentName failedComponent = intent.getParcelableExtra(EXTRA_FAILED_COMPONENT);
179         onCreate(savedInstanceState, category, services, failedComponent);
180     }
181 
182     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)183     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
184         DisplayAppInfo info = (DisplayAppInfo) mListAdapter.getItem(position);
185         mCardEmuManager.setDefaultForNextTap(
186                 UserHandle.getUserHandleForUid(info.serviceInfo.getUid()).getIdentifier(),
187                 info.serviceInfo.getComponent());
188         Intent dialogIntent = new Intent(this, TapAgainDialog.class);
189         dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, mCategory);
190         dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, info.serviceInfo);
191         startActivity(dialogIntent);
192         finish();
193     }
194 
195     final class DisplayAppInfo {
196         ApduServiceInfo serviceInfo;
197         CharSequence displayLabel;
198         Drawable displayIcon;
199         Drawable displayBanner;
200 
DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon, Drawable banner)201         public DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon,
202                 Drawable banner) {
203             this.serviceInfo = serviceInfo;
204             displayIcon = icon;
205             displayLabel = label;
206             displayBanner = banner;
207         }
208     }
209 
210     final class ListAdapter extends BaseAdapter {
211         private final LayoutInflater mInflater;
212         private final boolean mIsPayment;
213         private List<DisplayAppInfo> mList;
214 
ListAdapter(Context context, ArrayList<ApduServiceInfo> services)215         public ListAdapter(Context context, ArrayList<ApduServiceInfo> services) {
216             LayoutInflater inflater = null;
217             try {
218                 inflater = context.getSystemService(LayoutInflater.class);
219             } catch (Exception e) {
220                 Log.e(TAG, "ListAdapter: Initiate mInflater failed ", e);
221             }
222             mInflater = inflater;
223             // For each component, get the corresponding app name and icon
224             PackageManager pm = getPackageManager();
225             mList = new ArrayList<DisplayAppInfo>();
226             mIsPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
227             for (ApduServiceInfo service : services) {
228                 CharSequence label = service.getDescription();
229                 if (label == null) label = service.loadLabel(pm);
230 
231                 Drawable icon = pm.getUserBadgedIcon(service.loadIcon(pm),
232                         UserHandle.getUserHandleForUid(service.getUid()));
233 
234                 Drawable banner = null;
235                 if (mIsPayment) {
236                     banner = service.loadBanner(pm);
237                     if (banner == null) {
238                         Log.e(TAG, "ListAdapter: Not showing " + label
239                                 + " because no banner specified");
240                         continue;
241                     }
242                 }
243                 DisplayAppInfo info = new DisplayAppInfo(service, label, icon, banner);
244                 mList.add(info);
245             }
246         }
247 
248         @Override
getCount()249         public int getCount() {
250             return mList.size();
251         }
252 
253         @Override
getItem(int position)254         public Object getItem(int position) {
255             return mList.get(position);
256         }
257 
258         @Override
getItemId(int position)259         public long getItemId(int position) {
260             return position;
261         }
262 
263         @Override
getView(int position, View convertView, ViewGroup parent)264         public View getView(int position, View convertView, ViewGroup parent) {
265             View view;
266             if (convertView == null && mInflater != null) {
267                 if (mIsPayment) {
268                     view = mInflater.inflate(
269                             com.android.nfc.R.layout.cardemu_payment_item, parent, false);
270                 } else {
271                     view = mInflater.inflate(
272                             com.android.nfc.R.layout.cardemu_item, parent, false);
273                 }
274                 final ViewHolder holder = new ViewHolder(view);
275                 view.setTag(holder);
276 
277             } else {
278                 view = convertView;
279             }
280 
281             final ViewHolder holder = (ViewHolder) view.getTag();
282             DisplayAppInfo appInfo = mList.get(position);
283             if (mIsPayment) {
284                 holder.banner.setImageDrawable(appInfo.displayBanner);
285             } else {
286                 holder.icon.setImageDrawable(appInfo.displayIcon);
287                 holder.text.setText(appInfo.displayLabel);
288             }
289             return view;
290         }
291     }
292 
293     static class ViewHolder {
294         public TextView text;
295         public ImageView icon;
296         public ImageView banner;
ViewHolder(View view)297         public ViewHolder(View view) {
298             text = (TextView) view.findViewById(com.android.nfc.R.id.applabel);
299             icon = (ImageView) view.findViewById(com.android.nfc.R.id.appicon);
300             banner = (ImageView) view.findViewById(com.android.nfc.R.id.banner);
301         }
302     }
303 }
304