• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.launcher3;
2 
3 import android.annotation.TargetApi;
4 import android.appwidget.AppWidgetHost;
5 import android.content.ComponentName;
6 import android.content.ContentResolver;
7 import android.content.ContentValues;
8 import android.content.pm.PackageInstaller;
9 import android.content.pm.PackageInstaller.SessionParams;
10 import android.content.pm.PackageManager;
11 import android.database.Cursor;
12 import android.os.Build;
13 import android.os.Bundle;
14 import android.support.test.uiautomator.UiSelector;
15 import android.test.suitebuilder.annotation.LargeTest;
16 
17 import com.android.launcher3.compat.AppWidgetManagerCompat;
18 import com.android.launcher3.compat.PackageInstallerCompat;
19 import com.android.launcher3.ui.LauncherInstrumentationTestCase;
20 import com.android.launcher3.util.ManagedProfileHeuristic;
21 import com.android.launcher3.widget.PendingAddWidgetInfo;
22 import com.android.launcher3.widget.WidgetHostViewLoader;
23 
24 import java.util.Set;
25 import java.util.concurrent.Callable;
26 import java.util.concurrent.CountDownLatch;
27 import 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)
36 public 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
setUp()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
tearDown()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 
testBindNormalWidget_withConfig()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 
testBindNormalWidget_withoutConfig()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 
testUnboundWidget_removed()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 
testPendingWidget_autoRestored()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 
testPendingWidget_withConfigScreen()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 
testPendingWidget_notRestored_removed()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 
testPendingWidget_notRestored_brokenInstall()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 
testPendingWidget_notRestored_activeInstall()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      */
setupAndVerifyContents( LauncherAppWidgetInfo item, Class<?> widgetClass, String desc)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      */
createWidgetInfo( LauncherAppWidgetProviderInfo info, boolean bindWidget)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      */
getInvalidWidgetInfo()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      */
waitUntilLoaderIdle()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