Merge "Adding tests for fallback recents when a 3rd party launcher is installed" into ub-launcher3-master

This commit is contained in:
TreeHugger Robot 2018-10-09 21:32:19 +00:00 committed by Android (Google) Code Review
commit 02e900c3c9
24 changed files with 609 additions and 206 deletions

View File

@ -0,0 +1,126 @@
/*
* Copyright (C) 2018 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 com.android.quickstep;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
import static org.junit.Assert.assertTrue;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.quickstep.QuickStepOnOffRule.QuickstepOnOff;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
@LargeTest
@RunWith(AndroidJUnit4.class)
/**
* TODO: Fix fallback when quickstep is enabled
*/
public class FallbackRecentsTest {
private final UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
private final ActivityInfo mOtherLauncherActivity;
@Rule public final TestRule mDisableHeadsUpNotification = disableHeadsUpNotification();
@Rule public final TestRule mQuickstepOnOffExecutor;
@Rule public final TestRule mSetLauncherCommand;
public FallbackRecentsTest() {
Instrumentation instrumentation = getInstrumentation();
Context context = instrumentation.getContext();
mDevice = UiDevice.getInstance(instrumentation);
mLauncher = new LauncherInstrumentation(instrumentation);
mQuickstepOnOffExecutor = new QuickStepOnOffRule(new MainThreadExecutor(), mLauncher);
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
getHomeIntentInPackage(context),
MATCH_DISABLED_COMPONENTS).get(0).activityInfo;
mSetLauncherCommand = (base, desc) -> new Statement() {
@Override
public void evaluate() throws Throwable {
TestCommandReceiver.callCommand(TestCommandReceiver.ENABLE_TEST_LAUNCHER);
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
getLauncherCommand(mOtherLauncherActivity));
try {
base.evaluate();
} finally {
TestCommandReceiver.callCommand(TestCommandReceiver.DISABLE_TEST_LAUNCHER);
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
getLauncherCommand(getLauncherInMyProcess()));
}
}
};
}
@QuickstepOnOff(mode = OFF)
@Test
public void goToOverviewFromHome() {
mDevice.pressHome();
assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg(
mOtherLauncherActivity.packageName)), WAIT_TIME_MS));
mLauncher.getBackground().switchToOverview();
}
@QuickstepOnOff(mode = OFF)
@Test
public void goToOverviewFromApp() {
startAppFast("com.android.settings");
mLauncher.getBackground().switchToOverview();
}
private void startAppFast(String packageName) {
final Instrumentation instrumentation = getInstrumentation();
final Intent intent = instrumentation.getContext().getPackageManager().
getLaunchIntentForPackage(packageName);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
instrumentation.getTargetContext().startActivity(intent);
assertTrue(packageName + " didn't start",
mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), WAIT_TIME_MS));
}
}

View File

@ -16,10 +16,14 @@
package com.android.quickstep;
import static com.android.quickstep.QuickStepOnOffRule.Mode.BOTH;
import static com.android.quickstep.QuickStepOnOffRule.Mode.OFF;
import static com.android.quickstep.QuickStepOnOffRule.Mode.ON;
import androidx.test.InstrumentationRegistry;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.ui.TestHelpers;
import com.android.launcher3.tapl.TestHelpers;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@ -36,10 +40,16 @@ import java.util.concurrent.Executor;
* The test should be annotated with @QuickstepOnOff.
*/
public class QuickStepOnOffRule implements TestRule {
public enum Mode {
ON, OFF, BOTH
}
// Annotation for tests that need to be run with quickstep enabled and disabled.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface QuickstepOnOff {
Mode mode() default BOTH;
}
private final Executor mMainThreadExecutor;
@ -54,12 +64,17 @@ public class QuickStepOnOffRule implements TestRule {
public Statement apply(Statement base, Description description) {
if (TestHelpers.isInLauncherProcess() &&
description.getAnnotation(QuickstepOnOff.class) != null) {
Mode mode = description.getAnnotation(QuickstepOnOff.class).mode();
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
evaluateWithQuickstepOn();
evaluateWithQuickstepOff();
if (mode == ON || mode == BOTH) {
evaluateWithQuickstepOn();
}
if (mode == OFF || mode == BOTH) {
evaluateWithQuickstepOff();
}
} finally {
overrideSwipeUpEnabled(null);
}

View File

@ -67,5 +67,33 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<provider
android:name="com.android.launcher3.testcomponent.TestCommandReceiver"
android:authorities="${packageName}.commands"
android:exported="true" />
<activity
android:name="com.android.launcher3.testcomponent.TestLauncherActivity"
android:launchMode="singleTask"
android:clearTaskOnLaunch="true"
android:label="Test launcher"
android:stateNotNeeded="true"
android:theme="@android:style/Theme.DeviceDefault.Light"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="unspecified"
android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize"
android:resizeableActivity="true"
android:taskAffinity=""
android:process=":testLauncherProcess"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.MONKEY"/>
<category android:name="android.intent.category.LAUNCHER_APP" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2018 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 com.android.launcher3.testcomponent;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.DONT_KILL_APP;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
/**
* Content provider to receive commands from tests
*/
public class TestCommandReceiver extends ContentProvider {
public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
@Override
public boolean onCreate() {
return true;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public String getType(Uri uri) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("unimplemented mock method");
}
@Override
public Bundle call(String method, String arg, Bundle extras) {
switch (method) {
case ENABLE_TEST_LAUNCHER: {
getContext().getPackageManager().setComponentEnabledSetting(
new ComponentName(getContext(), TestLauncherActivity.class),
COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
return null;
}
case DISABLE_TEST_LAUNCHER: {
getContext().getPackageManager().setComponentEnabledSetting(
new ComponentName(getContext(), TestLauncherActivity.class),
COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP);
return null;
}
}
return super.call(method, arg, extras);
}
public static Bundle callCommand(String command) {
Instrumentation inst = InstrumentationRegistry.getInstrumentation();
Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
return inst.getTargetContext().getContentResolver().call(uri, command, null, null);
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 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 com.android.launcher3.testcomponent;
import static android.content.Intent.ACTION_MAIN;
import static android.content.Intent.CATEGORY_LAUNCHER;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
import android.app.LauncherActivity;
import android.content.Intent;
public class TestLauncherActivity extends LauncherActivity {
@Override
protected Intent getTargetIntent() {
return new Intent(ACTION_MAIN, null)
.addCategory(CATEGORY_LAUNCHER)
.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
}

View File

@ -19,6 +19,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@ -53,10 +55,12 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.compat.AppWidgetManagerCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.LauncherActivityRule;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.After;
import org.junit.Before;
@ -95,7 +99,7 @@ public abstract class AbstractLauncherUiTest {
private static final String TAG = "AbstractLauncherUiTest";
protected AbstractLauncherUiTest() {
final Instrumentation instrumentation = TestHelpers.getInstrumentation();
final Instrumentation instrumentation = getInstrumentation();
mDevice = UiDevice.getInstance(instrumentation);
try {
mDevice.setOrientationNatural();
@ -109,6 +113,11 @@ public abstract class AbstractLauncherUiTest {
@Rule
public LauncherActivityRule mActivityMonitor = new LauncherActivityRule();
@Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
@Rule public ShellCommandRule mDisableHeadsUpNotification =
ShellCommandRule.disableHeadsUpNotification();
// Annotation for tests that need to be run in portrait and landscape modes.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ -156,12 +165,10 @@ public abstract class AbstractLauncherUiTest {
public void setUp() throws Exception {
mTargetContext = InstrumentationRegistry.getTargetContext();
mTargetPackage = mTargetContext.getPackageName();
mDevice.executeShellCommand("settings put global heads_up_notifications_enabled 0");
}
@After
public void tearDown() throws Exception {
mDevice.executeShellCommand("settings put global heads_up_notifications_enabled 1");
// Limits UI tests affecting tests running after them.
resetLoaderState();
}
@ -282,7 +289,7 @@ public abstract class AbstractLauncherUiTest {
protected void sendPointer(int action, Point point) {
MotionEvent event = MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), action, point.x, point.y, 0);
TestHelpers.getInstrumentation().sendPointerSync(event);
getInstrumentation().sendPointerSync(event);
event.recycle();
}
@ -374,7 +381,7 @@ public abstract class AbstractLauncherUiTest {
getOnUiThread(new Callable<LauncherAppWidgetProviderInfo>() {
@Override
public LauncherAppWidgetProviderInfo call() throws Exception {
ComponentName cn = new ComponentName(TestHelpers.getInstrumentation().getContext(),
ComponentName cn = new ComponentName(getInstrumentation().getContext(),
hasConfigureScreen ? AppWidgetWithConfig.class : AppWidgetNoConfig.class);
Log.d(TAG, "findWidgetProvider componentName=" + cn.flattenToString());
return AppWidgetManagerCompat.getInstance(mTargetContext)

View File

@ -11,10 +11,8 @@ import androidx.test.uiautomator.Until;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -25,8 +23,6 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class AllAppsIconToHomeTest extends AbstractLauncherUiTest {
@Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
@Test
@Ignore
public void testDragIcon_portrait() throws Throwable {

View File

@ -15,10 +15,8 @@ import android.view.MotionEvent;
import com.android.launcher3.R;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -29,8 +27,6 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ShortcutsLaunchTest extends AbstractLauncherUiTest {
@Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
@Test
@Ignore
public void testAppLauncher_portrait() throws Exception {

View File

@ -15,10 +15,8 @@ import android.view.MotionEvent;
import com.android.launcher3.R;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -29,8 +27,6 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ShortcutsToHomeTest extends AbstractLauncherUiTest {
@Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
@Test
@Ignore
public void testDragIcon_portrait() throws Throwable {

View File

@ -1,36 +0,0 @@
/*
* Copyright (C) 2018 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 com.android.launcher3.ui;
import android.app.Instrumentation;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
public class TestHelpers {
private static final boolean IS_IN_LAUNCHER_PROCESS =
getInstrumentation().getTargetContext().getPackageName().equals(
UiDevice.getInstance(getInstrumentation()).getLauncherPackageName());
public static Instrumentation getInstrumentation() {
return InstrumentationRegistry.getInstrumentation();
}
public static boolean isInLauncherProcess() {
return IS_IN_LAUNCHER_PROCESS;
}
}

View File

@ -22,19 +22,14 @@ import static org.junit.Assert.assertTrue;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class WorkTabTest extends AbstractLauncherUiTest {
@Rule
public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
private int mProfileUserId;

View File

@ -20,6 +20,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import android.appwidget.AppWidgetManager;
import android.content.Intent;
import androidx.test.filters.LargeTest;
@ -34,7 +36,7 @@ import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.Workspace;
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestHelpers;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
@ -136,8 +138,7 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
}
private void setResult(boolean success) {
TestHelpers.getInstrumentation().getTargetContext().sendBroadcast(
getInstrumentation().getTargetContext().sendBroadcast(
WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
success ? "clickOK" : "clickCancel"));
}

View File

@ -56,6 +56,7 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import java.util.Set;
@ -71,8 +72,6 @@ import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class BindWidgetTest extends AbstractLauncherUiTest {
@Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
private ContentResolver mResolver;
private AppWidgetManagerCompat mWidgetManager;

View File

@ -60,10 +60,9 @@ import java.util.UUID;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class RequestPinItemTest extends AbstractLauncherUiTest {
public class RequestPinItemTest extends AbstractLauncherUiTest {
@Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind();
@Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
private String mCallbackAction;
private String mShortcutId;

View File

@ -15,10 +15,14 @@
*/
package com.android.launcher3.util.rule;
import static com.android.launcher3.tapl.TestHelpers.getHomeIntentInPackage;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import android.app.Activity;
import android.app.Application;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.Intent;
import android.os.Bundle;
import androidx.test.InstrumentationRegistry;
@ -65,19 +69,12 @@ public class LauncherActivityRule implements TestRule {
* Starts the launcher activity in the target package.
*/
public void startLauncher() {
InstrumentationRegistry.getInstrumentation().startActivitySync(getHomeIntent());
getInstrumentation().startActivitySync(getHomeIntentInPackage(getTargetContext()));
}
public void returnToHome() {
InstrumentationRegistry.getTargetContext().startActivity(getHomeIntent());
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
public static Intent getHomeIntent() {
return new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(InstrumentationRegistry.getTargetContext().getPackageName())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getTargetContext().startActivity(getHomeIntentInPackage(getTargetContext()));
getInstrumentation().waitForIdleSync();
}
private class MyStatement extends Statement implements ActivityLifecycleCallbacks {

View File

@ -15,17 +15,20 @@
*/
package com.android.launcher3.util.rule;
import static com.android.launcher3.tapl.TestHelpers.getLauncherInMyProcess;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.os.ParcelFileDescriptor;
import androidx.test.InstrumentationRegistry;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.io.FileInputStream;
import java.io.IOException;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
/**
* Test rule which executes a shell command at the start of the test.
@ -33,40 +36,28 @@ import java.io.IOException;
public class ShellCommandRule implements TestRule {
private final String mCmd;
private final String mRevertCommand;
public ShellCommandRule(String cmd) {
public ShellCommandRule(String cmd, @Nullable String revertCommand) {
mCmd = cmd;
mRevertCommand = revertCommand;
}
@Override
public Statement apply(Statement base, Description description) {
return new MyStatement(base, mCmd);
}
public static void runShellCommand(String command) throws IOException {
ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(command);
// Read the input stream fully.
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
while (fis.read() != -1);
fis.close();
}
private static class MyStatement extends Statement {
private final Statement mBase;
private final String mCmd;
public MyStatement(Statement base, String cmd) {
mBase = base;
mCmd = cmd;
}
@Override
public void evaluate() throws Throwable {
runShellCommand(mCmd);
mBase.evaluate();
}
return new Statement() {
@Override
public void evaluate() throws Throwable {
UiDevice.getInstance(getInstrumentation()).executeShellCommand(mCmd);
try {
base.evaluate();
} finally {
if (mRevertCommand != null) {
UiDevice.getInstance(getInstrumentation()).executeShellCommand(mRevertCommand);
}
}
}
};
}
/**
@ -74,17 +65,26 @@ public class ShellCommandRule implements TestRule {
*/
public static ShellCommandRule grandWidgetBind() {
return new ShellCommandRule("appwidget grantbind --package "
+ InstrumentationRegistry.getTargetContext().getPackageName());
+ InstrumentationRegistry.getTargetContext().getPackageName(), null);
}
/**
* Sets the target launcher as default launcher.
*/
public static ShellCommandRule setDefaultLauncher() {
ActivityInfo launcher = InstrumentationRegistry.getTargetContext().getPackageManager()
.queryIntentActivities(LauncherActivityRule.getHomeIntent(), 0).get(0)
.activityInfo;
return new ShellCommandRule("cmd package set-home-activity " +
new ComponentName(launcher.packageName, launcher.name).flattenToString());
return new ShellCommandRule(getLauncherCommand(getLauncherInMyProcess()), null);
}
public static String getLauncherCommand(ActivityInfo launcher) {
return "cmd package set-home-activity " +
new ComponentName(launcher.packageName, launcher.name).flattenToString();
}
/**
* Disables heads up notification for the duration of the test
*/
public static ShellCommandRule disableHeadsUpNotification() {
return new ShellCommandRule("settings put global heads_up_notifications_enabled 0",
"settings put global heads_up_notifications_enabled 1");
}
}

View File

@ -16,10 +16,22 @@
package com.android.launcher3.tapl;
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
import static org.junit.Assert.assertTrue;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
/**
* Operations on a state when Launcher is inactive because some other app is active.
* Indicates the base state with a UI other than Overview running as foreground. It can also
* indicate Launcher as long as Launcher is not in Overview state.
*/
public final class Background extends Home {
public class Background extends LauncherInstrumentation.VisibleContainer {
Background(LauncherInstrumentation launcher) {
super(launcher);
@ -29,4 +41,34 @@ public final class Background extends Home {
protected LauncherInstrumentation.ContainerType getContainerType() {
return LauncherInstrumentation.ContainerType.BACKGROUND;
}
/**
* Swipes up or presses the square button to switch to Overview.
* Returns the base overview, which can be either in Launcher or the fallback recents.
*
* @return the Overview panel object.
*/
@NonNull
public BaseOverview switchToOverview() {
verifyActiveContainer();
goToOverviewUnchecked();
assertTrue("Overview not visible", mLauncher.getDevice().wait(Until.hasObject(By.pkg(
getTargetContext().getPackageName())),
WAIT_TIME_MS));
return new BaseOverview(mLauncher);
}
protected void goToOverviewUnchecked() {
if (mLauncher.isSwipeUpEnabled()) {
final int height = mLauncher.getDevice().getDisplayHeight();
final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
mLauncher.swipe(
navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
navBar.getVisibleBounds().centerX(), height - 300);
} else {
mLauncher.getSystemUiObject("recent_apps").click();
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2018 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 com.android.launcher3.tapl;
import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
/**
* Common overview pane for both Launcher and fallback recents
*/
public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
private static final int DEFAULT_FLING_SPEED = 15000;
BaseOverview(LauncherInstrumentation launcher) {
super(launcher);
}
@Override
protected LauncherInstrumentation.ContainerType getContainerType() {
return LauncherInstrumentation.ContainerType.BASE_OVERVIEW;
}
/**
* Flings forward (left) and waits the fling's end.
*/
public void flingForward() {
final UiObject2 overview = verifyActiveContainer();
LauncherInstrumentation.log("Overview.flingForward before fling");
overview.fling(Direction.LEFT, DEFAULT_FLING_SPEED);
mLauncher.waitForIdle();
verifyActiveContainer();
}
/**
* Flings backward (right) and waits the fling's end.
*/
public void flingBackward() {
final UiObject2 overview = verifyActiveContainer();
LauncherInstrumentation.log("Overview.flingBackward before fling");
overview.fling(Direction.RIGHT, DEFAULT_FLING_SPEED);
mLauncher.waitForIdle();
verifyActiveContainer();
}
/**
* Gets the current task in the carousel, or fails if the carousel is empty.
*
* @return the task in the middle of the visible tasks list.
*/
@NonNull
public OverviewTask getCurrentTask() {
verifyActiveContainer();
final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
LauncherInstrumentation.getLauncherObjectSelector("snapshot"));
LauncherInstrumentation.assertNotEquals("Unable to find a task", 0, taskViews.size());
// taskViews contains up to 3 task views: the 'main' (having the widest visible
// part) one in the center, and parts of its right and left siblings. Find the
// main task view by its width.
final UiObject2 widestTask = Collections.max(taskViews,
(t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
t2.getVisibleBounds().width()));
return new OverviewTask(mLauncher, widestTask, this);
}
}

View File

@ -16,8 +16,6 @@
package com.android.launcher3.tapl;
import androidx.test.uiautomator.UiObject2;
import androidx.annotation.NonNull;
/**
@ -28,33 +26,28 @@ import androidx.annotation.NonNull;
* that essentially represents these two activity states. Any gestures (e.g., switchToOverview) that
* can be performed in both of these states can be defined here.
*/
public abstract class Home extends LauncherInstrumentation.VisibleContainer {
public abstract class Home extends Background {
protected Home(LauncherInstrumentation launcher) {
super(launcher);
verifyActiveContainer();
}
@Override
protected LauncherInstrumentation.ContainerType getContainerType() {
return LauncherInstrumentation.ContainerType.WORKSPACE;
}
/**
* Swipes up or presses the square button to switch to Overview.
*
* @return the Overview panel object.
*/
@NonNull
@Override
public Overview switchToOverview() {
verifyActiveContainer();
if (mLauncher.isSwipeUpEnabled()) {
final int height = mLauncher.getDevice().getDisplayHeight();
final UiObject2 navBar = mLauncher.getSystemUiObject("navigation_bar_frame");
mLauncher.swipe(
navBar.getVisibleBounds().centerX(), navBar.getVisibleBounds().centerY(),
navBar.getVisibleBounds().centerX(), height - 300
);
} else {
mLauncher.getSystemUiObject("recent_apps").click();
}
goToOverviewUnchecked();
return new Overview(mLauncher);
}
}

View File

@ -28,13 +28,6 @@ import android.util.Log;
import android.view.Surface;
import android.view.accessibility.AccessibilityEvent;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import com.android.launcher3.TestProtocol;
import com.android.quickstep.SwipeUpSetting;
@ -43,6 +36,13 @@ import org.junit.Assert;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeoutException;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
/**
* The main tapl object. The only object that can be explicitly constructed by the using code. It
* produces all other objects.
@ -54,7 +54,7 @@ public final class LauncherInstrumentation {
// Types for launcher containers that the user is interacting with. "Background" is a
// pseudo-container corresponding to inactive launcher covered by another app.
enum ContainerType {
WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND
WORKSPACE, ALL_APPS, OVERVIEW, WIDGETS, BACKGROUND, BASE_OVERVIEW
}
// Base class for launcher containers.
@ -84,7 +84,7 @@ public final class LauncherInstrumentation {
private static final String OVERVIEW_RES_ID = "overview_panel";
private static final String WIDGETS_RES_ID = "widgets_list_view";
static final String LAUNCHER_PKG = "com.google.android.apps.nexuslauncher";
static final int WAIT_TIME_MS = 60000;
public static final int WAIT_TIME_MS = 60000;
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
@ -112,11 +112,8 @@ public final class LauncherInstrumentation {
// Launcher should run in test harness so that custom accessibility protocol between
// Launcher and TAPL is enabled. In-process tests enable this protocol with a direct call
// into Launcher.
final boolean isInLauncherProcess =
instrumentation.getTargetContext().getPackageName().equals(
UiDevice.getInstance(instrumentation).getLauncherPackageName());
assertTrue("Device must run in a test harness",
isInLauncherProcess || ActivityManager.isRunningInTestHarness());
TestHelpers.isInLauncherProcess() || ActivityManager.isRunningInTestHarness());
}
// Used only by TaplTests.
@ -203,8 +200,12 @@ public final class LauncherInstrumentation {
} else {
waitUntilGone(APPS_RES_ID);
}
// Fall through
}
case BASE_OVERVIEW: {
waitUntilGone(WORKSPACE_RES_ID);
waitUntilGone(WIDGETS_RES_ID);
return waitForLauncherObject(OVERVIEW_RES_ID);
}
case BACKGROUND: {
@ -309,6 +310,17 @@ public final class LauncherInstrumentation {
return new Overview(this);
}
/**
* Gets the Base overview object if either Launcher is in overview state or the fallback
* overview activity is showing. Fails otherwise.
*
* @return BaseOverview object.
*/
@NonNull
public BaseOverview getBaseOverview() {
return new BaseOverview(this);
}
/**
* Gets the All Apps object if the current state is showing the all apps panel opened by swiping
* from workspace. Fails if the launcher is not in that state. Please don't call this method if

View File

@ -18,18 +18,15 @@ package com.android.launcher3.tapl;
import android.graphics.Point;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
import com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import java.util.Collections;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.test.uiautomator.UiObject2;
/**
* Overview pane.
*/
public final class Overview extends LauncherInstrumentation.VisibleContainer {
private static final int DEFAULT_FLING_SPEED = 15000;
public final class Overview extends BaseOverview {
Overview(LauncherInstrumentation launcher) {
super(launcher);
@ -37,54 +34,10 @@ public final class Overview extends LauncherInstrumentation.VisibleContainer {
}
@Override
protected LauncherInstrumentation.ContainerType getContainerType() {
protected ContainerType getContainerType() {
return LauncherInstrumentation.ContainerType.OVERVIEW;
}
/**
* Flings forward (left) and waits the fling's end.
*/
public void flingForward() {
final UiObject2 overview = verifyActiveContainer();
LauncherInstrumentation.log("Overview.flingForward before fling");
overview.fling(Direction.LEFT, DEFAULT_FLING_SPEED);
mLauncher.waitForIdle();
verifyActiveContainer();
}
/**
* Flings backward (right) and waits the fling's end.
*/
public void flingBackward() {
final UiObject2 overview = verifyActiveContainer();
LauncherInstrumentation.log("Overview.flingBackward before fling");
overview.fling(Direction.RIGHT, DEFAULT_FLING_SPEED);
mLauncher.waitForIdle();
verifyActiveContainer();
}
/**
* Gets the current task in the carousel, or fails if the carousel is empty.
*
* @return the task in the middle of the visible tasks list.
*/
@NonNull
public OverviewTask getCurrentTask() {
verifyActiveContainer();
final List<UiObject2> taskViews = mLauncher.getDevice().findObjects(
LauncherInstrumentation.getLauncherObjectSelector("snapshot"));
LauncherInstrumentation.assertNotEquals("Unable to find a task", 0, taskViews.size());
// taskViews contains up to 3 task views: the 'main' (having the widest visible
// part) one in the center, and parts of its right and left siblings. Find the
// main task view by its width.
final UiObject2 widestTask = Collections.max(taskViews,
(t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
t2.getVisibleBounds().width()));
return new OverviewTask(mLauncher, widestTask, this);
}
/**
* Swipes up to All Apps.
*

View File

@ -26,9 +26,9 @@ import androidx.test.uiautomator.Until;
public final class OverviewTask {
private final LauncherInstrumentation mLauncher;
private final UiObject2 mTask;
private final Overview mOverview;
private final BaseOverview mOverview;
OverviewTask(LauncherInstrumentation launcher, UiObject2 task, Overview overview) {
OverviewTask(LauncherInstrumentation launcher, UiObject2 task, BaseOverview overview) {
mLauncher = launcher;
mTask = task;
mOverview = overview;

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2018 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 com.android.launcher3.tapl;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import java.util.List;
public class TestHelpers {
private static Boolean sIsInLauncherProcess;
public static boolean isInLauncherProcess() {
if (sIsInLauncherProcess == null) {
sIsInLauncherProcess = initIsInLauncherProcess();
}
return sIsInLauncherProcess;
}
private static boolean initIsInLauncherProcess() {
ActivityInfo info = getLauncherInMyProcess();
// If we are in the same process, we can instantiate the class name.
try {
Class launcherClazz = Class.forName("com.android.launcher3.Launcher");
return launcherClazz.isAssignableFrom(Class.forName(info.name));
} catch (Exception e) {
return false;
}
}
public static Intent getHomeIntentInPackage(Context context) {
return new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
.setPackage(context.getPackageName())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
public static ActivityInfo getLauncherInMyProcess() {
Instrumentation instrumentation = getInstrumentation();
if (instrumentation.getTargetContext() == null) {
return null;
}
List<ResolveInfo> launchers = getTargetContext().getPackageManager()
.queryIntentActivities(getHomeIntentInPackage(getTargetContext()), 0);
if (launchers.size() != 1) {
return null;
}
return launchers.get(0).activityInfo;
}
}

View File

@ -38,11 +38,6 @@ public final class Workspace extends Home {
mHotseat = launcher.waitForLauncherObject("hotseat");
}
@Override
protected LauncherInstrumentation.ContainerType getContainerType() {
return LauncherInstrumentation.ContainerType.WORKSPACE;
}
/**
* Swipes up to All Apps.
*