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