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