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, "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 mCardEmuManager = CardEmulation.getInstance(adapter); 101 102 final ActivityManager am = getSystemService(ActivityManager.class); 103 mIconSize = am.getLauncherLargeIconSize(); 104 105 // Three cases: 106 // 1. Failed component and no alternatives: just an OK box 107 // 2. Failed component and alternatives: pick alternative 108 // 3. No failed component and alternatives: pick alternative 109 PackageManager pm = getPackageManager(); 110 111 setContentView(com.android.nfc.R.layout.cardemu_resolver_bottomsheet); 112 113 114 findViewById(com.android.nfc.R.id.touch_outside).setOnClickListener(v -> finish()); 115 BottomSheetBehavior.from(findViewById(com.android.nfc.R.id.bottom_sheet)) 116 .setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { 117 @Override 118 public void onStateChanged(@NonNull View bottomSheet, int newState) { 119 switch (newState) { 120 case BottomSheetBehavior.STATE_HIDDEN: 121 finish(); 122 break; 123 } 124 } 125 126 @Override 127 public void onSlide(@NonNull View bottomSheet, float slideOffset) { 128 // no op 129 } 130 }); 131 132 CharSequence applicationLabel = "unknown"; 133 if (failedComponent != null) { 134 try { 135 ApplicationInfo info = pm.getApplicationInfo(failedComponent.getPackageName(), 0); 136 applicationLabel = info.loadLabel(pm); 137 } catch (NameNotFoundException e) { 138 } 139 140 } 141 mTextView = (TextView) findViewById(com.android.nfc.R.id.appchooser_text); 142 if (options.size() == 0 && failedComponent != null) { 143 String formatString = getString(com.android.nfc.R.string.transaction_failure); 144 mTextView.setText(String.format(formatString, applicationLabel)); 145 } else { 146 mListAdapter = new ListAdapter(this, options); 147 if (failedComponent != null) { 148 String formatString = getString(com.android.nfc.R.string.could_not_use_app); 149 mTextView.setText(String.format(formatString, applicationLabel)); 150 } 151 152 mListView = (ListView) findViewById(com.android.nfc.R.id.resolver_list); 153 mListView.setDivider(getResources().getDrawable(android.R.color.transparent)); 154 if (isPayment) { 155 int height = (int) (getResources().getDisplayMetrics().density * 16); 156 mListView.setDividerHeight(height); 157 } else { 158 mListView.setPadding(0, 0, 0, 0); 159 } 160 mListView.setAdapter(mListAdapter); 161 mListView.setOnItemClickListener(this); 162 } 163 Window window = getWindow(); 164 window.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND); 165 window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); 166 } 167 168 @Override onCreate(Bundle savedInstanceState)169 protected void onCreate(Bundle savedInstanceState) { 170 Intent intent = getIntent(); 171 ArrayList<ApduServiceInfo> services = intent.getParcelableArrayListExtra(EXTRA_APDU_SERVICES); 172 String category = intent.getStringExtra(EXTRA_CATEGORY); 173 ComponentName failedComponent = intent.getParcelableExtra(EXTRA_FAILED_COMPONENT); 174 onCreate(savedInstanceState, category, services, failedComponent); 175 } 176 177 @Override onItemClick(AdapterView<?> parent, View view, int position, long id)178 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 179 DisplayAppInfo info = (DisplayAppInfo) mListAdapter.getItem(position); 180 mCardEmuManager.setDefaultForNextTap( 181 UserHandle.getUserHandleForUid(info.serviceInfo.getUid()).getIdentifier(), 182 info.serviceInfo.getComponent()); 183 Intent dialogIntent = new Intent(this, TapAgainDialog.class); 184 dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, mCategory); 185 dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, info.serviceInfo); 186 startActivity(dialogIntent); 187 finish(); 188 } 189 190 final class DisplayAppInfo { 191 ApduServiceInfo serviceInfo; 192 CharSequence displayLabel; 193 Drawable displayIcon; 194 Drawable displayBanner; 195 DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon, Drawable banner)196 public DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon, 197 Drawable banner) { 198 this.serviceInfo = serviceInfo; 199 displayIcon = icon; 200 displayLabel = label; 201 displayBanner = banner; 202 } 203 } 204 205 final class ListAdapter extends BaseAdapter { 206 private final LayoutInflater mInflater; 207 private final boolean mIsPayment; 208 private List<DisplayAppInfo> mList; 209 ListAdapter(Context context, ArrayList<ApduServiceInfo> services)210 public ListAdapter(Context context, ArrayList<ApduServiceInfo> services) { 211 mInflater = context.getSystemService(LayoutInflater.class); 212 // For each component, get the corresponding app name and icon 213 PackageManager pm = getPackageManager(); 214 mList = new ArrayList<DisplayAppInfo>(); 215 mIsPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory); 216 for (ApduServiceInfo service : services) { 217 CharSequence label = service.getDescription(); 218 if (label == null) label = service.loadLabel(pm); 219 220 Drawable icon = pm.getUserBadgedIcon(service.loadIcon(pm), 221 UserHandle.getUserHandleForUid(service.getUid())); 222 223 Drawable banner = null; 224 if (mIsPayment) { 225 banner = service.loadBanner(pm); 226 if (banner == null) { 227 Log.e(TAG, "Not showing " + label + " because no banner specified."); 228 continue; 229 } 230 } 231 DisplayAppInfo info = new DisplayAppInfo(service, label, icon, banner); 232 mList.add(info); 233 } 234 } 235 236 @Override getCount()237 public int getCount() { 238 return mList.size(); 239 } 240 241 @Override getItem(int position)242 public Object getItem(int position) { 243 return mList.get(position); 244 } 245 246 @Override getItemId(int position)247 public long getItemId(int position) { 248 return position; 249 } 250 251 @Override getView(int position, View convertView, ViewGroup parent)252 public View getView(int position, View convertView, ViewGroup parent) { 253 View view; 254 if (convertView == null) { 255 if (mIsPayment) { 256 view = mInflater.inflate( 257 com.android.nfc.R.layout.cardemu_payment_item, parent, false); 258 } else { 259 view = mInflater.inflate( 260 com.android.nfc.R.layout.cardemu_item, parent, false); 261 } 262 final ViewHolder holder = new ViewHolder(view); 263 view.setTag(holder); 264 265 } else { 266 view = convertView; 267 } 268 269 final ViewHolder holder = (ViewHolder) view.getTag(); 270 DisplayAppInfo appInfo = mList.get(position); 271 if (mIsPayment) { 272 holder.banner.setImageDrawable(appInfo.displayBanner); 273 } else { 274 holder.icon.setImageDrawable(appInfo.displayIcon); 275 holder.text.setText(appInfo.displayLabel); 276 } 277 return view; 278 } 279 } 280 281 static class ViewHolder { 282 public TextView text; 283 public ImageView icon; 284 public ImageView banner; ViewHolder(View view)285 public ViewHolder(View view) { 286 text = (TextView) view.findViewById(com.android.nfc.R.id.applabel); 287 icon = (ImageView) view.findViewById(com.android.nfc.R.id.appicon); 288 banner = (ImageView) view.findViewById(com.android.nfc.R.id.banner); 289 } 290 } 291 } 292