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