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