Adding CTS tests for the app widget APIs.

Change-Id: I2202996e8432d82b05c587c318a348a2d0c84331
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index d878d63..4fa5ba1 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -73,6 +73,7 @@
     CtsAdminTestCases \
     CtsAnimationTestCases \
     CtsAppTestCases \
+    CtsAppWidgetTestCases \
     CtsBluetoothTestCases \
     CtsCalendarcommon2TestCases \
     CtsContentTestCases \
diff --git a/tests/tests/appwidget/Android.mk b/tests/tests/appwidget/Android.mk
new file mode 100644
index 0000000..8641d53
--- /dev/null
+++ b/tests/tests/appwidget/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAppWidgetTestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := mockito-target ctstestrunner
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/appwidget/AndroidManifest.xml b/tests/tests/appwidget/AndroidManifest.xml
new file mode 100644
index 0000000..4d9b61d
--- /dev/null
+++ b/tests/tests/appwidget/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.appwidget">
+
+  <application>
+      <uses-library android:name="android.test.runner"/>
+
+      <receiver android:name="android.appwidget.cts.provider.FirstAppWidgetProvider" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/first_appwidget_info" />
+      </receiver>
+
+      <receiver android:name="android.appwidget.cts.provider.SecondAppWidgetProvider" >
+          <intent-filter>
+              <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+          </intent-filter>
+          <meta-data android:name="android.appwidget.provider"
+              android:resource="@xml/second_appwidget_info" />
+      </receiver>
+
+      <service android:name="android.appwidget.cts.service.MyAppWidgetService"
+          android:permission="android.permission.BIND_REMOTEVIEWS">
+      </service>
+
+  </application>
+
+  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+      android:targetPackage="android.cts.appwidget"
+      android:label="Tests for the app widget APIs.">
+      <meta-data android:name="listener"
+          android:value="com.android.cts.runner.CtsTestRunListener" />
+  </instrumentation>
+
+</manifest>
diff --git a/tests/tests/appwidget/res/drawable/first_android_icon.png b/tests/tests/appwidget/res/drawable/first_android_icon.png
new file mode 100644
index 0000000..4ba97a5
--- /dev/null
+++ b/tests/tests/appwidget/res/drawable/first_android_icon.png
Binary files differ
diff --git a/tests/tests/appwidget/res/drawable/second_android_icon.png b/tests/tests/appwidget/res/drawable/second_android_icon.png
new file mode 100644
index 0000000..4ba97a5
--- /dev/null
+++ b/tests/tests/appwidget/res/drawable/second_android_icon.png
Binary files differ
diff --git a/tests/tests/appwidget/res/layout/collection_widget_item_layout.xml b/tests/tests/appwidget/res/layout/collection_widget_item_layout.xml
new file mode 100644
index 0000000..50f49c1
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/collection_widget_item_layout.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/text_view"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+</TextView>
diff --git a/tests/tests/appwidget/res/layout/collection_widget_layout.xml b/tests/tests/appwidget/res/layout/collection_widget_layout.xml
new file mode 100644
index 0000000..0489bca
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/collection_widget_layout.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<StackView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/stack_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+</StackView>
diff --git a/tests/tests/appwidget/res/layout/first_initial_keyguard_layout.xml b/tests/tests/appwidget/res/layout/first_initial_keyguard_layout.xml
new file mode 100644
index 0000000..bebec9f
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/first_initial_keyguard_layout.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+</Button>
diff --git a/tests/tests/appwidget/res/layout/first_initial_layout.xml b/tests/tests/appwidget/res/layout/first_initial_layout.xml
new file mode 100644
index 0000000..bebec9f
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/first_initial_layout.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+</Button>
diff --git a/tests/tests/appwidget/res/layout/second_initial_keyguard_layout.xml b/tests/tests/appwidget/res/layout/second_initial_keyguard_layout.xml
new file mode 100644
index 0000000..bebec9f
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/second_initial_keyguard_layout.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+</Button>
diff --git a/tests/tests/appwidget/res/layout/second_initial_layout.xml b/tests/tests/appwidget/res/layout/second_initial_layout.xml
new file mode 100644
index 0000000..bebec9f
--- /dev/null
+++ b/tests/tests/appwidget/res/layout/second_initial_layout.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+</Button>
diff --git a/tests/tests/appwidget/res/values/constants.xml b/tests/tests/appwidget/res/values/constants.xml
new file mode 100644
index 0000000..375dae3
--- /dev/null
+++ b/tests/tests/appwidget/res/values/constants.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+
+    <!-- First widget provider -->
+
+    <dimen name="first_min_appwidget_size">40dp</dimen>
+
+    <dimen name="first_min_resize_appwidget_size">60dp</dimen>
+
+    <integer name="first_update_period_millis">86400000</integer>
+
+    <integer name="first_resize_mode">3</integer>
+
+    <integer name="first_widget_category">3</integer>
+
+    <item type="id" name="first_auto_advance_view_id"/>
+
+    <!-- Second widget provider -->
+
+    <dimen name="second_min_appwidget_size">50dp</dimen>
+
+    <dimen name="second_min_resize_appwidget_size">70dp</dimen>
+
+    <integer name="second_update_period_millis">86500000</integer>
+
+    <integer name="second_resize_mode">1</integer>
+
+    <integer name="second_widget_category">1</integer>
+
+    <item type="id" name="second_auto_advance_view_id"/>
+
+    <string name="foo">Foo</string>
+
+</resources>
diff --git a/tests/tests/appwidget/res/xml/first_appwidget_info.xml b/tests/tests/appwidget/res/xml/first_appwidget_info.xml
new file mode 100644
index 0000000..d7c1028
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/first_appwidget_info.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="@dimen/first_min_appwidget_size"
+    android:minHeight="@dimen/first_min_appwidget_size"
+    android:minResizeWidth="@dimen/first_min_resize_appwidget_size"
+    android:minResizeHeight="@dimen/first_min_resize_appwidget_size"
+    android:updatePeriodMillis="@integer/first_update_period_millis"
+    android:configure="android.appwidget.cts.provider.FirstAppWidgetConfigureActivity"
+    android:resizeMode="horizontal|vertical"
+    android:widgetCategory="home_screen|keyguard"
+    android:initialLayout="@layout/first_initial_layout"
+    android:initialKeyguardLayout="@layout/first_initial_keyguard_layout"
+    android:previewImage="@drawable/first_android_icon"
+    android:autoAdvanceViewId="@id/first_auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/res/xml/second_appwidget_info.xml b/tests/tests/appwidget/res/xml/second_appwidget_info.xml
new file mode 100644
index 0000000..d192b10
--- /dev/null
+++ b/tests/tests/appwidget/res/xml/second_appwidget_info.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:minWidth="@dimen/second_min_appwidget_size"
+    android:minHeight="@dimen/second_min_appwidget_size"
+    android:minResizeWidth="@dimen/second_min_resize_appwidget_size"
+    android:minResizeHeight="@dimen/second_min_resize_appwidget_size"
+    android:updatePeriodMillis="@integer/second_update_period_millis"
+    android:configure="android.appwidget.cts.provider.SecondAppWidgetConfigureActivity"
+    android:resizeMode="horizontal"
+    android:widgetCategory="home_screen"
+    android:initialLayout="@layout/second_initial_layout"
+    android:initialKeyguardLayout="@layout/second_initial_keyguard_layout"
+    android:previewImage="@drawable/second_android_icon"
+    android:autoAdvanceViewId="@id/second_auto_advance_view_id">
+</appwidget-provider>
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
new file mode 100644
index 0000000..c120d59
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -0,0 +1,1379 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.appwidget.cts.provider.AppWidgetProviderCallbacks;
+import android.appwidget.cts.provider.FirstAppWidgetProvider;
+import android.appwidget.cts.provider.SecondAppWidgetProvider;
+import android.appwidget.cts.service.MyAppWidgetService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.cts.appwidget.R;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService.RemoteViewsFactory;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.mockito.InOrder;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AppWidgetTest extends InstrumentationTestCase {
+
+    private static final long OPERATION_TIMEOUT = 20 * 1000; // 20 sec
+
+    private static final String FIRST_APP_WIDGET_CONFIGURE_ACTIVITY =
+            "android.appwidget.cts.provider.FirstAppWidgetConfigureActivity";
+
+    private static final String SECOND_APP_WIDGET_CONFIGURE_ACTIVITY =
+            "android.appwidget.cts.provider.SecondAppWidgetConfigureActivity";
+
+    private final Object mLock = new Object();
+
+    @Override
+    public void setUp() throws Exception {
+        // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
+        // Dexmaker is used by mockito.
+        System.setProperty("dexmaker.dexcache", getInstrumentation()
+                .getTargetContext().getCacheDir().getPath());
+    }
+
+    private static final String GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND =
+            "appwidget grantbind --package android.cts.appwidget --user 0";
+
+    private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
+            "appwidget revokebind --package android.cts.appwidget --user 0";
+
+    public void testGetAppInstalledProvidersForCurrentUserLegacy() throws Exception {
+        // By default we should get only providers for the current user.
+        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
+
+        // Make sure we have our two providers in the list.
+        assertExpectedInstalledProviders(providers);
+    }
+
+    public void testGetAppInstalledProvidersForCurrentUserNewCurrentProfile() throws Exception {
+        // We ask only for providers for the current user.
+        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders(
+                new UserHandle[] {Process.myUserHandle()});
+
+        // Make sure we have our two providers in the list.
+        assertExpectedInstalledProviders(providers);
+    }
+
+    public void testGetAppInstalledProvidersForCurrentUserNewAllProfiles() throws Exception {
+        // We ask only for providers for all current user's profiles
+        UserManager userManager = (UserManager) getInstrumentation()
+                .getTargetContext().getSystemService(Context.USER_SERVICE);
+
+        List<UserHandle> profilesList = userManager.getUserProfiles();
+        UserHandle[] profilesArray = new UserHandle[profilesList.size()];
+        profilesList.toArray(profilesArray);
+
+        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders(
+                profilesArray);
+
+        // Make sure we have our two providers in the list.
+        assertExpectedInstalledProviders(providers);
+    }
+
+    public void testBindAppWidget() throws Exception {
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(getInstrumentation().getTargetContext(), 0);
+        host.startListening();
+
+        // Allocate an app widget id to bind.
+        final int appWidgetId = host.allocateAppWidgetId();
+
+        // Grab a provider we defined to be bound.
+        AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+        // Bind the widget.
+        boolean widgetBound = getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+                provider.getProfile(), provider.provider, null);
+        assertFalse(widgetBound);
+
+        // Well, app do not have this permission unless explicitly granted
+        // by the user. Now we will pretent for the user and grant it.
+        grantBindAppWidgetPermission();
+
+        try {
+            // Bind the widget as we have a permission for that.
+            widgetBound = getAppWidgetManager().bindAppWidgetIdIfAllowed(
+                    appWidgetId, provider.getProfile(), provider.provider, null);
+            assertTrue(widgetBound);
+
+            // Deallocate the app widget id.
+            host.deleteAppWidgetId(appWidgetId);
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testAppWidgetProviderCallbacks() throws Exception {
+        final AtomicInteger disabledCallCounter = new AtomicInteger();
+
+        // Set a mock to intercept provider callbacks.
+        AppWidgetProviderCallbacks callbacks = mock(AppWidgetProviderCallbacks.class);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                synchronized (mLock) {
+                    disabledCallCounter.incrementAndGet();
+                    mLock.notifyAll();
+                }
+                return null;
+            }
+        }).when(callbacks).onDeleted(any(Context.class), any(int[].class));
+        FirstAppWidgetProvider.setCallbacks(callbacks);
+
+        final int firstAppWidgetId;
+        final int secondAppWidgetId;
+
+        final Bundle firstOptions;
+        final Bundle secondOptions;
+
+        // Create a host and start listening.
+        AppWidgetHost host = spy(new AppWidgetHost(getInstrumentation().getTargetContext(), 0));
+        host.startListening();
+
+        // We want to bind a widget.
+        grantBindAppWidgetPermission();
+        try {
+            // Allocate the first widget id to bind.
+            firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Grab a provider we defined to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Bind the first widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Update the first widget options.
+            firstOptions = new Bundle();
+            firstOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 1);
+            firstOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 2);
+            firstOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 3);
+            firstOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 4);
+            getAppWidgetManager().updateAppWidgetOptions(firstAppWidgetId, firstOptions);
+
+            // Allocate the second app widget id to bind.
+            secondAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the second widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Update the second widget options.
+            secondOptions = new Bundle();
+            secondOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 5);
+            secondOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 6);
+            secondOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 7);
+            secondOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 8);
+            getAppWidgetManager().updateAppWidgetOptions(secondAppWidgetId, secondOptions);
+
+            // Delete the first widget.
+            host.deleteAppWidgetId(firstAppWidgetId);
+
+            // Delete the second widget.
+            host.deleteAppWidgetId(secondAppWidgetId);
+
+            waitForCallCount(disabledCallCounter, 1);
+
+            // Make sure the provider callbacks are correct.
+            InOrder inOrder = inOrder(callbacks);
+
+            inOrder.verify(callbacks).onEnabled(any(Context.class));
+            inOrder.verify(callbacks).onUpdate(any(Context.class),
+                    any(AppWidgetManager.class), eq(new int[] {firstAppWidgetId}));
+            inOrder.verify(callbacks).onAppWidgetOptionsChanged(any(Context.class),
+                    any(AppWidgetManager.class), same(firstAppWidgetId), argThat(
+                            new OptionsMatcher(firstOptions)));
+            inOrder.verify(callbacks).onUpdate(any(Context.class),
+                    any(AppWidgetManager.class), eq(new int[] {secondAppWidgetId}));
+            inOrder.verify(callbacks).onAppWidgetOptionsChanged(any(Context.class),
+                    any(AppWidgetManager.class), same(secondAppWidgetId), argThat(
+                            new OptionsMatcher(secondOptions)));
+            inOrder.verify(callbacks).onDeleted(any(Context.class),
+                    argThat(new WidgetIdsMatcher(new int[]{firstAppWidgetId})));
+            inOrder.verify(callbacks).onDeleted(any(Context.class),
+                    argThat(new WidgetIdsMatcher(new int[]{secondAppWidgetId})));
+            inOrder.verify(callbacks).onDisabled(any(Context.class));
+        } finally {
+            // Clean up.
+            FirstAppWidgetProvider.setCallbacks(null);
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testTwoAppWidgetProviderCallbacks() throws Exception {
+        final AtomicInteger disabledCallCounter = new AtomicInteger();
+
+        // Set a mock to intercept first provider callbacks.
+        AppWidgetProviderCallbacks firstCallbacks = mock(AppWidgetProviderCallbacks.class);
+        FirstAppWidgetProvider.setCallbacks(firstCallbacks);
+
+        // Set a mock to intercept second provider callbacks.
+        AppWidgetProviderCallbacks secondCallbacks = mock(AppWidgetProviderCallbacks.class);
+        doAnswer(new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+                synchronized (mLock) {
+                    disabledCallCounter.incrementAndGet();
+                    mLock.notifyAll();
+                }
+                return null;
+            }
+        }).when(firstCallbacks).onDisabled(any(Context.class));
+        SecondAppWidgetProvider.setCallbacks(secondCallbacks);
+
+        final int firstAppWidgetId;
+        final int secondAppWidgetId;
+
+        // Create a host and start listening.
+        AppWidgetHost host = spy(new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0));
+        host.startListening();
+
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+        try {
+            // Allocate the first widget id to bind.
+            firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Allocate the second widget id to bind.
+            secondAppWidgetId = host.allocateAppWidgetId();
+
+            // Grab the first provider we defined to be bound.
+            AppWidgetProviderInfo firstProvider = getFirstAppWidgetProviderInfo();
+
+            // Bind the first widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    firstProvider.getProfile(), firstProvider.provider, null);
+
+            // Grab the second provider we defined to be bound.
+            AppWidgetProviderInfo secondProvider = getSecondAppWidgetProviderInfo();
+
+            // Bind the second widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                    secondProvider.getProfile(), secondProvider.provider, null);
+
+            // Delete the first widget.
+            host.deleteAppWidgetId(firstAppWidgetId);
+
+            // Delete the second widget.
+            host.deleteAppWidgetId(secondAppWidgetId);
+
+            // Wait for all callbacks to settle.
+            waitForCallCount(disabledCallCounter, 1);
+
+            // Make sure the first provider callbacks are correct.
+            InOrder firstInOrder = inOrder(firstCallbacks);
+            firstInOrder.verify(firstCallbacks).onEnabled(any(Context.class));
+            firstInOrder.verify(firstCallbacks).onUpdate(any(Context.class),
+                    any(AppWidgetManager.class), eq(new int[]{firstAppWidgetId}));
+            firstInOrder.verify(firstCallbacks).onDeleted(any(Context.class),
+                    argThat(new WidgetIdsMatcher(new int[]{firstAppWidgetId})));
+            firstInOrder.verify(firstCallbacks).onDisabled(any(Context.class));
+
+            // Make sure the second provider callbacks are correct.
+            InOrder secondInOrder = inOrder(secondCallbacks);
+            secondInOrder.verify(secondCallbacks).onEnabled(any(Context.class));
+            secondInOrder.verify(secondCallbacks).onUpdate(any(Context.class),
+                    any(AppWidgetManager.class), eq(new int[]{secondAppWidgetId}));
+            secondInOrder.verify(secondCallbacks).onDeleted(any(Context.class),
+                    argThat(new WidgetIdsMatcher(new int[] {secondAppWidgetId})));
+            secondInOrder.verify(secondCallbacks).onDisabled(any(Context.class));
+        } finally {
+            // Clean up.
+            FirstAppWidgetProvider.setCallbacks(null);
+            SecondAppWidgetProvider.setCallbacks(null);
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testGetAppWidgetIds() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0);
+        host.startListening();
+
+        try {
+            // Grab the provider we defined to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Initially we have no widgets.
+            int[] widgetsIds = getAppWidgetManager().getAppWidgetIds(provider.provider);
+            assertTrue(widgetsIds.length == 0);
+
+            // Allocate the first widget id to bind.
+            final int firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the first widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Allocate the second widget id to bind.
+            final int secondAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the second widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Now we have two widgets,
+            widgetsIds = getAppWidgetManager().getAppWidgetIds(provider.provider);
+            assertTrue(Arrays.equals(widgetsIds, new int[]{firstAppWidgetId, secondAppWidgetId}));
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testGetAppWidgetInfo() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0);
+        host.startListening();
+
+        try {
+            // Allocate an widget id to bind.
+            final int appWidgetId = host.allocateAppWidgetId();
+
+            // The widget is not bound, so no info.
+            AppWidgetProviderInfo foundProvider = getAppWidgetManager()
+                    .getAppWidgetInfo(appWidgetId);
+            assertNull(foundProvider);
+
+            // Grab the provider we defined to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Bind the app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // The widget is bound, so the provider info should be there.
+            foundProvider = getAppWidgetManager().getAppWidgetInfo(appWidgetId);
+            assertEquals(provider.provider, foundProvider.provider);
+            assertEquals(provider.getProfile(), foundProvider.getProfile());
+
+            Context context = getInstrumentation().getTargetContext();
+
+            // Let us make sure the provider info is sane.
+            String label = foundProvider.loadLabel(context.getPackageManager());
+            assertTrue(!TextUtils.isEmpty(label));
+
+            Drawable icon = foundProvider.loadIcon(context, DisplayMetrics.DENSITY_DEFAULT);
+            assertNotNull(icon);
+
+            Drawable previewImage = foundProvider.loadPreviewImage(context, 0);
+            assertNotNull(previewImage);
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testGetAppWidgetOptions() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0);
+        host.startListening();
+
+        try {
+            // Grab the provider we defined to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate an widget id to bind.
+            final int appWidgetId = host.allocateAppWidgetId();
+
+            // Initially we have no options.
+            Bundle foundOptions = getAppWidgetManager().getAppWidgetOptions(appWidgetId);
+            assertTrue(foundOptions.isEmpty());
+
+            // We want to set the options when binding.
+            Bundle setOptions = new Bundle();
+            setOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, 1);
+            setOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 2);
+            setOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 3);
+            setOptions.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 4);
+
+            // Bind the app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+                    provider.getProfile(), provider.provider, setOptions);
+
+            // Make sure we get the options used when binding.
+            foundOptions = getAppWidgetManager().getAppWidgetOptions(appWidgetId);
+            assertTrue(equalOptions(setOptions, foundOptions));
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testDeleteHost() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0);
+        host.startListening();
+
+        try {
+            // Allocate an widget id to bind.
+            final int appWidgetId = host.allocateAppWidgetId();
+
+            // Grab the provider we defined to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Bind the app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // The widget should be there.
+            int[] widgetIds = getAppWidgetManager().getAppWidgetIds(provider.provider);
+            assertTrue(Arrays.equals(widgetIds, new int[]{appWidgetId}));
+
+            // Delete the host.
+            host.deleteHost();
+
+            // The host is gone and with it the widgets.
+            widgetIds = getAppWidgetManager().getAppWidgetIds(provider.provider);
+            assertTrue(widgetIds.length == 0);
+        } finally {
+            // Clean up.
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testDeleteHosts() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        // Create the first host and start listening.
+        AppWidgetHost firstHost = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0);
+        firstHost.startListening();
+
+        // Create the second host and start listening.
+        AppWidgetHost secondHost = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 1);
+        secondHost.startListening();
+
+        try {
+            // Grab the provider we defined to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate the first widget id to bind.
+            final int firstAppWidgetId = firstHost.allocateAppWidgetId();
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Allocate the second widget id to bind.
+            final int secondAppWidgetId = secondHost.allocateAppWidgetId();
+
+            // Bind the second app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // The widgets should be there.
+            int[] widgetIds = getAppWidgetManager().getAppWidgetIds(provider.provider);
+            assertTrue(Arrays.equals(widgetIds, new int[]{firstAppWidgetId, secondAppWidgetId}));
+
+            // Delete all hosts.
+            AppWidgetHost.deleteAllHosts();
+
+            // The hosts are gone and with it the widgets.
+            widgetIds = getAppWidgetManager().getAppWidgetIds(provider.provider);
+            assertTrue(widgetIds.length == 0);
+        } finally {
+            // Clean up.
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testOnProvidersChanged() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger onProvidersChangedCallCounter = new AtomicInteger();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0) {
+            @Override
+            public void onProvidersChanged() {
+                synchronized (mLock) {
+                    onProvidersChangedCallCounter.incrementAndGet();
+                    mLock.notifyAll();
+                }
+            }
+        };
+        host.startListening();
+
+        try {
+            // Grab the provider we defined to be bound.
+            AppWidgetProviderInfo firstLookupProvider = getFirstAppWidgetProviderInfo();
+
+            // Allocate a widget id to bind.
+            final int appWidgetId = host.allocateAppWidgetId();
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+                    firstLookupProvider.getProfile(), firstLookupProvider.provider, null);
+
+            // Disable the provider we just bound to.
+            PackageManager packageManager = getInstrumentation().getTargetContext()
+                    .getApplicationContext().getPackageManager();
+            packageManager.setComponentEnabledSetting(firstLookupProvider.provider,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP);
+
+            // Wait for the package change to propagate.
+            waitForCallCount(onProvidersChangedCallCounter, 1);
+
+            // The provider should not be present anymore.
+            AppWidgetProviderInfo secondLookupProvider = getFirstAppWidgetProviderInfo();
+            assertNull(secondLookupProvider);
+
+            // Enable the provider we disbaled.
+            packageManager.setComponentEnabledSetting(firstLookupProvider.provider,
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                    PackageManager.DONT_KILL_APP);
+
+            // Wait for the package change to propagate.
+            waitForCallCount(onProvidersChangedCallCounter, 2);
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testUpdateAppWidgetViaComponentName() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger updateAppWidgetCallCount = new AtomicInteger();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0) {
+            @Override
+            protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                    AppWidgetProviderInfo appWidget) {
+                return new MyAppWidgetHostView(context);
+            }
+        };
+        host.startListening();
+
+        try {
+            // Grab the provider to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate the first widget id to bind.
+            final int firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the first host view.
+            MyAppWidgetHostView firstHostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), firstAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener firstAppHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            firstHostView.setOnUpdateAppWidgetListener(firstAppHostViewListener);
+
+            // Allocate the second widget id to bind.
+            final int secondAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the second app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the second host view.
+            MyAppWidgetHostView secondHostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), secondAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener secondAppHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    synchronized (mLock) {
+                        updateAppWidgetCallCount.incrementAndGet();
+                        mLock.notifyAll();
+                    }
+                    return null;
+                }
+            }).when(secondAppHostViewListener).onUpdateAppWidget(any(RemoteViews.class));
+            secondHostView.setOnUpdateAppWidgetListener(secondAppHostViewListener);
+
+            // Update all app widgets.
+            final RemoteViews content = new RemoteViews(
+                    getInstrumentation().getContext().getPackageName(),
+                    R.layout.second_initial_layout);
+            getAppWidgetManager().updateAppWidget(provider.provider, content);
+
+            waitForCallCount(updateAppWidgetCallCount, 1);
+
+            // Verify the expected callbacks.
+            InOrder firstInOrder = inOrder(firstAppHostViewListener);
+            firstInOrder.verify(firstAppHostViewListener).onUpdateAppWidget(argThat(
+                    new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+
+            InOrder secondInOrder = inOrder(secondAppHostViewListener);
+            secondInOrder.verify(secondAppHostViewListener).onUpdateAppWidget(argThat(
+                    new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testUpdateAppWidgetViaWidgetId() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger updateAppWidgetCallCount = new AtomicInteger();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0) {
+            @Override
+            protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                    AppWidgetProviderInfo appWidget) {
+                return new MyAppWidgetHostView(context);
+            }
+        };
+        host.startListening();
+
+        try {
+            // Grab the provider to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate the first widget id to bind.
+            final int firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the first host view.
+            MyAppWidgetHostView hostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), firstAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener appHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    synchronized (mLock) {
+                        updateAppWidgetCallCount.incrementAndGet();
+                        mLock.notifyAll();
+                    }
+                    return null;
+                }
+            }).when(appHostViewListener).onUpdateAppWidget(any(RemoteViews.class));
+            hostView.setOnUpdateAppWidgetListener(appHostViewListener);
+
+            // Update all app widgets.
+            RemoteViews content = new RemoteViews(
+                    getInstrumentation().getContext().getPackageName(),
+                    R.layout.second_initial_layout);
+            getAppWidgetManager().updateAppWidget(firstAppWidgetId, content);
+
+            waitForCallCount(updateAppWidgetCallCount, 1);
+
+            // Verify the expected callbacks.
+            InOrder inOrder = inOrder(appHostViewListener);
+            inOrder.verify(appHostViewListener).onUpdateAppWidget(argThat(
+                    new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testUpdateAppWidgetViaWidgetIds() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger onUpdateAppWidgetCallCount = new AtomicInteger();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0) {
+            @Override
+            protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                    AppWidgetProviderInfo appWidget) {
+                return new MyAppWidgetHostView(context);
+            }
+        };
+        host.startListening();
+
+        try {
+            // Grab the provider to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate the first widget id to bind.
+            final int firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the first host view.
+            MyAppWidgetHostView firstHostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), firstAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener firstAppHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            firstHostView.setOnUpdateAppWidgetListener(firstAppHostViewListener);
+
+            // Allocate the second widget id to bind.
+            final int secondAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the second app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the second host view.
+            MyAppWidgetHostView secondHostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), secondAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener secondAppHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    synchronized (mLock) {
+                        onUpdateAppWidgetCallCount.incrementAndGet();
+                        mLock.notifyAll();
+                    }
+                    return null;
+                }
+            }).when(secondAppHostViewListener).onUpdateAppWidget(any(RemoteViews.class));
+            secondHostView.setOnUpdateAppWidgetListener(secondAppHostViewListener);
+
+            // Update all app widgets.
+            RemoteViews content = new RemoteViews(
+                    getInstrumentation().getContext().getPackageName(),
+                    R.layout.second_initial_layout);
+            getAppWidgetManager().updateAppWidget(new int[] {firstAppWidgetId,
+                    secondAppWidgetId}, content);
+
+            waitForCallCount(onUpdateAppWidgetCallCount, 1);
+
+            // Verify the expected callbacks.
+            InOrder firstInOrder = inOrder(firstAppHostViewListener);
+            firstInOrder.verify(firstAppHostViewListener).onUpdateAppWidget(
+                    argThat(new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+
+            InOrder secondInOrder = inOrder(secondAppHostViewListener);
+            secondInOrder.verify(secondAppHostViewListener).onUpdateAppWidget(
+                    argThat(new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testPartiallyUpdateAppWidgetViaWidgetId() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger updateAppWidgetCallCount = new AtomicInteger();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0) {
+            @Override
+            protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                    AppWidgetProviderInfo appWidget) {
+                return new MyAppWidgetHostView(context);
+            }
+        };
+
+        host.startListening();
+
+        try {
+            // Grab the provider to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate the first widget id to bind.
+            final int firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the first host view.
+            MyAppWidgetHostView hostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), firstAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener appHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    synchronized (mLock) {
+                        updateAppWidgetCallCount.incrementAndGet();
+                        mLock.notifyAll();
+                    }
+                    return null;
+                }
+            }).when(appHostViewListener).onUpdateAppWidget(any(RemoteViews.class));
+            hostView.setOnUpdateAppWidgetListener(appHostViewListener);
+
+            // Set the content for all app widgets.
+            RemoteViews content = new RemoteViews(
+                    getInstrumentation().getContext().getPackageName(),
+                    R.layout.first_initial_layout);
+            getAppWidgetManager().updateAppWidget(firstAppWidgetId, content);
+
+            waitForCallCount(updateAppWidgetCallCount, 1);
+
+            // Partially update the content for all app widgets (pretent we changed somehting).
+            getAppWidgetManager().partiallyUpdateAppWidget(firstAppWidgetId, content);
+
+            waitForCallCount(updateAppWidgetCallCount, 2);
+
+            // Verify the expected callbacks.
+            InOrder inOrder = inOrder(appHostViewListener);
+            inOrder.verify(appHostViewListener, times(2)).onUpdateAppWidget(
+                    argThat(new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testPartiallyUpdateAppWidgetViaWidgetIds() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger firstAppWidgetCallCounter = new AtomicInteger();
+        final AtomicInteger secondAppWidgetCallCounter = new AtomicInteger();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+                getInstrumentation().getTargetContext(), 0) {
+            @Override
+            protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                    AppWidgetProviderInfo appWidget) {
+                return new MyAppWidgetHostView(context);
+            }
+        };
+        host.startListening();
+
+        try {
+            // Grab the provider to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate the first widget id to bind.
+            final int firstAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the first host view.
+            MyAppWidgetHostView firstHostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), firstAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener firstAppHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    synchronized (mLock) {
+                        firstAppWidgetCallCounter.incrementAndGet();
+                        mLock.notifyAll();
+                    }
+                    return null;
+                }
+            }).when(firstAppHostViewListener).onUpdateAppWidget(any(RemoteViews.class));
+            firstHostView.setOnUpdateAppWidgetListener(firstAppHostViewListener);
+
+            // Allocate the second widget id to bind.
+            final int secondAppWidgetId = host.allocateAppWidgetId();
+
+            // Bind the second app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Create the second host view.
+            MyAppWidgetHostView secondHostView = (MyAppWidgetHostView) host.createView(
+                    getInstrumentation().getContext(), secondAppWidgetId, provider);
+            MyAppWidgetHostView.OnUpdateAppWidgetListener secondAppHostViewListener =
+                    mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    synchronized (mLock) {
+                        secondAppWidgetCallCounter.incrementAndGet();
+                        mLock.notifyAll();
+                    }
+                    return null;
+                }
+            }).when(secondAppHostViewListener).onUpdateAppWidget(any(RemoteViews.class));
+            secondHostView.setOnUpdateAppWidgetListener(secondAppHostViewListener);
+
+            // Set the content for all app widgets.
+            RemoteViews content = new RemoteViews(
+                    getInstrumentation().getContext().getPackageName(),
+                    R.layout.first_initial_layout);
+            getAppWidgetManager().updateAppWidget(new int[]{firstAppWidgetId,
+                    secondAppWidgetId}, content);
+
+            waitForCallCount(firstAppWidgetCallCounter, 1);
+            waitForCallCount(secondAppWidgetCallCounter, 1);
+
+            // Partially update the content for all app widgets (pretend we changed somehting).
+            getAppWidgetManager().partiallyUpdateAppWidget(new int[] {firstAppWidgetId,
+                    secondAppWidgetId}, content);
+
+            waitForCallCount(firstAppWidgetCallCounter, 2);
+            waitForCallCount(secondAppWidgetCallCounter, 2);
+
+            // Verify the expected callbacks.
+            InOrder firstInOrder = inOrder(firstAppHostViewListener);
+            firstInOrder.verify(firstAppHostViewListener, times(2)).onUpdateAppWidget(
+                    argThat(new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+
+            InOrder secondInOrder = inOrder(secondAppHostViewListener);
+            secondInOrder.verify(secondAppHostViewListener, times(2)).onUpdateAppWidget(
+                    argThat(new RemoteViewsMatcher(content.getLayoutId(),
+                            provider.provider.getPackageName())));
+        } finally {
+            // Clean up.
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    public void testCollectionWidgets() throws Exception {
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger getViewCounter = new AtomicInteger();
+        final Context context = getInstrumentation().getTargetContext();
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(context, 0);
+        host.startListening();
+
+        try {
+            // Configure the provider behavior.
+            AppWidgetProviderCallbacks callbacks = mock(AppWidgetProviderCallbacks.class);
+            doAnswer(new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) throws Throwable {
+                    final int appWidgetId = ((int[]) invocation.getArguments()[2])[0];
+
+                    Intent intent = new Intent(context, MyAppWidgetService.class);
+                    intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+                    intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+
+                    RemoteViews removeViews = new RemoteViews(context.getPackageName(),
+                            R.layout.collection_widget_layout);
+                    removeViews.setRemoteAdapter(R.id.stack_view, intent);
+
+                    getAppWidgetManager().updateAppWidget(appWidgetId, removeViews);
+
+                    return null;
+                }
+            }).when(callbacks).onUpdate(any(Context.class), any(AppWidgetManager.class),
+                    any(int[].class));
+            FirstAppWidgetProvider.setCallbacks(callbacks);
+
+            // Grab the provider to be bound.
+            AppWidgetProviderInfo provider = getFirstAppWidgetProviderInfo();
+
+            // Allocate a widget id to bind.
+            final int appWidgetId = host.allocateAppWidgetId();
+
+            // Bind the app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(appWidgetId,
+                    provider.getProfile(), provider.provider, null);
+
+            // Configure the app widget service behavior.
+            RemoteViewsFactory factory = mock(RemoteViewsFactory.class);
+            doAnswer(new Answer<Integer>() {
+                @Override
+                public Integer answer(InvocationOnMock invocation) throws Throwable {
+                    return 1;
+                }
+            }).when(factory).getCount();
+            doAnswer(new Answer<RemoteViews>() {
+                @Override
+                public RemoteViews answer(InvocationOnMock invocation) throws Throwable {
+                    RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
+                            R.layout.collection_widget_item_layout);
+                    remoteViews.setTextViewText(R.id.text_view, context.getText(R.string.foo));
+                    synchronized (mLock) {
+                        getViewCounter.incrementAndGet();
+                    }
+                    return remoteViews;
+                }
+            }).when(factory).getViewAt(any(int.class));
+            doAnswer(new Answer<Integer>() {
+                @Override
+                public Integer answer(InvocationOnMock invocation) throws Throwable {
+                    return 1;
+                }
+            }).when(factory).getViewTypeCount();
+            MyAppWidgetService.setFactory(factory);
+
+            host.createView(context, appWidgetId, provider);
+
+            // Wait for the interactions to occur.
+            waitForCallCount(getViewCounter, 1);
+
+            // Verify the interactions.
+            verify(factory, atLeastOnce()).hasStableIds();
+            verify(factory, atLeastOnce()).getViewTypeCount();
+            verify(factory, atLeastOnce()).getCount();
+            verify(factory, atLeastOnce()).getLoadingView();
+            verify(factory, atLeastOnce()).getViewAt(same(0));
+        } finally {
+            // Clean up.
+            FirstAppWidgetProvider.setCallbacks(null);
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    private void waitForCallCount(AtomicInteger counter, int expectedCount) {
+        synchronized (mLock) {
+            final long startTimeMillis = SystemClock.uptimeMillis();
+            while (counter.get() < expectedCount) {
+                final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+                final long remainingTimeMillis = OPERATION_TIMEOUT - elapsedTimeMillis;
+                if (remainingTimeMillis <= 0) {
+                    fail("Did not get expected call to onUpdateAppWidget");
+                }
+                try {
+                    mLock.wait(remainingTimeMillis);
+                } catch (InterruptedException ie) {
+                        /* ignore */
+                }
+            }
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private void assertExpectedInstalledProviders(List<AppWidgetProviderInfo> providers) {
+        boolean firstProviderVerified = false;
+        boolean secondProviderVerified = false;
+
+        ComponentName firstComponentName = new ComponentName(
+                getInstrumentation().getTargetContext().getPackageName(),
+                FirstAppWidgetProvider.class.getName());
+
+        ComponentName secondComponentName = new ComponentName(
+                getInstrumentation().getTargetContext().getPackageName(),
+                SecondAppWidgetProvider.class.getName());
+
+        final int providerCount = providers.size();
+        for (int i = 0; i < providerCount; i++) {
+            AppWidgetProviderInfo provider = providers.get(i);
+
+            if (firstComponentName.equals(provider.provider)
+                    && android.os.Process.myUserHandle().equals(provider.getProfile())) {
+                assertEquals(getNormalizedDimensionResource(R.dimen.first_min_appwidget_size),
+                        provider.minWidth);
+                assertEquals(getNormalizedDimensionResource(R.dimen.first_min_appwidget_size),
+                        provider.minHeight);
+                assertEquals(getNormalizedDimensionResource(
+                        R.dimen.first_min_resize_appwidget_size), provider.minResizeWidth);
+                assertEquals(getNormalizedDimensionResource(
+                        R.dimen.first_min_resize_appwidget_size), provider.minResizeHeight);
+                assertEquals(getIntResource(R.integer.first_update_period_millis),
+                        provider.updatePeriodMillis);
+                assertEquals(getInstrumentation().getTargetContext().getPackageName(),
+                        provider.configure.getPackageName());
+                assertEquals(FIRST_APP_WIDGET_CONFIGURE_ACTIVITY,
+                        provider.configure.getClassName());
+                assertEquals(getIntResource(R.integer.first_resize_mode),
+                        provider.resizeMode);
+                assertEquals(getIntResource(R.integer.first_widget_category),
+                        provider.widgetCategory);
+                assertEquals(R.layout.first_initial_layout,
+                        provider.initialLayout);
+                assertEquals(R.layout.first_initial_keyguard_layout,
+                        provider.initialKeyguardLayout);
+                assertEquals(R.drawable.first_android_icon,
+                        provider.previewImage);
+                assertEquals(R.id.first_auto_advance_view_id,
+                        provider.autoAdvanceViewId);
+                firstProviderVerified = true;
+            } else if (secondComponentName.equals(provider.provider)
+                    && android.os.Process.myUserHandle().equals(provider.getProfile())) {
+                assertEquals(getNormalizedDimensionResource(R.dimen.second_min_appwidget_size),
+                        provider.minWidth);
+                assertEquals(getNormalizedDimensionResource(R.dimen.second_min_appwidget_size),
+                        provider.minHeight);
+                assertEquals(getNormalizedDimensionResource(
+                        R.dimen.second_min_resize_appwidget_size), provider.minResizeWidth);
+                assertEquals(getNormalizedDimensionResource(
+                        R.dimen.second_min_resize_appwidget_size), provider.minResizeHeight);
+                assertEquals(getIntResource(R.integer.second_update_period_millis),
+                        provider.updatePeriodMillis);
+                assertEquals(getInstrumentation().getTargetContext().getPackageName(),
+                        provider.configure.getPackageName());
+                assertEquals(SECOND_APP_WIDGET_CONFIGURE_ACTIVITY,
+                        provider.configure.getClassName());
+                assertEquals(getIntResource(R.integer.second_resize_mode),
+                        provider.resizeMode);
+                assertEquals(getIntResource(R.integer.second_widget_category),
+                        provider.widgetCategory);
+                assertEquals(R.layout.second_initial_layout,
+                        provider.initialLayout);
+                assertEquals(R.layout.second_initial_keyguard_layout,
+                        provider.initialKeyguardLayout);
+                assertEquals(R.drawable.second_android_icon,
+                        provider.previewImage);
+                assertEquals(R.id.second_auto_advance_view_id,
+                        provider.autoAdvanceViewId);
+                secondProviderVerified = true;
+            }
+        }
+
+        assertTrue(firstProviderVerified && secondProviderVerified);
+    }
+
+    private void grantBindAppWidgetPermission() {
+        executeShellCommandIgnoreOutput(GRANT_BIND_APP_WIDGET_PERMISSION_COMMAND);
+    }
+
+    private void revokeBindAppWidgetPermission() {
+        executeShellCommandIgnoreOutput(REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND);
+    }
+
+    private void executeShellCommandIgnoreOutput(String command) {
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand(command);
+        try {
+            Streams.readFully(new FileInputStream(pfd.getFileDescriptor()));
+        } catch (IOException ioe) {
+            IoUtils.closeQuietly(pfd);
+        }
+    }
+
+    private AppWidgetProviderInfo getFirstAppWidgetProviderInfo() {
+        ComponentName firstComponentName = new ComponentName(
+                getInstrumentation().getTargetContext().getPackageName(),
+                FirstAppWidgetProvider.class.getName());
+
+        return getProviderInfo(firstComponentName);
+    }
+
+    private AppWidgetProviderInfo getSecondAppWidgetProviderInfo() {
+        ComponentName secondComponentName = new ComponentName(
+                getInstrumentation().getTargetContext().getPackageName(),
+                SecondAppWidgetProvider.class.getName());
+
+        return getProviderInfo(secondComponentName);
+    }
+
+    private AppWidgetProviderInfo getProviderInfo(ComponentName componentName) {
+        List<AppWidgetProviderInfo> providers = getAppWidgetManager().getInstalledProviders();
+
+        final int providerCount = providers.size();
+        for (int i = 0; i < providerCount; i++) {
+            AppWidgetProviderInfo provider = providers.get(i);
+            if (componentName.equals(provider.provider)
+                    && Process.myUserHandle().equals(provider.getProfile())) {
+                return provider;
+
+            }
+        }
+
+        return null;
+    }
+
+    private int getNormalizedDimensionResource(int resId) {
+        return getInstrumentation().getTargetContext().getResources()
+                .getDimensionPixelSize(resId);
+    }
+
+    private int getIntResource(int resId) {
+        return getInstrumentation().getTargetContext().getResources().getInteger(resId);
+    }
+
+    private AppWidgetManager getAppWidgetManager() {
+        return (AppWidgetManager) getInstrumentation().getTargetContext()
+                .getSystemService(Context.APPWIDGET_SERVICE);
+    }
+
+    private static boolean equalOptions(Bundle first, Bundle second) {
+        return first.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
+                       == second.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
+                && first.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
+                       == second.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)
+                && first.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
+                        == second.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
+                && first.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
+                        == second.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
+    }
+
+    private static final class OptionsMatcher extends BaseMatcher<Bundle> {
+        private Bundle mOptions;
+
+        public OptionsMatcher(Bundle options) {
+            mOptions = options;
+        }
+
+        @Override
+        public boolean matches(Object item) {
+            Bundle options = (Bundle) item;
+            return equalOptions(mOptions, options);
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            /* do nothing */
+        }
+    }
+
+    private static final class WidgetIdsMatcher extends BaseMatcher<int[]> {
+        private final int[] mWidgetIds;
+
+        public WidgetIdsMatcher(int[] widgetIds) {
+            mWidgetIds = widgetIds;
+        }
+
+        @Override
+        public boolean matches(Object item) {
+            final int[] widgetIds = (int[]) item;
+            return Arrays.equals(widgetIds, mWidgetIds);
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            /* do nothing */
+        }
+    }
+
+    private static final class RemoteViewsMatcher extends BaseMatcher<RemoteViews> {
+        private final int mLayoutId;
+        private final String mPackageName;
+
+        public RemoteViewsMatcher(int layoutId, String packageName) {
+            mLayoutId = layoutId;
+            mPackageName = packageName;
+        }
+
+        @Override
+        public boolean matches(Object item) {
+            final RemoteViews remoteViews = (RemoteViews) item;
+            return remoteViews != null && remoteViews.getLayoutId() == mLayoutId
+                    && remoteViews.getPackage().equals(mPackageName);
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            /* do nothing */
+        }
+    }
+
+    private static class MyAppWidgetHostView extends AppWidgetHostView {
+        private OnUpdateAppWidgetListener mOnUpdateAppWidgetListener;
+
+
+        public interface OnUpdateAppWidgetListener {
+            public void onUpdateAppWidget(RemoteViews remoteViews);
+        }
+
+        private MyAppWidgetHostView(Context context) {
+            super(context);
+        }
+
+        public void setOnUpdateAppWidgetListener(OnUpdateAppWidgetListener listener) {
+            mOnUpdateAppWidgetListener = listener;
+        }
+
+        @Override
+        public void updateAppWidget(RemoteViews remoteViews) {
+            super.updateAppWidget(remoteViews);
+            if (mOnUpdateAppWidgetListener != null) {
+                mOnUpdateAppWidgetListener.onUpdateAppWidget(remoteViews);
+            }
+        }
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderCallbacks.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderCallbacks.java
new file mode 100644
index 0000000..0657a42
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/AppWidgetProviderCallbacks.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts.provider;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.os.Bundle;
+
+public abstract class AppWidgetProviderCallbacks {
+
+    private AppWidgetProvider mProvider;
+
+    public final AppWidgetProvider getProvider() {
+        return mProvider;
+    }
+
+    public final void setProvider(AppWidgetProvider provider) {
+        mProvider = provider;
+    }
+
+    public abstract void onUpdate(Context context, AppWidgetManager appWidgetManager,
+             int[] appWidgetIds);
+
+    public abstract void onAppWidgetOptionsChanged(Context context,
+            AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions);
+
+    public abstract void onDeleted(Context context, int[] appWidgetIds);
+
+    public abstract void onEnabled(Context context);
+
+    public abstract void onDisabled(Context context);
+
+    public abstract void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds);
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java
new file mode 100644
index 0000000..7396ca2
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/FirstAppWidgetProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts.provider;
+
+public final class FirstAppWidgetProvider extends StubbableAppWidgetProvider {
+    private static final Object sLock = new Object();
+
+    private static AppWidgetProviderCallbacks sCallbacks;
+
+    public static void setCallbacks(AppWidgetProviderCallbacks callbacks) {
+        synchronized (sLock) {
+            sCallbacks = callbacks;
+        }
+    }
+
+    @Override
+    protected AppWidgetProviderCallbacks getCallbacks() {
+        synchronized (sLock) {
+            if (sCallbacks != null) {
+                sCallbacks.setProvider(this);
+            }
+            return sCallbacks;
+        }
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java
new file mode 100644
index 0000000..223f8f0
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/SecondAppWidgetProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts.provider;
+
+public final class SecondAppWidgetProvider extends StubbableAppWidgetProvider {
+    private static final Object sLock = new Object();
+
+    private static AppWidgetProviderCallbacks sCallbacks;
+
+    public static void setCallbacks(AppWidgetProviderCallbacks callbacks) {
+        synchronized (sLock) {
+            sCallbacks = callbacks;
+        }
+    }
+
+    @Override
+    protected AppWidgetProviderCallbacks getCallbacks() {
+        synchronized (sLock) {
+            if (sCallbacks != null) {
+                sCallbacks.setProvider(this);
+            }
+            return sCallbacks;
+        }
+    }
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/provider/StubbableAppWidgetProvider.java b/tests/tests/appwidget/src/android/appwidget/cts/provider/StubbableAppWidgetProvider.java
new file mode 100644
index 0000000..9023902
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/provider/StubbableAppWidgetProvider.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts.provider;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.os.Bundle;
+
+public abstract class StubbableAppWidgetProvider extends AppWidgetProvider {
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        AppWidgetProviderCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            callbacks.onUpdate(context, appWidgetManager, appWidgetIds);
+        }
+    }
+
+    @Override
+    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
+            int appWidgetId, Bundle newOptions) {
+        AppWidgetProviderCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            callbacks.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
+        }
+    }
+
+    @Override
+    public void onDeleted(Context context, int[] appWidgetIds) {
+        AppWidgetProviderCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            callbacks.onDeleted(context, appWidgetIds);
+        }
+    }
+
+    @Override
+    public void onEnabled(Context context) {
+        AppWidgetProviderCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            callbacks.onEnabled(context);
+        }
+    }
+
+    @Override
+    public void onDisabled(Context context) {
+        AppWidgetProviderCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            callbacks.onDisabled(context);
+        }
+    }
+
+    @Override
+    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
+        AppWidgetProviderCallbacks callbacks = getCallbacks();
+        if (callbacks != null) {
+            super.onRestored(context, oldWidgetIds, newWidgetIds);
+        }
+    }
+
+    protected abstract AppWidgetProviderCallbacks getCallbacks();
+}
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/service/MyAppWidgetService.java b/tests/tests/appwidget/src/android/appwidget/cts/service/MyAppWidgetService.java
new file mode 100644
index 0000000..313855b
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/service/MyAppWidgetService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appwidget.cts.service;
+
+import android.content.Intent;
+import android.widget.RemoteViewsService;
+
+public class MyAppWidgetService extends RemoteViewsService {
+    private static final Object sLock = new Object();
+
+    private static RemoteViewsFactory sFactory;
+
+    public static void setFactory(RemoteViewsFactory factory) {
+        synchronized (sLock) {
+            sFactory = factory;
+        }
+    }
+
+    @Override
+    public RemoteViewsFactory onGetViewFactory(Intent intent) {
+        synchronized (sLock) {
+            return sFactory;
+        }
+    }
+}