Merge "Move NativeConverter and java.nio.charset into core-icu4j"
diff --git a/android_icu4j/api/intra/current-api.txt b/android_icu4j/api/intra/current-api.txt
index 4911658..7df0c01 100644
--- a/android_icu4j/api/intra/current-api.txt
+++ b/android_icu4j/api/intra/current-api.txt
@@ -181,6 +181,15 @@
 
 }
 
+package com.android.icu.charset {
+
+  public final class NativeConverter {
+    method public static java.nio.charset.Charset charsetForName(String);
+    method public static String[] getAvailableCharsetNames();
+  }
+
+}
+
 package com.android.icu.util.regex {
 
   public class NativeMatcher {
diff --git a/android_icu4j/src/main/java/com/android/icu/charset/CharsetDecoderICU.java b/android_icu4j/src/main/java/com/android/icu/charset/CharsetDecoderICU.java
new file mode 100644
index 0000000..6e6aeee
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/charset/CharsetDecoderICU.java
@@ -0,0 +1,217 @@
+/**
+*******************************************************************************
+* Copyright (C) 1996-2006, International Business Machines Corporation and    *
+* others. All Rights Reserved.                                                *
+*******************************************************************************
+*
+*******************************************************************************
+*/
+ /**
+  * A JNI interface for ICU converters.
+  *
+  *
+  * @author Ram Viswanadha, IBM
+  */
+package com.android.icu.charset;
+
+import dalvik.annotation.optimization.ReachabilitySensitive;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import libcore.util.EmptyArray;
+
+final class CharsetDecoderICU extends CharsetDecoder {
+    private static final int MAX_CHARS_PER_BYTE = 2;
+
+    private static final int INPUT_OFFSET = 0;
+    private static final int OUTPUT_OFFSET = 1;
+    private static final int INVALID_BYTE_COUNT = 2;
+    /*
+     * data[INPUT_OFFSET]   = on input contains the start of input and on output the number of input bytes consumed
+     * data[OUTPUT_OFFSET]  = on input contains the start of output and on output the number of output chars written
+     * data[INVALID_BYTE_COUNT]  = number of invalid bytes
+     */
+    private final int[] data = new int[3];
+
+    /* Handle to the ICU converter that is opened, cleaned up via NativeAllocationRegistry. */
+    @ReachabilitySensitive
+    private long converterHandle = 0;
+
+    private byte[] input = null;
+    private char[] output= null;
+
+    private byte[] allocatedInput = null;
+    private char[] allocatedOutput = null;
+
+    // These instance variables are always assigned in the methods before being used. This class
+    // is inherently thread-unsafe so we don't have to worry about synchronization.
+    private int inEnd;
+    private int outEnd;
+
+    public static CharsetDecoderICU newInstance(Charset cs, String icuCanonicalName) {
+        // This complexity is necessary to ensure that even if the constructor, superclass
+        // constructor, or call to updateCallback throw, we still free the native peer.
+        long address = 0;
+        CharsetDecoderICU result;
+        try {
+            address = NativeConverter.openConverter(icuCanonicalName);
+            float averageCharsPerByte = NativeConverter.getAveCharsPerByte(address);
+            result = new CharsetDecoderICU(cs, averageCharsPerByte, address);
+        } catch (Throwable t) {
+            if (address != 0) {
+                NativeConverter.closeConverter(address);
+            }
+            throw t;
+        }
+        // An exception in registerConverter() will deallocate address:
+        NativeConverter.registerConverter(result, address);
+        result.updateCallback();
+        return result;
+    }
+
+    private CharsetDecoderICU(Charset cs, float averageCharsPerByte, long address) {
+        super(cs, averageCharsPerByte, MAX_CHARS_PER_BYTE);
+        this.converterHandle = address;
+    }
+
+    @Override protected void implReplaceWith(String newReplacement) {
+        updateCallback();
+    }
+
+    @Override protected final void implOnMalformedInput(CodingErrorAction newAction) {
+        updateCallback();
+    }
+
+    @Override protected final void implOnUnmappableCharacter(CodingErrorAction newAction) {
+        updateCallback();
+    }
+
+    private void updateCallback() {
+        NativeConverter.setCallbackDecode(converterHandle, this);
+    }
+
+    @Override protected void implReset() {
+        NativeConverter.resetByteToChar(converterHandle);
+        data[INPUT_OFFSET] = 0;
+        data[OUTPUT_OFFSET] = 0;
+        data[INVALID_BYTE_COUNT] = 0;
+        output = null;
+        input = null;
+        allocatedInput = null;
+        allocatedOutput = null;
+        inEnd = 0;
+        outEnd = 0;
+    }
+
+    @Override protected final CoderResult implFlush(CharBuffer out) {
+        try {
+            // ICU needs to see an empty input.
+            input = EmptyArray.BYTE;
+            inEnd = 0;
+            data[INPUT_OFFSET] = 0;
+
+            data[OUTPUT_OFFSET] = getArray(out);
+            data[INVALID_BYTE_COUNT] = 0; // Make sure we don't see earlier errors.
+
+            int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, true);
+            if (NativeConverter.U_FAILURE(error)) {
+                if (error == NativeConverter.U_BUFFER_OVERFLOW_ERROR) {
+                    return CoderResult.OVERFLOW;
+                } else if (error == NativeConverter.U_TRUNCATED_CHAR_FOUND) {
+                    if (data[INVALID_BYTE_COUNT] > 0) {
+                        return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
+                    }
+                }
+            }
+            return CoderResult.UNDERFLOW;
+       } finally {
+            setPosition(out);
+            implReset();
+       }
+    }
+
+    @Override protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
+        if (!in.hasRemaining()) {
+            return CoderResult.UNDERFLOW;
+        }
+
+        data[INPUT_OFFSET] = getArray(in);
+        data[OUTPUT_OFFSET]= getArray(out);
+
+        try {
+            int error = NativeConverter.decode(converterHandle, input, inEnd, output, outEnd, data, false);
+            if (NativeConverter.U_FAILURE(error)) {
+                if (error == NativeConverter.U_BUFFER_OVERFLOW_ERROR) {
+                    return CoderResult.OVERFLOW;
+                } else if (error == NativeConverter.U_INVALID_CHAR_FOUND) {
+                    return CoderResult.unmappableForLength(data[INVALID_BYTE_COUNT]);
+                } else if (error == NativeConverter.U_ILLEGAL_CHAR_FOUND) {
+                    return CoderResult.malformedForLength(data[INVALID_BYTE_COUNT]);
+                } else {
+                    throw new AssertionError(error);
+                }
+            }
+            // Decoding succeeded: give us more data.
+            return CoderResult.UNDERFLOW;
+        } finally {
+            setPosition(in);
+            setPosition(out);
+        }
+    }
+
+
+    private int getArray(CharBuffer out) {
+        if (out.hasArray()) {
+            output = out.array();
+            outEnd = out.arrayOffset() + out.limit();
+            return out.arrayOffset() + out.position();
+        } else {
+            outEnd = out.remaining();
+            if (allocatedOutput == null || outEnd > allocatedOutput.length) {
+                allocatedOutput = new char[outEnd];
+            }
+            // The array's start position is 0.
+            output = allocatedOutput;
+            return 0;
+        }
+    }
+
+    private  int getArray(ByteBuffer in) {
+        if (in.hasArray()) {
+            input = in.array();
+            inEnd = in.arrayOffset() + in.limit();
+            return in.arrayOffset() + in.position();
+        } else {
+            inEnd = in.remaining();
+            if (allocatedInput == null || inEnd > allocatedInput.length) {
+                allocatedInput = new byte[inEnd];
+            }
+            // Copy the input buffer into the allocated array.
+            int pos = in.position();
+            in.get(allocatedInput, 0, inEnd);
+            in.position(pos);
+            // The array's start position is 0.
+            input = allocatedInput;
+            return 0;
+        }
+    }
+
+    private void setPosition(CharBuffer out) {
+        if (out.hasArray()) {
+            out.position(out.position() + data[OUTPUT_OFFSET]);
+        } else {
+            out.put(output, 0, data[OUTPUT_OFFSET]);
+        }
+        // release reference to output array, which may not be ours
+        output = null;
+    }
+
+    private void setPosition(ByteBuffer in) {
+        in.position(in.position() + data[INPUT_OFFSET]);
+        // release reference to input array, which may not be ours
+        input = null;
+    }
+}
diff --git a/android_icu4j/src/main/java/com/android/icu/charset/CharsetEncoderICU.java b/android_icu4j/src/main/java/com/android/icu/charset/CharsetEncoderICU.java
new file mode 100644
index 0000000..23a339f
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/charset/CharsetEncoderICU.java
@@ -0,0 +1,255 @@
+/**
+*******************************************************************************
+* Copyright (C) 1996-2006, International Business Machines Corporation and    *
+* others. All Rights Reserved.                                                  *
+*******************************************************************************
+*
+*******************************************************************************
+*/
+/**
+ * A JNI interface for ICU converters.
+ *
+ *
+ * @author Ram Viswanadha, IBM
+ */
+package com.android.icu.charset;
+
+import dalvik.annotation.optimization.ReachabilitySensitive;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.util.HashMap;
+import java.util.Map;
+import libcore.util.EmptyArray;
+
+final class CharsetEncoderICU extends CharsetEncoder {
+    private static final Map<String, byte[]> DEFAULT_REPLACEMENTS = new HashMap<String, byte[]>();
+    static {
+        // ICU has different default replacements to the RI in some cases. There are many
+        // additional cases, but this covers all the charsets that Java guarantees will be
+        // available, which is where compatibility seems most important. (The RI even uses
+        // the byte corresponding to '?' in ASCII as the replacement byte for charsets where that
+        // byte corresponds to an entirely different character.)
+        // It's odd that UTF-8 doesn't use U+FFFD, given that (unlike ISO-8859-1 and US-ASCII) it
+        // can represent it, but this is what the RI does...
+        byte[] questionMark = new byte[] { (byte) '?' };
+        DEFAULT_REPLACEMENTS.put("UTF-8",      questionMark);
+        DEFAULT_REPLACEMENTS.put("ISO-8859-1", questionMark);
+        DEFAULT_REPLACEMENTS.put("US-ASCII",   questionMark);
+    }
+
+    private static final int INPUT_OFFSET = 0;
+    private static final int OUTPUT_OFFSET = 1;
+    private static final int INVALID_CHAR_COUNT = 2;
+    /*
+     * data[INPUT_OFFSET]   = on input contains the start of input and on output the number of input chars consumed
+     * data[OUTPUT_OFFSET]  = on input contains the start of output and on output the number of output bytes written
+     * data[INVALID_CHARS]  = number of invalid chars
+     */
+    private int[] data = new int[3];
+
+    /* handle to the ICU converter that is opened */
+    @ReachabilitySensitive
+    private final long converterHandle;
+
+    private char[] input = null;
+    private byte[] output = null;
+
+    private char[] allocatedInput = null;
+    private byte[] allocatedOutput = null;
+
+    // These instance variables are always assigned in the methods before being used. This class
+    // is inherently thread-unsafe so we don't have to worry about synchronization.
+    private int inEnd;
+    private int outEnd;
+
+    public static CharsetEncoderICU newInstance(Charset cs, String icuCanonicalName) {
+        // This complexity is necessary to ensure that even if the constructor, superclass
+        // constructor, or call to updateCallback throw, we still free the native peer.
+        long address = 0;
+        CharsetEncoderICU result;
+        try {
+            address = NativeConverter.openConverter(icuCanonicalName);
+            float averageBytesPerChar = NativeConverter.getAveBytesPerChar(address);
+            float maxBytesPerChar = NativeConverter.getMaxBytesPerChar(address);
+            byte[] replacement = makeReplacement(icuCanonicalName, address);
+            result = new CharsetEncoderICU(cs, averageBytesPerChar, maxBytesPerChar, replacement, address);
+        } catch (Throwable t) {
+            if (address != 0) {
+                NativeConverter.closeConverter(address);
+            }
+            throw t;
+        }
+        // An exception in registerConverter() will deallocate address:
+        NativeConverter.registerConverter(result, address);
+        result.updateCallback();
+        return result;
+    }
+
+    private static byte[] makeReplacement(String icuCanonicalName, long address) {
+        // We have our own map of RI-compatible default replacements (where ICU disagrees)...
+        byte[] replacement = DEFAULT_REPLACEMENTS.get(icuCanonicalName);
+        if (replacement != null) {
+            return replacement.clone();
+        }
+        // ...but fall back to asking ICU.
+        return NativeConverter.getSubstitutionBytes(address);
+    }
+
+    private CharsetEncoderICU(Charset cs, float averageBytesPerChar, float maxBytesPerChar, byte[] replacement, long address) {
+        super(cs, averageBytesPerChar, maxBytesPerChar, replacement, true);
+        // Our native peer needs to know what just happened...
+        this.converterHandle = address;
+    }
+
+    @Override protected void implReplaceWith(byte[] newReplacement) {
+        updateCallback();
+    }
+
+    @Override protected void implOnMalformedInput(CodingErrorAction newAction) {
+        updateCallback();
+    }
+
+    @Override protected void implOnUnmappableCharacter(CodingErrorAction newAction) {
+        updateCallback();
+    }
+
+    private void updateCallback() {
+        NativeConverter.setCallbackEncode(converterHandle, this);
+    }
+
+    @Override protected void implReset() {
+        NativeConverter.resetCharToByte(converterHandle);
+        data[INPUT_OFFSET] = 0;
+        data[OUTPUT_OFFSET] = 0;
+        data[INVALID_CHAR_COUNT] = 0;
+        output = null;
+        input = null;
+        allocatedInput = null;
+        allocatedOutput = null;
+        inEnd = 0;
+        outEnd = 0;
+    }
+
+    @Override protected CoderResult implFlush(ByteBuffer out) {
+        try {
+            // ICU needs to see an empty input.
+            input = EmptyArray.CHAR;
+            inEnd = 0;
+            data[INPUT_OFFSET] = 0;
+
+            data[OUTPUT_OFFSET] = getArray(out);
+            data[INVALID_CHAR_COUNT] = 0; // Make sure we don't see earlier errors.
+
+            int error = NativeConverter.encode(converterHandle, input, inEnd, output, outEnd, data, true);
+            if (NativeConverter.U_FAILURE(error)) {
+                if (error == NativeConverter.U_BUFFER_OVERFLOW_ERROR) {
+                    return CoderResult.OVERFLOW;
+                } else if (error == NativeConverter.U_TRUNCATED_CHAR_FOUND) {
+                    if (data[INVALID_CHAR_COUNT] > 0) {
+                        return CoderResult.malformedForLength(data[INVALID_CHAR_COUNT]);
+                    }
+                }
+            }
+            return CoderResult.UNDERFLOW;
+        } finally {
+            setPosition(out);
+            implReset();
+        }
+    }
+
+    @Override protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
+        if (!in.hasRemaining()) {
+            return CoderResult.UNDERFLOW;
+        }
+
+        data[INPUT_OFFSET] = getArray(in);
+        data[OUTPUT_OFFSET]= getArray(out);
+        data[INVALID_CHAR_COUNT] = 0; // Make sure we don't see earlier errors.
+
+        try {
+            int error = NativeConverter.encode(converterHandle, input, inEnd, output, outEnd, data, false);
+            if (NativeConverter.U_FAILURE(error)) {
+                if (error == NativeConverter.U_BUFFER_OVERFLOW_ERROR) {
+                    return CoderResult.OVERFLOW;
+                } else if (error == NativeConverter.U_INVALID_CHAR_FOUND) {
+                    return CoderResult.unmappableForLength(data[INVALID_CHAR_COUNT]);
+                } else if (error == NativeConverter.U_ILLEGAL_CHAR_FOUND) {
+                    return CoderResult.malformedForLength(data[INVALID_CHAR_COUNT]);
+                } else {
+                    throw new AssertionError(error);
+                }
+            }
+            // Decoding succeeded: give us more data.
+            return CoderResult.UNDERFLOW;
+        } finally {
+            setPosition(in);
+            setPosition(out);
+        }
+    }
+
+    private int getArray(ByteBuffer out) {
+        if (out.hasArray()) {
+            output = out.array();
+            outEnd = out.arrayOffset() + out.limit();
+            return out.arrayOffset() + out.position();
+        } else {
+            outEnd = out.remaining();
+            if (allocatedOutput == null || outEnd > allocatedOutput.length) {
+                allocatedOutput = new byte[outEnd];
+            }
+            // The array's start position is 0
+            output = allocatedOutput;
+            return 0;
+        }
+    }
+
+    private int getArray(CharBuffer in) {
+        if (in.hasArray()) {
+            input = in.array();
+            inEnd = in.arrayOffset() + in.limit();
+            return in.arrayOffset() + in.position();
+        } else {
+            inEnd = in.remaining();
+            if (allocatedInput == null || inEnd > allocatedInput.length) {
+                allocatedInput = new char[inEnd];
+            }
+            // Copy the input buffer into the allocated array.
+            int pos = in.position();
+            in.get(allocatedInput, 0, inEnd);
+            in.position(pos);
+            // The array's start position is 0
+            input = allocatedInput;
+            return 0;
+        }
+    }
+
+    private void setPosition(ByteBuffer out) {
+        if (out.hasArray()) {
+            out.position(data[OUTPUT_OFFSET] - out.arrayOffset());
+        } else {
+            out.put(output, 0, data[OUTPUT_OFFSET]);
+        }
+        // release reference to output array, which may not be ours
+        output = null;
+    }
+
+    private void setPosition(CharBuffer in) {
+        int position = in.position() + data[INPUT_OFFSET] - data[INVALID_CHAR_COUNT];
+        if (position < 0) {
+            // The calculated position might be negative if we encountered an
+            // invalid char that spanned input buffers. We adjust it to 0 in this case.
+            //
+            // NOTE: The API doesn't allow us to adjust the position of the previous
+            // input buffer. (Doing that wouldn't serve any useful purpose anyway.)
+            position = 0;
+        }
+
+        in.position(position);
+        // release reference to input array, which may not be ours
+        input = null;
+    }
+}
diff --git a/android_icu4j/src/main/java/com/android/icu/charset/CharsetICU.java b/android_icu4j/src/main/java/com/android/icu/charset/CharsetICU.java
new file mode 100644
index 0000000..5ca6dcf
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/charset/CharsetICU.java
@@ -0,0 +1,43 @@
+/**
+*******************************************************************************
+* Copyright (C) 1996-2005, International Business Machines Corporation and    *
+* others. All Rights Reserved.                                                *
+*******************************************************************************
+*
+*******************************************************************************
+*/
+
+package com.android.icu.charset;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+/**
+ * This class is used from native code associated with {@link NativeConverter}.
+ */
+final class CharsetICU extends Charset {
+    private final String icuCanonicalName;
+
+    protected CharsetICU(String canonicalName, String icuCanonName, String[] aliases) {
+         super(canonicalName, aliases);
+         icuCanonicalName = icuCanonName;
+    }
+
+    public CharsetDecoder newDecoder() {
+        return CharsetDecoderICU.newInstance(this, icuCanonicalName);
+    }
+
+    public CharsetEncoder newEncoder() {
+        return CharsetEncoderICU.newInstance(this, icuCanonicalName);
+    }
+
+    public boolean contains(Charset cs) {
+        if (cs == null) {
+            return false;
+        } else if (this.equals(cs)) {
+            return true;
+        }
+        return NativeConverter.contains(this.name(), cs.name());
+    }
+}
diff --git a/android_icu4j/src/main/java/com/android/icu/charset/NativeConverter.java b/android_icu4j/src/main/java/com/android/icu/charset/NativeConverter.java
new file mode 100644
index 0000000..f6d1e64
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/charset/NativeConverter.java
@@ -0,0 +1,96 @@
+/**
+*******************************************************************************
+* Copyright (C) 1996-2006, International Business Machines Corporation and    *
+* others. All Rights Reserved.                                                *
+*******************************************************************************
+*
+*******************************************************************************
+*/
+
+package com.android.icu.charset;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CodingErrorAction;
+
+public final class NativeConverter {
+
+    private static final NativeAllocationRegistry registry = new NativeAllocationRegistry(
+            NativeConverter.class.getClassLoader(), getNativeFinalizer(), getNativeSize());
+
+    public static native int decode(long converterHandle, byte[] input, int inEnd,
+            char[] output, int outEnd, int[] data, boolean flush);
+
+    public static native int encode(long converterHandle, char[] input, int inEnd,
+            byte[] output, int outEnd, int[] data, boolean flush);
+
+    public static native long openConverter(String charsetName);
+    public static native void closeConverter(long converterHandle);
+
+    public static void registerConverter(Object referrent, long converterHandle) {
+        registry.registerNativeAllocation(referrent, converterHandle);
+    }
+
+    public static native void resetByteToChar(long converterHandle);
+    public static native void resetCharToByte(long converterHandle);
+
+    public static native byte[] getSubstitutionBytes(long converterHandle);
+
+    public static native int getMaxBytesPerChar(long converterHandle);
+    public static native float getAveBytesPerChar(long converterHandle);
+    public static native float getAveCharsPerByte(long converterHandle);
+
+    public static native boolean contains(String converterName1, String converterName2);
+
+
+    @libcore.api.IntraCoreApi
+    public static native String[] getAvailableCharsetNames();
+
+    @libcore.api.IntraCoreApi
+    public static native Charset charsetForName(String charsetName);
+
+    // Translates from Java's enum to the magic numbers #defined in "NativeConverter.cpp".
+    private static int translateCodingErrorAction(CodingErrorAction action) {
+        if (action == CodingErrorAction.REPORT) {
+            return 0;
+        } else if (action == CodingErrorAction.IGNORE) {
+            return 1;
+        } else if (action == CodingErrorAction.REPLACE) {
+            return 2;
+        } else {
+            throw new AssertionError(); // Someone changed the enum.
+        }
+    }
+
+    public static void setCallbackDecode(long converterHandle, CharsetDecoder decoder) {
+        setCallbackDecode(converterHandle,
+                          translateCodingErrorAction(decoder.malformedInputAction()),
+                          translateCodingErrorAction(decoder.unmappableCharacterAction()),
+                          decoder.replacement());
+    }
+    private static native void setCallbackDecode(long converterHandle, int onMalformedInput, int onUnmappableInput, String subChars);
+
+    public static void setCallbackEncode(long converterHandle, CharsetEncoder encoder) {
+        setCallbackEncode(converterHandle,
+                          translateCodingErrorAction(encoder.malformedInputAction()),
+                          translateCodingErrorAction(encoder.unmappableCharacterAction()),
+                          encoder.replacement());
+    }
+    private static native void setCallbackEncode(long converterHandle, int onMalformedInput, int onUnmappableInput, byte[] subBytes);
+
+    public static native long getNativeFinalizer();
+    public static native long getNativeSize();
+
+    // Just the subset of error codes needed by CharsetDecoderICU/CharsetEncoderICU.
+    public static final int U_ZERO_ERROR = 0;
+    public static final int U_INVALID_CHAR_FOUND = 10;
+    public static final int U_TRUNCATED_CHAR_FOUND = 11;
+    public static final int U_ILLEGAL_CHAR_FOUND = 12;
+    public static final int U_BUFFER_OVERFLOW_ERROR = 15;
+    public static boolean U_FAILURE(int error) {
+      return error > U_ZERO_ERROR;
+    }
+}
diff --git a/android_icu4j/src/main/java/com/android/icu/charset/TEST_MAPPING b/android_icu4j/src/main/java/com/android/icu/charset/TEST_MAPPING
new file mode 100644
index 0000000..ec926f9
--- /dev/null
+++ b/android_icu4j/src/main/java/com/android/icu/charset/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLibcoreTestCases",
+      "options": [
+        {
+          "include-filter": "libcore.java.nio.charset"
+        },
+        {
+          "include-filter": "org.apache.harmony.tests.java.nio.charset"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/android_icu4j/src/main/jni/JniConstants.cpp b/android_icu4j/src/main/jni/JniConstants.cpp
index 56483b6..23f6f9f 100644
--- a/android_icu4j/src/main/jni/JniConstants.cpp
+++ b/android_icu4j/src/main/jni/JniConstants.cpp
@@ -40,7 +40,9 @@
 // Flag indicating whether cached constants are valid
 static bool g_constants_valid = false;
 
+jclass charsetICUClass;
 jclass patternSyntaxExceptionClass;
+jclass stringClass;
 
 // EnsureJniConstantsInitialized initializes cached constants. It should be
 // called before returning a heap object from the cache to ensure cache is
@@ -56,7 +58,9 @@
         return;
     }
 
+    charsetICUClass = findClass(env, "com/android/icu/charset/CharsetICU");
     patternSyntaxExceptionClass = findClass(env, "java/util/regex/PatternSyntaxException");
+    stringClass = findClass(env, "java/lang/String");
 
     g_constants_valid = true;
 }
@@ -67,11 +71,21 @@
     EnsureJniConstantsInitialized(env);
 }
 
+jclass JniConstants::GetCharsetICUClass(JNIEnv* env) {
+    EnsureJniConstantsInitialized(env);
+    return charsetICUClass;
+}
+
 jclass JniConstants::GetPatternSyntaxExceptionClass(JNIEnv* env) {
     EnsureJniConstantsInitialized(env);
     return patternSyntaxExceptionClass;
 }
 
+jclass JniConstants::GetStringClass(JNIEnv* env) {
+    EnsureJniConstantsInitialized(env);
+    return stringClass;
+}
+
 void JniConstants::Invalidate() {
     // Clean shutdown would require calling DeleteGlobalRef() for each of the
     // class references. However, JavaVM can't be used for cleanup during
diff --git a/android_icu4j/src/main/jni/JniConstants.h b/android_icu4j/src/main/jni/JniConstants.h
index 18275cb..97d3e64 100644
--- a/android_icu4j/src/main/jni/JniConstants.h
+++ b/android_icu4j/src/main/jni/JniConstants.h
@@ -28,5 +28,7 @@
     // Invalidate cached heap objects. This should be called in JNI_OnUnload.
     static void Invalidate();
 
+    static jclass GetCharsetICUClass(JNIEnv* env);
     static jclass GetPatternSyntaxExceptionClass(JNIEnv* env);
+    static jclass GetStringClass(JNIEnv* env);
 };
\ No newline at end of file
diff --git a/android_icu4j/src/main/jni/Register.cpp b/android_icu4j/src/main/jni/Register.cpp
index a66d59a..0df2e7e 100644
--- a/android_icu4j/src/main/jni/Register.cpp
+++ b/android_icu4j/src/main/jni/Register.cpp
@@ -36,6 +36,7 @@
 #define REGISTER(FN) extern void FN(JNIEnv*); FN(env)
     REGISTER(register_com_android_icu_util_regex_NativePattern);
     REGISTER(register_com_android_icu_util_regex_NativeMatcher);
+    REGISTER(register_com_android_icu_util_charset_NativeConverter);
 #undef REGISTER
 
     JniConstants::Initialize(env);
diff --git a/android_icu4j/src/main/jni/com_android_icu_charset_NativeConverter.cpp b/android_icu4j/src/main/jni/com_android_icu_charset_NativeConverter.cpp
new file mode 100644
index 0000000..26957d1
--- /dev/null
+++ b/android_icu4j/src/main/jni/com_android_icu_charset_NativeConverter.cpp
@@ -0,0 +1,694 @@
+/**
+*******************************************************************************
+* Copyright (C) 1996-2006, International Business Machines Corporation and    *
+* others. All Rights Reserved.                                                *
+*******************************************************************************
+*
+*
+*******************************************************************************
+*/
+/*
+ * (C) Copyright IBM Corp. 2000 - All Rights Reserved
+ *  A JNI wrapper to ICU native converter Interface
+ * @author: Ram Viswanadha
+ */
+
+#define LOG_TAG "NativeConverter"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <memory>
+#include <vector>
+
+#include <android/log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedStringChars.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <nativehelper/jni_macros.h>
+#include <nativehelper/toStringArray.h>
+
+#include "IcuUtilities.h"
+#include "JniConstants.h"
+#include "ustrenum.h"
+#include "unicode/ucnv.h"
+#include "unicode/ucnv_cb.h"
+#include "unicode/uniset.h"
+#include "unicode/ustring.h"
+#include "unicode/utypes.h"
+
+#define NativeConverter_REPORT 0
+#define NativeConverter_IGNORE 1
+#define NativeConverter_REPLACE 2
+
+#define MAX_REPLACEMENT_LENGTH 32 // equivalent to UCNV_ERROR_BUFFER_LENGTH
+
+struct DecoderCallbackContext {
+    UChar replacementChars[MAX_REPLACEMENT_LENGTH];
+    size_t replacementCharCount;
+    UConverterToUCallback onUnmappableInput;
+    UConverterToUCallback onMalformedInput;
+};
+
+struct EncoderCallbackContext {
+    char replacementBytes[MAX_REPLACEMENT_LENGTH];
+    size_t replacementByteCount;
+    UConverterFromUCallback onUnmappableInput;
+    UConverterFromUCallback onMalformedInput;
+};
+
+static UConverter* toUConverter(jlong address) {
+    return reinterpret_cast<UConverter*>(static_cast<uintptr_t>(address));
+}
+
+static bool collectStandardNames(JNIEnv* env, const char* canonicalName, const char* standard,
+                                 std::vector<std::string>& result) {
+  UErrorCode status = U_ZERO_ERROR;
+  icu::UStringEnumeration e(ucnv_openStandardNames(canonicalName, standard, &status));
+  if (maybeThrowIcuException(env, "ucnv_openStandardNames", status)) {
+    return false;
+  }
+
+  int32_t count = e.count(status);
+  if (maybeThrowIcuException(env, "StringEnumeration::count", status)) {
+    return false;
+  }
+
+  for (int32_t i = 0; i < count; ++i) {
+    const icu::UnicodeString* string = e.snext(status);
+    if (maybeThrowIcuException(env, "StringEnumeration::snext", status)) {
+      return false;
+    }
+    std::string s;
+    string->toUTF8String(s);
+    if (s.find_first_of("+,") == std::string::npos) {
+      result.push_back(s);
+    }
+  }
+
+  return true;
+}
+
+static const char* getICUCanonicalName(const char* name) {
+  UErrorCode error = U_ZERO_ERROR;
+  const char* canonicalName = NULL;
+  if ((canonicalName = ucnv_getCanonicalName(name, "MIME", &error)) != NULL) {
+    return canonicalName;
+  } else if ((canonicalName = ucnv_getCanonicalName(name, "IANA", &error)) != NULL) {
+    return canonicalName;
+  } else if ((canonicalName = ucnv_getCanonicalName(name, "", &error)) != NULL) {
+    return canonicalName;
+  } else if ((canonicalName = ucnv_getAlias(name, 0, &error)) != NULL) {
+    // We have some aliases in the form x-blah .. match those first.
+    return canonicalName;
+  } else if (strstr(name, "x-") == name) {
+    // Check if the converter can be opened with the name given.
+    error = U_ZERO_ERROR;
+    icu::LocalUConverterPointer cnv(ucnv_open(name + 2, &error));
+    if (U_SUCCESS(error)) {
+      return name + 2;
+    }
+  }
+  return NULL;
+}
+
+// If a charset listed in the IANA Charset Registry is supported by an implementation
+// of the Java platform then its canonical name must be the name listed in the registry.
+// Many charsets are given more than one name in the registry, in which case the registry
+// identifies one of the names as MIME-preferred. If a charset has more than one registry
+// name then its canonical name must be the MIME-preferred name and the other names in
+// the registry must be valid aliases. If a supported charset is not listed in the IANA
+// registry then its canonical name must begin with one of the strings "X-" or "x-".
+static jstring getJavaCanonicalName(JNIEnv* env, const char* icuCanonicalName) {
+  UErrorCode status = U_ZERO_ERROR;
+
+  // Check to see if this is a well-known MIME or IANA name.
+  const char* cName = NULL;
+  if ((cName = ucnv_getStandardName(icuCanonicalName, "MIME", &status)) != NULL) {
+    return env->NewStringUTF(cName);
+  } else if ((cName = ucnv_getStandardName(icuCanonicalName, "IANA", &status)) != NULL) {
+    return env->NewStringUTF(cName);
+  }
+
+  // Check to see if an alias already exists with "x-" prefix, if yes then
+  // make that the canonical name.
+  int32_t aliasCount = ucnv_countAliases(icuCanonicalName, &status);
+  for (int i = 0; i < aliasCount; ++i) {
+    const char* name = ucnv_getAlias(icuCanonicalName, i, &status);
+    if (name != NULL && name[0] == 'x' && name[1] == '-') {
+      return env->NewStringUTF(name);
+    }
+  }
+
+  // As a last resort, prepend "x-" to any alias and make that the canonical name.
+  status = U_ZERO_ERROR;
+  const char* name = ucnv_getStandardName(icuCanonicalName, "UTR22", &status);
+  if (name == NULL && strchr(icuCanonicalName, ',') != NULL) {
+    name = ucnv_getAlias(icuCanonicalName, 1, &status);
+  }
+  // If there is no UTR22 canonical name then just return the original name.
+  if (name == NULL) {
+    name = icuCanonicalName;
+  }
+  std::unique_ptr<char[]> result(new char[2 + strlen(name) + 1]);
+  strcpy(&result[0], "x-");
+  strcat(&result[0], name);
+  return env->NewStringUTF(&result[0]);
+}
+
+// Returns a canonical ICU converter name which may have a version number appended to it, based on
+// the normal canonical name. This is used to determine the actual native converter to use (the
+// normal unversioned name is used to determine the aliases and the Java name).
+static char const * getVersionedIcuCanonicalName(char const * icuCanonicalName) {
+  if (strcmp(icuCanonicalName, "UTF-16") == 0) {
+    // The ICU UTF-16 converter encodes strings as platform-endian bytes with a BOM. The
+    // UTF-16,version=2 one encodes as big-endian with a BOM, as what the Charset javadoc requires.
+    return "UTF-16,version=2";
+  } else {
+    return icuCanonicalName;
+  }
+}
+
+static jlong NativeConverter_openConverter(JNIEnv* env, jclass, jstring converterName) {
+    ScopedUtfChars converterNameChars(env, converterName);
+    if (converterNameChars.c_str() == NULL) {
+        // Extra debugging check that we do have an exception if the we could not
+        // create a string. See b/62612946.
+        if (env->ExceptionCheck()) {
+            return 0;
+        }
+        maybeThrowIcuException(env, "openConverter", U_ILLEGAL_ARGUMENT_ERROR);
+        return 0;
+    }
+    UErrorCode status = U_ZERO_ERROR;
+    UConverter* cnv = ucnv_open(converterNameChars.c_str(), &status);
+    maybeThrowIcuException(env, "ucnv_open", status);
+    if (env->ExceptionCheck()) {
+        return 0;
+    }
+    if (cnv == NULL) {
+        // Extra debugging exception in case cnv is null but ICU did not report
+        // an error. See b/62612946.
+        maybeThrowIcuException(env, "openConverter", U_ILLEGAL_ARGUMENT_ERROR);
+        return 0;
+    }
+    return reinterpret_cast<uintptr_t>(cnv);
+}
+
+static void NativeConverter_closeConverter(JNIEnv*, jclass, jlong address) {
+    ucnv_close(toUConverter(address));
+}
+
+static bool shouldCodecThrow(jboolean flush, UErrorCode error) {
+    if (flush) {
+        return (error != U_BUFFER_OVERFLOW_ERROR && error != U_TRUNCATED_CHAR_FOUND);
+    } else {
+        return (error != U_BUFFER_OVERFLOW_ERROR && error != U_INVALID_CHAR_FOUND && error != U_ILLEGAL_CHAR_FOUND);
+    }
+}
+
+static jint NativeConverter_encode(JNIEnv* env, jclass, jlong address,
+        jcharArray source, jint sourceEnd, jbyteArray target, jint targetEnd,
+        jintArray data, jboolean flush) {
+
+    UConverter* cnv = toUConverter(address);
+    if (cnv == NULL) {
+        maybeThrowIcuException(env, "toUConverter", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+    ScopedCharArrayRO uSource(env, source);
+    if (uSource.get() == NULL) {
+        maybeThrowIcuException(env, "uSource", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+    ScopedByteArrayRW uTarget(env, target);
+    if (uTarget.get() == NULL) {
+        maybeThrowIcuException(env, "uTarget", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+    ScopedIntArrayRW myData(env, data);
+    if (myData.get() == NULL) {
+        maybeThrowIcuException(env, "myData", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+
+    // Do the conversion.
+    jint* sourceOffset = &myData[0];
+    jint* targetOffset = &myData[1];
+    const jchar* mySource = uSource.get() + *sourceOffset;
+    const UChar* mySourceLimit= reinterpret_cast<const UChar*>(uSource.get()) + sourceEnd;
+    char* cTarget = reinterpret_cast<char*>(uTarget.get() + *targetOffset);
+    const char* cTargetLimit = reinterpret_cast<const char*>(uTarget.get() + targetEnd);
+    UErrorCode errorCode = U_ZERO_ERROR;
+    ucnv_fromUnicode(cnv, &cTarget, cTargetLimit, reinterpret_cast<const UChar**>(&mySource), mySourceLimit, NULL, (UBool) flush, &errorCode);
+    *sourceOffset = (mySource - uSource.get()) - *sourceOffset;
+    *targetOffset = (reinterpret_cast<jbyte*>(cTarget) - uTarget.get());
+
+    // If there was an error, count the problematic characters.
+    if (errorCode == U_ILLEGAL_CHAR_FOUND || errorCode == U_INVALID_CHAR_FOUND ||
+        errorCode == U_TRUNCATED_CHAR_FOUND) {
+        int8_t invalidUCharCount = 32;
+        UChar invalidUChars[32];
+        UErrorCode minorErrorCode = U_ZERO_ERROR;
+        ucnv_getInvalidUChars(cnv, invalidUChars, &invalidUCharCount, &minorErrorCode);
+        if (U_SUCCESS(minorErrorCode)) {
+            myData[2] = invalidUCharCount;
+        }
+    }
+
+    // Managed code handles some cases; throw all other errors.
+    if (shouldCodecThrow(flush, errorCode)) {
+        maybeThrowIcuException(env, "ucnv_fromUnicode", errorCode);
+    }
+    return errorCode;
+}
+
+static jint NativeConverter_decode(JNIEnv* env, jclass, jlong address,
+        jbyteArray source, jint sourceEnd, jcharArray target, jint targetEnd,
+        jintArray data, jboolean flush) {
+
+    UConverter* cnv = toUConverter(address);
+    if (cnv == NULL) {
+        maybeThrowIcuException(env, "toUConverter", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+    ScopedByteArrayRO uSource(env, source);
+    if (uSource.get() == NULL) {
+        maybeThrowIcuException(env, "uSource", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+    ScopedCharArrayRW uTarget(env, target);
+    if (uTarget.get() == NULL) {
+        maybeThrowIcuException(env, "uTarget", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+    ScopedIntArrayRW myData(env, data);
+    if (myData.get() == NULL) {
+        maybeThrowIcuException(env, "myData", U_ILLEGAL_ARGUMENT_ERROR);
+        return U_ILLEGAL_ARGUMENT_ERROR;
+    }
+
+    // Do the conversion.
+    jint* sourceOffset = &myData[0];
+    jint* targetOffset = &myData[1];
+    const char* mySource = reinterpret_cast<const char*>(uSource.get() + *sourceOffset);
+    const char* mySourceLimit = reinterpret_cast<const char*>(uSource.get() + sourceEnd);
+    UChar* cTarget = reinterpret_cast<UChar*>(uTarget.get()) + *targetOffset;
+    const UChar* cTargetLimit = reinterpret_cast<UChar*>(uTarget.get()) + targetEnd;
+    UErrorCode errorCode = U_ZERO_ERROR;
+    ucnv_toUnicode(cnv, &cTarget, cTargetLimit, &mySource, mySourceLimit, NULL, flush, &errorCode);
+    *sourceOffset = mySource - reinterpret_cast<const char*>(uSource.get()) - *sourceOffset;
+    *targetOffset = cTarget - reinterpret_cast<UChar*>(uTarget.get()) - *targetOffset;
+
+    // If there was an error, count the problematic bytes.
+    if (errorCode == U_ILLEGAL_CHAR_FOUND || errorCode == U_INVALID_CHAR_FOUND ||
+        errorCode == U_TRUNCATED_CHAR_FOUND) {
+        int8_t invalidByteCount = 32;
+        char invalidBytes[32] = {'\0'};
+        UErrorCode minorErrorCode = U_ZERO_ERROR;
+        ucnv_getInvalidChars(cnv, invalidBytes, &invalidByteCount, &minorErrorCode);
+        if (U_SUCCESS(minorErrorCode)) {
+            myData[2] = invalidByteCount;
+        }
+    }
+
+    // Managed code handles some cases; throw all other errors.
+    if (shouldCodecThrow(flush, errorCode)) {
+        maybeThrowIcuException(env, "ucnv_toUnicode", errorCode);
+    }
+    return errorCode;
+}
+
+static void NativeConverter_resetByteToChar(JNIEnv*, jclass, jlong address) {
+    UConverter* cnv = toUConverter(address);
+    if (cnv) {
+        ucnv_resetToUnicode(cnv);
+    }
+}
+
+static void NativeConverter_resetCharToByte(JNIEnv*, jclass, jlong address) {
+    UConverter* cnv = toUConverter(address);
+    if (cnv) {
+        ucnv_resetFromUnicode(cnv);
+    }
+}
+
+static jint NativeConverter_getMaxBytesPerChar(JNIEnv*, jclass, jlong address) {
+    UConverter* cnv = toUConverter(address);
+    return (cnv != NULL) ? ucnv_getMaxCharSize(cnv) : -1;
+}
+
+static jfloat NativeConverter_getAveBytesPerChar(JNIEnv*, jclass, jlong address) {
+    UConverter* cnv = toUConverter(address);
+    return (cnv != NULL) ? ((ucnv_getMaxCharSize(cnv) + ucnv_getMinCharSize(cnv)) / 2.0) : -1;
+}
+
+static jobjectArray NativeConverter_getAvailableCharsetNames(JNIEnv* env, jclass) {
+    int32_t num = ucnv_countAvailable();
+    jobjectArray result = env->NewObjectArray(num, JniConstants::GetStringClass(env), NULL);
+    if (result == NULL) {
+        return NULL;
+    }
+    for (int i = 0; i < num; ++i) {
+        const char* name = ucnv_getAvailableName(i);
+        ScopedLocalRef<jstring> javaCanonicalName(env, getJavaCanonicalName(env, name));
+        if (javaCanonicalName.get() == NULL) {
+            return NULL;
+        }
+        env->SetObjectArrayElement(result, i, javaCanonicalName.get());
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+    }
+    return result;
+}
+
+static void CHARSET_ENCODER_CALLBACK(const void* rawContext, UConverterFromUnicodeArgs* args,
+        const UChar* codeUnits, int32_t length, UChar32 codePoint, UConverterCallbackReason reason,
+        UErrorCode* status) {
+    if (!rawContext) {
+        return;
+    }
+    const EncoderCallbackContext* ctx = reinterpret_cast<const EncoderCallbackContext*>(rawContext);
+    switch(reason) {
+    case UCNV_UNASSIGNED:
+        ctx->onUnmappableInput(ctx, args, codeUnits, length, codePoint, reason, status);
+        return;
+    case UCNV_ILLEGAL:
+    case UCNV_IRREGULAR:
+        ctx->onMalformedInput(ctx, args, codeUnits, length, codePoint, reason, status);
+        return;
+    case UCNV_CLOSE:
+        delete ctx;
+        return;
+    default:
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return;
+    }
+}
+
+static void encoderReplaceCallback(const void* rawContext,
+        UConverterFromUnicodeArgs* fromArgs, const UChar*, int32_t, UChar32,
+        UConverterCallbackReason, UErrorCode * err) {
+    if (rawContext == NULL) {
+        return;
+    }
+    const EncoderCallbackContext* context = reinterpret_cast<const EncoderCallbackContext*>(rawContext);
+    *err = U_ZERO_ERROR;
+    ucnv_cbFromUWriteBytes(fromArgs, context->replacementBytes, context->replacementByteCount, 0, err);
+}
+
+static UConverterFromUCallback getFromUCallback(int32_t mode) {
+    switch(mode) {
+    case NativeConverter_IGNORE: return UCNV_FROM_U_CALLBACK_SKIP;
+    case NativeConverter_REPLACE: return encoderReplaceCallback;
+    case NativeConverter_REPORT: return UCNV_FROM_U_CALLBACK_STOP;
+    }
+    abort();
+}
+
+static void NativeConverter_setCallbackEncode(JNIEnv* env, jclass, jlong address,
+        jint onMalformedInput, jint onUnmappableInput, jbyteArray javaReplacement) {
+    UConverter* cnv = toUConverter(address);
+    if (cnv == NULL) {
+        maybeThrowIcuException(env, "toUConverter", U_ILLEGAL_ARGUMENT_ERROR);
+        return;
+    }
+
+    UConverterFromUCallback oldCallback = NULL;
+    const void* oldCallbackContext = NULL;
+    ucnv_getFromUCallBack(cnv, &oldCallback, const_cast<const void**>(&oldCallbackContext));
+
+    EncoderCallbackContext* callbackContext = const_cast<EncoderCallbackContext*>(
+            reinterpret_cast<const EncoderCallbackContext*>(oldCallbackContext));
+    // Hold the reference to any new callbackContext we create in a unique_ptr
+    // so that the default behavior is to collect it automatically if we exit
+    // early.
+    std::unique_ptr<EncoderCallbackContext> callbackContextDeleter;
+    if (callbackContext == NULL) {
+        callbackContext = new EncoderCallbackContext;
+        callbackContextDeleter.reset(callbackContext);
+    }
+
+    callbackContext->onMalformedInput = getFromUCallback(onMalformedInput);
+    callbackContext->onUnmappableInput = getFromUCallback(onUnmappableInput);
+
+    ScopedByteArrayRO replacementBytes(env, javaReplacement);
+    if (replacementBytes.get() == NULL
+            || replacementBytes.size() > sizeof(callbackContext->replacementBytes)) {
+        maybeThrowIcuException(env, "replacementBytes", U_ILLEGAL_ARGUMENT_ERROR);
+        return;
+    }
+    memcpy(callbackContext->replacementBytes, replacementBytes.get(), replacementBytes.size());
+    callbackContext->replacementByteCount = replacementBytes.size();
+
+    UErrorCode errorCode = U_ZERO_ERROR;
+    ucnv_setFromUCallBack(cnv, CHARSET_ENCODER_CALLBACK, callbackContext, NULL, NULL, &errorCode);
+    // Iff callbackContextDeleter holds a reference to a callbackContext we can
+    // prevent it being automatically deleted here as responsibility for deletion
+    // has passed to the code that closes the NativeConverter.
+    callbackContextDeleter.release();
+    maybeThrowIcuException(env, "ucnv_setFromUCallBack", errorCode);
+}
+
+static void decoderIgnoreCallback(const void*, UConverterToUnicodeArgs*, const char*, int32_t, UConverterCallbackReason, UErrorCode* err) {
+    // The icu4c UCNV_FROM_U_CALLBACK_SKIP callback requires that the context is NULL, which is
+    // never true for us.
+    *err = U_ZERO_ERROR;
+}
+
+static void decoderReplaceCallback(const void* rawContext,
+        UConverterToUnicodeArgs* toArgs, const char*, int32_t, UConverterCallbackReason,
+        UErrorCode* err) {
+    if (!rawContext) {
+        return;
+    }
+    const DecoderCallbackContext* context = reinterpret_cast<const DecoderCallbackContext*>(rawContext);
+    *err = U_ZERO_ERROR;
+    ucnv_cbToUWriteUChars(toArgs,context->replacementChars, context->replacementCharCount, 0, err);
+}
+
+static UConverterToUCallback getToUCallback(int32_t mode) {
+    switch (mode) {
+    case NativeConverter_IGNORE: return decoderIgnoreCallback;
+    case NativeConverter_REPLACE: return decoderReplaceCallback;
+    case NativeConverter_REPORT: return UCNV_TO_U_CALLBACK_STOP;
+    }
+    abort();
+}
+
+static void CHARSET_DECODER_CALLBACK(const void* rawContext, UConverterToUnicodeArgs* args,
+        const char* codeUnits, int32_t length,
+        UConverterCallbackReason reason, UErrorCode* status) {
+    if (!rawContext) {
+        return;
+    }
+    const DecoderCallbackContext* ctx = reinterpret_cast<const DecoderCallbackContext*>(rawContext);
+    switch(reason) {
+    case UCNV_UNASSIGNED:
+        ctx->onUnmappableInput(ctx, args, codeUnits, length, reason, status);
+        return;
+    case UCNV_ILLEGAL:
+    case UCNV_IRREGULAR:
+        ctx->onMalformedInput(ctx, args, codeUnits, length, reason, status);
+        return;
+    case UCNV_CLOSE:
+        delete ctx;
+        return;
+    default:
+        *status = U_ILLEGAL_ARGUMENT_ERROR;
+        return;
+    }
+}
+
+static void NativeConverter_setCallbackDecode(JNIEnv* env, jclass, jlong address,
+        jint onMalformedInput, jint onUnmappableInput, jstring javaReplacement) {
+    UConverter* cnv = toUConverter(address);
+    if (cnv == NULL) {
+        maybeThrowIcuException(env, "toConverter", U_ILLEGAL_ARGUMENT_ERROR);
+        return;
+    }
+
+    UConverterToUCallback oldCallback;
+    const void* oldCallbackContext;
+    ucnv_getToUCallBack(cnv, &oldCallback, &oldCallbackContext);
+
+    DecoderCallbackContext* callbackContext = const_cast<DecoderCallbackContext*>(
+            reinterpret_cast<const DecoderCallbackContext*>(oldCallbackContext));
+    // Hold the reference to any new callbackContext we create in a unique_ptr
+    // so that the default behavior is to collect it automatically if we exit
+    // early.
+    std::unique_ptr<DecoderCallbackContext> callbackContextDeleter;
+    if (callbackContext == NULL) {
+        callbackContext = new DecoderCallbackContext;
+        callbackContextDeleter.reset(callbackContext);
+    }
+
+    callbackContext->onMalformedInput = getToUCallback(onMalformedInput);
+    callbackContext->onUnmappableInput = getToUCallback(onUnmappableInput);
+
+    ScopedStringChars replacement(env, javaReplacement);
+    if (replacement.get() == NULL
+                || replacement.size() > sizeof(callbackContext->replacementChars) / sizeof(UChar)) {
+        maybeThrowIcuException(env, "replacement", U_ILLEGAL_ARGUMENT_ERROR);
+        return;
+    }
+    u_strncpy(callbackContext->replacementChars, reinterpret_cast<const UChar*>(replacement.get()), replacement.size());
+    callbackContext->replacementCharCount = replacement.size();
+
+    UErrorCode errorCode = U_ZERO_ERROR;
+    ucnv_setToUCallBack(cnv, CHARSET_DECODER_CALLBACK, callbackContext, NULL, NULL, &errorCode);
+    // Iff callbackContextDeleter holds a reference to a callbackContext we can
+    // prevent it being automatically deleted here as responsibility for deletion
+    // has passed to the code that closes the NativeConverter.
+    callbackContextDeleter.release();
+    maybeThrowIcuException(env, "ucnv_setToUCallBack", errorCode);
+}
+
+static jfloat NativeConverter_getAveCharsPerByte(JNIEnv* env, jclass, jlong handle) {
+    return (1 / (jfloat) NativeConverter_getMaxBytesPerChar(env, NULL, handle));
+}
+
+static jbyteArray NativeConverter_getSubstitutionBytes(JNIEnv* env, jclass, jlong address) {
+    UConverter* cnv = toUConverter(address);
+    if (cnv == NULL) {
+        return NULL;
+    }
+    UErrorCode status = U_ZERO_ERROR;
+    char replacementBytes[MAX_REPLACEMENT_LENGTH];
+    int8_t len = sizeof(replacementBytes);
+    ucnv_getSubstChars(cnv, replacementBytes, &len, &status);
+    if (!U_SUCCESS(status)) {
+        return env->NewByteArray(0);
+    }
+    jbyteArray result = env->NewByteArray(len);
+    if (result == NULL) {
+        return NULL;
+    }
+    env->SetByteArrayRegion(result, 0, len, reinterpret_cast<jbyte*>(replacementBytes));
+    return result;
+}
+
+static jboolean NativeConverter_contains(JNIEnv* env, jclass, jstring name1, jstring name2) {
+    ScopedUtfChars name1Chars(env, name1);
+    if (name1Chars.c_str() == NULL) {
+        return JNI_FALSE;
+    }
+    ScopedUtfChars name2Chars(env, name2);
+    if (name2Chars.c_str() == NULL) {
+        return JNI_FALSE;
+    }
+
+    UErrorCode errorCode = U_ZERO_ERROR;
+    icu::LocalUConverterPointer converter1(ucnv_open(name1Chars.c_str(), &errorCode));
+    icu::UnicodeSet set1;
+    ucnv_getUnicodeSet(&*converter1, set1.toUSet(), UCNV_ROUNDTRIP_SET, &errorCode);
+
+    icu::LocalUConverterPointer converter2(ucnv_open(name2Chars.c_str(), &errorCode));
+    icu::UnicodeSet set2;
+    ucnv_getUnicodeSet(&*converter2, set2.toUSet(), UCNV_ROUNDTRIP_SET, &errorCode);
+
+    return U_SUCCESS(errorCode) && set1.containsAll(set2);
+}
+
+static jobject NativeConverter_charsetForName(JNIEnv* env, jclass, jstring charsetName) {
+    ScopedUtfChars charsetNameChars(env, charsetName);
+    if (charsetNameChars.c_str() == NULL) {
+        return NULL;
+    }
+
+    // Get ICU's canonical name for this charset.
+    const char* icuCanonicalName = getICUCanonicalName(charsetNameChars.c_str());
+    if (icuCanonicalName == NULL) {
+        return NULL;
+    }
+
+    // Get Java's canonical name for this charset.
+    jstring javaCanonicalName = getJavaCanonicalName(env, icuCanonicalName);
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+
+    // Check that this charset is supported.
+    {
+        // ICU doesn't offer any "isSupported", so we just open and immediately close.
+        UErrorCode error = U_ZERO_ERROR;
+        icu::LocalUConverterPointer cnv(ucnv_open(icuCanonicalName, &error));
+        if (!U_SUCCESS(error)) {
+            return NULL;
+        }
+    }
+
+    // Get the aliases for this charset.
+    std::vector<std::string> aliases;
+    if (!collectStandardNames(env, icuCanonicalName, "IANA", aliases)) {
+        return NULL;
+    }
+    if (!collectStandardNames(env, icuCanonicalName, "MIME", aliases)) {
+        return NULL;
+    }
+    if (!collectStandardNames(env, icuCanonicalName, "JAVA", aliases)) {
+        return NULL;
+    }
+    if (!collectStandardNames(env, icuCanonicalName, "WINDOWS", aliases)) {
+        return NULL;
+    }
+    jobjectArray javaAliases = toStringArray(env, aliases);
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+
+    // Construct the CharsetICU object.
+    static jmethodID charsetConstructor = env->GetMethodID(JniConstants::GetCharsetICUClass(env), "<init>",
+            "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V");
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+
+    char const * versionedIcuCanonicalName = getVersionedIcuCanonicalName(icuCanonicalName);
+    jstring versionedIcuCanonicalNameStr = env->NewStringUTF(versionedIcuCanonicalName);
+    if (env->ExceptionCheck()) {
+        return NULL;
+    }
+
+    return env->NewObject(JniConstants::GetCharsetICUClass(env), charsetConstructor,
+            javaCanonicalName, versionedIcuCanonicalNameStr, javaAliases);
+}
+
+static void FreeNativeConverter(void *converter) {
+    ucnv_close(reinterpret_cast<UConverter*>(converter));
+}
+
+static jlong NativeConverter_getNativeFinalizer(JNIEnv*, jclass) {
+    return reinterpret_cast<jlong>(&FreeNativeConverter);
+}
+
+static jlong NativeConverter_getNativeSize(JNIEnv*, jclass) {
+    // TODO: Improve estimate.
+    return 200;
+}
+
+static JNINativeMethod gMethods[] = {
+    NATIVE_METHOD(NativeConverter, charsetForName, "(Ljava/lang/String;)Ljava/nio/charset/Charset;"),
+    NATIVE_METHOD(NativeConverter, closeConverter, "(J)V"),
+    NATIVE_METHOD(NativeConverter, contains, "(Ljava/lang/String;Ljava/lang/String;)Z"),
+    NATIVE_METHOD(NativeConverter, decode, "(J[BI[CI[IZ)I"),
+    NATIVE_METHOD(NativeConverter, encode, "(J[CI[BI[IZ)I"),
+    NATIVE_METHOD(NativeConverter, getAvailableCharsetNames, "()[Ljava/lang/String;"),
+    NATIVE_METHOD(NativeConverter, getAveBytesPerChar, "(J)F"),
+    NATIVE_METHOD(NativeConverter, getAveCharsPerByte, "(J)F"),
+    NATIVE_METHOD(NativeConverter, getMaxBytesPerChar, "(J)I"),
+    NATIVE_METHOD(NativeConverter, getSubstitutionBytes, "(J)[B"),
+    NATIVE_METHOD(NativeConverter, openConverter, "(Ljava/lang/String;)J"),
+    NATIVE_METHOD(NativeConverter, resetByteToChar, "(J)V"),
+    NATIVE_METHOD(NativeConverter, resetCharToByte, "(J)V"),
+    NATIVE_METHOD(NativeConverter, setCallbackDecode, "(JIILjava/lang/String;)V"),
+    NATIVE_METHOD(NativeConverter, setCallbackEncode, "(JII[B)V"),
+    NATIVE_METHOD(NativeConverter, getNativeFinalizer, "()J"),
+    NATIVE_METHOD(NativeConverter, getNativeSize, "()J")
+};
+void register_com_android_icu_util_charset_NativeConverter(JNIEnv* env) {
+    jniRegisterNativeMethods(env, "com/android/icu/charset/NativeConverter", gMethods, NELEM(gMethods));
+}