1 /* 2 * Copyright (C) 2024 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 package com.android.nfc.emulator; 17 18 import android.app.Activity; 19 import android.app.role.RoleManager; 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.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.pm.ServiceInfo; 29 import android.content.res.XmlResourceParser; 30 import android.nfc.NfcAdapter; 31 import android.nfc.cardemulation.CardEmulation; 32 import android.nfc.cardemulation.HostApduService; 33 import android.os.Bundle; 34 import android.os.Handler; 35 import android.util.AttributeSet; 36 import android.util.Log; 37 import android.util.Xml; 38 39 import com.android.compatibility.common.util.CommonTestUtils; 40 import com.android.nfc.service.HceService; 41 import com.android.nfc.utils.HceUtils; 42 43 import org.xmlpull.v1.XmlPullParserException; 44 45 import java.io.IOException; 46 import java.util.concurrent.Executors; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.List; 50 51 public abstract class BaseEmulatorActivity extends Activity { 52 public static final String PACKAGE_NAME = "com.android.nfc.emulator"; 53 54 public static final String ACTION_ROLE_HELD = PACKAGE_NAME + ".ACTION_ROLE_HELD"; 55 56 // Intent action that's sent after the test condition is met. 57 protected static final String ACTION_TEST_PASSED = PACKAGE_NAME + ".ACTION_TEST_PASSED"; 58 protected static final String TAG = "BaseEmulatorActivity"; 59 protected NfcAdapter mAdapter; 60 protected CardEmulation mCardEmulation; 61 protected RoleManager mRoleManager; 62 63 final BroadcastReceiver mReceiver = 64 new BroadcastReceiver() { 65 @Override 66 public void onReceive(Context context, Intent intent) { 67 String action = intent.getAction(); 68 if (HceService.ACTION_APDU_SEQUENCE_COMPLETE.equals(action)) { 69 // Get component whose sequence was completed 70 ComponentName component = 71 intent.getParcelableExtra(HceService.EXTRA_COMPONENT); 72 long duration = intent.getLongExtra(HceService.EXTRA_DURATION, 0); 73 if (component != null) { 74 onApduSequenceComplete(component, duration); 75 } 76 } 77 } 78 }; 79 80 @Override onCreate(Bundle savedInstanceState)81 protected void onCreate(Bundle savedInstanceState) { 82 super.onCreate(savedInstanceState); 83 Log.d(TAG, "onCreate"); 84 mAdapter = NfcAdapter.getDefaultAdapter(this); 85 mCardEmulation = CardEmulation.getInstance(mAdapter); 86 mRoleManager = getSystemService(RoleManager.class); 87 IntentFilter filter = new IntentFilter(HceService.ACTION_APDU_SEQUENCE_COMPLETE); 88 registerReceiver(mReceiver, filter, RECEIVER_EXPORTED); 89 } 90 registerEventListener(CardEmulation.NfcEventListener eventListener)91 public void registerEventListener(CardEmulation.NfcEventListener eventListener) { 92 if (android.nfc.Flags.nfcEventListener()) { 93 Log.d(TAG, "registering event listener..."); 94 mCardEmulation.registerNfcEventListener(getMainExecutor(), eventListener); 95 } 96 } 97 98 @Override onResume()99 protected void onResume() { 100 super.onResume(); 101 } 102 103 @Override onDestroy()104 protected void onDestroy() { 105 super.onDestroy(); 106 unregisterReceiver(mReceiver); 107 disableServices(); 108 } 109 disableServices()110 public void disableServices() { 111 for (ComponentName component : getServices()) { 112 Log.d(TAG, "Disabling component " + component); 113 HceUtils.disableComponent(getPackageManager(), component); 114 } 115 } 116 117 /* Gets preferred service description */ getServiceDescriptionFromComponent(ComponentName component)118 public String getServiceDescriptionFromComponent(ComponentName component) { 119 try { 120 Bundle data = 121 getPackageManager() 122 .getServiceInfo(component, PackageManager.GET_META_DATA) 123 .metaData; 124 XmlResourceParser xrp = 125 getResources().getXml(data.getInt(HostApduService.SERVICE_META_DATA)); 126 boolean parsing = true; 127 while (parsing) { 128 try { 129 switch (xrp.next()) { 130 case XmlResourceParser.END_DOCUMENT: 131 parsing = false; 132 break; 133 case XmlResourceParser.START_TAG: 134 if (xrp.getName().equals("host-apdu-service")) { 135 AttributeSet set = Xml.asAttributeSet(xrp); 136 int resId = 137 set.getAttributeResourceValue( 138 "http://schemas.android.com/apk/res/android", 139 "description", 140 -1); 141 if (resId != -1) { 142 return getResources().getString(resId); 143 } 144 return ""; 145 } 146 break; 147 default: 148 break; 149 } 150 } catch (XmlPullParserException | IOException e) { 151 Log.d(TAG, "error: " + e.toString()); 152 throw new IllegalStateException( 153 "Resource parsing failed. This shouldn't happen.", e); 154 } 155 } 156 } catch (NameNotFoundException e) { 157 Log.w(TAG, "NameNotFoundException. Test will probably fail."); 158 } catch (Exception e) { 159 Log.w(TAG, "Exception while parsing service description.", e); 160 } 161 return ""; 162 } 163 ensurePreferredService(String serviceDesc, Context context, CardEmulation cardEmulation)164 void ensurePreferredService(String serviceDesc, Context context, CardEmulation cardEmulation) { 165 Log.d(TAG, "ensurePreferredService: " + serviceDesc); 166 try { 167 CommonTestUtils.waitUntil( 168 "Default service hasn't updated", 169 6, 170 () -> 171 cardEmulation.getDescriptionForPreferredPaymentService() != null 172 && serviceDesc.equals( 173 cardEmulation 174 .getDescriptionForPreferredPaymentService() 175 .toString())); 176 } catch (Exception|AssertionError e) { 177 Log.e(TAG, "Default service not updated. This may cause tests to fail", e); 178 } 179 } 180 181 /** Sets observe mode. */ setObserveModeEnabled(boolean enable)182 public boolean setObserveModeEnabled(boolean enable) { 183 ensurePreferredService( 184 getServiceDescriptionFromComponent(getPreferredServiceComponent()), 185 this, 186 mCardEmulation); 187 return mAdapter.setObserveModeEnabled(enable); 188 } 189 190 /** Waits for preferred service to be set, and sends broadcast afterwards. */ waitForPreferredService()191 public void waitForPreferredService() { 192 ensurePreferredService( 193 getServiceDescriptionFromComponent(getPreferredServiceComponent()), 194 this, 195 mCardEmulation); 196 } 197 198 /** Waits for given service to be set */ waitForService(ComponentName componentName)199 public void waitForService(ComponentName componentName) { 200 ensurePreferredService( 201 getServiceDescriptionFromComponent(componentName), this, mCardEmulation); 202 } 203 waitForObserveModeEnabled(boolean enabled)204 void waitForObserveModeEnabled(boolean enabled) { 205 Log.d(TAG, "waitForObserveModeEnabled: " + enabled); 206 try { 207 CommonTestUtils.waitUntil("Observe mode has not been set", 6, 208 () -> mAdapter.isObserveModeEnabled() == enabled); 209 } catch (Exception|AssertionError e) { 210 Log.e(TAG, "Observe mode not set to " + enabled + ". This may cause tests to fail", e); 211 } 212 } 213 getPreferredServiceComponent()214 public abstract ComponentName getPreferredServiceComponent(); 215 isObserveModeEnabled()216 public boolean isObserveModeEnabled() { 217 return mAdapter.isObserveModeEnabled(); 218 } 219 220 /** Sets up HCE services for this emulator */ setupServices(ComponentName... componentNames)221 public void setupServices(ComponentName... componentNames) { 222 List<ComponentName> enableComponents = Arrays.asList(componentNames); 223 for (ComponentName component : getServices()) { 224 if (enableComponents.contains(component)) { 225 Log.d(TAG, "Enabling component " + component); 226 HceUtils.enableComponent(getPackageManager(), component); 227 } else { 228 Log.d(TAG, "Disabling component " + component); 229 HceUtils.disableComponent(getPackageManager(), component); 230 } 231 } 232 ComponentName bogusComponent = 233 new ComponentName( 234 PACKAGE_NAME, 235 PACKAGE_NAME + ".BogusService"); 236 mCardEmulation.isDefaultServiceForCategory(bogusComponent, CardEmulation.CATEGORY_PAYMENT); 237 238 onServicesSetup(); 239 } 240 241 /** Executed after services are set up */ onServicesSetup()242 protected void onServicesSetup() {} 243 244 /** Executed after successful APDU sequence received */ onApduSequenceComplete(ComponentName component, long duration)245 protected void onApduSequenceComplete(ComponentName component, long duration) {} 246 247 /** Call this in child classes when test condition is met */ setTestPassed()248 protected void setTestPassed() { 249 Intent intent = new Intent(ACTION_TEST_PASSED); 250 sendBroadcast(intent); 251 } 252 253 /** Makes this package the default wallet role holder */ makeDefaultWalletRoleHolder()254 public void makeDefaultWalletRoleHolder() { 255 if (!isWalletRoleHeld()) { 256 Log.d(TAG, "Wallet Role not currently held. Setting default role now"); 257 setDefaultWalletRole(); 258 } else { 259 Intent intent = new Intent(ACTION_ROLE_HELD); 260 sendBroadcast(intent); 261 } 262 } 263 isWalletRoleHeld()264 protected boolean isWalletRoleHeld() { 265 assert mRoleManager != null; 266 return mRoleManager.isRoleHeld(RoleManager.ROLE_WALLET); 267 } 268 setDefaultWalletRole()269 protected void setDefaultWalletRole() { 270 if (HceUtils.setDefaultWalletRoleHolder(this, PACKAGE_NAME)) { 271 Log.d(TAG, "Default role holder set: " + isWalletRoleHeld()); 272 Intent intent = new Intent(ACTION_ROLE_HELD); 273 sendBroadcast(intent); 274 } 275 } 276 277 /** Set Listen tech */ setListenTech(int listenTech)278 public void setListenTech(int listenTech) { 279 mAdapter.setDiscoveryTechnology(this, NfcAdapter.FLAG_READER_KEEP, listenTech); 280 } 281 282 /** Reset Listen tech */ resetListenTech()283 public void resetListenTech() { 284 mAdapter.resetDiscoveryTechnology(this); 285 } 286 287 /* Fetch all services in the package */ getServices()288 private List<ComponentName> getServices() { 289 List<ComponentName> services = new ArrayList<>(); 290 try { 291 PackageInfo packageInfo = getPackageManager().getPackageInfo(PACKAGE_NAME, 292 PackageManager.GET_SERVICES 293 | PackageManager.MATCH_DISABLED_COMPONENTS); 294 295 if (packageInfo.services != null) { 296 for (ServiceInfo info : packageInfo.services) { 297 services.add(new ComponentName(PACKAGE_NAME, info.name)); 298 } 299 } 300 301 } catch (PackageManager.NameNotFoundException e) { 302 Log.e(TAG, "Package, application or component name cannot be found", e); 303 } 304 return services; 305 } 306 } 307