/* * Copyright (C) 2020 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.view.autofill; import static com.android.compatibility.common.util.ShellUtils.runShellCommand; import android.perftests.utils.SettingsHelper; import android.provider.Settings; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import com.android.compatibility.common.util.Timeout; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Custom {@link TestWatcher} that does the setup and reset tasks for the tests. */ final class AutofillTestWatcher extends TestWatcher { private static final String TAG = "AutofillTestWatcher"; private static final long GENERIC_TIMEOUT_MS = 10_000; private static ServiceWatcher sServiceWatcher; private String mOriginalLogLevel; @Override protected void starting(Description description) { super.starting(description); final String testName = description.getDisplayName(); Log.i(TAG, "Starting " + testName); prepareDevice(); enableVerboseLog(); // Prepare the service before each test. // Disable the current AutofillService. resetAutofillService(); // Set MyAutofillService status enable, it can start to accept the calls. enableMyAutofillService(); setServiceWatcher(); } @Override protected void finished(Description description) { super.finished(description); final String testName = description.getDisplayName(); Log.i(TAG, "Finished " + testName); restoreLogLevel(); // Set MyAutofillService status disable, so the calls are ignored. disableMyAutofillService(); clearServiceWatcher(); } void waitServiceConnect() throws InterruptedException { if (sServiceWatcher != null) { Log.d(TAG, "waitServiceConnect()"); sServiceWatcher.waitOnConnected(); } } /** * Uses the {@code settings} binary to set the autofill service. */ void setAutofillService() { String serviceName = MyAutofillService.COMPONENT_NAME; SettingsHelper.syncSet(InstrumentationRegistry.getTargetContext(), SettingsHelper.NAMESPACE_SECURE, Settings.Secure.AUTOFILL_SERVICE, serviceName); // Waits until the service is actually enabled. Timeout timeout = new Timeout("CONNECTION_TIMEOUT", GENERIC_TIMEOUT_MS, 2F, GENERIC_TIMEOUT_MS); try { timeout.run("Enabling Autofill service", () -> { return isAutofillServiceEnabled(serviceName) ? serviceName : null; }); } catch (Exception e) { throw new AssertionError("Enabling Autofill service failed."); } } /** * Uses the {@code settings} binary to reset the autofill service. */ void resetAutofillService() { SettingsHelper.syncDelete(InstrumentationRegistry.getTargetContext(), SettingsHelper.NAMESPACE_SECURE, Settings.Secure.AUTOFILL_SERVICE); } /** * Checks whether the given service is set as the autofill service for the default user. */ private boolean isAutofillServiceEnabled(String serviceName) { String actualName = SettingsHelper.get(SettingsHelper.NAMESPACE_SECURE, Settings.Secure.AUTOFILL_SERVICE); return serviceName.equals(actualName); } private void 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"); } private void enableMyAutofillService() { MyAutofillService.resetStaticState(); MyAutofillService.setEnabled(true); } private void disableMyAutofillService() { // Must disable service so calls are ignored in case of errors during the test case; // otherwise, other tests will fail because these calls are made in the UI thread (as both // the service, the tests, and the app run in the same process). MyAutofillService.setEnabled(false); } private void enableVerboseLog() { mOriginalLogLevel = runShellCommand("cmd autofill get log_level"); Log.d(TAG, "enableVerboseLog(), mOriginalLogLevel=" + mOriginalLogLevel); if (!mOriginalLogLevel.equals("verbose")) { runShellCommand("cmd autofill set log_level verbose"); } } private void restoreLogLevel() { Log.d(TAG, "restoreLogLevel to " + mOriginalLogLevel); if (!mOriginalLogLevel.equals("verbose")) { runShellCommand("cmd autofill set log_level %s", mOriginalLogLevel); } } private static void setServiceWatcher() { if (sServiceWatcher == null) { sServiceWatcher = new ServiceWatcher(); } } private static void clearServiceWatcher() { if (sServiceWatcher != null) { sServiceWatcher = null; } } public static final class ServiceWatcher { private final CountDownLatch mConnected = new CountDownLatch(1); public static void onConnected() { Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher); sServiceWatcher.mConnected.countDown(); } @NonNull public void waitOnConnected() throws InterruptedException { await(mConnected, "not connected"); } private void await(@NonNull CountDownLatch latch, @NonNull String fmt, @Nullable Object... args) throws InterruptedException { final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS); if (!called) { throw new IllegalStateException(String.format(fmt, args) + " in " + GENERIC_TIMEOUT_MS + "ms"); } } } }