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