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