1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.appwidget;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.any;
22 import static org.mockito.ArgumentMatchers.anyInt;
23 import static org.mockito.ArgumentMatchers.anyString;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.reset;
27 import static org.mockito.Mockito.times;
28 import static org.mockito.Mockito.verify;
29 import static org.mockito.Mockito.when;
30 
31 import android.annotation.NonNull;
32 import android.annotation.Nullable;
33 import android.app.AppOpsManagerInternal;
34 import android.app.admin.DevicePolicyManagerInternal;
35 import android.appwidget.AppWidgetManager;
36 import android.appwidget.AppWidgetManagerInternal;
37 import android.appwidget.AppWidgetProviderInfo;
38 import android.appwidget.PendingHostUpdate;
39 import android.content.BroadcastReceiver;
40 import android.content.ComponentName;
41 import android.content.ContextWrapper;
42 import android.content.Intent;
43 import android.content.IntentFilter;
44 import android.content.pm.LauncherApps;
45 import android.content.pm.PackageManagerInternal;
46 import android.content.pm.ShortcutServiceInternal;
47 import android.os.Handler;
48 import android.os.UserHandle;
49 import android.test.InstrumentationTestCase;
50 import android.test.suitebuilder.annotation.SmallTest;
51 import android.util.AtomicFile;
52 import android.util.Xml;
53 import android.widget.RemoteViews;
54 
55 import com.android.frameworks.servicestests.R;
56 import com.android.internal.appwidget.IAppWidgetHost;
57 import com.android.modules.utils.TypedXmlPullParser;
58 import com.android.modules.utils.TypedXmlSerializer;
59 import com.android.server.LocalServices;
60 
61 import org.mockito.ArgumentCaptor;
62 import org.xmlpull.v1.XmlPullParser;
63 import org.xmlpull.v1.XmlPullParserException;
64 
65 import java.io.File;
66 import java.io.FileInputStream;
67 import java.io.FileOutputStream;
68 import java.io.IOException;
69 import java.util.Iterator;
70 import java.util.List;
71 import java.util.Objects;
72 import java.util.Random;
73 import java.util.concurrent.CountDownLatch;
74 
75 /**
76  * Tests for {@link AppWidgetManager} and {@link AppWidgetServiceImpl}.
77  *
78  m FrameworksServicesTests &&
79  adb install \
80  -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
81  adb shell am instrument -e class com.android.server.appwidget.AppWidgetServiceImplTest \
82  -w com.android.frameworks.servicestests/androidx.test.runner.AndroidJUnitRunner
83  */
84 @SmallTest
85 public class AppWidgetServiceImplTest extends InstrumentationTestCase {
86 
87     private static final int HOST_ID = 42;
88 
89     private TestContext mTestContext;
90     private String mPkgName;
91     private AppWidgetServiceImpl mService;
92     private AppWidgetManager mManager;
93 
94     private ShortcutServiceInternal mMockShortcutService;
95     private PackageManagerInternal mMockPackageManager;
96     private AppOpsManagerInternal mMockAppOpsManagerInternal;
97     private IAppWidgetHost mMockHost;
98 
99     @Override
setUp()100     protected void setUp() throws Exception {
101         super.setUp();
102         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
103         LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
104         LocalServices.removeServiceForTest(AppWidgetManagerInternal.class);
105         LocalServices.removeServiceForTest(PackageManagerInternal.class);
106         LocalServices.removeServiceForTest(AppOpsManagerInternal.class);
107 
108         mTestContext = new TestContext();
109         mPkgName = mTestContext.getOpPackageName();
110         mService = new AppWidgetServiceImpl(mTestContext);
111         mManager = new AppWidgetManager(mTestContext, mService);
112 
113         mMockShortcutService = mock(ShortcutServiceInternal.class);
114         mMockPackageManager = mock(PackageManagerInternal.class);
115         mMockAppOpsManagerInternal = mock(AppOpsManagerInternal.class);
116         mMockHost = mock(IAppWidgetHost.class);
117         LocalServices.addService(ShortcutServiceInternal.class, mMockShortcutService);
118         LocalServices.addService(PackageManagerInternal.class, mMockPackageManager);
119         LocalServices.addService(AppOpsManagerInternal.class, mMockAppOpsManagerInternal);
120         when(mMockPackageManager.filterAppAccess(anyString(), anyInt(), anyInt()))
121                 .thenReturn(false);
122         mService.onStart();
123         mService.systemServicesReady();
124     }
125 
testLoadDescription()126     public void testLoadDescription() {
127         AppWidgetProviderInfo info =
128                 mManager.getInstalledProvidersForPackage(mPkgName, null).get(0);
129         assertEquals(info.loadDescription(mTestContext), "widget description string");
130     }
131 
testParseSizeConfiguration()132     public void testParseSizeConfiguration() {
133         AppWidgetProviderInfo info =
134                 mManager.getInstalledProvidersForPackage(mPkgName, null).get(0);
135 
136         assertThat(info.minWidth).isEqualTo(getDimensionResource(R.dimen.widget_min_width));
137         assertThat(info.minHeight).isEqualTo(getDimensionResource(R.dimen.widget_min_height));
138         assertThat(info.minResizeWidth)
139                 .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_width));
140         assertThat(info.minResizeHeight)
141                 .isEqualTo(getDimensionResource(R.dimen.widget_min_resize_height));
142         assertThat(info.maxResizeWidth)
143                 .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_width));
144         assertThat(info.maxResizeHeight)
145                 .isEqualTo(getDimensionResource(R.dimen.widget_max_resize_height));
146         assertThat(info.targetCellWidth)
147                 .isEqualTo(getIntegerResource(R.integer.widget_target_cell_width));
148         assertThat(info.targetCellHeight)
149                 .isEqualTo(getIntegerResource(R.integer.widget_target_cell_height));
150     }
151 
testRequestPinAppWidget_otherProvider()152     public void testRequestPinAppWidget_otherProvider() {
153         ComponentName otherProvider = null;
154         for (AppWidgetProviderInfo provider : mManager.getInstalledProviders()) {
155             if (!provider.provider.getPackageName().equals(mTestContext.getPackageName())) {
156                 otherProvider = provider.provider;
157                 break;
158             }
159         }
160         if (otherProvider == null) {
161             // No other provider found. Ignore this test.
162         }
163         assertFalse(mManager.requestPinAppWidget(otherProvider, null, null));
164     }
165 
testRequestPinAppWidget()166     public void testRequestPinAppWidget() {
167         ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
168         // Set up users.
169         when(mMockShortcutService.requestPinAppWidget(anyString(),
170                 any(AppWidgetProviderInfo.class), eq(null), eq(null), anyInt()))
171                 .thenReturn(true);
172         assertTrue(mManager.requestPinAppWidget(provider, null, null));
173 
174         final ArgumentCaptor<AppWidgetProviderInfo> providerCaptor =
175                 ArgumentCaptor.forClass(AppWidgetProviderInfo.class);
176         verify(mMockShortcutService, times(1)).requestPinAppWidget(anyString(),
177                 providerCaptor.capture(), eq(null), eq(null), anyInt());
178         assertEquals(provider, providerCaptor.getValue().provider);
179     }
180 
testIsRequestPinAppWidgetSupported()181     public void testIsRequestPinAppWidgetSupported() {
182         // Set up users.
183         when(mMockShortcutService.isRequestPinItemSupported(anyInt(), anyInt()))
184                 .thenReturn(true, false);
185         assertTrue(mManager.isRequestPinAppWidgetSupported());
186         assertFalse(mManager.isRequestPinAppWidgetSupported());
187 
188         verify(mMockShortcutService, times(2)).isRequestPinItemSupported(anyInt(),
189                 eq(LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET));
190     }
191 
testProviderUpdatesReceived()192     public void testProviderUpdatesReceived() throws Exception {
193         int widgetId = setupHostAndWidget();
194         RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
195         mManager.updateAppWidget(widgetId, view);
196         mManager.updateAppWidget(widgetId, view);
197         mManager.updateAppWidget(widgetId, view);
198         mManager.updateAppWidget(widgetId, view);
199 
200         flushMainThread();
201         verify(mMockHost, times(4)).updateAppWidget(eq(widgetId), any(RemoteViews.class));
202 
203         reset(mMockHost);
204         mManager.notifyAppWidgetViewDataChanged(widgetId, 22);
205         flushMainThread();
206         verify(mMockHost, times(1)).viewDataChanged(eq(widgetId), eq(22));
207     }
208 
testProviderUpdatesNotReceived()209     public void testProviderUpdatesNotReceived() throws Exception {
210         int widgetId = setupHostAndWidget();
211         mService.stopListening(mPkgName, HOST_ID);
212         RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
213         mManager.updateAppWidget(widgetId, view);
214         mManager.notifyAppWidgetViewDataChanged(widgetId, 22);
215 
216         flushMainThread();
217         verify(mMockHost, times(0)).updateAppWidget(anyInt(), any(RemoteViews.class));
218         verify(mMockHost, times(0)).viewDataChanged(anyInt(), eq(22));
219     }
220 
testNoUpdatesReceived_queueEmpty()221     public void testNoUpdatesReceived_queueEmpty() {
222         int widgetId = setupHostAndWidget();
223         RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
224         mManager.updateAppWidget(widgetId, view);
225         mManager.notifyAppWidgetViewDataChanged(widgetId, 22);
226         mService.stopListening(mPkgName, HOST_ID);
227 
228         List<PendingHostUpdate> updates = mService.startListening(
229                 mMockHost, mPkgName, HOST_ID, new int[0]).getList();
230         assertTrue(updates.isEmpty());
231     }
232 
233     /**
234      * Sends placeholder widget updates to {@link #mManager}.
235      * @param widgetId widget to update
236      * @param viewIds a list of view ids for which
237      *                {@link AppWidgetManager#notifyAppWidgetViewDataChanged} will be called
238      */
sendDummyUpdates(int widgetId, int... viewIds)239     private void sendDummyUpdates(int widgetId, int... viewIds) {
240         Random r = new Random();
241         RemoteViews view = new RemoteViews(mPkgName, android.R.layout.simple_list_item_1);
242         for (int i = r.nextInt(10) + 2; i >= 0; i--) {
243             mManager.updateAppWidget(widgetId, view);
244         }
245 
246         for (int viewId : viewIds) {
247             mManager.notifyAppWidgetViewDataChanged(widgetId, viewId);
248             for (int i = r.nextInt(3); i >= 0; i--) {
249                 mManager.updateAppWidget(widgetId, view);
250             }
251         }
252     }
253 
testNoUpdatesReceived_queueNonEmpty_noWidgetId()254     public void testNoUpdatesReceived_queueNonEmpty_noWidgetId() {
255         int widgetId = setupHostAndWidget();
256         mService.stopListening(mPkgName, HOST_ID);
257 
258         sendDummyUpdates(widgetId, 22, 23);
259         List<PendingHostUpdate> updates = mService.startListening(
260                 mMockHost, mPkgName, HOST_ID, new int[0]).getList();
261         assertTrue(updates.isEmpty());
262     }
263 
testUpdatesReceived_queueNotEmpty_widgetIdProvided()264     public void testUpdatesReceived_queueNotEmpty_widgetIdProvided() {
265         int widgetId = setupHostAndWidget();
266         int widgetId2 = bindNewWidget();
267         mService.stopListening(mPkgName, HOST_ID);
268 
269         sendDummyUpdates(widgetId, 22, 23);
270         sendDummyUpdates(widgetId2, 100, 101, 102);
271 
272         List<PendingHostUpdate> updates = mService.startListening(
273                 mMockHost, mPkgName, HOST_ID, new int[]{widgetId}).getList();
274         // 3 updates corresponding to the first widget
275         assertEquals(3, updates.size());
276     }
277 
testUpdatesReceived_queueNotEmpty_widgetIdProvided2()278     public void testUpdatesReceived_queueNotEmpty_widgetIdProvided2() {
279         int widgetId = setupHostAndWidget();
280         int widgetId2 = bindNewWidget();
281         mService.stopListening(mPkgName, HOST_ID);
282 
283         sendDummyUpdates(widgetId, 22, 23);
284         sendDummyUpdates(widgetId2, 100, 101, 102);
285 
286         List<PendingHostUpdate> updates = mService.startListening(
287                 mMockHost, mPkgName, HOST_ID, new int[]{widgetId2}).getList();
288         // 4 updates corresponding to the second widget
289         assertEquals(4, updates.size());
290     }
291 
testReceiveBroadcastBehavior_enableAndUpdate()292     public void testReceiveBroadcastBehavior_enableAndUpdate() {
293         TestAppWidgetProvider testAppWidgetProvider = new TestAppWidgetProvider();
294         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLE_AND_UPDATE);
295 
296         testAppWidgetProvider.onReceive(mTestContext, intent);
297 
298         assertTrue(testAppWidgetProvider.isBehaviorSuccess());
299     }
300 
301 
testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided()302     public void testUpdatesReceived_queueNotEmpty_multipleWidgetIdProvided() {
303         int widgetId = setupHostAndWidget();
304         int widgetId2 = bindNewWidget();
305         mService.stopListening(mPkgName, HOST_ID);
306 
307         sendDummyUpdates(widgetId, 22, 23);
308         sendDummyUpdates(widgetId2, 100, 101, 102);
309 
310         List<PendingHostUpdate> updates = mService.startListening(
311                 mMockHost, mPkgName, HOST_ID, new int[]{widgetId, widgetId2}).getList();
312         // 3 updates for first widget and 4 for second
313         assertEquals(7, updates.size());
314     }
315 
testUpdatesReceived_queueEmptyAfterStartListening()316     public void testUpdatesReceived_queueEmptyAfterStartListening() {
317         int widgetId = setupHostAndWidget();
318         int widgetId2 = bindNewWidget();
319         mService.stopListening(mPkgName, HOST_ID);
320 
321         sendDummyUpdates(widgetId, 22, 23);
322         sendDummyUpdates(widgetId2, 100, 101, 102);
323 
324         List<PendingHostUpdate> updates = mService.startListening(
325                 mMockHost, mPkgName, HOST_ID, new int[]{widgetId, widgetId2}).getList();
326         // 3 updates for first widget and 4 for second
327         assertEquals(7, updates.size());
328 
329         // Stop and start listening again
330         mService.stopListening(mPkgName, HOST_ID);
331         updates = mService.startListening(
332                 mMockHost, mPkgName, HOST_ID, new int[]{widgetId, widgetId2}).getList();
333         assertTrue(updates.isEmpty());
334     }
335 
testGetInstalledProvidersForPackage()336     public void testGetInstalledProvidersForPackage() {
337         List<AppWidgetProviderInfo> allProviders = mManager.getInstalledProviders();
338         assertTrue(!allProviders.isEmpty());
339         String packageName = allProviders.get(0).provider.getPackageName();
340         List<AppWidgetProviderInfo> providersForPackage = mManager.getInstalledProvidersForPackage(
341                 packageName, null);
342         // Remove providers from allProviders that don't have the given package name.
343         Iterator<AppWidgetProviderInfo> iter = allProviders.iterator();
344         while (iter.hasNext()) {
345             if (!iter.next().provider.getPackageName().equals(packageName)) {
346                 iter.remove();
347             }
348         }
349         assertEquals(allProviders.size(), providersForPackage.size());
350         for (int i = 0; i < allProviders.size(); i++) {
351             assertEquals(allProviders.get(i).provider, providersForPackage.get(i).provider);
352         }
353     }
354 
testGetPreviewLayout()355     public void testGetPreviewLayout() {
356         AppWidgetProviderInfo info =
357                 mManager.getInstalledProvidersForPackage(mPkgName, null).get(0);
358 
359         assertThat(info.previewLayout).isEqualTo(R.layout.widget_preview);
360     }
361 
testWidgetProviderInfoPersistence()362     public void testWidgetProviderInfoPersistence() throws IOException {
363         final AppWidgetProviderInfo original = new AppWidgetProviderInfo();
364         original.minWidth = 40;
365         original.minHeight = 40;
366         original.maxResizeWidth = 250;
367         original.maxResizeHeight = 120;
368         original.targetCellWidth = 1;
369         original.targetCellHeight = 1;
370         original.updatePeriodMillis = 86400000;
371         original.previewLayout = R.layout.widget_preview;
372         original.label = "test";
373 
374         final File file = new File(mTestContext.getDataDir(), "appwidget_provider_info.xml");
375         saveWidgetProviderInfoLocked(file, original);
376         final AppWidgetProviderInfo target = loadAppWidgetProviderInfoLocked(file);
377 
378         assertThat(target.minWidth).isEqualTo(original.minWidth);
379         assertThat(target.minHeight).isEqualTo(original.minHeight);
380         assertThat(target.minResizeWidth).isEqualTo(original.minResizeWidth);
381         assertThat(target.minResizeHeight).isEqualTo(original.minResizeHeight);
382         assertThat(target.maxResizeWidth).isEqualTo(original.maxResizeWidth);
383         assertThat(target.maxResizeHeight).isEqualTo(original.maxResizeHeight);
384         assertThat(target.targetCellWidth).isEqualTo(original.targetCellWidth);
385         assertThat(target.targetCellHeight).isEqualTo(original.targetCellHeight);
386         assertThat(target.updatePeriodMillis).isEqualTo(original.updatePeriodMillis);
387         assertThat(target.previewLayout).isEqualTo(original.previewLayout);
388     }
389 
setupHostAndWidget()390     private int setupHostAndWidget() {
391         List<PendingHostUpdate> updates = mService.startListening(
392                 mMockHost, mPkgName, HOST_ID, new int[0]).getList();
393         assertTrue(updates.isEmpty());
394         return bindNewWidget();
395     }
396 
bindNewWidget()397     private int bindNewWidget() {
398         ComponentName provider = new ComponentName(mTestContext, TestAppWidgetProvider.class);
399         int widgetId = mService.allocateAppWidgetId(mPkgName, HOST_ID);
400         assertTrue(mManager.bindAppWidgetIdIfAllowed(widgetId, provider));
401         assertEquals(provider, mManager.getAppWidgetInfo(widgetId).provider);
402 
403         return widgetId;
404     }
405 
flushMainThread()406     private void flushMainThread() throws Exception {
407         CountDownLatch latch = new CountDownLatch(1);
408         new Handler(mTestContext.getMainLooper()).post(latch::countDown);
409         latch.await();
410     }
411 
getDimensionResource(int resId)412     private int getDimensionResource(int resId) {
413         return mTestContext.getResources().getDimensionPixelSize(resId);
414     }
415 
getIntegerResource(int resId)416     private int getIntegerResource(int resId) {
417         return mTestContext.getResources().getInteger(resId);
418     }
419 
saveWidgetProviderInfoLocked(@onNull final File dst, @Nullable final AppWidgetProviderInfo info)420     private static void saveWidgetProviderInfoLocked(@NonNull final File dst,
421             @Nullable final AppWidgetProviderInfo info)
422             throws IOException {
423         Objects.requireNonNull(dst);
424         if (info == null) {
425             return;
426         }
427         final AtomicFile file = new AtomicFile(dst);
428         final FileOutputStream stream = file.startWrite();
429         final TypedXmlSerializer out = Xml.resolveSerializer(stream);
430         out.startDocument(null, true);
431         out.startTag(null, "p");
432         AppWidgetXmlUtil.writeAppWidgetProviderInfoLocked(out, info);
433         out.endTag(null, "p");
434         out.endDocument();
435         file.finishWrite(stream);
436     }
437 
loadAppWidgetProviderInfoLocked(@onNull final File dst)438     public static AppWidgetProviderInfo loadAppWidgetProviderInfoLocked(@NonNull final File dst) {
439         Objects.requireNonNull(dst);
440         final AtomicFile file = new AtomicFile(dst);
441         try (FileInputStream stream = file.openRead()) {
442             final TypedXmlPullParser parser = Xml.resolvePullParser(stream);
443             int type;
444             while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
445                     && type != XmlPullParser.START_TAG) {
446                 // drain whitespace, comments, etc.
447             }
448             final String nodeName = parser.getName();
449             if (!"p".equals(nodeName)) {
450                 return null;
451             }
452             return AppWidgetXmlUtil.readAppWidgetProviderInfoLocked(parser);
453         } catch (IOException | XmlPullParserException e) {
454             return null;
455         }
456     }
457 
458     private class TestContext extends ContextWrapper {
459 
TestContext()460         public TestContext() {
461             super(getInstrumentation().getContext());
462         }
463 
464         @Override
registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler)465         public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
466                 IntentFilter filter, String broadcastPermission, Handler scheduler) {
467             // ignore.
468             return null;
469         }
470 
471         @Override
unregisterReceiver(BroadcastReceiver receiver)472         public void unregisterReceiver(BroadcastReceiver receiver) {
473             // ignore.
474         }
475 
476         @Override
enforceCallingOrSelfPermission(String permission, String message)477         public void enforceCallingOrSelfPermission(String permission, String message) {
478             // ignore.
479         }
480 
481         @Override
sendBroadcastAsUser(Intent intent, UserHandle user)482         public void sendBroadcastAsUser(Intent intent, UserHandle user) {
483             // ignore.
484         }
485     }
486 }
487