mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-28 07:46:55 +00:00
> Moving definition to bp files > Adding mokito-inline to allow mocking final classes > Moving src_common to src to avoid unnecessary code split Bug: 196825541 Test: Presubmit Change-Id: Ib6879cda6aee74f5b9050a16eb6ca94ed3b7e28c
287 lines
11 KiB
Java
287 lines
11 KiB
Java
/*
|
|
* 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 androidx.test.InstrumentationRegistry.getTargetContext;
|
|
|
|
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.util.WidgetUtils.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.filters.Suppress;
|
|
import androidx.test.runner.AndroidJUnit4;
|
|
import androidx.test.uiautomator.By;
|
|
import androidx.test.uiautomator.UiDevice;
|
|
import androidx.test.uiautomator.Until;
|
|
|
|
import com.android.launcher3.LauncherSettings;
|
|
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
|
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.launcher3.widget.LauncherAppWidgetProviderInfo;
|
|
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 stub 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();
|
|
|
|
// Workaround for b/142351228, when there are no activities, the system may not destroy the
|
|
// activity correctly for activities under instrumentation, which can leave two concurrent
|
|
// activities, which changes the order in which the activities are cleaned up (overlapping
|
|
// stop and start) leading to all sort of issues. To workaround this, ensure that the test
|
|
// is started only after starting another app.
|
|
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
|
|
|
|
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)
|
|
@Suppress // until b/190618549 is fixed
|
|
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)
|
|
@Suppress // until b/190729479 is fixed
|
|
public void testSwipeUpFromApp_widget_update() {
|
|
String stubText = "Some random stub text";
|
|
|
|
executeSwipeUpTestWithWidget(
|
|
widgetId -> { },
|
|
widgetId -> AppWidgetManager.getInstance(getContext())
|
|
.updateAppWidget(widgetId, createMainWidgetViews(stubText)),
|
|
stubText);
|
|
}
|
|
|
|
@Test
|
|
@NavigationModeSwitch(mode = ZERO_BUTTON)
|
|
@Suppress // until b/190729479 is fixed
|
|
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);
|
|
// Make sure the widget is big enough to show a list of items
|
|
info.minSpanX = 2;
|
|
info.minSpanY = 2;
|
|
info.spanX = 2;
|
|
info.spanY = 2;
|
|
LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), 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();
|
|
}
|
|
}
|
|
}
|