1 /* 2 * Copyright (C) 2017 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 android.autofillservice.cts.commontests; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import static org.junit.Assume.assumeTrue; 21 22 import android.app.Activity; 23 import android.app.ActivityOptions; 24 import android.app.PendingIntent; 25 import android.autofillservice.cts.R; 26 import android.autofillservice.cts.activities.AbstractAutoFillActivity; 27 import android.autofillservice.cts.testcore.Helper; 28 import android.autofillservice.cts.testcore.Timeouts; 29 import android.autofillservice.cts.testcore.UiBot; 30 import android.content.Intent; 31 import android.service.autofill.CustomDescription; 32 import android.widget.RemoteViews; 33 34 import androidx.annotation.NonNull; 35 import androidx.test.filters.FlakyTest; 36 import androidx.test.uiautomator.By; 37 import androidx.test.uiautomator.UiObject2; 38 39 import org.junit.Test; 40 41 /** 42 * Template for tests cases that test what happens when a link in the {@link CustomDescription} is 43 * tapped by the user. 44 * 45 * <p>It must be extend by 2 sub-class to provide tests for the 2 distinct scenarios: 46 * <ul> 47 * <li>Save is triggered by 1st activity finishing and launching a 2nd activity. 48 * <li>Save is triggered by explicit {@link android.view.autofill.AutofillManager#commit()} call 49 * and shown in the same activity. 50 * </ul> 51 * 52 * <p>The overall behavior should be the same in both cases, although the implementation of the 53 * tests per se will be sligthly different. 54 */ 55 public abstract class CustomDescriptionWithLinkTestCase<A extends AbstractAutoFillActivity> extends 56 AutoFillServiceTestCase.AutoActivityLaunch<A> { 57 58 private static final String ID_LINK = "link"; 59 60 private final Class<A> mActivityClass; 61 62 protected A mActivity; 63 CustomDescriptionWithLinkTestCase(@onNull Class<A> activityClass)64 protected CustomDescriptionWithLinkTestCase(@NonNull Class<A> activityClass) { 65 mActivityClass = activityClass; 66 } 67 startActivity()68 protected void startActivity() { 69 startActivity(false); 70 } 71 startActivity(boolean remainOnRecents)72 protected void startActivity(boolean remainOnRecents) { 73 final Intent intent = new Intent(mContext, mActivityClass); 74 if (remainOnRecents) { 75 intent.setFlags( 76 Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK); 77 } 78 mActivity = launchActivity(intent); 79 } 80 81 /** 82 * Tests scenarios when user taps a link in the custom description and then taps back: 83 * the Save UI should have been restored. 84 */ 85 @Test testTapLink_tapBack()86 public final void testTapLink_tapBack() throws Exception { 87 saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.TAP_BACK_BUTTON); 88 } 89 90 /** 91 * Tests scenarios when user taps a link in the custom description, change the screen 92 * orientation while the new activity is show, then taps back: 93 * the Save UI should have been restored. 94 */ 95 @Test testTapLink_changeOrientationThenTapBack()96 public final void testTapLink_changeOrientationThenTapBack() throws Exception { 97 assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext)); 98 assumeTrue("Device state is not REAR_DISPLAY", 99 !Helper.isDeviceInState(mContext, Helper.DeviceStateEnum.REAR_DISPLAY)); 100 101 mUiBot.assumeMinimumResolution(500); 102 mUiBot.assumeMinimumResolutionInDp(480); 103 mUiBot.setScreenOrientation(UiBot.PORTRAIT); 104 try { 105 saveUiRestoredAfterTappingLinkTest( 106 PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON); 107 } finally { 108 try { 109 if (!Helper.isDeviceInState(mContext, Helper.DeviceStateEnum.OPENED)) { 110 mUiBot.setScreenOrientation(UiBot.PORTRAIT); 111 cleanUpAfterScreenOrientationIsBackToPortrait(); 112 } 113 } catch (Exception e) { 114 mSafeCleanerRule.add(e); 115 } finally { 116 mUiBot.resetScreenResolution(); 117 } 118 } 119 } 120 121 /** 122 * Tests scenarios when user taps a link in the custom description, then the new activity 123 * finishes: 124 * the Save UI should have been restored. 125 */ 126 @Test testTapLink_finishActivity()127 public final void testTapLink_finishActivity() throws Exception { 128 saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction.FINISH_ACTIVITY); 129 } 130 saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)131 protected abstract void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type) 132 throws Exception; 133 cleanUpAfterScreenOrientationIsBackToPortrait()134 protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception { 135 } 136 137 /** 138 * Tests scenarios when user taps a link in the custom description, taps back to return to the 139 * activity with the Save UI, and touch outside the Save UI to dismiss it. 140 * 141 * <p>Then user starts a new session by focusing in a field. 142 */ 143 @Test testTapLink_tapBack_thenStartOverByTouchOutsideAndFocus()144 public final void testTapLink_tapBack_thenStartOverByTouchOutsideAndFocus() 145 throws Exception { 146 mUiBot.assumeMinimumResolution(500); 147 tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, false); 148 } 149 150 /** 151 * Tests scenarios when user taps a link in the custom description, taps back to return to the 152 * activity with the Save UI, and touch outside the Save UI to dismiss it. 153 * 154 * <p>Then user starts a new session by forcing autofill. 155 */ 156 @Test testTapLink_tapBack_thenStartOverByTouchOutsideAndManualRequest()157 public void testTapLink_tapBack_thenStartOverByTouchOutsideAndManualRequest() 158 throws Exception { 159 mUiBot.assumeMinimumResolution(500); 160 tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TOUCH_OUTSIDE, true); 161 } 162 163 /** 164 * Tests scenarios when user taps a link in the custom description, taps back to return to the 165 * activity with the Save UI, and tap the "No" button to dismiss it. 166 * 167 * <p>Then user starts a new session by focusing in a field. 168 */ 169 @Test testTapLink_tapBack_thenStartOverBySayingNoAndFocus()170 public final void testTapLink_tapBack_thenStartOverBySayingNoAndFocus() 171 throws Exception { 172 tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI, 173 false); 174 } 175 176 /** 177 * Tests scenarios when user taps a link in the custom description, taps back to return to the 178 * activity with the Save UI, and tap the "No" button to dismiss it. 179 * 180 * <p>Then user starts a new session by forcing autofill. 181 */ 182 @Test testTapLink_tapBack_thenStartOverBySayingNoAndManualRequest()183 public final void testTapLink_tapBack_thenStartOverBySayingNoAndManualRequest() 184 throws Exception { 185 tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_NO_ON_SAVE_UI, true); 186 } 187 188 /** 189 * Tests scenarios when user taps a link in the custom description, taps back to return to the 190 * activity with the Save UI, and the "Yes" button to save it. 191 * 192 * <p>Then user starts a new session by focusing in a field. 193 */ 194 @Test testTapLink_tapBack_thenStartOverBySayingYesAndFocus()195 public final void testTapLink_tapBack_thenStartOverBySayingYesAndFocus() 196 throws Exception { 197 tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI, 198 false); 199 } 200 201 /** 202 * Tests scenarios when user taps a link in the custom description, taps back to return to the 203 * activity with the Save UI, and the "Yes" button to save it. 204 * 205 * <p>Then user starts a new session by forcing autofill. 206 */ 207 @Test testTapLink_tapBack_thenStartOverBySayingYesAndManualRequest()208 public final void testTapLink_tapBack_thenStartOverBySayingYesAndManualRequest() 209 throws Exception { 210 tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction.TAP_YES_ON_SAVE_UI, true); 211 } 212 tapLinkThenTapBackThenStartOverTest( PostSaveLinkTappedAction action, boolean manualRequest)213 protected abstract void tapLinkThenTapBackThenStartOverTest( 214 PostSaveLinkTappedAction action, boolean manualRequest) throws Exception; 215 216 /** 217 * Tests scenarios when user taps a link in the custom description, then re-launches the 218 * original activity: 219 * the Save UI should have been canceled. 220 */ 221 @Test testTapLink_backToPreviousActivityByLaunchingIt()222 public final void testTapLink_backToPreviousActivityByLaunchingIt() 223 throws Exception { 224 saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_PREVIOUS_ACTIVITY); 225 } 226 227 /** 228 * Tests scenarios when user taps a link in the custom description, then launches a 3rd 229 * activity: 230 * the Save UI should have been canceled. 231 */ 232 @Test testTapLink_launchNewActivityThenTapBack()233 public final void testTapLink_launchNewActivityThenTapBack() throws Exception { 234 saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY); 235 } 236 saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)237 protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type) 238 throws Exception; 239 240 @Test 241 @FlakyTest(bugId = 177259617) testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()242 public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession() 243 throws Exception { 244 // Reset AutofillOptions to avoid cts package was added to augmented autofill allowlist. 245 Helper.resetApplicationAutofillOptions(sContext); 246 247 tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest(); 248 249 // Clear AutofillOptions. 250 Helper.clearApplicationAutofillOptions(sContext); 251 } 252 tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()253 protected abstract void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest() 254 throws Exception; 255 256 @Test testTapLinkAfterUpdateAppliedToLinkView()257 public final void testTapLinkAfterUpdateAppliedToLinkView() throws Exception { 258 tapLinkAfterUpdateAppliedTest(true); 259 } 260 261 @Test testTapLinkAfterUpdateAppliedToAnotherView()262 public final void testTapLinkAfterUpdateAppliedToAnotherView() throws Exception { 263 tapLinkAfterUpdateAppliedTest(false); 264 } 265 tapLinkAfterUpdateAppliedTest(boolean updateLinkView)266 protected abstract void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception; 267 268 public enum PostSaveLinkTappedAction { 269 TAP_BACK_BUTTON, 270 ROTATE_THEN_TAP_BACK_BUTTON, 271 FINISH_ACTIVITY, 272 LAUNCH_NEW_ACTIVITY, 273 LAUNCH_PREVIOUS_ACTIVITY, 274 TOUCH_OUTSIDE, 275 TAP_NO_ON_SAVE_UI, 276 TAP_YES_ON_SAVE_UI 277 } 278 startActivityOnNewTask(Class<?> clazz)279 protected final void startActivityOnNewTask(Class<?> clazz) { 280 final Intent intent = new Intent(mContext, clazz); 281 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 282 mContext.startActivity(intent); 283 } 284 newTemplate()285 protected RemoteViews newTemplate() { 286 final RemoteViews presentation = new RemoteViews(mPackageName, 287 R.layout.custom_description_with_link); 288 return presentation; 289 } 290 newCustomDescriptionBuilder( Class<? extends Activity> activityClass)291 protected final CustomDescription.Builder newCustomDescriptionBuilder( 292 Class<? extends Activity> activityClass) { 293 final Intent intent = new Intent(mContext, activityClass); 294 intent.setFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 295 return newCustomDescriptionBuilder(intent); 296 } 297 newCustomDescription( Class<? extends Activity> activityClass)298 protected final CustomDescription newCustomDescription( 299 Class<? extends Activity> activityClass) { 300 return newCustomDescriptionBuilder(activityClass).build(); 301 } 302 newCustomDescriptionBuilder(Intent intent)303 protected final CustomDescription.Builder newCustomDescriptionBuilder(Intent intent) { 304 final RemoteViews presentation = newTemplate(); 305 final PendingIntent pendingIntent = 306 PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE, 307 ActivityOptions.makeBasic() 308 .setPendingIntentCreatorBackgroundActivityStartMode( 309 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 310 .toBundle()); 311 presentation.setOnClickPendingIntent(R.id.link, pendingIntent); 312 return new CustomDescription.Builder(presentation); 313 } 314 newCustomDescription(Intent intent)315 protected final CustomDescription newCustomDescription(Intent intent) { 316 return newCustomDescriptionBuilder(intent).build(); 317 } 318 assertSaveUiWithLinkIsShown(int saveType)319 protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType) throws Exception { 320 return assertSaveUiWithLinkIsShown(saveType, "DON'T TAP ME!"); 321 } 322 assertSaveUiWithLinkIsShown(int saveType, String expectedText)323 protected final UiObject2 assertSaveUiWithLinkIsShown(int saveType, String expectedText) 324 throws Exception { 325 // First make sure the UI is shown... 326 final UiObject2 saveUi = mUiBot.assertSaveShowing(saveType); 327 // Then make sure it does have the custom view with link on it... 328 final UiObject2 link = getLink(saveUi); 329 assertThat(link.getText()).isEqualTo(expectedText); 330 return saveUi; 331 } 332 getLink(final UiObject2 container)333 protected final UiObject2 getLink(final UiObject2 container) throws Exception{ 334 final UiObject2 link = mUiBot.waitForObject(container, ID_LINK, Timeouts.UI_TIMEOUT); 335 return link; 336 } 337 tapSaveUiLink(UiObject2 saveUi)338 protected final void tapSaveUiLink(UiObject2 saveUi) throws Exception{ 339 getLink(saveUi).click(); 340 } 341 } 342