Dup view ids should not cause dup Autofill ids

The views' state are restored based on their id. Ids can be duplicate.
Autofill Ids should not. Hence if there are views with duplucate ids
only one view's autofill should be restored.

Test: Ran this test
Bug: 62658714
Change-Id: Ida6ef95d3ba26b09c4c37d6e90f690bec2a25569
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 7bfb742..173f122 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -63,6 +63,8 @@
         <activity android:name=".OutOfProcessLoginActivity"
             android:process="android.autofillservice.cts.outside"/>
         <activity android:name=".FragmentContainerActivity" />
+        <activity android:name=".DuplicateIdActivity"
+            android:theme="@android:style/Theme.NoTitleBar" />
 
         <service
             android:name=".InstrumentedAutoFillService"
diff --git a/tests/autofillservice/res/layout/duplicate_id_layout.xml b/tests/autofillservice/res/layout/duplicate_id_layout.xml
new file mode 100644
index 0000000..a5643ea
--- /dev/null
+++ b/tests/autofillservice/res/layout/duplicate_id_layout.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  * Copyright (C) 2017 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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <CheckBox
+        android:id="@+id/duplicate_id"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <CheckBox
+        android:id="@+id/duplicate_id"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java
new file mode 100644
index 0000000..31ac8f7
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 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.autofillservice.cts;
+
+import android.os.Bundle;
+import android.util.Log;
+
+public class DuplicateIdActivity extends AbstractAutoFillActivity {
+    private static final String LOG_TAG = DuplicateIdActivity.class.getSimpleName();
+
+    static final String DUPLICATE_ID = "duplicate_id";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState != null) {
+            Log.i(LOG_TAG, "onCreate(" + savedInstanceState + ")");
+        }
+
+        setContentView(R.layout.duplicate_id_layout);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
new file mode 100644
index 0000000..b155fdd
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DuplicateIdActivityTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2017 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.autofillservice.cts;
+
+import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.DuplicateIdActivity.DUPLICATE_ID;
+import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
+import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.assist.AssistStructure;
+import android.support.test.rule.ActivityTestRule;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * This is the test case covering most scenarios - other test cases will cover characteristics
+ * specific to that test's activity (for example, custom views).
+ */
+public class DuplicateIdActivityTest extends AutoFillServiceTestCase {
+    private static final String LOG_TAG = DuplicateIdActivityTest.class.getSimpleName();
+    @Rule
+    public final ActivityTestRule<DuplicateIdActivity> mActivityRule = new ActivityTestRule<>(
+            DuplicateIdActivity.class);
+
+    private DuplicateIdActivity mActivity;
+
+    @Before
+    public void setup() {
+        Helper.disableAutoRotation();
+        Helper.setOrientation(0);
+
+        mActivity = mActivityRule.getActivity();
+    }
+
+    @After
+    public void teardown() {
+        mActivity.finish();
+
+        Helper.allowAutoRotation();
+    }
+
+    /**
+     * Find the views that are tested from the structure in the request
+     *
+     * @param request The request
+     *
+     * @return An array containing the two tested views
+     */
+    private AssistStructure.ViewNode[] findViews(InstrumentedAutoFillService.FillRequest request) {
+        assertThat(request.structure.getWindowNodeCount()).isEqualTo(1);
+        AssistStructure.WindowNode windowNode = request.structure.getWindowNodeAt(0);
+
+        AssistStructure.ViewNode rootNode = windowNode.getRootViewNode();
+
+        assertThat(rootNode.getChildCount()).isEqualTo(2);
+        return new AssistStructure.ViewNode[]{rootNode.getChildAt(0), rootNode.getChildAt(1)};
+    }
+
+    @Test
+    public void testDoNotRestoreDuplicateAutofillIds() throws Exception {
+        enableService();
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(DUPLICATE_ID, "value")
+                        .setPresentation(createPresentation("dataset"))
+                        .build())
+                .build());
+
+        // Select field to start autofill
+        runShellCommand("input keyevent KEYCODE_TAB");
+
+        waitUntilConnected();
+        InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+
+        AssistStructure.ViewNode[] views = findViews(request);
+        AssistStructure.ViewNode view1 = views[0];
+        AssistStructure.ViewNode view2 = views[1];
+        AutofillId id1 = view1.getAutofillId();
+        AutofillId id2 = view2.getAutofillId();
+
+        Log.i(LOG_TAG, "view1=" + id1);
+        Log.i(LOG_TAG, "view2=" + id2);
+
+        // Both checkboxes use the same id
+        assertThat(view1.getId()).isEqualTo(view2.getId());
+
+        // They got different autofill ids though
+        assertThat(id1).isNotEqualTo(id2);
+
+        sReplier.addResponse(NO_RESPONSE);
+
+        // Force rotation to force onDestroy->onCreate cycle
+        Helper.setOrientation(1);
+
+        // Select other field to trigger new partition
+        runShellCommand("input keyevent KEYCODE_TAB");
+
+        request = sReplier.getNextFillRequest();
+
+        views = findViews(request);
+        AutofillId recreatedId1 = views[0].getAutofillId();
+        AutofillId recreatedId2 = views[1].getAutofillId();
+
+        Log.i(LOG_TAG, "restored view1=" + recreatedId1);
+        Log.i(LOG_TAG, "restored view2=" + recreatedId2);
+
+        // For the restoring logic the two views are the same. Hence it might happen that the first
+        // view is restored with the id of the second view or the other way round.
+        // We just need
+        // - to restore as many views as we can (i.e. one)
+        // - make sure the autofill ids are still unique after
+        boolean view1WasRestored = (recreatedId1.equals(id1) || recreatedId1.equals(id2));
+        boolean view2WasRestored = (recreatedId2.equals(id1) || recreatedId2.equals(id2));
+
+        // One id was restored
+        assertThat(view1WasRestored || view2WasRestored).isTrue();
+
+        // The views still have different autofill ids
+        assertThat(recreatedId1).isNotEqualTo(recreatedId2);
+
+        waitUntilDisconnected();
+    }
+}