Merge "Migrating widgets tests off TAPL" into main

This commit is contained in:
Treehugger Robot
2024-11-16 09:16:30 +00:00
committed by Android (Google) Code Review
9 changed files with 284 additions and 255 deletions
-1
View File
@@ -63,7 +63,6 @@ filegroup {
"src/com/android/launcher3/dragging/TaplDragTest.java",
"src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java",
"src/com/android/launcher3/ui/TaplTestsLauncher3Test.java",
"src/com/android/launcher3/ui/widget/TaplWidgetPickerTest.java",
"src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java",
],
}
@@ -21,11 +21,8 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static org.junit.Assert.assertTrue;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Process;
import android.system.OsConstants;
import android.util.Log;
@@ -53,7 +50,6 @@ import org.junit.rules.TestRule;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
@@ -216,38 +212,6 @@ public abstract class AbstractLauncherUiTest<LAUNCHER_TYPE extends Launcher>
}, mLauncher, timeout);
}
/**
* Broadcast receiver which blocks until the result is received.
*/
public class BlockingBroadcastReceiver extends BroadcastReceiver {
private final CountDownLatch latch = new CountDownLatch(1);
private Intent mIntent;
public BlockingBroadcastReceiver(String action) {
mTargetContext.registerReceiver(this, new IntentFilter(action),
Context.RECEIVER_EXPORTED/*UNAUDITED*/);
}
@Override
public void onReceive(Context context, Intent intent) {
mIntent = intent;
latch.countDown();
}
public Intent blockingGetIntent() throws InterruptedException {
assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS));
mTargetContext.unregisterReceiver(this);
return mIntent;
}
public Intent blockingGetExtraIntent() throws InterruptedException {
Intent intent = blockingGetIntent();
return intent == null ? null : (Intent) intent.getParcelableExtra(
Intent.EXTRA_INTENT);
}
}
public static void startAppFast(String packageName) {
startIntent(
getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage(
@@ -1,17 +1,17 @@
/*
* 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
* 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
* 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.
* 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.widget;
@@ -24,23 +24,34 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.os.Process;
import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.BaseLauncherActivityTest;
import com.android.launcher3.util.BlockingBroadcastReceiver;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.WidgetsListAdapter;
import com.android.launcher3.widget.picker.WidgetsRecyclerView;
import org.junit.Before;
import org.junit.Rule;
@@ -52,7 +63,7 @@ import org.junit.runner.RunWith;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest<Launcher> {
public class AddConfigWidgetTest extends BaseLauncherActivityTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@@ -62,12 +73,10 @@ public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest<Launcher> {
private int mWidgetId;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
mWidgetInfo = TestViewHelpers.findWidgetProvider(true /* hasConfigureScreen */);
mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext);
mAppWidgetManager = AppWidgetManager.getInstance(targetContext());
}
@Test
@@ -82,80 +91,86 @@ public class TaplAddConfigWidgetTest extends AbstractLauncherUiTest<Launcher> {
runTest(false);
}
/**
* @param acceptConfig accept the config activity
*/
private void runTest(boolean acceptConfig) throws Throwable {
commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
new FavoriteItemsTransaction(targetContext()).commit();
loadLauncherSync();
// Drag widget to homescreen
// Add widget to homescreen
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
mLauncher.getWorkspace()
.openAllWidgets()
.getWidget(mWidgetInfo.getLabel())
.dragToWorkspace(true, false);
executeOnLauncher(OptionsPopupView::openWidgets);
uiDevice.waitForIdle();
// Select the widget header
Context testContext = getInstrumentation().getContext();
String packageName = testContext.getPackageName();
executeOnLauncher(l -> {
WidgetsRecyclerView wrv = WidgetsFullSheet.getWidgetsView(l);
WidgetsListAdapter adapter = (WidgetsListAdapter) wrv.getAdapter();
int pos = adapter.getItems().indexOf(
adapter.getItems().stream()
.filter(entry -> packageName.equals(entry.mPkgItem.packageName))
.findFirst()
.get());
wrv.getLayoutManager().scrollToPosition(pos);
adapter.onHeaderClicked(true, new PackageUserKey(packageName, Process.myUserHandle()));
});
uiDevice.waitForIdle();
View widgetView = getOnceNotNull("Widget not found", l -> searchView(l.getDragLayer(), v ->
v instanceof WidgetCell
&& v.getTag() instanceof PendingAddWidgetInfo pawi
&& mWidgetInfo.provider.equals(pawi.componentName)));
addToWorkspace(widgetView);
// Widget id for which the config activity was opened
mWidgetId = monitor.getWidgetId();
// Verify that the widget id is valid and bound
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
setResult(acceptConfig);
setResultAndWaitForAnimation(acceptConfig);
if (acceptConfig) {
Wait.atMost("", new WidgetSearchCondition(), mLauncher);
getOnceNotNull("Widget was not added", l -> {
// Close the resize frame before searching for widget
AbstractFloatingView.closeAllOpenViews(l);
return l.getWorkspace().getFirstMatch(new WidgetSearchCondition());
});
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
} else {
// Verify that the widget id is deleted.
Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
mLauncher);
Wait.atMost("", () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null);
}
}
private static void setResult(boolean success) {
private void setResult(boolean success) {
getInstrumentation().getTargetContext().sendBroadcast(
WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class,
success ? "clickOK" : "clickCancel"));
}
private void setResultAndWaitForAnimation(boolean success) {
if (mLauncher.isLauncher3()) {
setResult(success);
} else {
mLauncher.executeAndWaitForWallpaperAnimation(
() -> setResult(success),
"setting widget coinfig result");
}
uiDevice.waitForIdle();
}
/**
* Condition for searching widget id
*/
private class WidgetSearchCondition implements Wait.Condition, ItemOperator {
@Override
public boolean isTrue() throws Throwable {
return mMainThreadExecutor.submit(() -> {
Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedContext();
return l != null && l.getWorkspace().getFirstMatch(this) != null;
}).get();
}
private class WidgetSearchCondition implements ItemOperator {
@Override
public boolean evaluate(ItemInfo info, View view) {
return info instanceof LauncherAppWidgetInfo
&& ((LauncherAppWidgetInfo) info).providerName.getClassName().equals(
mWidgetInfo.provider.getClassName())
&& ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId;
return info instanceof LauncherAppWidgetInfo lawi
&& lawi.providerName.equals(mWidgetInfo.provider)
&& lawi.appWidgetId == mWidgetId;
}
}
/**
* Broadcast receiver for receiving widget config activity status.
*/
private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver {
private static class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver {
public WidgetConfigStartupMonitor() {
WidgetConfigStartupMonitor() {
super(WidgetConfigActivity.class.getName());
}
@@ -1,17 +1,17 @@
/*
* 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
* 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
* 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.
* 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.widget;
@@ -22,12 +22,12 @@ import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVID
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.TestUtil.getOnUiThread;
import static com.android.launcher3.util.Wait.atMost;
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
@@ -36,6 +36,7 @@ import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.RemoteViews;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -49,13 +50,12 @@ import com.android.launcher3.R;
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.tapl.Widget;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.BaseLauncherActivityTest;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetManagerHelper;
import org.junit.After;
@@ -67,6 +67,7 @@ import org.junit.runner.RunWith;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Tests for bind widget flow.
@@ -75,7 +76,7 @@ import java.util.function.Consumer;
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
public class BindWidgetTest extends BaseLauncherActivityTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@@ -87,11 +88,9 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
private LauncherModel mModel;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
mModel = LauncherAppState.getInstance(mTargetContext).getModel();
mModel = LauncherAppState.getInstance(targetContext()).getModel();
}
@After
@@ -101,7 +100,7 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
}
if (mSessionId > -1) {
mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
targetContext().getPackageManager().getPackageInstaller().abandonSession(mSessionId);
}
}
@@ -122,13 +121,12 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
item -> item.appWidgetId = -33);
final Workspace workspace = mLauncher.getWorkspace();
// Item deleted from db
mCursor = queryItem();
assertEquals(0, mCursor.getCount());
// The view does not exist
assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null);
verifyItemEventuallyNull("Widget exists", widgetProvider(info));
}
@Test
@@ -154,18 +152,19 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
// Widget has a valid Id now.
assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
& FLAG_ID_NOT_VALID);
assertNotNull(AppWidgetManager.getInstance(mTargetContext)
assertNotNull(AppWidgetManager.getInstance(targetContext())
.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
LauncherSettings.Favorites.APPWIDGET_ID))));
// send OPTION_APPWIDGET_RESTORE_COMPLETED
int appWidgetId = mCursor.getInt(
mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID));
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(targetContext());
Bundle b = new Bundle();
b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true);
RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready);
RemoteViews remoteViews = new RemoteViews(
targetContext().getPackageName(), R.layout.appwidget_not_ready);
appWidgetManager.updateAppWidgetOptions(appWidgetId, b);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
@@ -175,15 +174,14 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED));
executeOnLauncher(l -> l.getAppWidgetHolder().startListening());
verifyWidgetPresent(info);
assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100));
verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider());
}
@Test
public void testPendingWidget_notRestored_removed() {
addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
assertTrue("Pending widget exists",
mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
verifyItemEventuallyNull("Pending widget exists", pendingWidgetProvider());
// Item deleted from db
mCursor = queryItem();
assertEquals(0, mCursor.getCount());
@@ -216,7 +214,7 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
// Create an active installer session
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
params.setAppPackageName(item.providerName.getPackageName());
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
PackageInstaller installer = targetContext().getPackageManager().getPackageInstaller();
mSessionId = installer.createSession(params);
addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
@@ -234,36 +232,47 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
}
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label,
TestUtil.DEFAULT_UI_TIMEOUT);
assertTrue("Widget is not present",
widget != null);
getOnceNotNull("Widget is not present", widgetProvider(info));
}
private void verifyPendingWidgetPresent() {
final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(
TestUtil.DEFAULT_UI_TIMEOUT);
assertTrue("Pending widget is not present",
widget != null);
getOnceNotNull("Widget is not present", pendingWidgetProvider());
}
private Function<Launcher, Object> pendingWidgetProvider() {
return l -> l.getWorkspace().getFirstMatch(
(item, view) -> view instanceof PendingAppWidgetHostView);
}
private Function<Launcher, Object> widgetProvider(LauncherAppWidgetProviderInfo info) {
return l -> l.getWorkspace().getFirstMatch((item, view) ->
view instanceof LauncherAppWidgetHostView
&& TextUtils.equals(info.label, view.getContentDescription()));
}
private void verifyItemEventuallyNull(String message, Function<Launcher, Object> provider) {
atMost(message, () -> getFromLauncher(provider) == null);
}
private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) {
item.restoreStatus = restoreStatus;
item.screenId = FIRST_SCREEN_ID;
commitTransactionAndLoadHome(
new FavoriteItemsTransaction(mTargetContext).addItem(() -> item));
new FavoriteItemsTransaction(targetContext()).addItem(() -> item).commit();
loadLauncherSync();
}
private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen,
boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen);
commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)
new FavoriteItemsTransaction(targetContext())
.addItem(() -> {
LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget);
LauncherAppWidgetInfo item =
createWidgetInfo(info, targetContext(), bindWidget);
item.screenId = FIRST_SCREEN_ID;
itemOverride.accept(item);
return item;
}));
}).commit();
loadLauncherSync();
return info;
}
@@ -277,13 +286,13 @@ public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
Set<String> activePackage = getOnUiThread(() -> {
Set<String> packages = new HashSet<>();
InstallSessionHelper.INSTANCE.get(mTargetContext).getActiveSessions()
InstallSessionHelper.INSTANCE.get(targetContext()).getActiveSessions()
.keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName));
return packages;
});
while (true) {
try {
mTargetContext.getPackageManager().getPackageInfo(
targetContext().getPackageManager().getPackageInfo(
pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (Exception e) {
if (!activePackage.contains(pkg)) {
@@ -1,34 +1,41 @@
/*
* 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
* 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
* 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.
* 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.widget;
import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.BySelector;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -37,14 +44,13 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.tapl.AddToHomeScreenPrompt;
import com.android.launcher3.testcomponent.AppWidgetNoConfig;
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.testcomponent.RequestPinItemActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.BaseLauncherActivityTest;
import com.android.launcher3.util.BlockingBroadcastReceiver;
import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Wait;
import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Before;
@@ -53,25 +59,27 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.UUID;
import java.util.regex.Pattern;
/**
* Test to verify pin item request flow.
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
public class RequestPinItemTest extends BaseLauncherActivityTest<Launcher> {
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
@Rule
public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
private String mCallbackAction;
private String mShortcutId;
private int mAppWidgetId;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
mCallbackAction = UUID.randomUUID().toString();
mShortcutId = UUID.randomUUID().toString();
}
@@ -81,9 +89,9 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
@Test
public void testPinWidgetNoConfig() throws Throwable {
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
((LauncherAppWidgetInfo) info).providerName.getClassName()
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo
&& ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId
&& ((LauncherAppWidgetInfo) info).providerName.getClassName()
.equals(AppWidgetNoConfig.class.getName()));
}
@@ -94,18 +102,18 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
((LauncherAppWidgetInfo) info).providerName.getClassName()
runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo
&& ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId
&& ((LauncherAppWidgetInfo) info).providerName.getClassName()
.equals(AppWidgetNoConfig.class.getName()), command);
}
@Test
public void testPinWidgetWithConfig() throws Throwable {
runTest("pinWidgetWithConfig", true,
(info, view) -> info instanceof LauncherAppWidgetInfo &&
((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId &&
((LauncherAppWidgetInfo) info).providerName.getClassName()
(info, view) -> info instanceof LauncherAppWidgetInfo
&& ((LauncherAppWidgetInfo) info).appWidgetId == mAppWidgetId
&& ((LauncherAppWidgetInfo) info).providerName.getClassName()
.equals(AppWidgetWithConfig.class.getName()));
}
@@ -119,47 +127,48 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
runTest("pinShortcut", false, new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View view) {
return info instanceof WorkspaceItemInfo &&
info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId);
return info instanceof WorkspaceItemInfo
&& info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
&& ShortcutKey.fromItemInfo(info).getId().equals(mShortcutId);
}
}, command);
}
private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
Intent... commandIntents) throws Throwable {
commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
new FavoriteItemsTransaction(targetContext()).commit();
loadLauncherSync();
// Open Pin item activity
BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
RequestPinItemActivity.class.getName());
mLauncher.
getWorkspace().
switchToAllApps().
getAppIcon("Test Pin Item").
launch(getAppPackageName());
Context testContext = getInstrumentation().getContext();
startAppFast(
testContext.getPackageName(),
new Intent(testContext, RequestPinItemActivity.class));
assertNotNull(openMonitor.blockingGetExtraIntent());
// Set callback
PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0,
new Intent(mCallbackAction).setPackage(mTargetContext.getPackageName()),
PendingIntent callback = PendingIntent.getBroadcast(targetContext(), 0,
new Intent(mCallbackAction).setPackage(targetContext().getPackageName()),
FLAG_ONE_SHOT | FLAG_MUTABLE);
mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent(
RequestPinItemActivity.class, "setCallback").putExtra(
RequestPinItemActivity.EXTRA_PARAM + "0", callback));
for (Intent command : commandIntents) {
mTargetContext.sendBroadcast(command);
targetContext().sendBroadcast(command);
}
// call the requested method to start the flow
mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
targetContext().sendBroadcast(RequestPinItemActivity.getCommandIntent(
RequestPinItemActivity.class, activityMethod));
final AddToHomeScreenPrompt addToHomeScreenPrompt = mLauncher.getAddToHomeScreenPrompt();
// Accept confirmation:
BlockingBroadcastReceiver resultReceiver = new BlockingBroadcastReceiver(mCallbackAction);
addToHomeScreenPrompt.addAutomatically();
BySelector selector = By.text(Pattern.compile("^Add to home screen$", CASE_INSENSITIVE))
.pkg(targetContext().getPackageName());
uiDevice.wait(device -> device.findObject(selector), TestUtil.DEFAULT_UI_TIMEOUT).click();
Intent result = resultReceiver.blockingGetIntent();
assertNotNull(result);
mAppWidgetId = result.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
@@ -167,28 +176,9 @@ public class TaplRequestPinItemTest extends AbstractLauncherUiTest<Launcher> {
assertNotSame(-1, mAppWidgetId);
}
// Go back to home
mLauncher.goHome();
Wait.atMost("", new ItemSearchCondition(itemMatcher), mLauncher);
}
/**
* Condition for for an item
*/
private class ItemSearchCondition implements Condition {
private final ItemOperator mOp;
ItemSearchCondition(ItemOperator op) {
mOp = op;
}
@Override
public boolean isTrue() throws Throwable {
return mMainThreadExecutor.submit(() -> {
Launcher l = Launcher.ACTIVITY_TRACKER.getCreatedContext();
return l != null && l.getWorkspace().getFirstMatch(mOp) != null;
}).get();
}
// Reload activity, so that the activity is focused
closeCurrentActivity();
loadLauncherSync();
getOnceNotNull("", l -> l.getWorkspace().getFirstMatch(itemMatcher));
}
}
@@ -22,24 +22,30 @@ import static org.junit.Assert.assertTrue;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.tapl.Widgets;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.BaseLauncherActivityTest;
import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.WidgetsRecyclerView;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
/**
* This test run in both Out of process (Oop) and in-process (Ipc).
* Make sure the basic interactions with the WidgetPicker works.
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
public class TaplWidgetPickerTest extends AbstractLauncherUiTest<Launcher> {
public class WidgetPickerTest extends BaseLauncherActivityTest<Launcher> {
@Rule
public TestRule screenRecordRule = new ScreenRecordRule();
private WidgetsRecyclerView getWidgetsView(Launcher launcher) {
return WidgetsFullSheet.getWidgetsView(launcher);
@@ -56,30 +62,21 @@ public class TaplWidgetPickerTest extends AbstractLauncherUiTest<Launcher> {
@ScreenRecord
@PortraitLandscape
public void testWidgets() {
mLauncher.goHome();
loadLauncherSync();
// Test opening widgets.
executeOnLauncher(launcher ->
assertTrue("Widgets is initially opened", getWidgetsView(launcher) == null));
Widgets widgets = mLauncher.getWorkspace().openAllWidgets();
assertNotNull("openAllWidgets() returned null", widgets);
widgets = mLauncher.getAllWidgets();
assertNotNull("openAllWidgets() returned null",
getFromLauncher(OptionsPopupView::openWidgets));
WidgetsRecyclerView widgets = getFromLauncher(this::getWidgetsView);
assertNotNull("getAllWidgets() returned null", widgets);
executeOnLauncher(launcher ->
assertTrue("Widgets is not shown", getWidgetsView(launcher).isShown()));
executeOnLauncher(launcher -> assertTrue("Widgets is not shown", widgets.isShown()));
executeOnLauncher(launcher -> assertEquals("Widgets is scrolled upon opening",
0, getWidgetsScroll(launcher)));
// Test flinging widgets.
widgets.flingForward();
Integer flingForwardY = getFromLauncher(launcher -> getWidgetsScroll(launcher));
executeOnLauncher(launcher -> assertTrue("Flinging forward didn't scroll widgets",
flingForwardY > 0));
executeOnLauncher(AbstractFloatingView::closeAllOpenViews);
uiDevice.waitForIdle();
widgets.flingBackward();
executeOnLauncher(launcher -> assertTrue("Flinging backward didn't scroll widgets",
getWidgetsScroll(launcher) < flingForwardY));
mLauncher.goHome();
waitForLauncherCondition("Widgets were not closed",
launcher -> getWidgetsView(launcher) == null);
}
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.ui.workspace;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.AbstractFloatingView.TYPE_ACTION_POPUP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
@@ -29,11 +27,9 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.view.View;
import android.view.ViewGroup;
import androidx.test.filters.LargeTest;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
@@ -49,9 +45,6 @@ import com.android.launcher3.util.TestUtil;
import org.junit.Test;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* Tests for theme icon support in Launcher
*
@@ -137,27 +130,10 @@ public class ThemeIconsTest extends BaseLauncherActivityTest<Launcher> {
} catch (Exception e) {
throw new RuntimeException(e);
}
// Find the app icon
Queue<View> viewQueue = new ArrayDeque<>();
viewQueue.add(parent);
BubbleTextView icon = null;
while (!viewQueue.isEmpty()) {
View view = viewQueue.poll();
if (view instanceof ViewGroup) {
parent = (ViewGroup) view;
for (int i = parent.getChildCount() - 1; i >= 0; i--) {
viewQueue.add(parent.getChildAt(i));
}
} else if (view instanceof BubbleTextView btv) {
if (btv.getContentDescription() != null
&& title.equals(btv.getContentDescription().toString())) {
icon = btv;
break;
}
}
}
return icon;
return (BubbleTextView) searchView(parent, v ->
v instanceof BubbleTextView btv
&& btv.getContentDescription() != null
&& title.equals(btv.getContentDescription().toString()));
}
private BubbleTextView verifyIconTheme(String title, ViewGroup parent, boolean isThemed) {
@@ -193,11 +169,4 @@ public class ThemeIconsTest extends BaseLauncherActivityTest<Launcher> {
rv.getLayoutManager().scrollToPosition(pos);
});
}
private void addToWorkspace(View btv) {
TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () ->
btv.getAccessibilityDelegate().performAccessibilityAction(
btv, com.android.launcher3.R.id.action_add_to_workspace, null));
UiDevice.getInstance(getInstrumentation()).waitForIdle();
}
}
@@ -22,6 +22,9 @@ import android.view.InputDevice
import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.lifecycle.Lifecycle.State.RESUMED
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ActivityScenario.ActivityAction
@@ -30,11 +33,13 @@ import androidx.test.uiautomator.UiDevice
import com.android.launcher3.Launcher
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherState
import com.android.launcher3.R
import com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST
import com.android.launcher3.tapl.TestHelpers
import com.android.launcher3.util.ModelTestExtensions.loadModelSync
import com.android.launcher3.util.Wait.atMost
import java.util.function.Function
import java.util.function.Predicate
import java.util.function.Supplier
import org.junit.After
@@ -56,6 +61,8 @@ open class BaseLauncherActivityTest<LAUNCHER_TYPE : Launcher> {
)
.also { currentScenario = it }
@JvmField val uiDevice = UiDevice.getInstance(getInstrumentation())
@After
fun closeCurrentActivity() {
currentScenario?.close()
@@ -136,18 +143,43 @@ open class BaseLauncherActivityTest<LAUNCHER_TYPE : Launcher> {
event.recycle()
}
fun startAppFast(packageName: String) {
val intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!!
@JvmOverloads
fun startAppFast(
packageName: String,
intent: Intent = targetContext().packageManager.getLaunchIntentForPackage(packageName)!!,
) {
intent.addCategory(Intent.CATEGORY_LAUNCHER)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
targetContext().startActivity(intent)
UiDevice.getInstance(getInstrumentation()).waitForIdle()
uiDevice.waitForIdle()
}
fun freezeAllApps() = executeOnLauncher {
it.appsView.appsStore.enableDeferUpdates(DEFER_UPDATES_TEST)
}
fun executeShellCommand(cmd: String) =
UiDevice.getInstance(getInstrumentation()).executeShellCommand(cmd)
fun executeShellCommand(cmd: String) = uiDevice.executeShellCommand(cmd)
fun addToWorkspace(view: View) {
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {
view.accessibilityDelegate.performAccessibilityAction(
view,
R.id.action_add_to_workspace,
null,
)
}
UiDevice.getInstance(getInstrumentation()).waitForIdle()
}
fun ViewGroup.searchView(filter: Predicate<View>): View? {
if (filter.test(this)) return this
for (child in children) {
if (filter.test(child)) return child
if (child is ViewGroup)
child.searchView(filter)?.let {
return it
}
}
return null
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2024 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.util
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Parcelable
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit.SECONDS
private const val DEFAULT_BROADCAST_TIMEOUT_SECS: Long = 10
/** Broadcast receiver which blocks until the result is received. */
open class BlockingBroadcastReceiver(action: String) : BroadcastReceiver() {
val value = CompletableFuture<Intent>()
init {
getInstrumentation()
.targetContext
.registerReceiver(this, IntentFilter(action), Context.RECEIVER_EXPORTED)
}
override fun onReceive(context: Context, intent: Intent) {
value.complete(intent)
}
@Throws(InterruptedException::class)
fun blockingGetIntent(): Intent =
value.get(DEFAULT_BROADCAST_TIMEOUT_SECS, SECONDS).also {
getInstrumentation().targetContext.unregisterReceiver(this)
}
@Throws(InterruptedException::class)
fun blockingGetExtraIntent(): Intent? =
blockingGetIntent().getParcelableExtra<Parcelable>(Intent.EXTRA_INTENT) as Intent?
}