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