• 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 
17 package com.android.nfc.cardemulation;
18 
19 import static android.app.Activity.RESULT_CANCELED;
20 import static android.nfc.cardemulation.CardEmulation.CATEGORY_PAYMENT;
21 import static android.view.WindowManager.LayoutParams.FLAG_BLUR_BEHIND;
22 import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
23 import static androidx.test.espresso.Espresso.onView;
24 import static androidx.test.espresso.action.ViewActions.click;
25 import static androidx.test.espresso.assertion.ViewAssertions.matches;
26 import static androidx.test.espresso.matcher.ViewMatchers.withId;
27 import static androidx.test.espresso.matcher.ViewMatchers.withText;
28 import static com.android.nfc.cardemulation.AppChooserActivity.EXTRA_CATEGORY;
29 import static com.android.nfc.cardemulation.AppChooserActivity.EXTRA_APDU_SERVICES;
30 import static com.android.nfc.cardemulation.AppChooserActivity.EXTRA_FAILED_COMPONENT;
31 import static com.google.common.truth.Truth.assertThat;
32 
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.content.pm.ServiceInfo;
40 import android.nfc.cardemulation.ApduServiceInfo;
41 import android.widget.ListView;
42 import android.view.Window;
43 import android.view.View;
44 
45 import androidx.lifecycle.Lifecycle;
46 import androidx.test.core.app.ActivityScenario;
47 import androidx.test.espresso.UiController;
48 import androidx.test.espresso.ViewAction;
49 import androidx.test.espresso.matcher.ViewMatchers;
50 import androidx.test.ext.junit.runners.AndroidJUnit4;
51 import androidx.test.platform.app.InstrumentationRegistry;
52 
53 import com.android.nfc.R;
54 
55 import java.lang.IllegalStateException;
56 import java.util.ArrayList;
57 import java.util.concurrent.CountDownLatch;
58 import java.util.concurrent.TimeUnit;
59 
60 import org.hamcrest.Matcher;
61 
62 import org.junit.Rule;
63 import org.junit.Before;
64 import org.junit.Ignore;
65 import org.junit.Test;
66 import org.junit.runner.RunWith;
67 
68 @RunWith(AndroidJUnit4.class)
69 public class AppChooserActivityTest {
70   private static final String UNKNOWN_LABEL = "unknown";
71   private Context context;
72 
73   @Before
setUp()74   public void setUp() throws Exception {
75     context = InstrumentationRegistry.getInstrumentation().getTargetContext();
76   }
77 
78   @Test
79   @Ignore // TODO(rpius): Fix these tests.
testNoFailedComponentAndNoAlternatives()80   public void testNoFailedComponentAndNoAlternatives() throws Exception {
81     ActivityScenario<AppChooserActivity> scenario
82         = ActivityScenario.launch(getIntent(/*isPayment = */ true,
83                                             /* withFailedComponent = */ false,
84                                             /* withServices = */ false));
85 
86     assertThat(scenario.getState()).isEqualTo(Lifecycle.State.DESTROYED);
87   }
88 
89   @Test
90   @Ignore // TODO(rpius): Fix these tests.
testExistingFailedComponentAndNoAlternatives()91   public void testExistingFailedComponentAndNoAlternatives() throws Exception {
92     ActivityScenario<AppChooserActivity> scenario
93         = ActivityScenario.launch(getIntent(/*isPayment = */ true,
94                                             /* withFailedComponent = */ true,
95                                             /* withServices = */ false));
96 
97     assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
98     String expectedText
99         = String.format(context.getString(R.string.transaction_failure), UNKNOWN_LABEL);
100     onView(withId(R.id.appchooser_text)).check(matches(withText(expectedText)));
101     scenario.onActivity(activity -> {
102       int flags = activity.getWindow().getAttributes().flags;
103       assertThat(flags & FLAG_BLUR_BEHIND).isEqualTo(FLAG_BLUR_BEHIND);
104       assertThat(flags & FLAG_DISMISS_KEYGUARD).isEqualTo(FLAG_DISMISS_KEYGUARD);
105     });
106   }
107 
108   @Test
109   @Ignore // TODO(rpius): Fix these tests.
testNonPayment()110   public void testNonPayment() throws Exception {
111     ActivityScenario<AppChooserActivity> scenario
112         = ActivityScenario.launch(getIntent(/*isPayment = */ false,
113                                             /* withFailedComponent = */ true,
114                                             /* withServices = */ true));
115 
116     scenario.onActivity(activity -> {
117       ListView listView = (ListView) activity.findViewById(R.id.resolver_list);
118       assertThat(listView.getDividerHeight()).isEqualTo(-1);
119       assertThat(listView.getPaddingEnd()).isEqualTo(0);
120       assertThat(listView.getPaddingLeft()).isEqualTo(0);
121       assertThat(listView.getPaddingRight()).isEqualTo(0);
122       assertThat(listView.getPaddingStart()).isEqualTo(0);
123     });
124   }
125 
126   @Test
127   @Ignore // TODO(rpius): Fix these tests.
testExistingFailedComponentAndExistingAlternatives()128   public void testExistingFailedComponentAndExistingAlternatives() throws Exception {
129     ActivityScenario<AppChooserActivity> scenario
130         = ActivityScenario.launch(getIntent(/*isPayment = */ true,
131                                                     /* withFailedComponent = */ true,
132                                                     /* withServices = */ true));
133 
134     assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
135     String expectedText
136         = String.format(context.getString(R.string.could_not_use_app), UNKNOWN_LABEL);
137     onView(withId(R.id.appchooser_text)).check(matches(withText(expectedText)));
138     scenario.onActivity(activity -> {
139       int flags = activity.getWindow().getAttributes().flags;
140       assertThat(flags & FLAG_BLUR_BEHIND).isEqualTo(FLAG_BLUR_BEHIND);
141       assertThat(flags & FLAG_DISMISS_KEYGUARD).isEqualTo(FLAG_DISMISS_KEYGUARD);
142 
143       ListView listView = (ListView) activity.findViewById(R.id.resolver_list);
144       assertThat(listView.getDivider()).isNotNull();
145       assertThat((int) listView.getDividerHeight())
146           .isEqualTo((int) (context.getResources().getDisplayMetrics().density * 16));
147       assertThat(listView.getAdapter()).isNotNull();
148     });
149 
150     // Test that onItemClick() does not throw an Exception
151     onView(withId(R.id.resolver_list)).perform(customClick());
152   }
153 
154   @Test
155   @Ignore // TODO(rpius): Fix these tests.
testNoFailedComponentAndExistingAlternatives()156   public void testNoFailedComponentAndExistingAlternatives() throws Exception {
157     ActivityScenario<AppChooserActivity> scenario
158         = ActivityScenario.launch(getIntent(/*isPayment = */ true,
159                                                     /* withFailedComponent = */ false,
160                                                     /* withServices = */ true));
161 
162     assertThat(scenario.getState()).isAtLeast(Lifecycle.State.CREATED);
163     scenario.moveToState(Lifecycle.State.RESUMED);
164     String expectedText = context.getString(R.string.appchooser_description);
165     onView(withId(R.id.appchooser_text)).check(matches(withText(expectedText)));
166     scenario.onActivity(activity -> {
167       int flags = activity.getWindow().getAttributes().flags;
168       assertThat(flags & FLAG_BLUR_BEHIND).isEqualTo(FLAG_BLUR_BEHIND);
169       assertThat(flags & FLAG_DISMISS_KEYGUARD).isEqualTo(FLAG_DISMISS_KEYGUARD);
170 
171       ListView listView = (ListView) activity.findViewById(R.id.resolver_list);
172       assertThat(listView.getDivider()).isNotNull();
173       assertThat((int) listView.getDividerHeight())
174           .isEqualTo((int) (context.getResources().getDisplayMetrics().density * 16));
175       assertThat(listView.getAdapter()).isNotNull();
176     });
177 
178     // Test that onItemClick() does not throw an Exception
179     onView(withId(R.id.resolver_list)).perform(customClick());
180   }
181 
getIntent(boolean isPayment, boolean withFailedComponent, boolean withServices)182   private Intent getIntent(boolean isPayment, boolean withFailedComponent, boolean withServices) {
183     Intent intent = new Intent(context, AppChooserActivity.class);
184     if (isPayment) {
185       intent.putExtra(EXTRA_CATEGORY, CATEGORY_PAYMENT);
186     } else {
187       intent.putExtra(EXTRA_CATEGORY, "");
188     }
189 
190     ArrayList<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
191     if (withServices) {
192       ServiceInfo serviceInfo = new ServiceInfo();
193       serviceInfo.packageName = "com.nfc.test";
194       serviceInfo.name = "hce_service";
195       serviceInfo.applicationInfo = new ApplicationInfo();
196       ResolveInfo resolveInfo = new ResolveInfo();
197       resolveInfo.serviceInfo = serviceInfo;
198       ApduServiceInfo service
199           = new ApduServiceInfo(resolveInfo,
200                                 /* onHost = */ false,
201                                 /* description = */ "",
202                                 /* staticAidGroups = */ new ArrayList<>(),
203                                 /* dynamicAidGroups = */ new ArrayList<>(),
204                                 /* requiresUnlock = */ false,
205                                 /* bannerResource = */ 0,
206                                 /* uid = */ 0,
207                                 /* settingsActivityName = */ "",
208                                 /* offHost = */ "",
209                                 /* staticOffHost = */ "");
210       services.add(service);
211     }
212     intent.putParcelableArrayListExtra(EXTRA_APDU_SERVICES, services);
213 
214     if (withFailedComponent) {
215       ComponentName failedComponent
216           = new ComponentName("com.android.test.walletroleholder",
217                               "com.android.test.walletroleholder.WalletRoleHolderApduService");
218       intent.putExtra(EXTRA_FAILED_COMPONENT, failedComponent);
219     }
220     return intent;
221   }
222 
223   // Bypasses the view.getGlobalVisibleRect() requirement on the default click() action
customClick()224   private ViewAction customClick() {
225     return new ViewAction() {
226       @Override
227       public Matcher<View> getConstraints() {
228         return ViewMatchers.isEnabled();
229       }
230 
231       @Override
232       public String getDescription() {
233         return "";
234       }
235 
236       @Override
237       public void perform(UiController uiController, View view) {
238         view.performClick();
239       }
240     };
241   }
242 }
243