Copy libcore.utils.HexEncoding into Wifi module

@CorePlatformApi's cannot be depended on by Wifi
Mainline module. Thus create a copy and migrate
usages over.

Bug: 145409537
Test: atest FrameworksWifiApiTests
Change-Id: If4133bdd9ac9ca005538a03592462d2e1365c0d8
diff --git a/wifi/java/android/net/wifi/aware/PublishConfig.java b/wifi/java/android/net/wifi/aware/PublishConfig.java
index 1886b7e..a8844c1 100644
--- a/wifi/java/android/net/wifi/aware/PublishConfig.java
+++ b/wifi/java/android/net/wifi/aware/PublishConfig.java
@@ -19,11 +19,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.wifi.util.HexEncoding;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import libcore.util.HexEncoding;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
diff --git a/wifi/java/android/net/wifi/aware/SubscribeConfig.java b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
index f0f7581..76780f4 100644
--- a/wifi/java/android/net/wifi/aware/SubscribeConfig.java
+++ b/wifi/java/android/net/wifi/aware/SubscribeConfig.java
@@ -19,11 +19,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.wifi.util.HexEncoding;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import libcore.util.HexEncoding;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.charset.StandardCharsets;
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
index 5ec4c8b..c667334 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareAgentNetworkSpecifier.java
@@ -17,12 +17,11 @@
 package android.net.wifi.aware;
 
 import android.net.NetworkSpecifier;
+import android.net.wifi.util.HexEncoding;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Log;
 
-import libcore.util.HexEncoding;
-
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.Arrays;
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 7b37d65..65a6c4d 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -26,6 +26,7 @@
 import android.net.ConnectivityManager;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
+import android.net.wifi.util.HexEncoding;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -36,8 +37,6 @@
 import android.os.RemoteException;
 import android.util.Log;
 
-import libcore.util.HexEncoding;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
diff --git a/wifi/java/android/net/wifi/util/HexEncoding.java b/wifi/java/android/net/wifi/util/HexEncoding.java
new file mode 100644
index 0000000..9ebf947
--- /dev/null
+++ b/wifi/java/android/net/wifi/util/HexEncoding.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2014 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.net.wifi.util;
+
+/**
+ * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
+ *
+ * Note: this is copied from {@link libcore.util.HexEncoding}.
+ *
+ * @hide
+ */
+public class HexEncoding {
+
+    private static final char[] LOWER_CASE_DIGITS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    private static final char[] UPPER_CASE_DIGITS = {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    /** Hidden constructor to prevent instantiation. */
+    private HexEncoding() {}
+
+    /**
+     * Encodes the provided byte as a two-digit hexadecimal String value.
+     */
+    public static String encodeToString(byte b, boolean upperCase) {
+        char[] digits = upperCase ? UPPER_CASE_DIGITS : LOWER_CASE_DIGITS;
+        char[] buf = new char[2]; // We always want two digits.
+        buf[0] = digits[(b >> 4) & 0xf];
+        buf[1] = digits[b & 0xf];
+        return new String(buf, 0, 2);
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static char[] encode(byte[] data) {
+        return encode(data, 0, data.length, true /* upperCase */);
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static char[] encode(byte[] data, boolean upperCase) {
+        return encode(data, 0, data.length, upperCase);
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static char[] encode(byte[] data, int offset, int len) {
+        return encode(data, offset, len, true /* upperCase */);
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    private static char[] encode(byte[] data, int offset, int len, boolean upperCase) {
+        char[] digits = upperCase ? UPPER_CASE_DIGITS : LOWER_CASE_DIGITS;
+        char[] result = new char[len * 2];
+        for (int i = 0; i < len; i++) {
+            byte b = data[offset + i];
+            int resultIndex = 2 * i;
+            result[resultIndex] = (digits[(b >> 4) & 0x0f]);
+            result[resultIndex + 1] = (digits[b & 0x0f]);
+        }
+
+        return result;
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static String encodeToString(byte[] data) {
+        return encodeToString(data, true /* upperCase */);
+    }
+
+    /**
+     * Encodes the provided data as a sequence of hexadecimal characters.
+     */
+    public static String encodeToString(byte[] data, boolean upperCase) {
+        return new String(encode(data, upperCase));
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array.  Odd-length inputs
+     * are not allowed.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(String encoded) throws IllegalArgumentException {
+        return decode(encoded.toCharArray());
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
+     * is {@code true} odd-length inputs are allowed and the first character is interpreted
+     * as the lower bits of the first result byte.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(String encoded, boolean allowSingleChar)
+            throws IllegalArgumentException {
+        return decode(encoded.toCharArray(), allowSingleChar);
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array.  Odd-length inputs
+     * are not allowed.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(char[] encoded) throws IllegalArgumentException {
+        return decode(encoded, false);
+    }
+
+    /**
+     * Decodes the provided hexadecimal string into a byte array. If {@code allowSingleChar}
+     * is {@code true} odd-length inputs are allowed and the first character is interpreted
+     * as the lower bits of the first result byte.
+     *
+     * Throws an {@code IllegalArgumentException} if the input is malformed.
+     */
+    public static byte[] decode(char[] encoded, boolean allowSingleChar)
+            throws IllegalArgumentException {
+        int encodedLength = encoded.length;
+        int resultLengthBytes = (encodedLength + 1) / 2;
+        byte[] result = new byte[resultLengthBytes];
+
+        int resultOffset = 0;
+        int i = 0;
+        if (allowSingleChar) {
+            if ((encodedLength % 2) != 0) {
+                // Odd number of digits -- the first digit is the lower 4 bits of the first result
+                // byte.
+                result[resultOffset++] = (byte) toDigit(encoded, i);
+                i++;
+            }
+        } else {
+            if ((encodedLength % 2) != 0) {
+                throw new IllegalArgumentException("Invalid input length: " + encodedLength);
+            }
+        }
+
+        for (; i < encodedLength; i += 2) {
+            result[resultOffset++] = (byte) ((toDigit(encoded, i) << 4) | toDigit(encoded, i + 1));
+        }
+
+        return result;
+    }
+
+    private static int toDigit(char[] str, int offset) throws IllegalArgumentException {
+        // NOTE: that this isn't really a code point in the traditional sense, since we're
+        // just rejecting surrogate pairs outright.
+        int pseudoCodePoint = str[offset];
+
+        if ('0' <= pseudoCodePoint && pseudoCodePoint <= '9') {
+            return pseudoCodePoint - '0';
+        } else if ('a' <= pseudoCodePoint && pseudoCodePoint <= 'f') {
+            return 10 + (pseudoCodePoint - 'a');
+        } else if ('A' <= pseudoCodePoint && pseudoCodePoint <= 'F') {
+            return 10 + (pseudoCodePoint - 'A');
+        }
+
+        throw new IllegalArgumentException("Illegal char: " + str[offset] + " at offset " + offset);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index 3483ff8..200c0e3 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -36,6 +36,7 @@
 import android.content.pm.PackageManager;
 import android.net.MacAddress;
 import android.net.wifi.RttManager;
+import android.net.wifi.util.HexEncoding;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -44,8 +45,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import libcore.util.HexEncoding;
-
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/wifi/tests/src/android/net/wifi/util/HexEncodingTest.java b/wifi/tests/src/android/net/wifi/util/HexEncodingTest.java
new file mode 100644
index 0000000..0d75138
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/util/HexEncodingTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 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.net.wifi.util;
+
+import static android.net.wifi.util.HexEncoding.decode;
+import static android.net.wifi.util.HexEncoding.encode;
+import static android.net.wifi.util.HexEncoding.encodeToString;
+
+import junit.framework.TestCase;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Locale;
+
+/** Copied from {@link libcore.libcore.util.HexEncodingTest}. */
+public class HexEncodingTest extends TestCase {
+
+    public void testEncodeByte() {
+        Object[][] testCases = new Object[][]{
+                {0x01, "01"},
+                {0x09, "09"},
+                {0x0A, "0A"},
+                {0x0F, "0F"},
+                {0x10, "10"},
+                {0x1F, "1F"},
+                {0x20, "20"},
+                {0x7F, "7F"},
+                {0x80, "80"},
+                {0xFF, "FF"},
+        };
+        for (Object[] testCase : testCases) {
+            Number toEncode = (Number) testCase[0];
+            String expected = (String) testCase[1];
+
+            String actualUpper = encodeToString(toEncode.byteValue(), true /* upperCase */);
+            assertEquals(upper(expected), actualUpper);
+
+            String actualLower = encodeToString(toEncode.byteValue(), false /* upperCase */);
+            assertEquals(lower(expected), actualLower);
+        }
+    }
+
+    public void testEncodeBytes() {
+        Object[][] testCases = new Object[][]{
+                {"avocados".getBytes(StandardCharsets.UTF_8), "61766F6361646F73"},
+        };
+
+        for (Object[] testCase : testCases) {
+            byte[] bytes = (byte[]) testCase[0];
+            String encodedLower = lower((String) testCase[1]);
+            String encodedUpper = upper((String) testCase[1]);
+
+            assertArraysEqual(encodedUpper.toCharArray(), encode(bytes));
+            assertArraysEqual(encodedUpper.toCharArray(), encode(bytes, true /* upperCase */));
+            assertArraysEqual(encodedLower.toCharArray(), encode(bytes, false /* upperCase */));
+
+            assertArraysEqual(bytes, decode(encode(bytes), false /* allowSingleChar */));
+
+            // Make sure we can handle lower case hex encodings as well.
+            assertArraysEqual(bytes,
+                    decode(encodedLower.toCharArray(), false /* allowSingleChar */));
+        }
+    }
+
+    public void testDecode_allow4Bit() {
+        assertArraysEqual(new byte[]{6}, decode("6".toCharArray(), true));
+        assertArraysEqual(new byte[]{6, 0x76}, decode("676".toCharArray(), true));
+    }
+
+    public void testDecode_disallow4Bit() {
+        try {
+            decode("676".toCharArray(), false /* allowSingleChar */);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    public void testDecode_invalid() {
+        try {
+            decode("DEADBARD".toCharArray(), false /* allowSingleChar */);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        // This demonstrates a difference in behaviour from apache commons : apache
+        // commons uses Character.isDigit and would successfully decode a string with
+        // arabic and devanagari characters.
+        try {
+            decode("६१٧٥٥F6361646F73".toCharArray(), false /* allowSingleChar */);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        try {
+            decode("#%6361646F73".toCharArray(), false /* allowSingleChar */);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    private static void assertArraysEqual(char[] lhs, char[] rhs) {
+        assertEquals(new String(lhs), new String(rhs));
+    }
+
+    private static void assertArraysEqual(byte[] lhs, byte[] rhs) {
+        assertEquals(Arrays.toString(lhs), Arrays.toString(rhs));
+    }
+
+    private static String lower(String string) {
+        return string.toLowerCase(Locale.ROOT);
+    }
+
+    private static String upper(String string) {
+        return string.toUpperCase(Locale.ROOT);
+    }
+}