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