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