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