Adding test to check view inflation during swipe up

Bug: 137851409
Change-Id: Ic8e6f0b3c667051b921d1d4fad03c94122ee92e9
This commit is contained in:
Sunny Goyal 2019-09-18 22:29:40 -07:00
parent 8fbbbe0687
commit 8f90d5da8a
8 changed files with 578 additions and 146 deletions

View File

@ -0,0 +1,268 @@
/*
* Copyright (C) 2019 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 androidx.test.InstrumentationRegistry.getContext;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
import static com.android.launcher3.ui.widget.BindWidgetTest.createWidgetInfo;
import static com.android.quickstep.NavigationModeSwitchRule.Mode.ZERO_BUTTON;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.RemoteViews;
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;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.tapl.Background;
import com.android.launcher3.testcomponent.ListViewService;
import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.ui.TaplTestsLauncher3;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.lang.reflect.Field;
import java.util.function.IntConsumer;
/**
* Test to verify view inflation does not happen during swipe up.
* To verify view inflation, we setup a dummy ViewConfiguration and check if any call to that class
* does from a View.init method or not.
*
* Alternative approaches considered:
* Overriding LayoutInflater: This does not cover views initialized
* directly (ex: new LinearLayout)
* Using ExtendedMockito: Mocking static methods from platform classes (loaded in zygote) makes
* the main thread extremely slow and untestable
*/
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
private ContentResolver mResolver;
private SparseArray<ViewConfiguration> mConfigMap;
private InitTracker mInitTracker;
@Before
public void setUp() throws Exception {
super.setUp();
TaplTestsLauncher3.initialize(this);
mResolver = mTargetContext.getContentResolver();
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
// Get static configuration map
Field field = ViewConfiguration.class.getDeclaredField("sConfigurations");
field.setAccessible(true);
mConfigMap = (SparseArray<ViewConfiguration>) field.get(null);
mInitTracker = new InitTracker();
}
@Test
@NavigationModeSwitch(mode = ZERO_BUTTON)
public void testSwipeUpFromApp() throws Exception {
try {
// Go to overview once so that all views are initialized and cached
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
mLauncher.getBackground().switchToOverview().dismissAllTasks();
// Track view creations
mInitTracker.startTracking();
startTestActivity(2);
mLauncher.getBackground().switchToOverview();
assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
} finally {
mConfigMap.clear();
}
}
@Test
@NavigationModeSwitch(mode = ZERO_BUTTON)
public void testSwipeUpFromApp_widget_update() {
String dummyText = "Some random dummy text";
executeSwipeUpTestWithWidget(
widgetId -> { },
widgetId -> AppWidgetManager.getInstance(getContext())
.updateAppWidget(widgetId, createMainWidgetViews(dummyText)),
dummyText);
}
@Test
@NavigationModeSwitch(mode = ZERO_BUTTON)
public void testSwipeUp_with_list_widgets() {
SimpleViewsFactory viewFactory = new SimpleViewsFactory();
viewFactory.viewCount = 1;
Bundle args = new Bundle();
args.putBinder(EXTRA_VALUE, viewFactory.toBinder());
TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, args);
try {
executeSwipeUpTestWithWidget(
widgetId -> {
// Initialize widget
RemoteViews views = createMainWidgetViews("List widget title");
views.setRemoteAdapter(android.R.id.list,
new Intent(getContext(), ListViewService.class));
AppWidgetManager.getInstance(getContext()).updateAppWidget(widgetId, views);
verifyWidget(viewFactory.getLabel(0));
},
widgetId -> {
// Update widget
viewFactory.viewCount = 2;
AppWidgetManager.getInstance(getContext())
.notifyAppWidgetViewDataChanged(widgetId, android.R.id.list);
},
viewFactory.getLabel(1)
);
} finally {
TestCommandReceiver.callCommand(SET_LIST_VIEW_SERVICE_BINDER, null, new Bundle());
}
}
private void executeSwipeUpTestWithWidget(IntConsumer widgetIdCreationCallback,
IntConsumer updateBeforeSwipeUp, String finalWidgetText) {
try {
// Clear all existing data
LauncherSettings.Settings.call(mResolver,
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
LauncherSettings.Settings.call(mResolver,
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
LauncherAppWidgetInfo item = createWidgetInfo(info, true);
addItemToScreen(item);
assertTrue("Widget is not present",
mLauncher.pressHome().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
int widgetId = item.appWidgetId;
// Verify widget id
widgetIdCreationCallback.accept(widgetId);
// Go to overview once so that all views are initialized and cached
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
mLauncher.getBackground().switchToOverview().dismissAllTasks();
// Track view creations
mInitTracker.startTracking();
startTestActivity(2);
Background background = mLauncher.getBackground();
// Update widget
updateBeforeSwipeUp.accept(widgetId);
background.switchToOverview();
assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
// Widget is updated when going home
mInitTracker.disableLog();
mLauncher.pressHome();
verifyWidget(finalWidgetText);
assertNotEquals(1, mInitTracker.viewInitCount);
} finally {
mConfigMap.clear();
}
}
private void verifyWidget(String text) {
assertNotNull("Widget not updated",
UiDevice.getInstance(getInstrumentation())
.wait(Until.findObject(By.text(text)), DEFAULT_UI_TIMEOUT));
}
private RemoteViews createMainWidgetViews(String title) {
Context c = getContext();
int layoutId = c.getResources().getIdentifier(
"test_layout_widget_list", "layout", c.getPackageName());
RemoteViews views = new RemoteViews(c.getPackageName(), layoutId);
views.setTextViewText(android.R.id.text1, title);
return views;
}
private class InitTracker implements Answer {
public int viewInitCount = 0;
public boolean log = true;
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Exception ex = new Exception();
boolean found = false;
for (StackTraceElement ste : ex.getStackTrace()) {
if ("<init>".equals(ste.getMethodName())
&& View.class.getName().equals(ste.getClassName())) {
found = true;
break;
}
}
if (found) {
viewInitCount++;
if (log) {
Log.d("InitTracker", "New view inflated", ex);
}
}
return invocation.callRealMethod();
}
public void disableLog() {
log = false;
}
public void startTracking() {
ViewConfiguration vc = ViewConfiguration.get(mTargetContext);
ViewConfiguration spyVC = spy(vc);
mConfigMap.put(mConfigMap.keyAt(mConfigMap.indexOfValue(vc)), spyVC);
doAnswer(this).when(spyVC).getScaledTouchSlop();
}
}
}

View File

@ -73,8 +73,12 @@
</intent-filter>
</activity>
<service
android:name="com.android.launcher3.testcomponent.ListViewService"
android:permission="android.permission.BIND_REMOTEVIEWS" />
<provider
android:name="com.android.launcher3.testcomponent.TestCommandReceiver"
android:name="com.android.launcher3.testcomponent.TestCommandProvider"
android:authorities="${packageName}.commands"
android:exported="true"/>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0000FF"
android:id="@android:id/text1"
android:padding="10dp" />
<ListView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@android:id/list" />
</LinearLayout>

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2019 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 android.content.Intent;
import android.os.IBinder;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
public class ListViewService extends RemoteViewsService {
public static IBinder sBinderForTest;
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new SimpleViewsFactory();
}
@Override
public IBinder onBind(Intent intent) {
return sBinderForTest != null ? sBinderForTest : super.onBind(intent);
}
public static class SimpleViewsFactory implements RemoteViewsFactory {
public int viewCount = 0;
@Override
public void onCreate() { }
@Override
public void onDataSetChanged() { }
@Override
public void onDestroy() { }
@Override
public int getCount() {
return viewCount;
}
@Override
public RemoteViews getViewAt(int i) {
RemoteViews views = new RemoteViews("android", android.R.layout.simple_list_item_1);
views.setTextViewText(android.R.id.text1, getLabel(i));
return views;
}
public String getLabel(int i) {
return "Item " + i;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public boolean hasStableIds() {
return false;
}
public IBinder toBinder() {
return new RemoteViewsService() {
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return SimpleViewsFactory.this;
}
}.onBind(new Intent("dummy_intent"));
}
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (C) 2019 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 static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static com.android.launcher3.testcomponent.TestCommandReceiver.DISABLE_TEST_LAUNCHER;
import static com.android.launcher3.testcomponent.TestCommandReceiver.ENABLE_TEST_LAUNCHER;
import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
import static com.android.launcher3.testcomponent.TestCommandReceiver.GET_SYSTEM_HEALTH_MESSAGE;
import static com.android.launcher3.testcomponent.TestCommandReceiver.KILL_PROCESS;
import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
import android.app.Activity;
import android.app.ActivityManager;
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 android.os.ParcelFileDescriptor;
import android.util.Base64;
import com.android.launcher3.tapl.TestHelpers;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
public class TestCommandProvider extends ContentProvider {
@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;
}
case KILL_PROCESS: {
((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE))
.killBackgroundProcesses(arg);
return null;
}
case GET_SYSTEM_HEALTH_MESSAGE: {
final Bundle response = new Bundle();
response.putString("result", TestHelpers.getSystemHealthMessage(getContext()));
return response;
}
case SET_LIST_VIEW_SERVICE_BINDER: {
ListViewService.sBinderForTest = extras.getBinder(EXTRA_VALUE);
return null;
}
}
return super.call(method, arg, extras);
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String path = Base64.encodeToString(uri.getPath().getBytes(),
Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
File file = new File(getContext().getCacheDir(), path);
if (!file.exists()) {
// Create an empty file so that we can pass its descriptor
try {
file.createNewFile();
} catch (IOException e) {
}
}
return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
}
}

View File

@ -15,125 +15,36 @@
*/
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 static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import android.app.Activity;
import android.app.ActivityManager;
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 android.os.ParcelFileDescriptor;
import android.util.Base64;
import androidx.test.InstrumentationRegistry;
import com.android.launcher3.tapl.TestHelpers;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* Content provider to receive commands from tests
*/
public class TestCommandReceiver extends ContentProvider {
public class TestCommandReceiver {
public static final String ENABLE_TEST_LAUNCHER = "enable-test-launcher";
public static final String DISABLE_TEST_LAUNCHER = "disable-test-launcher";
public static final String KILL_PROCESS = "kill-process";
public static final String GET_SYSTEM_HEALTH_MESSAGE = "get-system-health-message";
public static final String SET_LIST_VIEW_SERVICE_BINDER = "set-list-view-service-binder";
@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;
}
case KILL_PROCESS: {
((ActivityManager) getContext().getSystemService(Activity.ACTIVITY_SERVICE)).
killBackgroundProcesses(arg);
return null;
}
case GET_SYSTEM_HEALTH_MESSAGE: {
final Bundle response = new Bundle();
response.putString("result", TestHelpers.getSystemHealthMessage(getContext()));
return response;
}
}
return super.call(method, arg, extras);
}
public static final String EXTRA_VALUE = "value";
public static Bundle callCommand(String command) {
return callCommand(command, null);
}
public static Bundle callCommand(String command, String arg) {
return callCommand(command, arg, null);
}
public static Bundle callCommand(String command, String arg, Bundle extras) {
Instrumentation inst = InstrumentationRegistry.getInstrumentation();
Uri uri = Uri.parse("content://" + inst.getContext().getPackageName() + ".commands");
return inst.getTargetContext().getContentResolver().call(uri, command, arg, null);
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
String path = Base64.encodeToString(uri.getPath().getBytes(),
Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP);
File file = new File(getContext().getCacheDir(), path);
if (!file.exists()) {
// Create an empty file so that we can pass its descriptor
try {
file.createNewFile();
} catch (IOException e) {
}
}
return ParcelFileDescriptor.open(file, MODE_READ_WRITE);
return inst.getTargetContext().getContentResolver().call(uri, command, arg, extras);
}
}

View File

@ -17,6 +17,7 @@ package com.android.launcher3.ui;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@ -27,6 +28,7 @@ import static java.lang.System.exit;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@ -43,6 +45,7 @@ import androidx.test.uiautomator.BySelector;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
@ -56,6 +59,7 @@ import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.testcomponent.TestCommandReceiver;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Wait;
@ -230,6 +234,35 @@ public abstract class AbstractLauncherUiTest {
});
}
/**
* Adds {@param item} on the homescreen on the 0th screen
*/
protected void addItemToScreen(ItemInfo item) {
ContentResolver resolver = mTargetContext.getContentResolver();
int screenId = FIRST_SCREEN_ID;
// Update the screen id counter for the provider.
LauncherSettings.Settings.call(resolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
if (screenId > FIRST_SCREEN_ID) {
screenId = FIRST_SCREEN_ID;
}
// Insert the item
ContentWriter writer = new ContentWriter(mTargetContext);
item.id = LauncherSettings.Settings.call(
resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
item.screenId = screenId;
item.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites._ID, item.id);
resolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
resetLoaderState();
// Launch the home activity
mDevice.pressHome();
waitForModelLoaded();
}
/**
* Runs the callback on the UI thread and returns the result.
*/

View File

@ -15,7 +15,9 @@
*/
package com.android.launcher3.ui.widget;
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -25,6 +27,7 @@ import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
@ -43,11 +46,8 @@ import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.tapl.Workspace;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.WidgetHostViewLoader;
import org.junit.After;
import org.junit.Before;
@ -57,7 +57,6 @@ import org.junit.runner.RunWith;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
/**
* Tests for bind widget flow.
@ -72,7 +71,6 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
private ContentResolver mResolver;
private AppWidgetManagerCompat mWidgetManager;
// Objects created during test, which should be cleaned up in the end.
private Cursor mCursor;
@ -85,7 +83,6 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
super.setUp();
mResolver = mTargetContext.getContentResolver();
mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext);
// Clear all existing data
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
@ -108,7 +105,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true);
LauncherAppWidgetInfo item = createWidgetInfo(info, true);
setupContents(item);
addItemToScreen(item);
verifyWidgetPresent(info);
}
@ -117,7 +114,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false);
LauncherAppWidgetInfo item = createWidgetInfo(info, true);
setupContents(item);
addItemToScreen(item);
verifyWidgetPresent(info);
}
@ -127,7 +124,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.appWidgetId = -33;
setupContents(item);
addItemToScreen(item);
final Workspace workspace = mLauncher.getWorkspace();
// Item deleted from db
@ -148,7 +145,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
setupContents(item);
addItemToScreen(item);
verifyWidgetPresent(info);
}
@ -161,7 +158,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
LauncherAppWidgetInfo item = createWidgetInfo(info, false);
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
setupContents(item);
addItemToScreen(item);
verifyPendingWidgetPresent();
// Item deleted from db
@ -183,7 +180,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
setupContents(item);
addItemToScreen(item);
assertTrue("Pending widget exists",
mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
@ -202,7 +199,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
| LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
setupContents(item);
addItemToScreen(item);
verifyPendingWidgetPresent();
// Verify item still exists in db
@ -230,7 +227,7 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
mSessionId = installer.createSession(params);
setupContents(item);
addItemToScreen(item);
verifyPendingWidgetPresent();
// Verify item still exists in db
@ -245,35 +242,6 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
}
/**
* Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the
* widget class is displayed on the homescreen.
*/
private void setupContents(LauncherAppWidgetInfo item) {
int screenId = FIRST_SCREEN_ID;
// Update the screen id counter for the provider.
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
if (screenId > FIRST_SCREEN_ID) {
screenId = FIRST_SCREEN_ID;
}
// Insert the item
ContentWriter writer = new ContentWriter(mTargetContext);
item.id = LauncherSettings.Settings.call(
mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
item.screenId = screenId;
item.onAddToDatabase(writer);
writer.put(LauncherSettings.Favorites._ID, item.id);
mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, writer.getValues(mTargetContext));
resetLoaderState();
// Launch the home activity
mDevice.pressHome();
waitForModelLoaded();
}
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
assertTrue("Widget is not present",
mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
@ -289,8 +257,10 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
* @param bindWidget if true the info is bound and a valid widgetId is assigned to
* the LauncherAppWidgetInfo
*/
private LauncherAppWidgetInfo createWidgetInfo(
public static LauncherAppWidgetInfo createWidgetInfo(
LauncherAppWidgetProviderInfo info, boolean bindWidget) {
Context targetContext = getTargetContext();
LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(
LauncherAppWidgetInfo.NO_ID, info.provider);
item.spanX = info.minSpanX;
@ -308,11 +278,12 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
pendingInfo.spanY = item.spanY;
pendingInfo.minSpanX = item.minSpanX;
pendingInfo.minSpanY = item.minSpanY;
Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo);
Bundle options = getDefaultOptionsForWidget(targetContext, pendingInfo);
AppWidgetHost host = new LauncherAppWidgetHost(mTargetContext);
AppWidgetHost host = new LauncherAppWidgetHost(targetContext);
int widgetId = host.allocateAppWidgetId();
if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) {
if (!AppWidgetManagerCompat.getInstance(targetContext)
.bindAppWidgetIdIfAllowed(widgetId, info, options)) {
host.deleteAppWidgetId(widgetId);
throw new IllegalArgumentException("Unable to bind widget id");
}