Added new tests for SaveInfo.

Also changed some exception-related tests to use TestNg for consistency.

Bug: 38044993

Test: SaveInfoTest
Test: LoginActivityTest.testSaveNoRequiredField_NoneFilled()
Test: LoginActivityTest.testSaveNoRequiredField_OneFilled()
Test: LoginActivityTest.testSaveNoRequiredField_BothFilled()

Change-Id: Ifab511fbb17256f3414aaa11c0c0f83005bcac45
diff --git a/tests/autofillservice/Android.mk b/tests/autofillservice/Android.mk
index 1908600..eaf2f17 100644
--- a/tests/autofillservice/Android.mk
+++ b/tests/autofillservice/Android.mk
@@ -28,7 +28,7 @@
     ctstestrunner \
     truth-prebuilt \
     ub-uiautomator \
-    testng
+    testng # TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index d5c5770..5ba68c8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -130,8 +130,11 @@
             }
         }
         if (mRequiredSavableIds != null) {
-            final SaveInfo.Builder saveInfo = new SaveInfo.Builder(mSaveType,
-                    getAutofillIds(nodeResolver, mRequiredSavableIds));
+            final SaveInfo.Builder saveInfo =
+                    mRequiredSavableIds == null || mRequiredSavableIds.length == 0
+                        ? new SaveInfo.Builder(mSaveType)
+                            : new SaveInfo.Builder(mSaveType,
+                                    getAutofillIds(nodeResolver, mRequiredSavableIds));
 
             saveInfo.setFlags(mFlags);
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
index b994dfb..20c311b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
 
 import android.service.autofill.CharSequenceTransformation;
 import android.service.autofill.ValueFinder;
@@ -38,29 +39,35 @@
 
 @RunWith(AndroidJUnit4.class)
 public class CharSequenceTransformationTest {
-    @Test(expected = NullPointerException.class)
+
+    @Test
     public void testAllNullBuilder() {
-        new CharSequenceTransformation.Builder(null, null, null);
+        assertThrows(NullPointerException.class,
+                () ->  new CharSequenceTransformation.Builder(null, null, null));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void testNullAutofillIdBuilder() {
-        new CharSequenceTransformation.Builder(null, "", "");
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(null, "", ""));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void testNullRegexBuilder() {
-        new CharSequenceTransformation.Builder(new AutofillId(1), null, "");
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(new AutofillId(1), null, ""));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void testNullSubstBuilder() {
-        new CharSequenceTransformation.Builder(new AutofillId(1), "", null);
+        assertThrows(NullPointerException.class,
+                () -> new CharSequenceTransformation.Builder(new AutofillId(1), "", null));
     }
 
-    @Test(expected = PatternSyntaxException.class)
+    @Test
     public void testBadRegexBuilder() {
-        new CharSequenceTransformation.Builder(new AutofillId(1), "(", "");
+        assertThrows(PatternSyntaxException.class,
+                () -> new CharSequenceTransformation.Builder(new AutofillId(1), "(", ""));
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
index cff35aa..ba9e214 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/ImageTransformationTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.only;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
 
 import android.service.autofill.ImageTransformation;
 import android.service.autofill.ValueFinder;
@@ -36,29 +37,35 @@
 
 @RunWith(AndroidJUnit4.class)
 public class ImageTransformationTest {
-    @Test(expected = NullPointerException.class)
+
+    @Test
     public void testAllNullBuilder() {
-        new ImageTransformation.Builder(null, null, 0);
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(null, null, 0));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void testNullAutofillIdBuilder() {
-        new ImageTransformation.Builder(null, "", 1);
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(null, "", 1));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void testNullRegexBuilder() {
-        new ImageTransformation.Builder(new AutofillId(1), null, 1);
+        assertThrows(NullPointerException.class,
+                () ->  new ImageTransformation.Builder(new AutofillId(1), null, 1));
     }
 
-    @Test(expected = IllegalArgumentException.class)
+    @Test
     public void testNullSubstBuilder() {
-        new ImageTransformation.Builder(new AutofillId(1), "", 0);
+        assertThrows(IllegalArgumentException.class,
+                () ->  new ImageTransformation.Builder(new AutofillId(1), "", 0));
     }
 
-    @Test(expected = PatternSyntaxException.class)
+    @Test
     public void testBadRegexBuilder() {
-        new ImageTransformation.Builder(new AutofillId(1), "(", 1);
+        assertThrows(PatternSyntaxException.class,
+                () ->  new ImageTransformation.Builder(new AutofillId(1), "(", 1));
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index 9a0d70c..1206c62 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.text.TextUtils;
 import android.util.Log;
-import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
@@ -50,6 +50,7 @@
 
     static final String ID_USERNAME_CONTAINER = "username_container";
     static final String AUTHENTICATION_MESSAGE = "Authentication failed. D'OH!";
+    static final String BACKDOOR_USERNAME = "LemmeIn";
 
     private TextView mUsernameLabel;
     private EditText mUsernameEditText;
@@ -110,7 +111,10 @@
     private void login() {
         final String username = mUsernameEditText.getText().toString();
         final String password = mPasswordEditText.getText().toString();
-        final boolean valid = username.equals(password) || password.contains("pass");
+        final boolean valid = username.equals(password)
+                || (TextUtils.isEmpty(username) && TextUtils.isEmpty(password))
+                || password.contains("pass")
+                || username.equals(BACKDOOR_USERNAME);
 
         if (valid) {
             Log.d(TAG, "login ok: " + username);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 9a7765c..82cc39f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -39,6 +39,7 @@
 import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilDisconnected;
 import static android.autofillservice.cts.LoginActivity.AUTHENTICATION_MESSAGE;
 import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER;
+import static android.autofillservice.cts.LoginActivity.BACKDOOR_USERNAME;
 import static android.autofillservice.cts.LoginActivity.getWelcomeMessage;
 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
@@ -1405,6 +1406,88 @@
     }
 
     @Test
+    public void testSaveNoRequiredField_NoneFilled() throws Exception {
+        optionalOnlyTest(FilledFields.NONE);
+    }
+
+    @Test
+    public void testSaveNoRequiredField_OneFilled() throws Exception {
+        optionalOnlyTest(FilledFields.USERNAME_ONLY);
+    }
+
+    @Test
+    public void testSaveNoRequiredField_BothFilled() throws Exception {
+        optionalOnlyTest(FilledFields.BOTH);
+    }
+
+    enum FilledFields {
+        NONE,
+        USERNAME_ONLY,
+        BOTH
+    }
+
+    private void optionalOnlyTest(FilledFields filledFields) throws Exception {
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD)
+                .setOptionalSavableIds(ID_USERNAME, ID_PASSWORD)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Sanity check.
+        sUiBot.assertNoDatasets();
+
+        // Wait for onFill() before proceeding, otherwise the fields might be changed before
+        // the session started
+        sReplier.getNextFillRequest();
+
+        // Set credentials...
+        final String expectedUsername;
+        if (filledFields == FilledFields.USERNAME_ONLY || filledFields == FilledFields.BOTH) {
+            expectedUsername = BACKDOOR_USERNAME;
+            mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
+        } else {
+            expectedUsername = "";
+        }
+        mActivity.onPassword(View::requestFocus);
+        if (filledFields == FilledFields.BOTH) {
+            mActivity.onPassword((v) -> v.setText("whatever"));
+        }
+
+        // ...and login
+        final String expectedMessage = getWelcomeMessage(expectedUsername);
+        final String actualMessage = mActivity.tapLogin();
+        assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
+
+        if (filledFields == FilledFields.NONE) {
+            sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+            assertNoDanglingSessions();
+            return;
+        }
+
+        // Assert the snack bar is shown and tap "Save".
+        sUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
+
+        final SaveRequest saveRequest = sReplier.getNextSaveRequest();
+
+        // Assert value of expected fields - should not be sanitized.
+        final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
+        assertTextAndValue(username, BACKDOOR_USERNAME);
+
+        if (filledFields == FilledFields.BOTH) {
+            final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
+            assertTextAndValue(password, "whatever");
+        }
+
+        // Sanity check: once saved, the session should be finished.
+        assertNoDanglingSessions();
+    }
+
+    @Test
     public void testGenericSave() throws Exception {
         customizedSaveTest(SAVE_DATA_TYPE_GENERIC);
     }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java
index 8cc4791..09600a8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LuhnChecksumValidatorTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
 
 import android.service.autofill.LuhnChecksumValidator;
 import android.service.autofill.ValueFinder;
@@ -31,14 +32,17 @@
 
 @RunWith(AndroidJUnit4.class)
 public class LuhnChecksumValidatorTest {
-    @Test(expected = NullPointerException.class)
+
+    @Test
     public void nullId() {
-        new LuhnChecksumValidator(null);
+        assertThrows(NullPointerException.class,
+                () -> new LuhnChecksumValidator((AutofillId[]) null));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void nullAndOtherId() {
-        new LuhnChecksumValidator(new AutofillId(1), null);
+        assertThrows(NullPointerException.class,
+                () -> new LuhnChecksumValidator(new AutofillId(1), null));
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
new file mode 100644
index 0000000..f93dd89
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SaveInfoTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.service.autofill.SaveInfo;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.autofill.AutofillId;
+
+import static org.testng.Assert.assertThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SaveInfoTest {
+
+    @Test
+    public void testRequiredIdsBuilder_null() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, null));
+    }
+
+    @Test
+    public void testRequiredIdsBuilder_empty() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC, new AutofillId[] {}));
+    }
+
+    @Test
+    public void testRequiredIdsBuilder_nullEntry() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+                        new AutofillId[] { null }));
+    }
+
+    @Test
+    public void testBuild_noOptionalIds() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC);
+        assertThrows(IllegalStateException.class, ()-> builder.build());
+    }
+
+    @Test
+    public void testSetOptionalIds_null() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+                new AutofillId[] { new AutofillId(42) });
+        assertThrows(IllegalArgumentException.class, ()-> builder.setOptionalIds(null));
+    }
+
+    @Test
+    public void testSetOptional_empty() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+                new AutofillId[] { new AutofillId(42) });
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setOptionalIds(new AutofillId[] {}));
+    }
+
+    @Test
+    public void testSetOptional_nullEntry() {
+        final SaveInfo.Builder builder = new SaveInfo.Builder(SaveInfo.SAVE_DATA_TYPE_GENERIC,
+                new AutofillId[] { new AutofillId(42) });
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.setOptionalIds(new AutofillId[] { null }));
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleRegexValidatorTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleRegexValidatorTest.java
index b252668..9a32c1f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleRegexValidatorTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleRegexValidatorTest.java
@@ -20,6 +20,7 @@
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
 
 import android.service.autofill.SimpleRegexValidator;
 import android.service.autofill.ValueFinder;
@@ -33,24 +34,27 @@
 
 @RunWith(AndroidJUnit4.class)
 public class SimpleRegexValidatorTest {
-    @Test(expected = NullPointerException.class)
+
+    @Test
     public void allNullConstructor() {
-        new SimpleRegexValidator(null, null);
+        assertThrows(NullPointerException.class, () -> new SimpleRegexValidator(null, null));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void nullRegexConstructor() {
-        new SimpleRegexValidator(new AutofillId(1), null);
+        assertThrows(NullPointerException.class,
+                () -> new SimpleRegexValidator(new AutofillId(1), null));
     }
 
-    @Test(expected = NullPointerException.class)
+    @Test
     public void nullAutofillIdConstructor() {
-        new SimpleRegexValidator(null, ".");
+        assertThrows(NullPointerException.class, () -> new SimpleRegexValidator(null, "."));
     }
 
-    @Test(expected = PatternSyntaxException.class)
+    @Test
     public void badRegexBuilder() {
-        new SimpleRegexValidator(new AutofillId(1), "(");
+        assertThrows(PatternSyntaxException.class,
+                () -> new SimpleRegexValidator(new AutofillId(1), "("));
     }
 
     @Test