Add tests for setOnCheckedChangePendingIntent

Bug: 179245670
Test: atest
Change-Id: I07450c7ec5644ba64df577c48235e3beaaf83855
diff --git a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
index ec621da..d9dbe8a 100644
--- a/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
+++ b/tests/tests/appwidget/res/layout/remoteviews_adapter_item.xml
@@ -13,11 +13,22 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<TextView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/item"
-    android:layout_width="120dp"
-    android:layout_height="120dp"
-    android:gravity="center"
-    android:textStyle="bold"
-    android:textSize="44sp" />
+    android:id="@+id/root"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+    <Switch
+        android:id="@+id/toggle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+    <TextView
+        android:id="@+id/item"
+        android:layout_width="120dp"
+        android:layout_height="120dp"
+        android:gravity="center"
+        android:textStyle="bold"
+        android:textSize="44sp" />
+</LinearLayout>
+
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
index 2bc4e47..925fa69 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/CollectionAppWidgetTest.java
@@ -19,6 +19,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -40,7 +41,9 @@
 import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.AbsListView;
+import android.widget.CompoundButton;
 import android.widget.ListView;
 import android.widget.RemoteViews;
 import android.widget.RemoteViewsService;
@@ -64,6 +67,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 
 /**
  * Test AppWidgets which host collection widgets.
@@ -164,7 +168,9 @@
                 extras.putString(BlockingBroadcastReceiver.KEY_PARAM, COUNTRY_LIST[position]);
                 Intent fillInIntent = new Intent();
                 fillInIntent.putExtras(extras);
-                remoteViews.setOnClickFillInIntent(R.id.item, fillInIntent);
+                remoteViews.setOnClickFillInIntent(R.id.root, fillInIntent);
+                remoteViews.setOnCheckedChangeResponse(
+                        R.id.toggle, RemoteViews.RemoteResponse.fromFillInIntent(fillInIntent));
 
                 if (position == 0) {
                     factoryCountDownLatch.countDown();
@@ -339,6 +345,82 @@
         verifyItemClickIntents(3);
     }
 
+    /**
+     * Verifies that setting the item at {@code index} to {@code newChecked} sends a broadcast with
+     * the proper country and checked extras filled in.
+     *
+     * @param index the index to test
+     * @param newChecked the new checked state, which must be different from the current value
+     */
+    private void verifyItemCheckedChangeIntents(int index, boolean newChecked) throws Throwable {
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver();
+        mActivityRule.runOnUiThread(() -> receiver.register(BROADCAST_ACTION));
+
+        mStackView = (StackView) mAppWidgetHostView.findViewById(R.id.remoteViews_stack);
+        PollingCheck.waitFor(() -> mStackView.getCurrentView() != null);
+
+        mActivityRule.runOnUiThread(
+                () -> {
+                    ViewGroup currentView = (ViewGroup) mStackView.getCurrentView();
+                    CompoundButton toggle =
+                            findFirstMatchingView(currentView, v -> v instanceof CompoundButton);
+                    if (newChecked) {
+                        assertFalse(toggle.isChecked());
+                    } else {
+                        assertTrue(toggle.isChecked());
+                    }
+                    toggle.setChecked(newChecked);
+                });
+        assertEquals(COUNTRY_LIST[index],
+                receiver.getParam(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        if (newChecked) {
+            assertTrue(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+        } else {
+            assertFalse(receiver.result.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+        }
+    }
+
+    @Test
+    public void testSetOnCheckedChangePendingIntent() throws Throwable {
+        if (!mHasAppWidgets) {
+            return;
+        }
+
+        // Toggle the view twice to get the true and false broadcasts.
+        verifyItemCheckedChangeIntents(0, true);
+        verifyItemCheckedChangeIntents(0, false);
+        verifyItemCheckedChangeIntents(0, true);
+
+        // Switch to another child
+        verifySetDisplayedChild(2);
+        verifyItemCheckedChangeIntents(2, true);
+
+        // And one more
+        verifyShowCommand(CollectionAppWidgetProvider.KEY_SHOW_NEXT, 3);
+        verifyItemCheckedChangeIntents(3, true);
+    }
+
+    // Casting type for convenience. Test will fail either way if it's wrong.
+    @SuppressWarnings("unchecked")
+    private static <T extends View> T findFirstMatchingView(View view, Predicate<View> predicate) {
+        if (predicate.test(view)) {
+            return (T) view;
+        }
+        if ((!(view instanceof ViewGroup))) {
+            return null;
+        }
+
+        ViewGroup viewGroup = (ViewGroup) view;
+        for (int i = 0; i < viewGroup.getChildCount(); i++) {
+            View result = findFirstMatchingView(viewGroup.getChildAt(i), predicate);
+            if (result != null) {
+                return (T) result;
+            }
+        }
+
+        return null;
+    }
+
     private class ListScrollListener implements AbsListView.OnScrollListener {
         private CountDownLatch mLatchToNotify;
 
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index d7d7438..a3d6bf7 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -30,8 +30,10 @@
 import android.app.Instrumentation;
 import android.app.Instrumentation.ActivityMonitor;
 import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -50,6 +52,7 @@
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.Chronometer;
+import android.widget.CompoundButton;
 import android.widget.DatePicker;
 import android.widget.EditText;
 import android.widget.FrameLayout;
@@ -719,6 +722,48 @@
     }
 
     @Test
+    public void testSetOnCheckedChangePendingIntent() throws Throwable {
+        String action = "my-checked-change-action";
+        MockBroadcastReceiver receiver =  new MockBroadcastReceiver();
+        mContext.registerReceiver(receiver, new IntentFilter(action));
+
+        Intent intent = new Intent(action).setPackage(mContext.getPackageName());
+        PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        intent,
+                        PendingIntent.FLAG_UPDATE_CURRENT);
+        mRemoteViews.setOnCheckedChangeResponse(R.id.remoteView_checkBox,
+                RemoteViews.RemoteResponse.fromPendingIntent(pendingIntent));
+
+        // View being checked to true should launch the intent with the extra set to true.
+        CompoundButton view = mResult.findViewById(R.id.remoteView_checkBox);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mActivityRule.runOnUiThread(() -> view.setChecked(true));
+        mInstrumentation.waitForIdleSync();
+        assertNotNull(receiver.mIntent);
+        assertTrue(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, false));
+
+        // Changing the checked state from a RemoteViews action should not launch the intent.
+        receiver.mIntent = null;
+        mRemoteViews.setCompoundButtonChecked(R.id.remoteView_checkBox, false);
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(view.isChecked());
+        assertNull(receiver.mIntent);
+
+        // View being checked to false should launch the intent with the extra set to false.
+        receiver.mIntent = null;
+        mActivityRule.runOnUiThread(() -> mRemoteViews.reapply(mContext, mResult));
+        mActivityRule.runOnUiThread(() -> view.setChecked(true));
+        mActivityRule.runOnUiThread(() -> view.setChecked(false));
+        mInstrumentation.waitForIdleSync();
+        assertNotNull(receiver.mIntent);
+        assertFalse(receiver.mIntent.getBooleanExtra(RemoteViews.EXTRA_CHECKED, true));
+    }
+
+    @Test
     public void testSetLong() throws Throwable {
         long base1 = 50;
         long base2 = -50;
@@ -1189,4 +1234,14 @@
             }
         }
     }
+
+    private static final class MockBroadcastReceiver extends BroadcastReceiver {
+
+        Intent mIntent;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIntent = intent;
+        }
+    }
 }