Add coretest for android.view.InputDevice.

Add coretest cases for android.view.InputDevice, for parcel/unparcel
input device object.

Bug: 173326051
Test: atest InputDeviceTest
Change-Id: I5dea2783b11a195dc2eb8337aad77368b98cf068
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 02a9788..aa1acc1 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.input.InputManager;
 import android.os.Build;
@@ -25,6 +26,8 @@
 import android.util.AndroidRuntimeException;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.text.Normalizer;
 
 /**
@@ -297,6 +300,8 @@
     private static native char nativeGetDisplayLabel(long ptr, int keyCode);
     private static native int nativeGetKeyboardType(long ptr);
     private static native KeyEvent[] nativeGetEvents(long ptr, char[] chars);
+    private static native KeyCharacterMap nativeObtainEmptyKeyCharacterMap(int deviceId);
+    private static native boolean nativeEquals(long ptr1, long ptr2);
 
     private KeyCharacterMap(Parcel in) {
         if (in == null) {
@@ -323,6 +328,18 @@
     }
 
     /**
+     * Obtain empty key character map
+     * @param deviceId The input device ID
+     * @return The KeyCharacterMap object
+     * @hide
+     */
+    @VisibleForTesting
+    @Nullable
+    public static KeyCharacterMap obtainEmptyMap(int deviceId) {
+        return nativeObtainEmptyKeyCharacterMap(deviceId);
+    }
+
+    /**
      * Loads the key character maps for the keyboard with the specified device id.
      *
      * @param deviceId The device id of the keyboard.
@@ -729,6 +746,18 @@
         return 0;
     }
 
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof KeyCharacterMap)) {
+            return false;
+        }
+        KeyCharacterMap peer = (KeyCharacterMap) obj;
+        if (mPtr == 0 || peer.mPtr == 0) {
+            return mPtr == peer.mPtr;
+        }
+        return nativeEquals(mPtr, peer.mPtr);
+    }
+
     /**
      * Thrown by {@link KeyCharacterMap#load} when a key character map could not be loaded.
      */
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index ebc507a..469e577 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -16,9 +16,10 @@
 
 #include <android_runtime/AndroidRuntime.h>
 
-#include <input/KeyCharacterMap.h>
-#include <input/Input.h>
 #include <binder/Parcel.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/KeyCharacterMap.h>
 
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
@@ -75,6 +76,10 @@
                           reinterpret_cast<jlong>(nativeMap));
 }
 
+static jobject nativeObtainEmptyKeyCharacterMap(JNIEnv* env, jobject /* clazz */, jint deviceId) {
+    return android_view_KeyCharacterMap_create(env, deviceId, nullptr);
+}
+
 static jlong nativeReadFromParcel(JNIEnv *env, jobject clazz, jobject parcelObj) {
     Parcel* parcel = parcelForJavaObject(env, parcelObj);
     if (!parcel) {
@@ -224,33 +229,37 @@
     return result;
 }
 
+static jboolean nativeEquals(JNIEnv* env, jobject clazz, jlong ptr1, jlong ptr2) {
+    const std::shared_ptr<KeyCharacterMap>& map1 =
+            (reinterpret_cast<NativeKeyCharacterMap*>(ptr1))->getMap();
+    const std::shared_ptr<KeyCharacterMap>& map2 =
+            (reinterpret_cast<NativeKeyCharacterMap*>(ptr2))->getMap();
+    if (map1 == nullptr || map2 == nullptr) {
+        return map1 == map2;
+    }
+    return static_cast<jboolean>(*map1 == *map2);
+}
 
 /*
  * JNI registration.
  */
 
 static const JNINativeMethod g_methods[] = {
-    /* name, signature, funcPtr */
-    { "nativeReadFromParcel", "(Landroid/os/Parcel;)J",
-            (void*)nativeReadFromParcel },
-    { "nativeWriteToParcel", "(JLandroid/os/Parcel;)V",
-            (void*)nativeWriteToParcel },
-    { "nativeDispose", "(J)V",
-            (void*)nativeDispose },
-    { "nativeGetCharacter", "(JII)C",
-            (void*)nativeGetCharacter },
-    { "nativeGetFallbackAction", "(JIILandroid/view/KeyCharacterMap$FallbackAction;)Z",
-            (void*)nativeGetFallbackAction },
-    { "nativeGetNumber", "(JI)C",
-            (void*)nativeGetNumber },
-    { "nativeGetMatch", "(JI[CI)C",
-            (void*)nativeGetMatch },
-    { "nativeGetDisplayLabel", "(JI)C",
-            (void*)nativeGetDisplayLabel },
-    { "nativeGetKeyboardType", "(J)I",
-            (void*)nativeGetKeyboardType },
-    { "nativeGetEvents", "(J[C)[Landroid/view/KeyEvent;",
-            (void*)nativeGetEvents },
+        /* name, signature, funcPtr */
+        {"nativeReadFromParcel", "(Landroid/os/Parcel;)J", (void*)nativeReadFromParcel},
+        {"nativeWriteToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteToParcel},
+        {"nativeDispose", "(J)V", (void*)nativeDispose},
+        {"nativeGetCharacter", "(JII)C", (void*)nativeGetCharacter},
+        {"nativeGetFallbackAction", "(JIILandroid/view/KeyCharacterMap$FallbackAction;)Z",
+         (void*)nativeGetFallbackAction},
+        {"nativeGetNumber", "(JI)C", (void*)nativeGetNumber},
+        {"nativeGetMatch", "(JI[CI)C", (void*)nativeGetMatch},
+        {"nativeGetDisplayLabel", "(JI)C", (void*)nativeGetDisplayLabel},
+        {"nativeGetKeyboardType", "(J)I", (void*)nativeGetKeyboardType},
+        {"nativeGetEvents", "(J[C)[Landroid/view/KeyEvent;", (void*)nativeGetEvents},
+        {"nativeObtainEmptyKeyCharacterMap", "(I)Landroid/view/KeyCharacterMap;",
+         (void*)nativeObtainEmptyKeyCharacterMap},
+        {"nativeEquals", "(JJ)Z", (void*)nativeEquals},
 };
 
 int register_android_view_KeyCharacterMap(JNIEnv* env)
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index 335c8d0..eacf5b2 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -9,14 +9,17 @@
 
 android_test {
     name: "InputTests",
-    srcs: ["src/**/*.kt"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     platform_apis: true,
     certificate: "platform",
     static_libs: [
-            "androidx.test.ext.junit",
-            "androidx.test.rules",
-            "truth-prebuilt",
-            "ub-uiautomator",
-        ],
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "truth-prebuilt",
+        "ub-uiautomator",
+    ],
     test_suites: ["device-tests"],
 }
diff --git a/tests/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java
new file mode 100644
index 0000000..6350077
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 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.view;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputDeviceTest {
+    private static final float DELTA = 0.01f;
+    private static final int DEVICE_ID = 1000;
+
+    private void assertMotionRangeEquals(InputDevice.MotionRange range,
+            InputDevice.MotionRange outRange) {
+        assertEquals(range.getAxis(), outRange.getAxis());
+        assertEquals(range.getSource(), outRange.getSource());
+        assertEquals(range.getMin(), outRange.getMin(), DELTA);
+        assertEquals(range.getMax(), outRange.getMax(), DELTA);
+        assertEquals(range.getFlat(), outRange.getFlat(), DELTA);
+        assertEquals(range.getFuzz(), outRange.getFuzz(), DELTA);
+        assertEquals(range.getResolution(), outRange.getResolution(), DELTA);
+    }
+
+    private void assertDeviceEquals(InputDevice device, InputDevice outDevice) {
+        assertEquals(device.getId(), outDevice.getId());
+        assertEquals(device.getGeneration(), outDevice.getGeneration());
+        assertEquals(device.getControllerNumber(), outDevice.getControllerNumber());
+        assertEquals(device.getName(), outDevice.getName());
+        assertEquals(device.getVendorId(), outDevice.getVendorId());
+        assertEquals(device.getProductId(), outDevice.getProductId());
+        assertEquals(device.getDescriptor(), outDevice.getDescriptor());
+        assertEquals(device.isExternal(), outDevice.isExternal());
+        assertEquals(device.getSources(), outDevice.getSources());
+        assertEquals(device.getKeyboardType(), outDevice.getKeyboardType());
+        assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size());
+
+        KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap();
+        KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap();
+        assertTrue("keyCharacterMap not equal", keyCharacterMap.equals(outKeyCharacterMap));
+
+        for (int j = 0; j < device.getMotionRanges().size(); j++) {
+            assertMotionRangeEquals(device.getMotionRanges().get(j),
+                    outDevice.getMotionRanges().get(j));
+        }
+    }
+
+    private void assertInputDeviceParcelUnparcel(KeyCharacterMap keyCharacterMap) {
+        final InputDevice device =
+                new InputDevice(DEVICE_ID, 0 /* generation */, 0 /* controllerNumber */, "name",
+                0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */,
+                0 /* sources */, 0 /* keyboardType */, keyCharacterMap,
+                false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */,
+                true /* hasSensor */, false /* hasBattery */);
+
+        Parcel parcel = Parcel.obtain();
+        device.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        InputDevice outDevice = InputDevice.CREATOR.createFromParcel(parcel);
+        assertDeviceEquals(device, outDevice);
+    }
+
+    @Test
+    public void testParcelUnparcelInputDevice_VirtualCharacterMap() {
+        final KeyCharacterMap keyCharacterMap =
+                KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+        assertInputDeviceParcelUnparcel(keyCharacterMap);
+    }
+
+    @Test
+    public void testParcelUnparcelInputDevice_EmptyCharacterMap() {
+        final KeyCharacterMap keyCharacterMap = KeyCharacterMap.obtainEmptyMap(DEVICE_ID);
+        assertInputDeviceParcelUnparcel(keyCharacterMap);
+    }
+}