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, &region);
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