Adding app widget CTS tests.
Bug: 16483829
Change-Id: If698c6cd5103c28bb1c65af2a15ebcf3beb78f0d
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..6a02486
--- /dev/null
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -0,0 +1,1375 @@
+/*
+ * 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.isNull;
+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.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.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 grant_bind_permission --package android.cts.appwidget --user 0";
+
+ private static final String REVOKE_BIND_APP_WIDGET_PERMISSION_COMMAND =
+ "appwidget revoke_bind_permission --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.
+ 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.
+ 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());
+ } 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, 2);
+
+ // Verify the expected callbacks.
+ InOrder firstInOrder = inOrder(firstAppHostViewListener);
+ firstInOrder.verify(firstAppHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ firstInOrder.verify(firstAppHostViewListener).onUpdateAppWidget(argThat(
+ new RemoteViewsMatcher(content.getLayoutId(),
+ provider.provider.getPackageName())));
+
+ InOrder secondInOrder = inOrder(secondAppHostViewListener);
+ secondInOrder.verify(secondAppHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ 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, 2);
+
+ // Verify the expected callbacks.
+ InOrder inOrder = inOrder(appHostViewListener);
+ inOrder.verify(appHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ 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, 2);
+
+ // Verify the expected callbacks.
+ InOrder firstInOrder = inOrder(firstAppHostViewListener);
+ firstInOrder.verify(firstAppHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ firstInOrder.verify(firstAppHostViewListener).onUpdateAppWidget(
+ argThat(new RemoteViewsMatcher(content.getLayoutId(),
+ provider.provider.getPackageName())));
+
+ InOrder secondInOrder = inOrder(secondAppHostViewListener);
+ secondInOrder.verify(secondAppHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ 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, 2);
+
+ // Partially update the content for all app widgets (pretent we changed somehting).
+ getAppWidgetManager().partiallyUpdateAppWidget(firstAppWidgetId, content);
+
+ waitForCallCount(updateAppWidgetCallCount, 3);
+
+ // Verify the expected callbacks.
+ InOrder inOrder = inOrder(appHostViewListener);
+ inOrder.verify(appHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ 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 updateAppWidgetCallCounter = 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) {
+ updateAppWidgetCallCounter.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(updateAppWidgetCallCounter, 2);
+
+ // Partially update the content for all app widgets (pretent we changed somehting).
+ getAppWidgetManager().partiallyUpdateAppWidget(new int[] {firstAppWidgetId,
+ secondAppWidgetId}, content);
+
+ waitForCallCount(updateAppWidgetCallCounter, 3);
+
+ // Verify the expected callbacks.
+ InOrder firstInOrder = inOrder(firstAppHostViewListener);
+ firstInOrder.verify(firstAppHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ firstInOrder.verify(firstAppHostViewListener, times(2)).onUpdateAppWidget(
+ argThat(new RemoteViewsMatcher(content.getLayoutId(),
+ provider.provider.getPackageName())));
+
+ InOrder secondInOrder = inOrder(secondAppHostViewListener);
+ secondInOrder.verify(secondAppHostViewListener).onUpdateAppWidget(
+ isNull(RemoteViews.class));
+ 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.
+ 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)) {
+ assertEquals(android.os.Process.myUserHandle(),
+ 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)) {
+ assertEquals(android.os.Process.myUserHandle(),
+ 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;
+
+ // This is somehow gross but we cannot set the listener in the
+ // constructor since this means returning different instance
+ // for different widget id from the host factory method but the
+ // widget ids are not created at this point.
+ private boolean mInitiallCallWithNullViewsReceived;
+
+ public interface OnUpdateAppWidgetListener {
+ public void onUpdateAppWidget(RemoteViews remoteViews);
+ }
+
+ private MyAppWidgetHostView(Context context) {
+ super(context);
+ }
+
+ public void setOnUpdateAppWidgetListener(OnUpdateAppWidgetListener listener) {
+ mOnUpdateAppWidgetListener = listener;
+ if (mInitiallCallWithNullViewsReceived) {
+ mOnUpdateAppWidgetListener.onUpdateAppWidget(null);
+ }
+ }
+
+ @Override
+ public void updateAppWidget(RemoteViews remoteViews) {
+ super.updateAppWidget(remoteViews);
+ if (mOnUpdateAppWidgetListener != null) {
+ mOnUpdateAppWidgetListener.onUpdateAppWidget(remoteViews);
+ } else if (remoteViews == null) {
+ mInitiallCallWithNullViewsReceived = true;
+ }
+ }
+ }
+}
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;
+ }
+ }
+}