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