• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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