#include "ui/base/resource/resource_bundle_android.h"
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "base/android/apk_assets.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "ui/base/buildflags.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/data_pack.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/resource/resource_scale_factor.h"
#include "ui/base/ui_base_paths.h"
#include "ui/base/ui_base_jni_headers/ResourceBundle_jni.h"
namespace ui {
namespace {
using FileDescriptor = int;
bool g_locale_paks_in_apk = false;
bool g_load_non_webview_locale_paks = false;
FileDescriptor g_chrome_100_percent_fd = -1;
FileDescriptor g_chrome_200_percent_fd = -1;
FileDescriptor g_resources_pack_fd = -1;
std::vector<ResourceBundle::FdAndRegion>& GetLocalePaksGlobal() {
static base::NoDestructor<std::vector<ResourceBundle::FdAndRegion>>
locale_paks;
return *locale_paks;
}
base::MemoryMappedFile::Region g_chrome_100_percent_region;
base::MemoryMappedFile::Region g_chrome_200_percent_region;
base::MemoryMappedFile::Region g_resources_pack_region;
bool LoadFromApkOrFile(const char* apk_path,
const base::FilePath* disk_path,
FileDescriptor* out_fd,
base::MemoryMappedFile::Region* out_region) {
DCHECK_EQ(*out_fd, -1) << "Attempt to load " << apk_path << " twice.";
if (apk_path != nullptr) {
*out_fd = base::android::OpenApkAsset(apk_path, out_region);
}
if (*out_fd < 0 && disk_path != nullptr) {
auto flags =
static_cast<uint32_t>(base::File::FLAG_OPEN | base::File::FLAG_READ);
*out_fd = base::File(*disk_path, flags).TakePlatformFile();
*out_region = base::MemoryMappedFile::Region::kWholeFile;
}
bool success = *out_fd >= 0;
if (!success) {
LOG(ERROR) << "Failed to open pak file: " << apk_path;
base::android::DumpLastOpenApkAssetFailure();
}
return success;
}
std::string GetPathForAndroidLocalePakWithinApk(std::string_view locale,
ResourceBundle::Gender gender,
bool in_bundle,
bool log_error) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> ret =
Java_ResourceBundle_getLocalePakResourcePath(
env, base::android::ConvertUTF8ToJavaString(env, locale),
static_cast<jint>(gender), in_bundle, log_error);
if (ret.obj() == nullptr) {
return std::string();
}
return base::android::ConvertJavaStringToUTF8(env, ret.obj());
}
FileDescriptor LoadLocalePakFromApk(
const std::string& app_locale,
ResourceBundle::Gender gender,
bool in_split,
base::MemoryMappedFile::Region* out_region) {
bool log_error = true;
std::string locale_path_within_apk = GetPathForAndroidLocalePakWithinApk(
app_locale, gender, in_split, log_error);
if (locale_path_within_apk.empty()) {
return -1;
}
return base::android::OpenApkAsset(locale_path_within_apk, out_region);
}
std::unique_ptr<DataPack> LoadDataPackFromLocalePak(
FileDescriptor locale_pack_fd,
const base::MemoryMappedFile::Region& region) {
auto data_pack = std::make_unique<DataPack>(k100Percent);
CHECK(data_pack->LoadFromFileRegion(base::File(locale_pack_fd), region))
<< "failed to load locale.pak";
return data_pack;
}
bool LocaleDataPakExists(std::string_view locale,
ResourceBundle::Gender gender,
bool in_split,
bool log_error) {
return !GetPathForAndroidLocalePakWithinApk(locale, gender, in_split,
log_error)
.empty();
}
bool LoadLocaleResourcesForLocaleAndGender(
const std::string& app_locale,
const ResourceBundle::Gender gender,
ResourceBundle::FdAndRegion* webview_locale_pack,
ResourceBundle::FdAndRegion* non_webview_locale_pack,
std::vector<std::unique_ptr<ResourceHandle>>* locale_resources_data) {
if (g_locale_paks_in_apk) {
webview_locale_pack->fd = LoadLocalePakFromApk(
app_locale, gender, false , &webview_locale_pack->region);
} else {
webview_locale_pack->fd = -1;
CHECK(ResourceBundle::HasSharedInstance());
base::FilePath locale_file_path =
ResourceBundle::GetSharedInstance().GetOverriddenPakPath();
if (locale_file_path.empty()) {
webview_locale_pack->fd =
LoadLocalePakFromApk(app_locale, gender, true ,
&webview_locale_pack->region);
}
if (webview_locale_pack->fd < 0) {
if (locale_file_path.empty()) {
auto path =
ResourceBundle::GetSharedInstance().GetLocaleFilePath(app_locale);
if (base::PathExists(path)) {
locale_file_path = std::move(path);
}
}
if (locale_file_path.empty()) {
LOG(WARNING) << "locale_file_path.empty() for locale " << app_locale;
return false;
}
auto flags =
static_cast<uint32_t>(base::File::FLAG_OPEN | base::File::FLAG_READ);
webview_locale_pack->fd =
base::File(locale_file_path, flags).TakePlatformFile();
webview_locale_pack->region = base::MemoryMappedFile::Region::kWholeFile;
}
}
auto locale_data = LoadDataPackFromLocalePak(webview_locale_pack->fd,
webview_locale_pack->region);
if (!locale_data.get()) {
return false;
}
locale_resources_data->push_back(std::move(locale_data));
if (g_load_non_webview_locale_paks) {
non_webview_locale_pack->fd =
LoadLocalePakFromApk(app_locale, gender, false ,
&non_webview_locale_pack->region);
locale_data = LoadDataPackFromLocalePak(non_webview_locale_pack->fd,
non_webview_locale_pack->region);
if (!locale_data.get()) {
return false;
}
locale_resources_data->push_back(std::move(locale_data));
}
return true;
}
}
void ResourceBundle::LoadCommonResources() {
base::FilePath disk_path;
base::PathService::Get(ui::DIR_RESOURCE_PAKS_ANDROID, &disk_path);
disk_path = disk_path.AppendASCII("chrome_100_percent.pak");
bool success =
LoadFromApkOrFile("assets/chrome_100_percent.pak", &disk_path,
&g_chrome_100_percent_fd, &g_chrome_100_percent_region);
DCHECK(success);
AddDataPackFromFileRegion(base::File(g_chrome_100_percent_fd),
g_chrome_100_percent_region, k100Percent);
if constexpr (BUILDFLAG(ENABLE_HIDPI)) {
disk_path = disk_path.DirName().AppendASCII("chrome_200_percent.pak");
success = LoadFromApkOrFile("assets/chrome_200_percent.pak", &disk_path,
&g_chrome_200_percent_fd,
&g_chrome_200_percent_region);
if (success) {
AddDataPackFromFileRegion(base::File(g_chrome_200_percent_fd),
g_chrome_200_percent_region, k200Percent);
}
}
}
bool ResourceBundle::LocaleDataPakExists(std::string_view locale,
Gender gender) {
const bool in_split = !g_locale_paks_in_apk;
const bool exists = ::ui::LocaleDataPakExists(locale, gender, in_split,
false);
if (exists || !in_split) {
return exists;
}
const auto path = GetLocaleFilePath(locale);
return !path.empty() && base::PathExists(path);
}
std::string ResourceBundle::LoadLocaleResources(const std::string& pref_locale,
bool crash_on_failure) {
DCHECK_EQ(locale_resources_data_.size(), 0u) << "locale.pak already loaded";
std::string app_locale = l10n_util::GetApplicationLocale(pref_locale);
const Gender gender = Gender::kDefault;
FdAndRegion webview_locale_pack;
FdAndRegion non_webview_locale_pack;
non_webview_locale_pack.fd = -1;
if (!LoadLocaleResourcesForLocaleAndGender(
app_locale, gender, &webview_locale_pack, &non_webview_locale_pack,
&locale_resources_data_)) {
return std::string();
}
std::vector<ResourceBundle::FdAndRegion>& locale_packs =
GetLocalePaksGlobal();
CHECK_EQ(locale_packs.size(), 0u);
webview_locale_pack.purpose = LocalePakPurpose::kWebViewMain;
locale_packs.push_back(webview_locale_pack);
if (non_webview_locale_pack.fd >= 0) {
non_webview_locale_pack.purpose = LocalePakPurpose::kNonWebViewMain;
locale_packs.push_back(non_webview_locale_pack);
}
if (gender != Gender::kDefault) {
non_webview_locale_pack.fd = -1;
if (!LoadLocaleResourcesForLocaleAndGender(
app_locale, Gender::kDefault, &webview_locale_pack,
&non_webview_locale_pack, &locale_resources_data_)) {
return std::string();
}
webview_locale_pack.purpose = LocalePakPurpose::kWebViewFallback;
locale_packs.push_back(webview_locale_pack);
if (non_webview_locale_pack.fd >= 0) {
non_webview_locale_pack.purpose = LocalePakPurpose::kNonWebViewFallback;
locale_packs.push_back(non_webview_locale_pack);
}
}
return app_locale;
}
gfx::Image& ResourceBundle::GetNativeImageNamed(int resource_id) {
return GetImageNamed(resource_id);
}
void SetLocalePaksStoredInApk(bool value) {
g_locale_paks_in_apk = value;
}
void DetectAndSetLoadNonWebViewLocalePaks() {
g_load_non_webview_locale_paks =
LocaleDataPakExists("en-US", ResourceBundle::Gender::kDefault,
false , false );
}
void LoadMainAndroidPackFile(const char* path_within_apk,
const base::FilePath& disk_file_path) {
if (LoadFromApkOrFile(path_within_apk,
&disk_file_path,
&g_resources_pack_fd,
&g_resources_pack_region)) {
ResourceBundle::GetSharedInstance().AddDataPackFromFileRegion(
base::File(g_resources_pack_fd), g_resources_pack_region,
kScaleFactorNone);
}
}
void LoadPackFileFromApk(const std::string& path,
const std::string& split_name) {
base::MemoryMappedFile::Region region;
FileDescriptor fd = base::android::OpenApkAsset(path, split_name, ®ion);
CHECK_GE(fd, 0) << "Could not find " << path << " in APK.";
ui::ResourceBundle::GetSharedInstance().AddDataPackFromFileRegion(
base::File(fd), region, ui::kScaleFactorNone);
}
FileDescriptor GetMainAndroidPackFd(
base::MemoryMappedFile::Region* out_region) {
DCHECK_GE(g_resources_pack_fd, 0);
*out_region = g_resources_pack_region;
return g_resources_pack_fd;
}
FileDescriptor GetCommonResourcesPackFd(
base::MemoryMappedFile::Region* out_region) {
DCHECK_GE(g_chrome_100_percent_fd, 0);
*out_region = g_chrome_100_percent_region;
return g_chrome_100_percent_fd;
}
FileDescriptor Get200PercentResourcesPackFd(
base::MemoryMappedFile::Region* out_region) {
DCHECK_GE(g_chrome_200_percent_fd, 0);
*out_region = g_chrome_200_percent_region;
return g_chrome_200_percent_fd;
}
const std::vector<ResourceBundle::FdAndRegion>& GetLocalePaks() {
const std::vector<ResourceBundle::FdAndRegion>& locale_packs =
GetLocalePaksGlobal();
CHECK_GT(locale_packs.size(), 0u);
return locale_packs;
}
void SetNoAvailableLocalePaksForTest() {
Java_ResourceBundle_setNoAvailableLocalePaks(
base::android::AttachCurrentThread());
}
void UnloadAndroidLocaleResources() {
GetLocalePaksGlobal().clear();
}
std::vector<ResourceBundle::FdAndRegion> SwapAndroidGlobalsForTesting(
const std::vector<ResourceBundle::FdAndRegion>& new_locale_packs) {
return std::exchange(GetLocalePaksGlobal(), new_locale_packs);
}
}
DEFINE_JNI(ResourceBundle)