Adding a widget to perform a few basic tests of the widget framework

Change-Id: I5180696ab6d08bc1461c6bd796a24f65416a07b9
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 7bf8387..d81abee 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -433,6 +433,29 @@
             </intent-filter>
         </activity-alias>
 
+        <activity android:name=".widget.WidgetTestActivity"
+                android:label="@string/widget_framework_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_other" />
+        </activity>
+
+        <receiver android:name=".widget.WidgetCtsProvider">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+            <!-- This specifies the widget provider info -->
+            <meta-data android:name="android.appwidget.provider"
+                    android:resource="@xml/widget_info" />
+        </receiver>
+
+        <!-- The service serving the RemoteViews to the collection widget -->
+        <service android:name=".widget.WidgetCtsService"
+            android:permission="android.permission.BIND_REMOTEVIEWS"
+            android:exported="false" />
+
    </application>
 
 </manifest>
diff --git a/apps/CtsVerifier/res/layout/list_item.xml b/apps/CtsVerifier/res/layout/list_item.xml
new file mode 100644
index 0000000..7fd9002
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/list_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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/list_item_text"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp"
+    android:gravity="center"
+    android:fontFamily="sans-serif-light"
+    android:textSize="14dp"
+    android:freezesText="true"
+    android:background="#ff1f1f1f" />
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/pass_fail_widget.xml b/apps/CtsVerifier/res/layout/pass_fail_widget.xml
new file mode 100644
index 0000000..31691cc
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/pass_fail_widget.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <include android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/widget_layout.xml b/apps/CtsVerifier/res/layout/widget_layout.xml
new file mode 100644
index 0000000..4e2a6b6
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/widget_layout.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_marginTop="@dimen/widget_margin_top"
+    android:layout_marginBottom="@dimen/widget_margin_bottom"
+    android:layout_marginLeft="@dimen/widget_margin_left"
+    android:layout_marginRight="@dimen/widget_margin_right"
+    android:padding="1dp"
+    android:background="#2f2f2f">
+
+    <LinearLayout
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingLeft="12dp"
+        android:paddingTop="12dp"
+        android:paddingBottom="12dp"
+        android:paddingRight="12dp"
+        android:layout_gravity="center"
+        android:background="#222">
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="top|left"
+            android:layout_marginBottom="10dp"
+            android:fontFamily="sans-serif"
+            android:textSize="20sp"
+            android:text="@string/widget_name"
+            android:freezesText="true"/>
+
+        <TextView
+            android:id="@+id/instruction"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="20dp"
+            android:fontFamily="sans-serif-light"
+            android:textSize="16sp"
+            android:freezesText="true"/>
+
+        <TextView
+            android:id="@+id/data"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="18dp"
+            android:layout_gravity="center_horizontal"
+            android:fontFamily="sans-serif-light"
+            android:textSize="16sp"/>
+
+        <ListView
+            android:id="@+id/list"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:layout_marginBottom="12dp"
+            android:padding="1dp"
+            android:background="#333"
+            android:visibility="gone"/>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:orientation="horizontal">
+            <Button
+                android:id="@+id/fail"
+                android:layout_marginRight="8dp"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minWidth="100dp"
+                android:text="@string/widget_fail" />
+            <Button
+                android:id="@+id/pass"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:minWidth="100dp"
+                android:text="@string/widget_pass" />
+        </LinearLayout>
+
+    </LinearLayout>
+</FrameLayout>
diff --git a/apps/CtsVerifier/res/values-v14/dimens.xml b/apps/CtsVerifier/res/values-v14/dimens.xml
new file mode 100644
index 0000000..8b5494e
--- /dev/null
+++ b/apps/CtsVerifier/res/values-v14/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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>
+    <dimen name="widget_margin_top">0dp</dimen>
+    <dimen name="widget_margin_bottom">0dp</dimen>
+    <dimen name="widget_margin_left">0dp</dimen>
+    <dimen name="widget_margin_right">0dp</dimen>
+</resources>
diff --git a/apps/CtsVerifier/res/values/dimens.xml b/apps/CtsVerifier/res/values/dimens.xml
new file mode 100644
index 0000000..00257a9
--- /dev/null
+++ b/apps/CtsVerifier/res/values/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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>
+    <dimen name="widget_margin_top">8dp</dimen>
+    <dimen name="widget_margin_bottom">8dp</dimen>
+    <dimen name="widget_margin_left">8dp</dimen>
+    <dimen name="widget_margin_right">8dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index c6f74cf..925151c 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -579,4 +579,12 @@
     <string name="camera_fov_enable_compass_mode_description">If enabled, the panorama orients
         itself according to the current rotation of the device.</string>
 
+    <!-- Strings for Widget -->
+    <string name="widget_framework_test">Widget Framework Test</string>
+    <string name="widget_framework_test_info">This test checks some basic features of the widget
+        framework. In order to perform the test, press the Home button. Add the widget
+        titled "CTS Verifier" to the home screen. Follow the instructions in the widget.</string>
+    <string name="widget_name">Widget Framework Test</string>
+    <string name="widget_pass">Pass</string>
+    <string name="widget_fail">Fail</string>
 </resources>
diff --git a/apps/CtsVerifier/res/xml/widget_info.xml b/apps/CtsVerifier/res/xml/widget_info.xml
new file mode 100644
index 0000000..f8754460
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/widget_info.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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="250dp"
+  android:minHeight="360dp"
+  android:updatePeriodMillis="1800000"
+  android:initialLayout="@layout/widget_layout"
+  android:resizeMode="vertical|horizontal"
+  android:minResizeWidth="40dp"
+  android:minResizeHeight="40dp"
+  android:widgetCategory="keyguard|home_screen">
+</appwidget-provider>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
new file mode 100644
index 0000000..74146f1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsProvider.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2013 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 com.android.cts.verifier.widget;
+
+import java.util.HashMap;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import com.android.cts.verifier.R;
+
+/**
+ * The weather widget's AppWidgetProvider.
+ */
+public class WidgetCtsProvider extends AppWidgetProvider {
+    class TextData {
+        String title;
+        String instruction;
+        String dataP;
+        String dataL;
+
+        public TextData(String t, String i, String dp, String dl) {
+            title = t;
+            instruction = i;
+            dataL = dl;
+            dataP = dp;
+        }
+    }
+
+    public static String PASS = "com.example.android.widgetcts.PASS";
+    public static String FAIL = "com.example.android.widgetcts.FAIL";
+
+    public static final int STATE_BEGIN = 0;
+    public static final int STATE_VERIFY_SIZE_CALLBACK = 1;
+    public static final int STATE_VERIFY_RESIZE = 2;
+    public static final int STATE_VERIFY_COLLECTIONS = 3;
+    public static final int STATE_VERIFY_HOME_OR_KEYGUARD_CALLBACK = 4;
+    public static final int STATE_COMPLETE = 5;
+
+    // If relevant, we want to verify the size callback first, before any
+    // resizing.
+    static HashMap<Integer, Integer> sStateMap = new HashMap<Integer, Integer>();
+    static HashMap<Integer, Integer> sTestCount = new HashMap<Integer, Integer>();
+    static HashMap<Integer, Integer> sPassCount = new HashMap<Integer, Integer>();
+
+    private static int sSDKLevel = android.os.Build.VERSION.SDK_INT;
+
+    public WidgetCtsProvider() {
+    }
+
+    @Override
+    public void onReceive(Context ctx, Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(PASS) || action.equals(FAIL)) {
+            boolean pass = action.equals(PASS);
+
+            int widgetId = (Integer) intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
+                    -1);
+
+            if (sStateMap.get(widgetId) != STATE_BEGIN && sStateMap.get(widgetId)
+                    != STATE_COMPLETE) {
+                if (!sTestCount.containsKey(widgetId)) {
+                    sTestCount.put(widgetId, 0);
+                }
+                if (!sPassCount.containsKey(widgetId)) {
+                    sPassCount.put(widgetId, 0);
+                }
+
+                sPassCount.put(widgetId, sPassCount.get(widgetId) + (pass ? 1 : 0));
+                sTestCount.put(widgetId, sTestCount.get(widgetId) + 1);
+            }
+            gotoNextTest(widgetId);
+
+            AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
+            Bundle options = getAppWidgetOptions(mgr, widgetId);
+            updateWidget(ctx, widgetId, mgr, options);
+        }
+        super.onReceive(ctx, intent);
+    }
+
+    @Override
+    public void onDeleted(Context ctx, int[] ids) {
+        for (int i = 0; i < ids.length; i++) {
+            sStateMap.remove(ids[i]);
+        }
+    }
+
+    Bundle getAppWidgetOptions(AppWidgetManager mgr, int widgetId) {
+        if (sSDKLevel >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
+            return mgr.getAppWidgetOptions(widgetId);
+        }
+        return null;
+    }
+
+    void gotoNextTest(int widgetId) {
+        int state = sStateMap.get(widgetId);
+        boolean foundNextTest = false;
+        do {
+            state = state == STATE_COMPLETE ? state : state + 1;
+            foundNextTest = shouldPerformTest(state);
+        } while (state < STATE_COMPLETE && !foundNextTest);
+        sStateMap.put(widgetId, state);
+    }
+
+    private boolean shouldPerformTest(int state) {
+        if (state == STATE_VERIFY_SIZE_CALLBACK
+                && sSDKLevel < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+            return false;
+        } else if (state == STATE_VERIFY_RESIZE
+                && sSDKLevel < android.os.Build.VERSION_CODES.HONEYCOMB) {
+            return false;
+        } else if (state == STATE_VERIFY_COLLECTIONS
+                && sSDKLevel < android.os.Build.VERSION_CODES.HONEYCOMB) {
+            return false;
+        } else if (state == STATE_VERIFY_HOME_OR_KEYGUARD_CALLBACK
+                && sSDKLevel < android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+        for (int i = 0; i < appWidgetIds.length; i++) {
+            int id = appWidgetIds[i];
+            if (!sStateMap.containsKey(id)) {
+                sStateMap.put(id, STATE_BEGIN);
+            }
+            updateWidget(context, appWidgetIds[i], appWidgetManager, null);
+        }
+    }
+
+    @Override
+    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
+            int appWidgetId, Bundle newOptions) {
+        updateWidget(context, appWidgetId, appWidgetManager, newOptions);
+    }
+
+    private TextData getInstruction(int state, Bundle options, int widgetId) {
+        String title = null;
+        String instruction = null;
+        String dataL = null;
+        String dataP = null;
+
+        int widthP = -1;
+        int heightP = -1;
+        int widthL = -1;
+        int heightL = -1;
+        int category = -1;
+
+        // We use nullness of options as a proxy for an sdk check >= JB
+        if (options != null) {
+            widthP = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, -1);
+            heightP = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, -1);
+            widthL = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, -1);
+            heightL = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, -1);
+            category = options.getInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, -1);
+        }
+
+        int step = 1;
+        if (sTestCount.containsKey(widgetId)) {
+            step = sTestCount.get(widgetId) + 1;
+        }
+        if (state == STATE_BEGIN) {
+            instruction = "This is a test of the widget framework";
+        } else if (state == STATE_VERIFY_SIZE_CALLBACK) {
+            title = "Step " + step + ": Verify dimensions";
+            instruction = "Verify that the width and height indicated below constitute reasonable"
+                    + " approximations of the widget's actual size:";
+            dataP = "Width: " + widthP + "     Height: " + heightP;
+            dataL = "Width: " + widthL + "     Height: " + heightL;
+        } else if (state == STATE_VERIFY_RESIZE) {
+            title = "Step " + step + ": Verify resizeability";
+            instruction = "Verify that there is a functional affordance which allows this widget"
+                    + " to be resized. For example, when picked up and dropped, there may be a "
+                    + " frame with handles. (This is not a requirement for widgets hosted on "
+                    + " a tablet keyguard).";
+            if (sSDKLevel >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
+                instruction = instruction
+                        + " Also, verify that after resize, the width and height below "
+                        + "are updated accordingly.";
+                dataP = "Width: " + widthP + "     Height: " + heightP;
+                dataL = "Width: " + widthL + "     Height: " + heightL;
+            }
+        } else if (state == STATE_VERIFY_COLLECTIONS) {
+            title = "Step " + step + ": Verify collections";
+            instruction = "Verify that the widget contains a scrollable list of numbers from 1"
+                    + " to " + WidgetCtsService.NUM_ITEMS;
+        } else if (state == STATE_VERIFY_HOME_OR_KEYGUARD_CALLBACK) {
+            title = "Step " + step + ": Verify category";
+            instruction = "Verify that the text below accurately reflects whether this widget is"
+                    + " on the home screen or the lock screen. ";
+            if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) {
+                dataL = dataP = "Widget is reportedly on: KEYGUARD";
+            } else if (category == AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN) {
+                dataL = dataP = "Widget is reportedly on: HOME SCREEN";
+            } else {
+                dataL = dataP = "Error.";
+            }
+        } else if (state == STATE_COMPLETE) {
+            title = "Test Complete";
+            instruction = "This completes the test of the widget framework. " +
+                    "Remove and re-add this widget to restart the test.";
+            dataL = dataP = sPassCount.get(widgetId) + " of " + sTestCount.get(widgetId)
+                    + " tests passed successfully.";
+        }
+        return new TextData(title, instruction, dataP, dataL);
+    }
+
+    private void updateWidget(Context context, int appWidgetId, AppWidgetManager appWidgetManager,
+            Bundle newOptions) {
+        // Pull them from the manager
+        if (newOptions == null) {
+            newOptions = getAppWidgetOptions(appWidgetManager, appWidgetId);
+        }
+
+        int baseLayout = R.layout.widget_layout;
+
+        RemoteViews rv = new RemoteViews(context.getPackageName(), baseLayout);
+        int state = sStateMap.get(appWidgetId);
+
+        TextData text = getInstruction(state, newOptions, appWidgetId);
+        rv.setTextViewText(R.id.instruction, text.instruction);
+
+        // Update the title
+        if (text.title != null) {
+            rv.setTextViewText(R.id.title, text.title);
+        }
+
+        if (state == STATE_VERIFY_COLLECTIONS) {
+            // Specify the service to provide data for the collection widget.
+            // Note that we need to
+            // embed the appWidgetId via the data otherwise it will be ignored.
+            final Intent intent = new Intent(context, WidgetCtsService.class);
+            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+            rv.setViewVisibility(R.id.list, View.VISIBLE);
+            rv.setRemoteAdapter(appWidgetId, R.id.list, intent);
+        } else {
+            rv.setViewVisibility(R.id.list, View.GONE);
+        }
+
+        if (state == STATE_BEGIN) {
+            rv.setViewVisibility(R.id.fail, View.GONE);
+            rv.setTextViewText(R.id.pass, "Start Test");
+        } else if (state == STATE_COMPLETE) {
+            rv.setViewVisibility(R.id.fail, View.GONE);
+            rv.setViewVisibility(R.id.pass, View.GONE);
+        } else {
+            rv.setViewVisibility(R.id.fail, View.VISIBLE);
+            rv.setTextViewText(R.id.pass, "Pass");
+        }
+
+        final Intent pass = new Intent(context, WidgetCtsProvider.class);
+        pass.setAction(WidgetCtsProvider.PASS);
+        pass.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        pass.setData(Uri.parse(pass.toUri(Intent.URI_INTENT_SCHEME)));
+        final PendingIntent passPendingIntent = PendingIntent.getBroadcast(context, 0, pass,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+        final Intent fail = new Intent(context, WidgetCtsProvider.class);
+        fail.setAction(WidgetCtsProvider.FAIL);
+        fail.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+        fail.setData(Uri.parse(fail.toUri(Intent.URI_INTENT_SCHEME)));
+        final PendingIntent failPendingIntent = PendingIntent.getBroadcast(context, 0, fail,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+        rv.setOnClickPendingIntent(R.id.pass, passPendingIntent);
+        rv.setOnClickPendingIntent(R.id.fail, failPendingIntent);
+
+        RemoteViews rvL = null;
+        if (text.dataP != null && !text.dataP.equals(text.dataL)) {
+            rvL = rv.clone();
+
+            System.out.println("hmmmm ok, made it innnnn");
+            if (text.dataL != null) {
+                rvL.setViewVisibility(R.id.data, View.VISIBLE);
+                rvL.setTextViewText(R.id.data, text.dataL);
+            } else {
+                rvL.setViewVisibility(R.id.data, View.GONE);
+            }
+        }
+
+        // Update the data
+        if (text.dataP != null) {
+            rv.setViewVisibility(R.id.data, View.VISIBLE);
+            rv.setTextViewText(R.id.data, text.dataP);
+        } else {
+            rv.setViewVisibility(R.id.data, View.GONE);
+        }
+
+        RemoteViews rvFinal = rv;
+        if (rvL != null) {
+            rvFinal = new RemoteViews(rvL, rv);
+        }
+
+        appWidgetManager.updateAppWidget(appWidgetId, rvFinal);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsService.java
new file mode 100644
index 0000000..7d210a2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetCtsService.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2013 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 com.android.cts.verifier.widget;
+
+import android.content.Context;
+import android.content.Intent;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import com.android.cts.verifier.R;
+
+/**
+ * This is the service that provides the factory to be bound to the collection
+ * service.
+ */
+public class WidgetCtsService extends RemoteViewsService {
+    public static final int NUM_ITEMS = 50;
+
+    @Override
+    public RemoteViewsFactory onGetViewFactory(Intent intent) {
+        return new CtsRemoteViewsFactory(this.getApplicationContext(), intent);
+    }
+}
+
+/**
+ * This is the factory that will provide data to the collection widget.
+ */
+class CtsRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
+
+    Context mContext;
+
+    public CtsRemoteViewsFactory(Context context, Intent intent) {
+        mContext = context;
+    }
+
+    public void onCreate() {
+    }
+
+    public void onDestroy() {
+    }
+
+    public int getCount() {
+        return WidgetCtsService.NUM_ITEMS;
+    }
+
+    public RemoteViews getViewAt(int position) {
+        RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.list_item);
+        String text = "List Item " + (position + 1);
+        rv.setTextViewText(R.id.list_item_text, text);
+        return rv;
+    }
+
+    public RemoteViews getLoadingView() {
+        // We aren't going to return a default loading view in this sample
+        return null;
+    }
+
+    public int getViewTypeCount() {
+        // Technically, we have two types of views (the dark and light
+        // background views)
+        return 1;
+    }
+
+    public long getItemId(int position) {
+        return position;
+    }
+
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    public void onDataSetChanged() {
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetTestActivity.java
new file mode 100644
index 0000000..25f0a7a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/widget/WidgetTestActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 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 com.android.cts.verifier.widget;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+
+/**
+ * CTS Verifier case for verifying basic widget framework functionality.
+ */
+public class WidgetTestActivity extends PassFailButtons.Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.pass_fail_widget);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.widget_framework_test, R.string.widget_framework_test_info, -1);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+}