1package com.android.launcher3; 2 3import android.annotation.TargetApi; 4import android.appwidget.AppWidgetHost; 5import android.content.ComponentName; 6import android.content.ContentResolver; 7import android.content.ContentValues; 8import android.content.pm.PackageInstaller; 9import android.content.pm.PackageInstaller.SessionParams; 10import android.content.pm.PackageManager; 11import android.database.Cursor; 12import android.os.Build; 13import android.os.Bundle; 14import android.support.test.uiautomator.UiSelector; 15import android.test.suitebuilder.annotation.LargeTest; 16 17import com.android.launcher3.compat.AppWidgetManagerCompat; 18import com.android.launcher3.compat.PackageInstallerCompat; 19import com.android.launcher3.ui.LauncherInstrumentationTestCase; 20import com.android.launcher3.util.ManagedProfileHeuristic; 21import com.android.launcher3.widget.PendingAddWidgetInfo; 22import com.android.launcher3.widget.WidgetHostViewLoader; 23 24import java.util.Set; 25import java.util.concurrent.Callable; 26import java.util.concurrent.CountDownLatch; 27import java.util.concurrent.TimeUnit; 28 29/** 30 * Tests for bind widget flow. 31 * 32 * Note running these tests will clear the workspace on the device. 33 */ 34@LargeTest 35@TargetApi(Build.VERSION_CODES.LOLLIPOP) 36public class BindWidgetTest extends LauncherInstrumentationTestCase { 37 38 private ContentResolver mResolver; 39 private AppWidgetManagerCompat mWidgetManager; 40 41 // Objects created during test, which should be cleaned up in the end. 42 private Cursor mCursor; 43 // App install session id. 44 private int mSessionId = -1; 45 46 @Override 47 protected void setUp() throws Exception { 48 super.setUp(); 49 50 mResolver = mTargetContext.getContentResolver(); 51 mWidgetManager = AppWidgetManagerCompat.getInstance(mTargetContext); 52 grantWidgetPermission(); 53 54 // Clear all existing data 55 LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 56 LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 57 } 58 59 @Override 60 protected void tearDown() throws Exception { 61 super.tearDown(); 62 if (mCursor != null) { 63 mCursor.close(); 64 } 65 66 if (mSessionId > -1) { 67 mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); 68 } 69 } 70 71 public void testBindNormalWidget_withConfig() { 72 LauncherAppWidgetProviderInfo info = findWidgetProvider(true); 73 LauncherAppWidgetInfo item = createWidgetInfo(info, true); 74 75 setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label); 76 } 77 78 public void testBindNormalWidget_withoutConfig() { 79 LauncherAppWidgetProviderInfo info = findWidgetProvider(false); 80 LauncherAppWidgetInfo item = createWidgetInfo(info, true); 81 82 setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label); 83 } 84 85 public void testUnboundWidget_removed() throws Exception { 86 LauncherAppWidgetProviderInfo info = findWidgetProvider(false); 87 LauncherAppWidgetInfo item = createWidgetInfo(info, false); 88 item.appWidgetId = -33; 89 90 // Since there is no widget to verify, just wait until the workspace is ready. 91 setupAndVerifyContents(item, Workspace.class, null); 92 93 waitUntilLoaderIdle(); 94 // Item deleted from db 95 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 96 null, null, null, null, null); 97 assertEquals(0, mCursor.getCount()); 98 99 // The view does not exist 100 assertFalse(mDevice.findObject(new UiSelector().description(info.label)).exists()); 101 } 102 103 public void testPendingWidget_autoRestored() { 104 // A non-restored widget with no config screen gets restored automatically. 105 LauncherAppWidgetProviderInfo info = findWidgetProvider(false); 106 107 // Do not bind the widget 108 LauncherAppWidgetInfo item = createWidgetInfo(info, false); 109 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; 110 111 setupAndVerifyContents(item, LauncherAppWidgetHostView.class, info.label); 112 } 113 114 public void testPendingWidget_withConfigScreen() throws Exception { 115 // A non-restored widget with config screen get bound and shows a 'Click to setup' UI. 116 LauncherAppWidgetProviderInfo info = findWidgetProvider(true); 117 118 // Do not bind the widget 119 LauncherAppWidgetInfo item = createWidgetInfo(info, false); 120 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; 121 122 setupAndVerifyContents(item, PendingAppWidgetHostView.class, null); 123 waitUntilLoaderIdle(); 124 // Item deleted from db 125 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 126 null, null, null, null, null); 127 mCursor.moveToNext(); 128 129 // Widget has a valid Id now. 130 assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) 131 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 132 assertNotNull(mWidgetManager.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex( 133 LauncherSettings.Favorites.APPWIDGET_ID)))); 134 } 135 136 public void testPendingWidget_notRestored_removed() throws Exception { 137 LauncherAppWidgetInfo item = getInvalidWidgetInfo(); 138 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID 139 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; 140 141 setupAndVerifyContents(item, Workspace.class, null); 142 // The view does not exist 143 assertFalse(mDevice.findObject( 144 new UiSelector().className(PendingAppWidgetHostView.class)).exists()); 145 waitUntilLoaderIdle(); 146 // Item deleted from db 147 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 148 null, null, null, null, null); 149 assertEquals(0, mCursor.getCount()); 150 } 151 152 public void testPendingWidget_notRestored_brokenInstall() throws Exception { 153 // A widget which is was being installed once, even if its not being 154 // installed at the moment is not removed. 155 LauncherAppWidgetInfo item = getInvalidWidgetInfo(); 156 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID 157 | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED 158 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; 159 160 setupAndVerifyContents(item, PendingAppWidgetHostView.class, null); 161 // Verify item still exists in db 162 waitUntilLoaderIdle(); 163 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 164 null, null, null, null, null); 165 assertEquals(1, mCursor.getCount()); 166 167 // Widget still has an invalid id. 168 mCursor.moveToNext(); 169 assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID, 170 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) 171 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 172 } 173 174 public void testPendingWidget_notRestored_activeInstall() throws Exception { 175 // A widget which is being installed is not removed 176 LauncherAppWidgetInfo item = getInvalidWidgetInfo(); 177 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID 178 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; 179 180 // Create an active installer session 181 SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); 182 params.setAppPackageName(item.providerName.getPackageName()); 183 PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); 184 mSessionId = installer.createSession(params); 185 186 setupAndVerifyContents(item, PendingAppWidgetHostView.class, null); 187 // Verify item still exists in db 188 waitUntilLoaderIdle(); 189 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 190 null, null, null, null, null); 191 assertEquals(1, mCursor.getCount()); 192 193 // Widget still has an invalid id. 194 mCursor.moveToNext(); 195 assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID, 196 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) 197 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 198 } 199 200 /** 201 * Adds {@param item} on the homescreen on the 0th screen at 0,0, and verifies that the 202 * widget class is displayed on the homescreen. 203 * @param widgetClass the View class which is displayed on the homescreen 204 * @param desc the content description of the view or null. 205 */ 206 private void setupAndVerifyContents( 207 LauncherAppWidgetInfo item, Class<?> widgetClass, String desc) { 208 long screenId = Workspace.FIRST_SCREEN_ID; 209 // Update the screen id counter for the provider. 210 LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_NEW_SCREEN_ID); 211 212 if (screenId > Workspace.FIRST_SCREEN_ID) { 213 screenId = Workspace.FIRST_SCREEN_ID; 214 } 215 ContentValues v = new ContentValues(); 216 v.put(LauncherSettings.WorkspaceScreens._ID, screenId); 217 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0); 218 mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v); 219 220 // Insert the item 221 v = new ContentValues(); 222 item.id = LauncherSettings.Settings.call( 223 mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID) 224 .getLong(LauncherSettings.Settings.EXTRA_VALUE); 225 item.screenId = screenId; 226 item.onAddToDatabase(mTargetContext, v); 227 v.put(LauncherSettings.Favorites._ID, item.id); 228 mResolver.insert(LauncherSettings.Favorites.CONTENT_URI, v); 229 230 // Reset loader 231 try { 232 runTestOnUiThread(new Runnable() { 233 @Override 234 public void run() { 235 LauncherClings.markFirstRunClingDismissed(mTargetContext); 236 ManagedProfileHeuristic.markExistingUsersForNoFolderCreation(mTargetContext); 237 LauncherAppState.getInstance().getModel().resetLoadedState(true, true); 238 } 239 }); 240 } catch (Throwable t) { 241 throw new IllegalArgumentException(t); 242 } 243 // Launch the home activity 244 startLauncher(); 245 // Verify UI 246 UiSelector selector = new UiSelector().packageName(mTargetContext.getPackageName()) 247 .className(widgetClass); 248 if (desc != null) { 249 selector = selector.description(desc); 250 } 251 assertTrue(mDevice.findObject(selector).waitForExists(DEFAULT_UI_TIMEOUT)); 252 } 253 254 /** 255 * Creates a LauncherAppWidgetInfo corresponding to {@param info} 256 * @param bindWidget if true the info is bound and a valid widgetId is assigned to 257 * the LauncherAppWidgetInfo 258 */ 259 private LauncherAppWidgetInfo createWidgetInfo( 260 LauncherAppWidgetProviderInfo info, boolean bindWidget) { 261 LauncherAppWidgetInfo item = new LauncherAppWidgetInfo( 262 LauncherAppWidgetInfo.NO_ID, info.provider); 263 item.spanX = info.minSpanX; 264 item.spanY = info.minSpanY; 265 item.minSpanX = info.minSpanX; 266 item.minSpanY = info.minSpanY; 267 item.user = mWidgetManager.getUser(info); 268 item.cellX = 0; 269 item.cellY = 1; 270 item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; 271 272 if (bindWidget) { 273 PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(mTargetContext, info); 274 pendingInfo.spanX = item.spanX; 275 pendingInfo.spanY = item.spanY; 276 pendingInfo.minSpanX = item.minSpanX; 277 pendingInfo.minSpanY = item.minSpanY; 278 Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(mTargetContext, pendingInfo); 279 280 AppWidgetHost host = new AppWidgetHost(mTargetContext, Launcher.APPWIDGET_HOST_ID); 281 int widgetId = host.allocateAppWidgetId(); 282 if (!mWidgetManager.bindAppWidgetIdIfAllowed(widgetId, info, options)) { 283 host.deleteAppWidgetId(widgetId); 284 throw new IllegalArgumentException("Unable to bind widget id"); 285 } 286 item.appWidgetId = widgetId; 287 } 288 return item; 289 } 290 291 /** 292 * Returns a LauncherAppWidgetInfo with package name which is not present on the device 293 */ 294 private LauncherAppWidgetInfo getInvalidWidgetInfo() { 295 String invalidPackage = "com.invalidpackage"; 296 int count = 0; 297 String pkg = invalidPackage; 298 299 Set<String> activePackage = getOnUiThread(new Callable<Set<String>>() { 300 @Override 301 public Set<String> call() throws Exception { 302 return PackageInstallerCompat.getInstance(mTargetContext) 303 .updateAndGetActiveSessionCache().keySet(); 304 } 305 }); 306 while(true) { 307 try { 308 mTargetContext.getPackageManager().getPackageInfo( 309 pkg, PackageManager.GET_UNINSTALLED_PACKAGES); 310 } catch (Exception e) { 311 if (!activePackage.contains(pkg)) { 312 break; 313 } 314 } 315 pkg = invalidPackage + count; 316 count ++; 317 } 318 LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10, 319 new ComponentName(pkg, "com.test.widgetprovider")); 320 item.spanX = 2; 321 item.spanY = 2; 322 item.minSpanX = 2; 323 item.minSpanY = 2; 324 item.cellX = 0; 325 item.cellY = 1; 326 item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; 327 return item; 328 } 329 330 /** 331 * Blocks the current thread until all the jobs in the main worker thread are complete. 332 */ 333 private void waitUntilLoaderIdle() throws InterruptedException { 334 final CountDownLatch latch = new CountDownLatch(1); 335 LauncherModel.sWorker.post(new Runnable() { 336 @Override 337 public void run() { 338 latch.countDown(); 339 } 340 }); 341 assertTrue(latch.await(5, TimeUnit.SECONDS)); 342 } 343} 344