1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/base/resource/resource_bundle_android.h"
6
7 #include <utility>
8
9 #include "base/android/apk_assets.h"
10 #include "base/android/jni_android.h"
11 #include "base/android/jni_string.h"
12 #include "base/files/file_util.h"
13 #include "base/logging.h"
14 #include "base/notreached.h"
15 #include "base/path_service.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/data_pack.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/base/ui_base_jni_headers/ResourceBundle_jni.h"
20 #include "ui/base/ui_base_paths.h"
21
22 namespace ui {
23
24 namespace {
25
26 bool g_locale_paks_in_apk = false;
27 bool g_load_secondary_locale_paks = false;
28 // It is okay to cache and share these file descriptors since the
29 // ResourceBundle singleton never closes the handles.
30 int g_chrome_100_percent_fd = -1;
31 int g_resources_pack_fd = -1;
32 int g_locale_pack_fd = -1;
33 int g_secondary_locale_pack_fd = -1;
34 base::MemoryMappedFile::Region g_chrome_100_percent_region;
35 base::MemoryMappedFile::Region g_resources_pack_region;
36 base::MemoryMappedFile::Region g_locale_pack_region;
37 base::MemoryMappedFile::Region g_secondary_locale_pack_region;
38
LoadFromApkOrFile(const char * apk_path,const base::FilePath * disk_path,int * out_fd,base::MemoryMappedFile::Region * out_region)39 bool LoadFromApkOrFile(const char* apk_path,
40 const base::FilePath* disk_path,
41 int* out_fd,
42 base::MemoryMappedFile::Region* out_region) {
43 DCHECK_EQ(*out_fd, -1) << "Attempt to load " << apk_path << " twice.";
44 if (apk_path != nullptr) {
45 *out_fd = base::android::OpenApkAsset(apk_path, out_region);
46 }
47 // For unit tests, the file exists on disk.
48 if (*out_fd < 0 && disk_path != nullptr) {
49 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
50 *out_fd = base::File(*disk_path, flags).TakePlatformFile();
51 *out_region = base::MemoryMappedFile::Region::kWholeFile;
52 }
53 bool success = *out_fd >= 0;
54 if (!success) {
55 LOG(ERROR) << "Failed to open pak file: " << apk_path;
56 }
57 return success;
58 }
59
LoadLocalePakFromApk(const std::string & app_locale,bool in_split,base::MemoryMappedFile::Region * out_region)60 int LoadLocalePakFromApk(const std::string& app_locale,
61 bool in_split,
62 base::MemoryMappedFile::Region* out_region) {
63 bool log_error = true;
64 std::string locale_path_within_apk =
65 GetPathForAndroidLocalePakWithinApk(app_locale, in_split, log_error);
66 if (locale_path_within_apk.empty()) {
67 return -1;
68 }
69 return base::android::OpenApkAsset(locale_path_within_apk, out_region);
70 }
71
LoadDataPackFromLocalePak(int locale_pack_fd,const base::MemoryMappedFile::Region & region)72 std::unique_ptr<DataPack> LoadDataPackFromLocalePak(
73 int locale_pack_fd,
74 const base::MemoryMappedFile::Region& region) {
75 auto data_pack = std::make_unique<DataPack>(SCALE_FACTOR_100P);
76 if (!data_pack->LoadFromFileRegion(base::File(locale_pack_fd), region)) {
77 LOG(WARNING) << "failed to load locale.pak";
78 NOTREACHED();
79 return nullptr;
80 }
81 return data_pack;
82 }
83
84 } // namespace
85
LoadCommonResources()86 void ResourceBundle::LoadCommonResources() {
87 base::FilePath disk_path;
88 base::PathService::Get(ui::DIR_RESOURCE_PAKS_ANDROID, &disk_path);
89 disk_path = disk_path.AppendASCII("chrome_100_percent.pak");
90 bool success =
91 LoadFromApkOrFile("assets/chrome_100_percent.pak", &disk_path,
92 &g_chrome_100_percent_fd, &g_chrome_100_percent_region);
93 DCHECK(success);
94
95 AddDataPackFromFileRegion(base::File(g_chrome_100_percent_fd),
96 g_chrome_100_percent_region, SCALE_FACTOR_100P);
97 }
98
99 // static
LocaleDataPakExists(const std::string & locale)100 bool ResourceBundle::LocaleDataPakExists(const std::string& locale) {
101 bool log_error = false;
102 bool in_split = !g_locale_paks_in_apk;
103 if (!in_split) {
104 return !GetPathForAndroidLocalePakWithinApk(locale, in_split, log_error)
105 .empty();
106 }
107 if (!GetPathForAndroidLocalePakWithinApk(locale, in_split, log_error).empty())
108 return true;
109 const auto path = GetLocaleFilePath(locale);
110 return !path.empty() && base::PathExists(path);
111 }
112
LoadLocaleResources(const std::string & pref_locale,bool crash_on_failure)113 std::string ResourceBundle::LoadLocaleResources(const std::string& pref_locale,
114 bool crash_on_failure) {
115 DCHECK(!locale_resources_data_.get() &&
116 !secondary_locale_resources_data_.get())
117 << "locale.pak already loaded";
118 std::string app_locale = l10n_util::GetApplicationLocale(pref_locale);
119
120 // Some Chromium apps have two sets of .pak files for their UI strings, i.e.:
121 //
122 // a) WebView strings, which are always stored uncompressed under
123 // assets/stored-locales/ inside the APK or App Bundle.
124 //
125 // b) For APKs, the Chrome UI strings are stored under assets/locales/
126 // in compressed form. The relevant pak files is extracted on startup
127 // and stored on the /data partition, with a version-specific suffix.
128 //
129 // c) For App Bundles, Chrome UI strings are stored uncompressed under
130 // assets/locales#lang_<lang>/ (where <lang> is an Android language code)
131 // and assets/fallback-locales/ (for en-US.pak only).
132 //
133 // Which .pak files to load are determined here by two global variables with
134 // the following meaning:
135 //
136 // g_locale_paks_in_apk:
137 // If true, load the WebView strings from stored-locales/<locale>.pak file
138 // as the primary locale pak file.
139 //
140 // If false, try to load it from the app bundle specific location
141 // (e.g. locales#lang_<language>/<locale>.pak). If the latter does not
142 // exist, try to lookup the extracted APK-specific locale .pak file
143 // from /data/app/.../<locale>.pak@<version> instead.
144 //
145 // g_locale_paks_in_apk is set by SetLocalePaksStoredInApk() which
146 // is called from the WebView startup code.
147 //
148 // g_load_secondary_locale_paks:
149 // If true, load the Webview strings from stored-locales/<locale>.pak file
150 // as the secondary locale pak file. Otherwise don't load a secondary
151 // locale at all.
152 //
153 // This is set by SetLoadSecondaryLocalePaks() which is called
154 // during ChromeMainDelegate::PostEarlyInitialization() with a value
155 // that is true iff there are stored-locale/ .pak files.
156 //
157 // In other words, if both |g_locale_paks_in_apk| and
158 // |g_load_secondary_locale_paks| are true, the stored-locales file will be
159 // loaded twice as both the primary and secondary. However, this should
160 // never happen in practice.
161
162 // Load primary locale .pak file.
163 if (g_locale_paks_in_apk) {
164 g_locale_pack_fd =
165 LoadLocalePakFromApk(app_locale, false, &g_locale_pack_region);
166 } else {
167 // Support overridden pak path for testing.
168 base::FilePath locale_file_path = GetOverriddenPakPath();
169 if (locale_file_path.empty()) {
170 // Try to find the uncompressed split-specific asset file.
171 g_locale_pack_fd =
172 LoadLocalePakFromApk(app_locale, true, &g_locale_pack_region);
173 }
174 if (g_locale_pack_fd < 0) {
175 // Otherwise, try to locate the extracted locale .pak file.
176 if (locale_file_path.empty()) {
177 auto path = GetLocaleFilePath(app_locale);
178 if (base::PathExists(path))
179 locale_file_path = std::move(path);
180 }
181
182 if (locale_file_path.empty()) {
183 // It's possible that there is no locale.pak.
184 LOG(WARNING) << "locale_file_path.empty() for locale " << app_locale;
185 return std::string();
186 }
187 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
188 g_locale_pack_fd = base::File(locale_file_path, flags).TakePlatformFile();
189 g_locale_pack_region = base::MemoryMappedFile::Region::kWholeFile;
190 }
191 }
192
193 locale_resources_data_ = LoadDataPackFromLocalePak(
194 g_locale_pack_fd, g_locale_pack_region);
195
196 if (!locale_resources_data_.get())
197 return std::string();
198
199 // Load secondary locale .pak file if it exists. For debug build monochrome,
200 // a secondary locale pak will always be loaded; however, it should be
201 // unnecessary for loading locale resources because the primary locale pak
202 // would have a copy of all the resources in the secondary locale pak.
203 if (g_load_secondary_locale_paks) {
204 g_secondary_locale_pack_fd = LoadLocalePakFromApk(
205 app_locale, false, &g_secondary_locale_pack_region);
206
207 secondary_locale_resources_data_ = LoadDataPackFromLocalePak(
208 g_secondary_locale_pack_fd, g_secondary_locale_pack_region);
209
210 if (!secondary_locale_resources_data_.get())
211 return std::string();
212 }
213
214 return app_locale;
215 }
216
GetNativeImageNamed(int resource_id)217 gfx::Image& ResourceBundle::GetNativeImageNamed(int resource_id) {
218 return GetImageNamed(resource_id);
219 }
220
SetLocalePaksStoredInApk(bool value)221 void SetLocalePaksStoredInApk(bool value) {
222 g_locale_paks_in_apk = value;
223 }
224
SetLoadSecondaryLocalePaks(bool value)225 void SetLoadSecondaryLocalePaks(bool value) {
226 g_load_secondary_locale_paks = value;
227 }
228
LoadMainAndroidPackFile(const char * path_within_apk,const base::FilePath & disk_file_path)229 void LoadMainAndroidPackFile(const char* path_within_apk,
230 const base::FilePath& disk_file_path) {
231 if (LoadFromApkOrFile(path_within_apk,
232 &disk_file_path,
233 &g_resources_pack_fd,
234 &g_resources_pack_region)) {
235 ResourceBundle::GetSharedInstance().AddDataPackFromFileRegion(
236 base::File(g_resources_pack_fd), g_resources_pack_region,
237 SCALE_FACTOR_NONE);
238 }
239 }
240
LoadPackFileFromApk(const std::string & path,const std::string & split_name)241 void LoadPackFileFromApk(const std::string& path,
242 const std::string& split_name) {
243 base::MemoryMappedFile::Region region;
244 int fd = base::android::OpenApkAsset(path, split_name, ®ion);
245 CHECK_GE(fd, 0) << "Could not find " << path << " in APK.";
246 ui::ResourceBundle::GetSharedInstance().AddDataPackFromFileRegion(
247 base::File(fd), region, ui::SCALE_FACTOR_NONE);
248 }
249
GetMainAndroidPackFd(base::MemoryMappedFile::Region * out_region)250 int GetMainAndroidPackFd(base::MemoryMappedFile::Region* out_region) {
251 DCHECK_GE(g_resources_pack_fd, 0);
252 *out_region = g_resources_pack_region;
253 return g_resources_pack_fd;
254 }
255
GetCommonResourcesPackFd(base::MemoryMappedFile::Region * out_region)256 int GetCommonResourcesPackFd(base::MemoryMappedFile::Region* out_region) {
257 DCHECK_GE(g_chrome_100_percent_fd, 0);
258 *out_region = g_chrome_100_percent_region;
259 return g_chrome_100_percent_fd;
260 }
261
GetLocalePackFd(base::MemoryMappedFile::Region * out_region)262 int GetLocalePackFd(base::MemoryMappedFile::Region* out_region) {
263 DCHECK_GE(g_locale_pack_fd, 0);
264 *out_region = g_locale_pack_region;
265 return g_locale_pack_fd;
266 }
267
GetSecondaryLocalePackFd(base::MemoryMappedFile::Region * out_region)268 int GetSecondaryLocalePackFd(base::MemoryMappedFile::Region* out_region) {
269 *out_region = g_secondary_locale_pack_region;
270 return g_secondary_locale_pack_fd;
271 }
272
GetPathForAndroidLocalePakWithinApk(const std::string & locale,bool in_bundle,bool log_error)273 std::string GetPathForAndroidLocalePakWithinApk(const std::string& locale,
274 bool in_bundle,
275 bool log_error) {
276 JNIEnv* env = base::android::AttachCurrentThread();
277 base::android::ScopedJavaLocalRef<jstring> ret =
278 Java_ResourceBundle_getLocalePakResourcePath(
279 env, base::android::ConvertUTF8ToJavaString(env, locale), in_bundle,
280 log_error);
281 if (ret.obj() == nullptr) {
282 return std::string();
283 }
284 return base::android::ConvertJavaStringToUTF8(env, ret.obj());
285 }
286
SetNoAvailableLocalePaksForTest()287 void SetNoAvailableLocalePaksForTest() {
288 Java_ResourceBundle_setNoAvailableLocalePaks(
289 base::android::AttachCurrentThread());
290 }
291
292 } // namespace ui
293