Add telephony-common-testing

This is a library for testing related logic used by telephony unit and
CTS tests.

Bug: 154363919
Test: atest TeleServiceTests:SimPhonebookProviderTest
Change-Id: I584150dd8352dbb49d70ea28c6d064a6336d018d
diff --git a/testing/Android.bp b/testing/Android.bp
new file mode 100644
index 0000000..527abc8
--- /dev/null
+++ b/testing/Android.bp
@@ -0,0 +1,23 @@
+android_library {
+    name: "telephony-common-testing",
+
+    srcs: ["**/*.java"],
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "guava",
+        "junit",
+        "mockito-target-minus-junit4",
+        "telephony-common",
+        "truth-prebuilt",
+    ],
+
+    sdk_version: "test_current",
+
+    visibility: [
+        "//cts/tests/tests/simphonebookprovider",
+        "//cts/tests/tests/simphonebookprovider/nosim",
+        "//frameworks/opt/telephony/tests",
+        "//packages/services/Telephony/tests",
+    ],
+}
diff --git a/testing/AndroidManifest.xml b/testing/AndroidManifest.xml
new file mode 100644
index 0000000..6be665b
--- /dev/null
+++ b/testing/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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.telephony.testing">
+
+    <application>
+
+    </application>
+
+</manifest>
+
diff --git a/testing/src/com/android/internal/telephony/testing/CursorSubject.java b/testing/src/com/android/internal/telephony/testing/CursorSubject.java
new file mode 100644
index 0000000..999e92c
--- /dev/null
+++ b/testing/src/com/android/internal/telephony/testing/CursorSubject.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2020 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.internal.telephony.testing;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.CursorWrapper;
+import android.database.DatabaseUtils;
+
+import androidx.annotation.Nullable;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.IterableSubject;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Truth subject for making assertions about {@link Cursor}s. */
+public final class CursorSubject extends Subject {
+
+    private final Cursor mActual;
+
+    private CursorSubject(FailureMetadata metadata, @Nullable Cursor actual) {
+        super(metadata, new StringableCursor(actual));
+        this.mActual = new StringableCursor(actual);
+    }
+
+    /** Returns the factory for this subject. */
+    public static Factory<CursorSubject, Cursor> cursors() {
+        return CursorSubject::new;
+    }
+
+    /** Starts an assertion. */
+    public static CursorSubject assertThat(Cursor cursor) {
+        return assertAbout(cursors()).that(cursor);
+    }
+
+    /** Asserts {@link Cursor#getCount()} has the specified value. */
+    public void hasCount(int count) {
+        check("getCount()").that(mActual.getCount()).isEqualTo(count);
+    }
+
+    /** Asserts {@link Cursor#getColumnNames()} match those specified. */
+    public void hasColumnNames(String... columnNames) {
+        check("getColumnNames()").that(mActual.getColumnNames()).asList()
+                .containsExactlyElementsIn(columnNames).inOrder();
+    }
+
+    /** Positions the cursor under test at the specified row to make an assertion about it. */
+    public CursorSubject atRow(int position) {
+        check("moveToPosition").that(mActual.moveToPosition(position)).isTrue();
+        return this;
+    }
+
+    /** Asserts that the row at the cursor's current position has the specified values. */
+    public CursorSubject hasRowValues(Object... values) {
+        check("getColumnCount()").that(mActual.getColumnCount()).isEqualTo(values.length);
+        ContentValues expectedValues = new ContentValues();
+        for (int i = 0; i < values.length; i++) {
+            expectedValues.put(mActual.getColumnName(i), values[i].toString());
+        }
+
+        ContentValues actualValues = new ContentValues();
+        DatabaseUtils.cursorRowToContentValues(mActual, actualValues);
+
+        check("Row: %s", mActual.getPosition()).that(actualValues).isEqualTo(expectedValues);
+        return this;
+    }
+
+    /** Asserts that the cursor has a single row with the specified values. */
+    public void hasSingleRow(Object... values) {
+        hasCount(1);
+        atRow(0).hasRowValues(values);
+    }
+
+    /**
+     * Asserts that the row at the cursor's current position has the specified value for the
+     * specified column.
+     */
+    public CursorSubject hasRowValue(String columnName, Object value) {
+        int index = mActual.getColumnIndex(columnName);
+        check("getColumnIndex()").that(index).isNotEqualTo(-1);
+
+        check("Row[%s]: %s", columnName, index).that(mActual.getString(index))
+                .isEqualTo(value.toString());
+        return this;
+    }
+
+    /** Starts an assertion about the value of the specified column for the current row. */
+    public IntegerSubject intField(String columnName) {
+        int index = mActual.getColumnIndex(columnName);
+        check("getColumnIndex()").that(index).isNotEqualTo(-1);
+        check("getType()").that(mActual.getType(index)).isEqualTo(Cursor.FIELD_TYPE_INTEGER);
+
+        return check("getInt()").that(mActual.getInt(index));
+    }
+
+    /** Starts an assertion about the value of the specified column for the current row. */
+    public StringSubject stringField(String columnName) {
+        int index = mActual.getColumnIndex(columnName);
+        check("getColumnIndex()").that(index).isNotEqualTo(-1);
+        check("getType()").that(mActual.getType(index)).isEqualTo(Cursor.FIELD_TYPE_STRING);
+
+        return check("getString()").that(mActual.getString(index));
+    }
+
+    /** Asserts that the cursor rows match the data specified. */
+    public void hasData(Object[][] rows) {
+        hasCount(rows.length);
+        for (int i = 0; i < rows.length; i++) {
+            atRow(i).hasRowValues(rows[i]);
+        }
+    }
+
+    /** Starts an assertion about the cursor's rows. */
+    public IterableSubject asLists() {
+        List<List<String>> result = new ArrayList<>();
+        mActual.moveToPosition(-1);
+        while (mActual.moveToNext()) {
+            List<String> row = new ArrayList<>();
+            for (int i = 0; i < mActual.getColumnCount(); i++) {
+                row.add(mActual.getString(i));
+            }
+            result.add(row);
+        }
+        return Truth.assertThat(result);
+    }
+
+    private static class StringableCursor extends CursorWrapper {
+
+        StringableCursor(Cursor cursor) {
+            super(cursor);
+        }
+
+        @Override
+        public String toString() {
+            return DatabaseUtils.dumpCursorToString(getWrappedCursor());
+        }
+    }
+
+}
diff --git a/testing/src/com/android/internal/telephony/testing/TelephonyAssertions.java b/testing/src/com/android/internal/telephony/testing/TelephonyAssertions.java
new file mode 100644
index 0000000..d18a5cf
--- /dev/null
+++ b/testing/src/com/android/internal/telephony/testing/TelephonyAssertions.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 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.internal.telephony.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Assert;
+
+/** Assertions used by telephony tests. */
+public class TelephonyAssertions {
+
+    /**
+     * Asserts that the provided action throws the specified exception.
+     *
+     * <p>TODO: Replace with org.junit.Assert.assertThrows when Android upgrades to JUnit 4.12
+     *
+     * @return the exception that was thrown when the assertion passes.
+     */
+    public static <T extends Throwable> T assertThrows(
+            Class<T> throwableClass, ThrowingRunnable action) {
+        try {
+            action.run();
+        } catch (Throwable t) {
+            assertThat(t).isInstanceOf(throwableClass);
+            return throwableClass.cast(t);
+        }
+        Assert.fail("Expected " + throwableClass.getSimpleName() + " but no exception was thrown");
+        // This is unreachable but needed to compile.
+        return null;
+    }
+
+    /** Runnable that can throw a checked exception. */
+    public interface ThrowingRunnable {
+        /** Method with code that may throw a checked exception. */
+        void run() throws Exception;
+    }
+}