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