[jnigen] Fix encoding bug (https://github.com/dart-lang/jnigen/issues/279)

* Use UTF-16 in strings
diff --git a/pkgs/jni/example/integration_test/on_device_jni_test.dart b/pkgs/jni/example/integration_test/on_device_jni_test.dart
index f9ae286..cdc7296 100644
--- a/pkgs/jni/example/integration_test/on_device_jni_test.dart
+++ b/pkgs/jni/example/integration_test/on_device_jni_test.dart
@@ -9,6 +9,7 @@
 import '../../test/jlist_test.dart' as jlist_test;
 import '../../test/jmap_test.dart' as jmap_test;
 import '../../test/jobject_test.dart' as jobject_test;
+import '../../test/jstring_test.dart' as jstring_test;
 import '../../test/jset_test.dart' as jset_test;
 import '../../test/jarray_test.dart' as jarray_test;
 import '../../test/boxed_test.dart' as boxed_test;
@@ -26,6 +27,7 @@
     jlist_test.run,
     jmap_test.run,
     jobject_test.run,
+    jstring_test.run,
     jset_test.run,
     jarray_test.run,
     boxed_test.run,
diff --git a/pkgs/jni/example/lib/main.dart b/pkgs/jni/example/lib/main.dart
index 50c3c75..b2ca2b7 100644
--- a/pkgs/jni/example/lib/main.dart
+++ b/pkgs/jni/example/lib/main.dart
@@ -55,6 +55,12 @@
   );
 }
 
+String backAndForth() {
+  final jstring = '🪓'.toJString();
+  final dartString = jstring.toDartString(deleteOriginal: true);
+  return dartString;
+}
+
 void quit() {
   JObject.fromRef(Jni.getCurrentActivity())
       .use((ac) => ac.callMethodByName<void>("finish", "()V", []));
@@ -94,6 +100,7 @@
     if (Platform.isAndroid) ...[
       Example("Minutes of usage since reboot",
           () => (uptime() / (60 * 1000)).floor()),
+      Example("Back and forth string conversion", () => backAndForth()),
       Example(
           "Device name",
           () => Jni.retrieveStaticField<String>(
diff --git a/pkgs/jni/lib/src/lang/jstring.dart b/pkgs/jni/lib/src/lang/jstring.dart
index 1361b53..fff169c 100644
--- a/pkgs/jni/lib/src/lang/jstring.dart
+++ b/pkgs/jni/lib/src/lang/jstring.dart
@@ -49,8 +49,8 @@
   JString.fromRef(JStringPtr reference) : super.fromRef(reference);
 
   static JStringPtr _toJavaString(String s) => using((arena) {
-        final chars = s.toNativeUtf8(allocator: arena).cast<Char>();
-        final jstr = Jni.env.NewStringUTF(chars);
+        final chars = s.toNativeUtf16(allocator: arena).cast<Uint16>();
+        final jstr = Jni.env.NewString(chars, s.length);
         if (jstr == nullptr) {
           throw 'Fatal: cannot convert string to Java string: $s';
         }
@@ -72,9 +72,10 @@
     if (reference == nullptr) {
       throw NullJStringException();
     }
-    final chars = Jni.env.GetStringUTFChars(reference, nullptr);
-    final result = chars.cast<Utf8>().toDartString();
-    Jni.env.ReleaseStringUTFChars(reference, chars);
+    final length = Jni.env.GetStringLength(reference);
+    final chars = Jni.env.GetStringChars(reference, nullptr);
+    final result = chars.cast<Utf16>().toDartString(length: length);
+    Jni.env.ReleaseStringChars(reference, chars);
     if (deleteOriginal) {
       delete();
     }
diff --git a/pkgs/jni/src/third_party/global_jni_env.c b/pkgs/jni/src/third_party/global_jni_env.c
index c5be7b0..7ddc164 100644
--- a/pkgs/jni/src/third_party/global_jni_env.c
+++ b/pkgs/jni/src/third_party/global_jni_env.c
@@ -2181,10 +2181,6 @@
 JniPointerResult globalEnv_GetStringCritical(jstring str, jboolean* isCopy) {
   attach_thread();
   const jchar* _result = (*jniEnv)->GetStringCritical(jniEnv, str, isCopy);
-  jthrowable _exception = check_exception();
-  if (_exception != NULL) {
-    return (JniPointerResult){.value = NULL, .exception = _exception};
-  }
   return (JniPointerResult){.value = _result, .exception = NULL};
 }
 
diff --git a/pkgs/jni/test/jstring_test.dart b/pkgs/jni/test/jstring_test.dart
new file mode 100644
index 0000000..1bc6436
--- /dev/null
+++ b/pkgs/jni/test/jstring_test.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:jni/jni.dart';
+import 'package:test/test.dart';
+
+import 'test_util/test_util.dart';
+
+void main() {
+  // Don't forget to initialize JNI.
+  if (!Platform.isAndroid) {
+    checkDylibIsUpToDate();
+    Jni.spawnIfNotExists(dylibDir: "build/jni_libs", jvmOptions: ["-Xmx128m"]);
+  }
+  run(testRunner: test);
+}
+
+void testStringBackAndForth(String str) {
+  final jstring = str.toJString();
+  final dartString = jstring.toDartString(deleteOriginal: true);
+  expect(dartString, str);
+}
+
+void run({required TestRunnerCallback testRunner}) {
+  group("String encoding tests", () {
+    testRunner('Long string back-and-forth', () {
+      testStringBackAndForth('1' * 8096);
+    });
+
+    testRunner('#278 UTF-8 bug', () {
+      testStringBackAndForth('🐬');
+    });
+
+    testRunner('String containing null character', () {
+      final str = 'A${String.fromCharCode(0)}B';
+      testStringBackAndForth(str);
+    });
+
+    testRunner('Zero length string', () {
+      testStringBackAndForth('');
+    });
+  });
+}
diff --git a/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart b/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
index cde47c9..f1e15b2 100644
--- a/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
+++ b/pkgs/jni/tool/wrapper_generators/generate_c_extensions.dart
@@ -206,6 +206,7 @@
 /// Methods which do not throw exceptions, and thus not need to be checked
 const _noCheckException = {
   'GetVersion',
+  'GetStringCritical',
   'ExceptionClear',
   'ExceptionDescribe',
 };