blob: 256df678481bdf4e856151d4054a4655365df713 [file] [log] [blame]
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nfc.cardemulation;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.nfc.NfcAdapter;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import java.util.ArrayList;
import java.util.List;
public class AppChooserActivity extends AppCompatActivity
implements AdapterView.OnItemClickListener {
static final String TAG = "AppChooserActivity";
public static final String EXTRA_APDU_SERVICES = "services";
public static final String EXTRA_CATEGORY = "category";
public static final String EXTRA_FAILED_COMPONENT = "failed_component";
private int mIconSize;
private TextView mTextView;
private ListView mListView;
private ListAdapter mListAdapter;
private CardEmulation mCardEmuManager;
private String mCategory;
final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
protected void onCreate(Bundle savedInstanceState, String category,
ArrayList<ApduServiceInfo> options, ComponentName failedComponent) {
super.onCreate(savedInstanceState);
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
if ((options == null || options.size() == 0) && failedComponent == null) {
Log.e(TAG, "No components passed in.");
finish();
return;
}
mCategory = category;
boolean isPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this);
mCardEmuManager = CardEmulation.getInstance(adapter);
final ActivityManager am = getSystemService(ActivityManager.class);
mIconSize = am.getLauncherLargeIconSize();
// Three cases:
// 1. Failed component and no alternatives: just an OK box
// 2. Failed component and alternatives: pick alternative
// 3. No failed component and alternatives: pick alternative
PackageManager pm = getPackageManager();
setContentView(com.android.nfc.R.layout.cardemu_resolver_bottomsheet);
findViewById(com.android.nfc.R.id.touch_outside).setOnClickListener(v -> finish());
BottomSheetBehavior.from(findViewById(com.android.nfc.R.id.bottom_sheet))
.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View bottomSheet, int newState) {
switch (newState) {
case BottomSheetBehavior.STATE_HIDDEN:
finish();
break;
}
}
@Override
public void onSlide(@NonNull View bottomSheet, float slideOffset) {
// no op
}
});
CharSequence applicationLabel = "unknown";
if (failedComponent != null) {
try {
ApplicationInfo info = pm.getApplicationInfo(failedComponent.getPackageName(), 0);
applicationLabel = info.loadLabel(pm);
} catch (NameNotFoundException e) {
}
}
mTextView = (TextView) findViewById(com.android.nfc.R.id.appchooser_text);
if (options.size() == 0 && failedComponent != null) {
String formatString = getString(com.android.nfc.R.string.transaction_failure);
mTextView.setText(String.format(formatString, applicationLabel));
} else {
mListAdapter = new ListAdapter(this, options);
if (failedComponent != null) {
String formatString = getString(com.android.nfc.R.string.could_not_use_app);
mTextView.setText(String.format(formatString, applicationLabel));
}
mListView = (ListView) findViewById(com.android.nfc.R.id.resolver_list);
mListView.setDivider(getResources().getDrawable(android.R.color.transparent));
if (isPayment) {
int height = (int) (getResources().getDisplayMetrics().density * 16);
mListView.setDividerHeight(height);
} else {
mListView.setPadding(0, 0, 0, 0);
}
mListView.setAdapter(mListAdapter);
mListView.setOnItemClickListener(this);
}
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Intent intent = getIntent();
ArrayList<ApduServiceInfo> services = intent.getParcelableArrayListExtra(EXTRA_APDU_SERVICES);
String category = intent.getStringExtra(EXTRA_CATEGORY);
ComponentName failedComponent = intent.getParcelableExtra(EXTRA_FAILED_COMPONENT);
onCreate(savedInstanceState, category, services, failedComponent);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
DisplayAppInfo info = (DisplayAppInfo) mListAdapter.getItem(position);
mCardEmuManager.setDefaultForNextTap(
UserHandle.getUserHandleForUid(info.serviceInfo.getUid()).getIdentifier(),
info.serviceInfo.getComponent());
Intent dialogIntent = new Intent(this, TapAgainDialog.class);
dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, mCategory);
dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, info.serviceInfo);
startActivity(dialogIntent);
finish();
}
final class DisplayAppInfo {
ApduServiceInfo serviceInfo;
CharSequence displayLabel;
Drawable displayIcon;
Drawable displayBanner;
public DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon,
Drawable banner) {
this.serviceInfo = serviceInfo;
displayIcon = icon;
displayLabel = label;
displayBanner = banner;
}
}
final class ListAdapter extends BaseAdapter {
private final LayoutInflater mInflater;
private final boolean mIsPayment;
private List<DisplayAppInfo> mList;
public ListAdapter(Context context, ArrayList<ApduServiceInfo> services) {
mInflater = context.getSystemService(LayoutInflater.class);
// For each component, get the corresponding app name and icon
PackageManager pm = getPackageManager();
mList = new ArrayList<DisplayAppInfo>();
mIsPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory);
for (ApduServiceInfo service : services) {
CharSequence label = service.getDescription();
if (label == null) label = service.loadLabel(pm);
Drawable icon = pm.getUserBadgedIcon(service.loadIcon(pm),
UserHandle.getUserHandleForUid(service.getUid()));
Drawable banner = null;
if (mIsPayment) {
banner = service.loadBanner(pm);
if (banner == null) {
Log.e(TAG, "Not showing " + label + " because no banner specified.");
continue;
}
}
DisplayAppInfo info = new DisplayAppInfo(service, label, icon, banner);
mList.add(info);
}
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
if (mIsPayment) {
view = mInflater.inflate(
com.android.nfc.R.layout.cardemu_payment_item, parent, false);
} else {
view = mInflater.inflate(
com.android.nfc.R.layout.cardemu_item, parent, false);
}
final ViewHolder holder = new ViewHolder(view);
view.setTag(holder);
} else {
view = convertView;
}
final ViewHolder holder = (ViewHolder) view.getTag();
DisplayAppInfo appInfo = mList.get(position);
if (mIsPayment) {
holder.banner.setImageDrawable(appInfo.displayBanner);
} else {
holder.icon.setImageDrawable(appInfo.displayIcon);
holder.text.setText(appInfo.displayLabel);
}
return view;
}
}
static class ViewHolder {
public TextView text;
public ImageView icon;
public ImageView banner;
public ViewHolder(View view) {
text = (TextView) view.findViewById(com.android.nfc.R.id.applabel);
icon = (ImageView) view.findViewById(com.android.nfc.R.id.appicon);
banner = (ImageView) view.findViewById(com.android.nfc.R.id.banner);
}
}
}