Adding test to check view inflation during swipe up
Bug: 137851409 Change-Id: Ic8e6f0b3c667051b921d1d4fad03c94122ee92e9
This commit is contained in:
parent
8fbbbe0687
commit
8f90d5da8a
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"/>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue