• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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