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