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 androidx.test.InstrumentationRegistry.getTargetContext; 19 20 import static com.android.launcher3.util.WidgetUtils.createWidgetInfo; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertTrue; 26 27 import android.appwidget.AppWidgetManager; 28 import android.content.ComponentName; 29 import android.content.ContentResolver; 30 import android.content.pm.PackageInstaller; 31 import android.content.pm.PackageInstaller.SessionParams; 32 import android.content.pm.PackageManager; 33 import android.database.Cursor; 34 import android.os.Bundle; 35 import android.widget.RemoteViews; 36 37 import androidx.test.filters.LargeTest; 38 import androidx.test.runner.AndroidJUnit4; 39 40 import com.android.launcher3.LauncherSettings; 41 import com.android.launcher3.R; 42 import com.android.launcher3.model.data.LauncherAppWidgetInfo; 43 import com.android.launcher3.pm.InstallSessionHelper; 44 import com.android.launcher3.tapl.Widget; 45 import com.android.launcher3.tapl.Workspace; 46 import com.android.launcher3.ui.AbstractLauncherUiTest; 47 import com.android.launcher3.ui.TestViewHelpers; 48 import com.android.launcher3.util.rule.ShellCommandRule; 49 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo; 50 import com.android.launcher3.widget.WidgetManagerHelper; 51 52 import org.junit.After; 53 import org.junit.Before; 54 import org.junit.Rule; 55 import org.junit.Test; 56 import org.junit.runner.RunWith; 57 58 import java.util.HashSet; 59 import java.util.Set; 60 61 /** 62 * Tests for bind widget flow. 63 * 64 * Note running these tests will clear the workspace on the device. 65 */ 66 @LargeTest 67 @RunWith(AndroidJUnit4.class) 68 public class BindWidgetTest extends AbstractLauncherUiTest { 69 70 @Rule 71 public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); 72 73 private ContentResolver mResolver; 74 75 // Objects created during test, which should be cleaned up in the end. 76 private Cursor mCursor; 77 // App install session id. 78 private int mSessionId = -1; 79 80 @Override 81 @Before setUp()82 public void setUp() throws Exception { 83 super.setUp(); 84 85 mResolver = mTargetContext.getContentResolver(); 86 87 // Clear all existing data 88 LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB); 89 LauncherSettings.Settings.call(mResolver, 90 LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); 91 } 92 93 @After tearDown()94 public void tearDown() { 95 if (mCursor != null) { 96 mCursor.close(); 97 } 98 99 if (mSessionId > -1) { 100 mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); 101 } 102 } 103 104 @Test testBindNormalWidget_withConfig()105 public void testBindNormalWidget_withConfig() { 106 LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true); 107 LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true); 108 109 addItemToScreen(item); 110 verifyWidgetPresent(info); 111 } 112 113 @Test testBindNormalWidget_withoutConfig()114 public void testBindNormalWidget_withoutConfig() { 115 LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); 116 LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true); 117 118 addItemToScreen(item); 119 verifyWidgetPresent(info); 120 } 121 122 @Test testUnboundWidget_removed()123 public void testUnboundWidget_removed() { 124 LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); 125 LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false); 126 item.appWidgetId = -33; 127 128 addItemToScreen(item); 129 130 final Workspace workspace = mLauncher.getWorkspace(); 131 // Item deleted from db 132 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 133 null, null, null, null, null); 134 assertEquals(0, mCursor.getCount()); 135 136 // The view does not exist 137 assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null); 138 } 139 140 @Test testPendingWidget_autoRestored()141 public void testPendingWidget_autoRestored() { 142 // A non-restored widget with no config screen gets restored automatically. 143 LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); 144 145 // Do not bind the widget 146 LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false); 147 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; 148 149 addItemToScreen(item); 150 verifyWidgetPresent(info); 151 } 152 153 @Test testPendingWidget_withConfigScreen()154 public void testPendingWidget_withConfigScreen() { 155 // A non-restored widget with config screen get bound and shows a 'Click to setup' UI. 156 LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, true); 157 158 // Do not bind the widget 159 LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false); 160 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID; 161 162 addItemToScreen(item); 163 verifyPendingWidgetPresent(); 164 165 // Item deleted from db 166 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 167 null, null, null, null, null); 168 mCursor.moveToNext(); 169 170 // Widget has a valid Id now. 171 assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) 172 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 173 assertNotNull(AppWidgetManager.getInstance(mTargetContext) 174 .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex( 175 LauncherSettings.Favorites.APPWIDGET_ID)))); 176 177 // send OPTION_APPWIDGET_RESTORE_COMPLETED 178 int appWidgetId = mCursor.getInt( 179 mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID)); 180 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext); 181 182 Bundle b = new Bundle(); 183 b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true); 184 RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready); 185 appWidgetManager.updateAppWidgetOptions(appWidgetId, b); 186 appWidgetManager.updateAppWidget(appWidgetId, remoteViews); 187 188 189 // verify changes are reflected 190 waitForLauncherCondition("App widget options did not update", 191 l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean( 192 WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED)); 193 executeOnLauncher(l -> l.getAppWidgetHost().startListening()); 194 verifyWidgetPresent(info); 195 assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100)); 196 } 197 198 @Test testPendingWidget_notRestored_removed()199 public void testPendingWidget_notRestored_removed() { 200 LauncherAppWidgetInfo item = getInvalidWidgetInfo(); 201 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID 202 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; 203 204 addItemToScreen(item); 205 206 assertTrue("Pending widget exists", 207 mLauncher.getWorkspace().tryGetPendingWidget(0) == null); 208 // Item deleted from db 209 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 210 null, null, null, null, null); 211 assertEquals(0, mCursor.getCount()); 212 } 213 214 @Test testPendingWidget_notRestored_brokenInstall()215 public void testPendingWidget_notRestored_brokenInstall() { 216 // A widget which is was being installed once, even if its not being 217 // installed at the moment is not removed. 218 LauncherAppWidgetInfo item = getInvalidWidgetInfo(); 219 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID 220 | LauncherAppWidgetInfo.FLAG_RESTORE_STARTED 221 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; 222 223 addItemToScreen(item); 224 verifyPendingWidgetPresent(); 225 226 // Verify item still exists in db 227 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 228 null, null, null, null, null); 229 assertEquals(1, mCursor.getCount()); 230 231 // Widget still has an invalid id. 232 mCursor.moveToNext(); 233 assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID, 234 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) 235 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 236 } 237 238 @Test testPendingWidget_notRestored_activeInstall()239 public void testPendingWidget_notRestored_activeInstall() throws Exception { 240 // A widget which is being installed is not removed 241 LauncherAppWidgetInfo item = getInvalidWidgetInfo(); 242 item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID 243 | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; 244 245 // Create an active installer session 246 SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); 247 params.setAppPackageName(item.providerName.getPackageName()); 248 PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller(); 249 mSessionId = installer.createSession(params); 250 251 addItemToScreen(item); 252 verifyPendingWidgetPresent(); 253 254 // Verify item still exists in db 255 mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id), 256 null, null, null, null, null); 257 assertEquals(1, mCursor.getCount()); 258 259 // Widget still has an invalid id. 260 mCursor.moveToNext(); 261 assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID, 262 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED)) 263 & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); 264 } 265 verifyWidgetPresent(LauncherAppWidgetProviderInfo info)266 private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) { 267 final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT); 268 assertTrue("Widget is not present", 269 widget != null); 270 } 271 verifyPendingWidgetPresent()272 private void verifyPendingWidgetPresent() { 273 final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT); 274 assertTrue("Pending widget is not present", 275 widget != null); 276 } 277 278 /** 279 * Returns a LauncherAppWidgetInfo with package name which is not present on the device 280 */ getInvalidWidgetInfo()281 private LauncherAppWidgetInfo getInvalidWidgetInfo() { 282 String invalidPackage = "com.invalidpackage"; 283 int count = 0; 284 String pkg = invalidPackage; 285 286 Set<String> activePackage = getOnUiThread(() -> { 287 Set<String> packages = new HashSet<>(); 288 InstallSessionHelper.INSTANCE.get(mTargetContext).getActiveSessions() 289 .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName)); 290 return packages; 291 }); 292 while (true) { 293 try { 294 mTargetContext.getPackageManager().getPackageInfo( 295 pkg, PackageManager.GET_UNINSTALLED_PACKAGES); 296 } catch (Exception e) { 297 if (!activePackage.contains(pkg)) { 298 break; 299 } 300 } 301 pkg = invalidPackage + count; 302 count++; 303 } 304 LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10, 305 new ComponentName(pkg, "com.test.widgetprovider")); 306 item.spanX = 2; 307 item.spanY = 2; 308 item.minSpanX = 2; 309 item.minSpanY = 2; 310 item.cellX = 0; 311 item.cellY = 1; 312 item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; 313 return item; 314 } 315 } 316