blob: 289e5fdb1f4fef91c4af6fa8d65917adc92f6376 [file] [log] [blame]
/*
* Copyright (C) 2016 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.platform.helpers;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Instrumentation;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Environment;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.helpers.exceptions.AccountException;
import android.platform.helpers.exceptions.UnknownUiException;
import android.platform.helpers.watchers.AppIsNotRespondingWatcher;
import android.support.test.launcherhelper.ILauncherStrategy;
import android.support.test.launcherhelper.LauncherStrategyFactory;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiWatcher;
import android.support.test.uiautomator.Until;
import androidx.test.InstrumentationRegistry;
import android.util.Log;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public abstract class AbstractStandardAppHelper implements IAppHelper {
private static final String LOG_TAG = AbstractStandardAppHelper.class.getSimpleName();
private static final String SCREENSHOT_DIR = "apphelper-screenshots";
private static final String FAVOR_CMD = "favor-shell-commands";
private static final String USE_HOME_CMD = "press-home-to-exit";
private static final String LAUNCH_TIMEOUT_OPTION = "app-launch-timeout_ms";
private static final String ERROR_NOT_FOUND =
"Element %s %s is not found in the application %s";
private static File sScreenshotDirectory;
public UiDevice mDevice;
public Instrumentation mInstrumentation;
public ILauncherStrategy mLauncherStrategy;
private final KeyCharacterMap mKeyCharacterMap =
KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
private final boolean mFavorShellCommands;
private final boolean mPressHomeToExit;
private final long mLaunchTimeout;
public AbstractStandardAppHelper(Instrumentation instr) {
mInstrumentation = instr;
mDevice = UiDevice.getInstance(instr);
mFavorShellCommands =
Boolean.valueOf(
InstrumentationRegistry.getArguments().getString(FAVOR_CMD, "false"));
mPressHomeToExit =
Boolean.valueOf(
InstrumentationRegistry.getArguments().getString(USE_HOME_CMD, "false"));
//TODO(b/127356533): Choose a sensible default for app launch timeout after b/125356281.
mLaunchTimeout =
Long.valueOf(
InstrumentationRegistry.getArguments()
.getString(
LAUNCH_TIMEOUT_OPTION,
String.valueOf(TimeUnit.SECONDS.toMillis(30))));
}
/**
* {@inheritDoc}
*/
@Override
public void open() {
// Turn on the screen if necessary.
try {
if (!mDevice.isScreenOn()) {
mDevice.wakeUp();
}
} catch (RemoteException e) {
throw new RuntimeException("Could not unlock the device.", e);
}
// Unlock the screen if necessary.
if (mDevice.hasObject(By.res("com.android.systemui", "keyguard_bottom_area"))) {
mDevice.pressMenu();
mDevice.waitForIdle();
}
// Launch the application as normal.
String pkg = getPackage();
long launchInitiationTimeMs = System.currentTimeMillis();
registerDialogWatchers();
if (mFavorShellCommands) {
String output = null;
try {
Intent intent =
mInstrumentation
.getContext()
.getPackageManager()
.getLaunchIntentForPackage(pkg);
mInstrumentation.getContext().startActivity(intent);
Log.i(LOG_TAG, String.format("Sent command to launch: %s", pkg));
} catch (ActivityNotFoundException e) {
removeDialogWatchers();
throw new RuntimeException(String.format("Failed to find package: %s", pkg), e);
}
} else {
// Launch using the UI and launcher strategy.
String id = getLauncherName();
if (!mDevice.hasObject(By.pkg(pkg).depth(0))) {
getLauncherStrategy().launch(id, pkg);
Log.i(LOG_TAG, "Launched package: id=" + id + ", pkg=" + pkg);
}
}
// Ensure the package is in the foreground for success.
if (!mDevice.wait(Until.hasObject(By.pkg(pkg).depth(0)), mLaunchTimeout)) {
removeDialogWatchers();
throw new IllegalStateException(
String.format(
"Did not find package, %s, in foreground after %d ms.",
pkg, System.currentTimeMillis() - launchInitiationTimeMs));
}
removeDialogWatchers();
}
/**
* {@inheritDoc}
*/
@Override
public void exit() {
if (mPressHomeToExit) {
mDevice.pressHome();
mDevice.waitForIdle();
if (!mDevice.hasObject(getLauncherStrategy().getWorkspaceSelector())) {
throw new IllegalStateException("Pressing Home failed to exit the app.");
}
} else {
int maxBacks = 4;
while (!mDevice.hasObject(getLauncherStrategy().getWorkspaceSelector())
&& maxBacks > 0) {
mDevice.pressBack();
mDevice.waitForIdle();
maxBacks--;
}
if (maxBacks == 0) {
mDevice.pressHome();
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String getVersion() throws NameNotFoundException {
String pkg = getPackage();
if (null == pkg || pkg.isEmpty()) {
throw new RuntimeException("Cannot find version of empty package");
}
PackageManager pm = mInstrumentation.getContext().getPackageManager();
PackageInfo pInfo = pm.getPackageInfo(pkg, 0);
String version = pInfo.versionName;
if (null == version || version.isEmpty()) {
throw new RuntimeException(String.format("Version isn't found for package, %s", pkg));
}
return version;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isAppInForeground() {
return mDevice.hasObject(By.pkg(getPackage()).depth(0));
}
protected int getOrientation() {
return mInstrumentation.getContext().getResources().getConfiguration().orientation;
}
protected void requiresGoogleAccount() {
if (!hasRegisteredGoogleAccount()) {
throw new AccountException("This method requires a Google account be registered.");
}
}
protected void requiresNoGoogleAccount() {
if (hasRegisteredGoogleAccount()) {
throw new AccountException("This method requires no Google account be registered.");
}
}
protected boolean hasRegisteredGoogleAccount() {
Context context = mInstrumentation.getContext();
Account[] accounts = AccountManager.get(context).getAccounts();
for (int i = 0; i < accounts.length; ++i) {
if (accounts[i].type.equals("com.google")) {
return true;
}
}
return false;
}
@Override
public boolean captureScreenshot(String name) throws IOException {
File scrOut = File.createTempFile(name, ".png", getScreenshotDirectory());
File uixOut = File.createTempFile(name, ".uix", getScreenshotDirectory());
mDevice.dumpWindowHierarchy(uixOut);
return mDevice.takeScreenshot(scrOut);
}
private static File getScreenshotDirectory() {
if (sScreenshotDirectory == null) {
File storage = Environment.getExternalStorageDirectory();
sScreenshotDirectory = new File(storage, SCREENSHOT_DIR);
if (!sScreenshotDirectory.exists()) {
if (!sScreenshotDirectory.mkdirs()) {
throw new RuntimeException(
"Failed to create a screenshot directory.");
}
}
}
return sScreenshotDirectory;
}
/**
* {@inheritDoc}
*/
@Override
public boolean sendTextEvents(String text, long delay) {
Log.v(LOG_TAG, String.format("Sending text events for %s", text));
KeyEvent[] events = mKeyCharacterMap.getEvents(text.toCharArray());
for (KeyEvent event : events) {
if (KeyEvent.ACTION_DOWN == event.getAction()) {
if (!mDevice.pressKeyCode(event.getKeyCode(), event.getMetaState())) {
return false;
}
SystemClock.sleep(delay);
}
}
return true;
}
protected UiObject2 findElementById(String id) {
UiObject2 element = mDevice.findObject(By.res(getPackage(), id));
if (element != null) {
return element;
} else {
throw new UnknownUiException(String.format(ERROR_NOT_FOUND, "with id", id, getPackage()));
}
}
protected UiObject2 findElementByText(String text) {
UiObject2 element = mDevice.findObject(By.text(text));
if (element != null) {
return element;
} else {
throw new UnknownUiException(
String.format(ERROR_NOT_FOUND, "with text", text, getPackage()));
}
}
protected UiObject2 findElementByDescription(String description) {
UiObject2 element = mDevice.findObject(By.desc(description));
if (element != null) {
return element;
} else {
throw new UnknownUiException(
String.format(ERROR_NOT_FOUND, "with description", description, getPackage()));
}
}
protected void clickOn(UiObject2 element) {
if (element != null) {
element.click();
} else {
throw new UnknownUiException(String.format(ERROR_NOT_FOUND, "", "", getPackage()));
}
}
protected void waitAndClickById(String packageStr, String id, long timeout) {
clickOn(mDevice.wait(Until.findObject(By.res(packageStr, id)), timeout));
}
protected void waitAndClickByText(String text, long timeout) {
clickOn(mDevice.wait(Until.findObject(By.text(text)), timeout));
}
protected void waitAndClickByDescription(String description, long timeout) {
clickOn(mDevice.wait(Until.findObject(By.desc(description)), timeout));
}
protected void checkElementWithIdExists(String packageStr, String id, long timeout) {
if (!mDevice.wait(Until.hasObject(By.res(packageStr, id)), timeout)) {
throw new UnknownUiException(String.format(ERROR_NOT_FOUND, "with id", id, getPackage()));
}
}
protected void checkElementWithTextExists(String text, long timeout) {
if (!mDevice.wait(Until.hasObject(By.text(text)), timeout)) {
throw new UnknownUiException(
String.format(ERROR_NOT_FOUND, "with text", text, getPackage()));
}
}
protected void checkElementWithDescriptionExists(String description, long timeout) {
if (!mDevice.wait(Until.hasObject(By.desc(description)), timeout)) {
throw new UnknownUiException(
String.format(ERROR_NOT_FOUND, "with description", description, getPackage()));
}
}
protected void checkIfElementChecked(UiObject2 element) {
if (!element.isChecked()) {
throw new UnknownUiException("Element " + element + " is not checked");
}
}
/**
* {@inheritDoc}
*/
@Override
public void registerWatcher(String name, UiWatcher watcher) {
mDevice.registerWatcher(name, watcher);
}
/**
* {@inheritDoc}
*/
@Override
public void removeWatcher(String name) {
mDevice.removeWatcher(name);
}
private void registerDialogWatchers() {
registerWatcher(
AppIsNotRespondingWatcher.class.getSimpleName(),
new AppIsNotRespondingWatcher(InstrumentationRegistry.getInstrumentation()));
}
private void removeDialogWatchers() {
removeWatcher(AppIsNotRespondingWatcher.class.getSimpleName());
}
private ILauncherStrategy getLauncherStrategy() {
if (mLauncherStrategy == null) {
mLauncherStrategy = LauncherStrategyFactory.getInstance(mDevice).getLauncherStrategy();
}
return mLauncherStrategy;
}
}