1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <memory>
18
19 #include <gtest/gtest.h>
20
21 #include <jni.h>
22 #include <android/log.h>
23 #include <unicode/uloc.h>
24 #include <unicode/utypes.h>
25
26 // provided by cts/common/device-side/nativetesthelper
27 JavaVM* GetJavaVM();
28
29
30 /**
31 * uloc_setDefault in unicode/uloc.h is not visible because the default Locale in ICU4C, ICU4J
32 * and java.util.Locale is synchronized. This function calls java.util.Locale#setDefault.
33 * If possible, call java.util.Locale#setDefault directly in java.
34 * @param locale ICU Locale ID
35 * @param status error code, e.g. U_UNSUPPORTED_ERROR in case of ClassNotFoundException
36 */
uloc_setDefault_java(JNIEnv * env,const char * localeID,UErrorCode * status)37 static void uloc_setDefault_java(JNIEnv* env, const char* localeID, UErrorCode* status) {
38 if (U_FAILURE(*status)) {
39 return;
40 }
41 if (localeID == nullptr || env == nullptr) {
42 *status = U_ILLEGAL_ARGUMENT_ERROR;
43 return;
44 }
45 if (env->ExceptionCheck()) {
46 *status = U_INVALID_STATE_ERROR;
47 return;
48 }
49
50 auto jdeleter = [env](jobject obj){ env->DeleteLocalRef(obj); };
51
52 constexpr const char log_tag[] = "uloc_jni";
53 // All java classes / methods used below are public in the Android SDK.
54 constexpr const char ulocale_classname[] = "android/icu/util/ULocale";
55 constexpr const char locale_classname[] = "java/util/Locale";
56 constexpr const char ctor_methodname[] = "<init>";
57 constexpr const char toLocale_methodname[] = "toLocale";
58 constexpr const char setDefault_methodname[] = "setDefault";
59
60 /* The below JNI code is equivalent to the following in java
61 ULocale ulocale = new Ulocale(localeID);
62 Locale.setDefault(ulocale.toLocale());
63 */
64
65 // Set error code, clear the exception to avoid throwing during an ongoing JNI call, and return.
66 #define ANDROID_UNICODE_RETURN_ON_ERROR(ptr, msg, ...) \
67 if (ptr == nullptr || env->ExceptionCheck()) { \
68 __android_log_print(ANDROID_LOG_DEBUG, log_tag, msg, __VA_ARGS__); \
69 *status = U_UNSUPPORTED_ERROR; \
70 env->ExceptionClear(); \
71 return; \
72 };
73
74 std::unique_ptr<_jclass, decltype(jdeleter)> ulocale_class(env->FindClass(ulocale_classname),
75 jdeleter);
76 ANDROID_UNICODE_RETURN_ON_ERROR(ulocale_class.get(), "class not found: %s", ulocale_classname)
77
78 std::unique_ptr<_jclass, decltype(jdeleter)> locale_class(env->FindClass(locale_classname),
79 jdeleter);
80 ANDROID_UNICODE_RETURN_ON_ERROR(locale_class.get(), "class not found: %s", locale_classname)
81
82 jmethodID ulocale_ctor = env->GetMethodID(ulocale_class.get(), ctor_methodname,
83 "(Ljava/lang/String;)V");
84 ANDROID_UNICODE_RETURN_ON_ERROR(ulocale_ctor, "method not found in class %s: %s",
85 ulocale_classname, ctor_methodname);
86
87 jmethodID ulocale_toLocale = env->GetMethodID(ulocale_class.get(), toLocale_methodname,
88 "()Ljava/util/Locale;");
89 ANDROID_UNICODE_RETURN_ON_ERROR(ulocale_toLocale, "method not found in class %s: %s",
90 ulocale_classname, toLocale_methodname)
91
92 jmethodID locale_setDefault = env->GetStaticMethodID(locale_class.get(), setDefault_methodname,
93 "(Ljava/util/Locale;)V");
94 ANDROID_UNICODE_RETURN_ON_ERROR(locale_setDefault, "method not found in class %s: %s",
95 ulocale_classname, setDefault_methodname)
96
97 // The following Java APIs are not expected to throw Exception.
98 std::unique_ptr<_jstring, decltype(jdeleter)> locale_str(env->NewStringUTF(localeID), jdeleter);
99 EXPECT_FALSE(env->ExceptionCheck());
100 std::unique_ptr<_jobject, decltype(jdeleter)> ulocale(
101 env->NewObject(ulocale_class.get(), ulocale_ctor, locale_str.get()),
102 jdeleter);
103 EXPECT_FALSE(env->ExceptionCheck());
104 std::unique_ptr<_jobject, decltype(jdeleter)> locale(
105 env->CallObjectMethod(ulocale.get(), ulocale_toLocale),
106 jdeleter);
107 EXPECT_FALSE(env->ExceptionCheck());
108 env->CallStaticVoidMethod(locale_class.get(), locale_setDefault, locale.get());
109 EXPECT_FALSE(env->ExceptionCheck());
110 #undef ANDROID_UNICODE_RETURN_ON_ERROR
111 }
112
113 class Icu4cLocaleJniTest : public ::testing::Test {
114 protected:
115 JNIEnv* env;
116 const char* orig_default_locale; // Do not release as specified by uloc_getDefault()
117
SetUp()118 virtual void SetUp() override {
119 orig_default_locale = uloc_getDefault();
120
121 JavaVM* jvm = GetJavaVM();
122 int envStat = jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
123 ASSERT_EQ(JNI_OK, envStat); // It should be attached to the current thread already.
124 }
125
TearDown()126 virtual void TearDown() override {
127 UErrorCode status = U_ZERO_ERROR;
128 uloc_setDefault_java(env, orig_default_locale, &status);
129 }
130
131 };
132
133 /**
134 * Test that java.util.Locale#setDefault changes the value returned from uloc_getDefault.
135 */
TEST_F(Icu4cLocaleJniTest,test_uloc_getDefault)136 TEST_F(Icu4cLocaleJniTest, test_uloc_getDefault) {
137 UErrorCode status = U_ZERO_ERROR;
138 uloc_setDefault_java(env, ULOC_JAPAN, &status);
139 EXPECT_EQ(U_ZERO_ERROR, status);
140 EXPECT_STREQ(ULOC_JAPAN, uloc_getDefault());
141
142 uloc_setDefault_java(env, "" /* root locale */, &status);
143 EXPECT_EQ(U_ZERO_ERROR, status);
144 EXPECT_STREQ("", uloc_getDefault());
145
146 // Canonicalize the locale by uloc_getName and then set it as the default.
147 constexpr const char locale_ja_with_extension[] = "ja_JP@calendar=japanese;currency=usd";
148 char localeID[ULOC_FULLNAME_CAPACITY];
149 uloc_getName(locale_ja_with_extension, localeID, ULOC_FULLNAME_CAPACITY, &status);
150 EXPECT_EQ(U_ZERO_ERROR, status);
151 EXPECT_STREQ(locale_ja_with_extension, localeID);
152 uloc_setDefault_java(env, localeID, &status);
153 EXPECT_EQ(U_ZERO_ERROR, status);
154 EXPECT_STREQ(locale_ja_with_extension, uloc_getDefault());
155 }
156