• 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.LauncherSettings.Favorites.TABLE_NAME;
19 import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
20 import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
21 import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
22 import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
23 import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
24 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
25 import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
26 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
27 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
28 
29 import static org.junit.Assert.assertEquals;
30 import static org.junit.Assert.assertNotNull;
31 import static org.junit.Assert.assertNull;
32 import static org.junit.Assert.assertTrue;
33 
34 import android.appwidget.AppWidgetManager;
35 import android.content.ComponentName;
36 import android.content.pm.PackageInstaller;
37 import android.content.pm.PackageInstaller.SessionParams;
38 import android.content.pm.PackageManager;
39 import android.database.Cursor;
40 import android.os.Bundle;
41 import android.widget.RemoteViews;
42 
43 import androidx.test.ext.junit.runners.AndroidJUnit4;
44 import androidx.test.filters.LargeTest;
45 
46 import com.android.launcher3.Launcher;
47 import com.android.launcher3.LauncherAppState;
48 import com.android.launcher3.LauncherModel;
49 import com.android.launcher3.LauncherSettings;
50 import com.android.launcher3.R;
51 import com.android.launcher3.celllayout.FavoriteItemsTransaction;
52 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
53 import com.android.launcher3.pm.InstallSessionHelper;
54 import com.android.launcher3.tapl.Widget;
55 import com.android.launcher3.tapl.Workspace;
56 import com.android.launcher3.ui.AbstractLauncherUiTest;
57 import com.android.launcher3.ui.TestViewHelpers;
58 import com.android.launcher3.util.rule.ShellCommandRule;
59 import com.android.launcher3.util.rule.TestStabilityRule;
60 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
61 import com.android.launcher3.widget.WidgetManagerHelper;
62 
63 import org.junit.After;
64 import org.junit.Before;
65 import org.junit.Rule;
66 import org.junit.Test;
67 import org.junit.runner.RunWith;
68 
69 import java.util.HashSet;
70 import java.util.Set;
71 import java.util.function.Consumer;
72 
73 /**
74  * Tests for bind widget flow.
75  *
76  * Note running these tests will clear the workspace on the device.
77  */
78 @LargeTest
79 @RunWith(AndroidJUnit4.class)
80 public class TaplBindWidgetTest extends AbstractLauncherUiTest<Launcher> {
81 
82     @Rule
83     public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
84 
85     // Objects created during test, which should be cleaned up in the end.
86     private Cursor mCursor;
87     // App install session id.
88     private int mSessionId = -1;
89 
90     private LauncherModel mModel;
91 
92     @Override
93     @Before
setUp()94     public void setUp() throws Exception {
95         super.setUp();
96         mModel = LauncherAppState.getInstance(mTargetContext).getModel();
97     }
98 
99     @After
tearDown()100     public void tearDown() {
101         if (mCursor != null) {
102             mCursor.close();
103         }
104 
105         if (mSessionId > -1) {
106             mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
107         }
108     }
109 
110     @Test
testBindNormalWidget_withConfig()111     public void testBindNormalWidget_withConfig() {
112         LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, true, i -> { });
113         verifyWidgetPresent(info);
114     }
115 
116     @Test
testBindNormalWidget_withoutConfig()117     public void testBindNormalWidget_withoutConfig() {
118         LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, true, i -> { });
119         verifyWidgetPresent(info);
120     }
121 
122     @Test
testUnboundWidget_removed()123     public void testUnboundWidget_removed() {
124         LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
125                 item -> item.appWidgetId = -33);
126 
127         final Workspace workspace = mLauncher.getWorkspace();
128         // Item deleted from db
129         mCursor = queryItem();
130         assertEquals(0, mCursor.getCount());
131 
132         // The view does not exist
133         assertTrue("Widget exists", workspace.tryGetWidget(info.label, 0) == null);
134     }
135 
136     @Test
testPendingWidget_autoRestored()137     public void testPendingWidget_autoRestored() {
138         // A non-restored widget with no config screen gets restored automatically.
139         // Do not bind the widget
140         LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
141                 item -> item.restoreStatus = FLAG_ID_NOT_VALID);
142         verifyWidgetPresent(info);
143     }
144 
145     @Test
146     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894
testPendingWidget_withConfigScreen()147     public void testPendingWidget_withConfigScreen() {
148         // A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
149         // Do not bind the widget
150         LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, false,
151                 item -> item.restoreStatus = FLAG_ID_NOT_VALID);
152         verifyPendingWidgetPresent();
153 
154         mCursor = queryItem();
155         mCursor.moveToNext();
156 
157         // Widget has a valid Id now.
158         assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
159                 & FLAG_ID_NOT_VALID);
160         assertNotNull(AppWidgetManager.getInstance(mTargetContext)
161                 .getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
162                         LauncherSettings.Favorites.APPWIDGET_ID))));
163 
164         // send OPTION_APPWIDGET_RESTORE_COMPLETED
165         int appWidgetId = mCursor.getInt(
166                 mCursor.getColumnIndex(LauncherSettings.Favorites.APPWIDGET_ID));
167         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mTargetContext);
168 
169         Bundle b = new Bundle();
170         b.putBoolean(WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED, true);
171         RemoteViews remoteViews = new RemoteViews(mTargetPackage, R.layout.appwidget_not_ready);
172         appWidgetManager.updateAppWidgetOptions(appWidgetId, b);
173         appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
174 
175         // verify changes are reflected
176         waitForLauncherCondition("App widget options did not update",
177                 l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean(
178                         WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED));
179         executeOnLauncher(l -> l.getAppWidgetHolder().startListening());
180         verifyWidgetPresent(info);
181         assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100));
182     }
183 
184     @Test
testPendingWidget_notRestored_removed()185     public void testPendingWidget_notRestored_removed() {
186         addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
187 
188         assertTrue("Pending widget exists",
189                 mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
190         // Item deleted from db
191         mCursor = queryItem();
192         assertEquals(0, mCursor.getCount());
193     }
194 
195     @Test
196     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/310242894
testPendingWidget_notRestored_brokenInstall()197     public void testPendingWidget_notRestored_brokenInstall() {
198         // A widget which is was being installed once, even if its not being
199         // installed at the moment is not removed.
200         addPendingItemToScreen(getInvalidWidgetInfo(),
201                 FLAG_ID_NOT_VALID | FLAG_RESTORE_STARTED | FLAG_PROVIDER_NOT_READY);
202         verifyPendingWidgetPresent();
203 
204         // Verify item still exists in db
205         mCursor = queryItem();
206         assertEquals(1, mCursor.getCount());
207 
208         // Widget still has an invalid id.
209         mCursor.moveToNext();
210         assertEquals(FLAG_ID_NOT_VALID,
211                 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
212                         & FLAG_ID_NOT_VALID);
213     }
214 
215     @Test
testPendingWidget_notRestored_activeInstall()216     public void testPendingWidget_notRestored_activeInstall() throws Exception {
217         // A widget which is being installed is not removed
218         LauncherAppWidgetInfo item = getInvalidWidgetInfo();
219 
220         // Create an active installer session
221         SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
222         params.setAppPackageName(item.providerName.getPackageName());
223         PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
224         mSessionId = installer.createSession(params);
225 
226         addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
227         verifyPendingWidgetPresent();
228 
229         // Verify item still exists in db
230         mCursor = queryItem();
231         assertEquals(1, mCursor.getCount());
232 
233         // Widget still has an invalid id.
234         mCursor.moveToNext();
235         assertEquals(FLAG_ID_NOT_VALID,
236                 mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
237                         & FLAG_ID_NOT_VALID);
238     }
239 
verifyWidgetPresent(LauncherAppWidgetProviderInfo info)240     private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
241         final Widget widget = mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT);
242         assertTrue("Widget is not present",
243                 widget != null);
244     }
245 
verifyPendingWidgetPresent()246     private void verifyPendingWidgetPresent() {
247         final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT);
248         assertTrue("Pending widget is not present",
249                 widget != null);
250     }
251 
addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus)252     private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) {
253         item.restoreStatus = restoreStatus;
254         item.screenId = FIRST_SCREEN_ID;
255         commitTransactionAndLoadHome(
256                 new FavoriteItemsTransaction(mTargetContext).addItem(() -> item));
257     }
258 
addWidgetToScreen(boolean hasConfigureScreen, boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride)259     private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen,
260             boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) {
261         LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen);
262         commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext)
263                 .addItem(() -> {
264                     LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget);
265                     item.screenId = FIRST_SCREEN_ID;
266                     itemOverride.accept(item);
267                     return item;
268                 }));
269         return info;
270     }
271 
272     /**
273      * Returns a LauncherAppWidgetInfo with package name which is not present on the device
274      */
getInvalidWidgetInfo()275     private LauncherAppWidgetInfo getInvalidWidgetInfo() {
276         String invalidPackage = "com.invalidpackage";
277         int count = 0;
278         String pkg = invalidPackage;
279 
280         Set<String> activePackage = getOnUiThread(() -> {
281             Set<String> packages = new HashSet<>();
282             InstallSessionHelper.INSTANCE.get(mTargetContext).getActiveSessions()
283                     .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName));
284             return packages;
285         });
286         while (true) {
287             try {
288                 mTargetContext.getPackageManager().getPackageInfo(
289                         pkg, PackageManager.GET_UNINSTALLED_PACKAGES);
290             } catch (Exception e) {
291                 if (!activePackage.contains(pkg)) {
292                     break;
293                 }
294             }
295             pkg = invalidPackage + count;
296             count++;
297         }
298         LauncherAppWidgetInfo item = new LauncherAppWidgetInfo(10,
299                 new ComponentName(pkg, "com.test.widgetprovider"));
300         item.spanX = 2;
301         item.spanY = 2;
302         item.minSpanX = 2;
303         item.minSpanY = 2;
304         item.cellX = 0;
305         item.cellY = 1;
306         item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
307         return item;
308     }
309 
queryItem()310     private Cursor queryItem() {
311         try {
312             return MODEL_EXECUTOR.submit(() ->
313                 mModel.getModelDbController().query(
314                                 TABLE_NAME, null, itemIdMatch(0), null, null)).get();
315         } catch (Exception e) {
316             throw new RuntimeException(e);
317         }
318     }
319 }
320