• 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 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