blob: f0e1179c77ddd5ab5292ec1a889b468bbc2bb4b5 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.autofillservice.cts;
import static android.autofillservice.cts.Helper.getContext;
import static android.autofillservice.cts.InstrumentedAutoFillService.SERVICE_NAME;
import static android.content.Context.CLIPBOARD_SERVICE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import android.app.PendingIntent;
import android.autofillservice.cts.InstrumentedAutoFillService.Replier;
import android.autofillservice.cts.augmented.AugmentedAuthActivity;
import android.autofillservice.cts.inline.InlineUiBot;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.autofill.InlinePresentation;
import android.util.Log;
import android.view.autofill.AutofillManager;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
import com.android.compatibility.common.util.RequiredFeatureRule;
import com.android.compatibility.common.util.RetryRule;
import com.android.compatibility.common.util.SafeCleanerRule;
import com.android.compatibility.common.util.SettingsStateKeeperRule;
import com.android.compatibility.common.util.TestNameUtils;
import com.android.cts.mockime.ImeSettings;
import com.android.cts.mockime.MockImeSessionRule;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
/**
* Placeholder for the base class for all integration tests:
*
* <ul>
* <li>{@link AutoActivityLaunch}
* <li>{@link ManualActivityLaunch}
* </ul>
*
* <p>These classes provide the common infrastructure such as:
*
* <ul>
* <li>Preserving the autofill service settings.
* <li>Cleaning up test state.
* <li>Wrapping the test under autofill-specific test rules.
* <li>Launching the activity used by the test.
* </ul>
*/
public final class AutoFillServiceTestCase {
/**
* Base class for all test cases that use an {@link AutofillActivityTestRule} to
* launch the activity.
*/
// Must be public because of @ClassRule
public abstract static class AutoActivityLaunch<A extends AbstractAutoFillActivity>
extends BaseTestCase {
/**
* Returns if inline suggestion is enabled.
*/
protected boolean isInlineMode() {
return false;
}
protected static UiBot getInlineUiBot() {
return sDefaultUiBot2;
}
protected static UiBot getDropdownUiBot() {
return sDefaultUiBot;
}
@ClassRule
public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
sTheRealServiceSettingsKeeper;
protected AutoActivityLaunch() {
super(sDefaultUiBot);
}
protected AutoActivityLaunch(UiBot uiBot) {
super(uiBot);
}
@Override
protected TestRule getMainTestRule() {
return getActivityRule();
}
/**
* Gets the rule to launch the main activity for this test.
*
* <p><b>Note: </b>the rule must be either lazily generated or a static singleton, otherwise
* this method could return {@code null} when the rule chain that uses it is constructed.
*
*/
protected abstract @NonNull AutofillActivityTestRule<A> getActivityRule();
protected @NonNull A launchActivity(@NonNull Intent intent) {
return getActivityRule().launchActivity(intent);
}
protected @NonNull A getActivity() {
return getActivityRule().getActivity();
}
}
/**
* Base class for all test cases that don't require an {@link AutofillActivityTestRule}.
*/
// Must be public because of @ClassRule
public abstract static class ManualActivityLaunch extends BaseTestCase {
@ClassRule
public static final SettingsStateKeeperRule sPublicServiceSettingsKeeper =
sTheRealServiceSettingsKeeper;
protected ManualActivityLaunch() {
this(sDefaultUiBot);
}
protected ManualActivityLaunch(@NonNull UiBot uiBot) {
super(uiBot);
}
@Override
protected TestRule getMainTestRule() {
// TODO: create a NoOpTestRule on common code
return new TestRule() {
@Override
public Statement apply(Statement base, Description description) {
// Returns a no-op statements
return new Statement() {
@Override
public void evaluate() throws Throwable {
base.evaluate();
}
};
}
};
}
protected SimpleSaveActivity startSimpleSaveActivity() throws Exception {
final Intent intent = new Intent(mContext, SimpleSaveActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
mUiBot.assertShownByRelativeId(SimpleSaveActivity.ID_LABEL);
return SimpleSaveActivity.getInstance();
}
protected PreSimpleSaveActivity startPreSimpleSaveActivity() throws Exception {
final Intent intent = new Intent(mContext, PreSimpleSaveActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivity(intent);
mUiBot.assertShownByRelativeId(PreSimpleSaveActivity.ID_PRE_LABEL);
return PreSimpleSaveActivity.getInstance();
}
}
@RunWith(AndroidJUnit4.class)
// Must be public because of @ClassRule
public abstract static class BaseTestCase {
private static final String TAG = "AutoFillServiceTestCase";
protected static final Replier sReplier = InstrumentedAutoFillService.getReplier();
protected static final Context sContext = getInstrumentation().getTargetContext();
// Hack because JUnit requires that @ClassRule instance belong to a public class.
protected static final SettingsStateKeeperRule sTheRealServiceSettingsKeeper =
new SettingsStateKeeperRule(sContext, Settings.Secure.AUTOFILL_SERVICE) {
@Override
protected void preEvaluate(Description description) {
TestNameUtils.setCurrentTestClass(description.getClassName());
}
@Override
protected void postEvaluate(Description description) {
TestNameUtils.setCurrentTestClass(null);
}
};
public static final MockImeSessionRule sMockImeSessionRule = new MockImeSessionRule(
InstrumentationRegistry.getTargetContext(),
InstrumentationRegistry.getInstrumentation().getUiAutomation(),
new ImeSettings.Builder().setInlineSuggestionsEnabled(true)
.setInlineSuggestionViewContentDesc(InlineUiBot.SUGGESTION_STRIP_DESC));
protected static final RequiredFeatureRule sRequiredFeatureRule =
new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
private final RetryRule mRetryRule =
new RetryRule(getNumberRetries(), () -> {
// Between testing and retries, clean all launched activities to avoid
// exception:
// Could not launch intent Intent { ... } within 45 seconds.
mTestWatcher.cleanAllActivities();
});
private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
protected final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
.setDumper(mLoggingRule)
.run(() -> sReplier.assertNoUnhandledFillRequests())
.run(() -> sReplier.assertNoUnhandledSaveRequests())
.add(() -> { return sReplier.getExceptions(); });
@Rule
public final RuleChain mLookAllTheseRules = RuleChain
//
// requiredFeatureRule should be first so the test can be skipped right away
.outerRule(getRequiredFeaturesRule())
//
// mTestWatcher should always be one the first rules, as it defines the name of the
// test being ran and finishes dangling activities at the end
.around(mTestWatcher)
//
// sMockImeSessionRule make sure MockImeSession.create() is used to launch mock IME
.around(sMockImeSessionRule)
//
// mLoggingRule wraps the test but doesn't interfere with it
.around(mLoggingRule)
//
// mSafeCleanerRule will catch errors
.around(mSafeCleanerRule)
//
// mRetryRule should be closest to the main test as possible
.around(mRetryRule)
//
// Augmented Autofill should be disabled by default
.around(new DeviceConfigStateChangerRule(sContext, DeviceConfig.NAMESPACE_AUTOFILL,
AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES,
Integer.toString(getSmartSuggestionMode())))
//
// Finally, let subclasses add their own rules (like ActivityTestRule)
.around(getMainTestRule());
protected final Context mContext = sContext;
protected final String mPackageName;
protected final UiBot mUiBot;
private BaseTestCase(@NonNull UiBot uiBot) {
mPackageName = mContext.getPackageName();
mUiBot = uiBot;
mUiBot.reset();
}
protected int getSmartSuggestionMode() {
return AutofillManager.FLAG_SMART_SUGGESTION_OFF;
}
/**
* Gets how many times a test should be retried.
*
* @return {@code 1} by default, unless overridden by subclasses or by a global settings
* named {@code CLASS_NAME + #getNumberRetries} or
* {@code CtsAutoFillServiceTestCases#getNumberRetries} (the former having a higher
* priority).
*/
protected int getNumberRetries() {
final String localProp = getClass().getName() + "#getNumberRetries";
final Integer localValue = getNumberRetries(localProp);
if (localValue != null) return localValue.intValue();
final String globalProp = "CtsAutoFillServiceTestCases#getNumberRetries";
final Integer globalValue = getNumberRetries(globalProp);
if (globalValue != null) return globalValue.intValue();
return 1;
}
private Integer getNumberRetries(String prop) {
final String value = Settings.Global.getString(sContext.getContentResolver(), prop);
if (value != null) {
Log.i(TAG, "getNumberRetries(): overriding to " + value + " because of '" + prop
+ "' global setting");
try {
return Integer.parseInt(value);
} catch (Exception e) {
Log.w(TAG, "error parsing property '" + prop + "'='" + value + "'", e);
}
}
return null;
}
/**
* Gets a rule that defines which features must be present for this test to run.
*
* <p>By default it returns a rule that requires {@link PackageManager#FEATURE_AUTOFILL},
* but subclass can override to be more specific.
*/
@NonNull
protected TestRule getRequiredFeaturesRule() {
return sRequiredFeatureRule;
}
/**
* Gets the test-specific {@link Rule @Rule}.
*
* <p>Sub-class <b>MUST</b> override this method instead of annotation their own rules,
* so the order is preserved.
*
*/
@NonNull
protected abstract TestRule getMainTestRule();
@BeforeClass
public static void disableDefaultAugmentedService() {
Log.v(TAG, "@BeforeClass: disableDefaultAugmentedService()");
Helper.setDefaultAugmentedAutofillServiceEnabled(false);
}
@AfterClass
public static void enableDefaultAugmentedService() {
Log.v(TAG, "@AfterClass: enableDefaultAugmentedService()");
Helper.setDefaultAugmentedAutofillServiceEnabled(true);
}
@Before
public void prepareDevice() throws Exception {
Log.v(TAG, "@Before: prepareDevice()");
// Unlock screen.
runShellCommand("input keyevent KEYCODE_WAKEUP");
// Dismiss keyguard, in case it's set as "Swipe to unlock".
runShellCommand("wm dismiss-keyguard");
// Collapse notifications.
runShellCommand("cmd statusbar collapse");
// Set orientation as portrait, otherwise some tests might fail due to elements not
// fitting in, IME orientation, etc...
mUiBot.setScreenOrientation(UiBot.PORTRAIT);
// Wait until device is idle to avoid flakiness
mUiBot.waitForIdle();
// Clear Clipboard
// TODO(b/117768051): remove try/catch once fixed
try {
((ClipboardManager) mContext.getSystemService(CLIPBOARD_SERVICE))
.clearPrimaryClip();
} catch (Exception e) {
Log.e(TAG, "Ignoring exception clearing clipboard", e);
}
}
@Before
public void preTestCleanup() {
Log.v(TAG, "@Before: preTestCleanup()");
prepareServicePreTest();
InstrumentedAutoFillService.resetStaticState();
AuthenticationActivity.resetStaticState();
AugmentedAuthActivity.resetStaticState();
sReplier.reset();
}
/**
* Prepares the service before each test - by default, disables it
*/
protected void prepareServicePreTest() {
Log.v(TAG, "prepareServicePreTest(): calling disableService()");
disableService();
}
/**
* Enables the {@link InstrumentedAutoFillService} for autofill for the current user.
*/
protected void enableService() {
Helper.enableAutofillService(getContext(), SERVICE_NAME);
}
/**
* Disables the {@link InstrumentedAutoFillService} for autofill for the current user.
*/
protected void disableService() {
Helper.disableAutofillService(getContext());
}
/**
* Asserts that the {@link InstrumentedAutoFillService} is enabled for the default user.
*/
protected void assertServiceEnabled() {
Helper.assertAutofillServiceStatus(SERVICE_NAME, true);
}
/**
* Asserts that the {@link InstrumentedAutoFillService} is disabled for the default user.
*/
protected void assertServiceDisabled() {
Helper.assertAutofillServiceStatus(SERVICE_NAME, false);
}
protected RemoteViews createPresentation(String message) {
return Helper.createPresentation(message);
}
protected RemoteViews createPresentationWithCancel(String message) {
final RemoteViews presentation = new RemoteViews(getContext()
.getPackageName(), R.layout.list_item_cancel);
presentation.setTextViewText(R.id.text1, message);
return presentation;
}
protected InlinePresentation createInlinePresentation(String message) {
return Helper.createInlinePresentation(message);
}
protected InlinePresentation createInlinePresentation(String message,
PendingIntent attribution) {
return Helper.createInlinePresentation(message, attribution);
}
@NonNull
protected AutofillManager getAutofillManager() {
return mContext.getSystemService(AutofillManager.class);
}
}
protected static final UiBot sDefaultUiBot = new UiBot();
protected static final UiBot sDefaultUiBot2 = new InlineUiBot();
private AutoFillServiceTestCase() {
throw new UnsupportedOperationException("Contain static stuff only");
}
}