1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.android.launcher3.ui.widget; 17 18 import android.app.Activity; 19 import android.app.Application; 20 import android.appwidget.AppWidgetManager; 21 import android.content.Intent; 22 import android.support.test.uiautomator.By; 23 import android.support.test.uiautomator.UiObject2; 24 import android.test.suitebuilder.annotation.LargeTest; 25 import android.view.View; 26 27 import com.android.launcher3.ItemInfo; 28 import com.android.launcher3.Launcher; 29 import com.android.launcher3.LauncherAppWidgetInfo; 30 import com.android.launcher3.LauncherAppWidgetProviderInfo; 31 import com.android.launcher3.MainThreadExecutor; 32 import com.android.launcher3.Workspace; 33 import com.android.launcher3.testcomponent.WidgetConfigActivity; 34 import com.android.launcher3.ui.LauncherInstrumentationTestCase; 35 import com.android.launcher3.util.Condition; 36 import com.android.launcher3.util.SimpleActivityMonitor; 37 import com.android.launcher3.util.Wait; 38 import com.android.launcher3.widget.WidgetCell; 39 40 import java.util.concurrent.Callable; 41 42 /** 43 * Test to verify widget configuration is properly shown. 44 */ 45 @LargeTest 46 public class AddConfigWidgetTest extends LauncherInstrumentationTestCase { 47 48 private LauncherAppWidgetProviderInfo mWidgetInfo; 49 private SimpleActivityMonitor mActivityMonitor; 50 private MainThreadExecutor mMainThreadExecutor; 51 private AppWidgetManager mAppWidgetManager; 52 53 private int mWidgetId; 54 55 @Override setUp()56 protected void setUp() throws Exception { 57 super.setUp(); 58 mWidgetInfo = findWidgetProvider(true /* hasConfigureScreen */); 59 mActivityMonitor = new SimpleActivityMonitor(); 60 ((Application) getInstrumentation().getTargetContext().getApplicationContext()) 61 .registerActivityLifecycleCallbacks(mActivityMonitor); 62 mMainThreadExecutor = new MainThreadExecutor(); 63 mAppWidgetManager = AppWidgetManager.getInstance(mTargetContext); 64 65 grantWidgetPermission(); 66 } 67 68 @Override tearDown()69 protected void tearDown() throws Exception { 70 ((Application) getInstrumentation().getTargetContext().getApplicationContext()) 71 .unregisterActivityLifecycleCallbacks(mActivityMonitor); 72 super.tearDown(); 73 } 74 testWidgetConfig()75 public void testWidgetConfig() throws Throwable { 76 runTest(false, true); 77 } 78 testWidgetConfig_rotate()79 public void testWidgetConfig_rotate() throws Throwable { 80 runTest(true, true); 81 } 82 testConfigCancelled()83 public void testConfigCancelled() throws Throwable { 84 runTest(false, false); 85 } 86 testConfigCancelled_rotate()87 public void testConfigCancelled_rotate() throws Throwable { 88 runTest(true, false); 89 } 90 91 /** 92 * @param rotateConfig should the config screen be rotated 93 * @param acceptConfig accept the config activity 94 */ runTest(boolean rotateConfig, boolean acceptConfig)95 private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable { 96 lockRotation(true); 97 98 clearHomescreen(); 99 startLauncher(); 100 101 // Open widget tray and wait for load complete. 102 final UiObject2 widgetContainer = openWidgetsTray(); 103 assertTrue(Wait.atMost(Condition.minChildCount(widgetContainer, 2), DEFAULT_UI_TIMEOUT)); 104 105 // Drag widget to homescreen 106 WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor(); 107 UiObject2 widget = scrollAndFind(widgetContainer, By.clazz(WidgetCell.class) 108 .hasDescendant(By.text(mWidgetInfo.getLabel(mTargetContext.getPackageManager())))); 109 dragToWorkspace(widget, false); 110 // Widget id for which the config activity was opened 111 mWidgetId = monitor.getWidgetId(); 112 113 if (rotateConfig) { 114 // Rotate the screen and verify that the config activity is recreated 115 monitor = new WidgetConfigStartupMonitor(); 116 lockRotation(false); 117 assertEquals(mWidgetId, monitor.getWidgetId()); 118 } 119 120 // Verify that the widget id is valid and bound 121 assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); 122 123 setResult(acceptConfig); 124 if (acceptConfig) { 125 assertTrue(Wait.atMost(new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT)); 126 assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); 127 } else { 128 // Verify that the widget id is deleted. 129 assertTrue(Wait.atMost(new Condition() { 130 @Override 131 public boolean isTrue() throws Throwable { 132 return mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null; 133 } 134 }, DEFAULT_ACTIVITY_TIMEOUT)); 135 } 136 } 137 setResult(boolean success)138 private void setResult(boolean success) { 139 140 getInstrumentation().getTargetContext().sendBroadcast( 141 WidgetConfigActivity.getCommandIntent(WidgetConfigActivity.class, 142 success ? "clickOK" : "clickCancel")); 143 } 144 145 /** 146 * Condition for searching widget id 147 */ 148 private class WidgetSearchCondition extends Condition 149 implements Callable<Boolean>, Workspace.ItemOperator { 150 151 @Override isTrue()152 public boolean isTrue() throws Throwable { 153 return mMainThreadExecutor.submit(this).get(); 154 } 155 156 @Override evaluate(ItemInfo info, View view)157 public boolean evaluate(ItemInfo info, View view) { 158 return info instanceof LauncherAppWidgetInfo && 159 ((LauncherAppWidgetInfo) info).providerName.equals(mWidgetInfo.provider) && 160 ((LauncherAppWidgetInfo) info).appWidgetId == mWidgetId; 161 } 162 163 @Override call()164 public Boolean call() throws Exception { 165 // Find the resumed launcher 166 Launcher launcher = null; 167 for (Activity a : mActivityMonitor.resumed) { 168 if (a instanceof Launcher) { 169 launcher = (Launcher) a; 170 } 171 } 172 if (launcher == null) { 173 return false; 174 } 175 return launcher.getWorkspace().getFirstMatch(this) != null; 176 } 177 } 178 179 /** 180 * Broadcast receiver for receiving widget config activity status. 181 */ 182 private class WidgetConfigStartupMonitor extends BlockingBroadcastReceiver { 183 WidgetConfigStartupMonitor()184 public WidgetConfigStartupMonitor() { 185 super(WidgetConfigActivity.class.getName()); 186 } 187 getWidgetId()188 public int getWidgetId() throws InterruptedException { 189 Intent intent = blockingGetExtraIntent(); 190 assertNotNull(intent); 191 assertEquals(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE, intent.getAction()); 192 int widgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 193 LauncherAppWidgetInfo.NO_ID); 194 assertNotSame(widgetId, LauncherAppWidgetInfo.NO_ID); 195 return widgetId; 196 } 197 } 198 } 199