Merge "Add OverlayManagerImpl to create frro, idmap and get overlayInfos"

This commit is contained in:
TreeHugger Robot
2022-11-24 18:47:11 +00:00
committed by Android (Google) Code Review
15 changed files with 1477 additions and 11 deletions

View File

@@ -71,6 +71,7 @@ cc_library {
host_supported: true,
srcs: [
"libidmap2/**/*.cpp",
"self_targeting/*.cpp",
],
export_include_dirs: ["include"],
target: {

View File

@@ -46,14 +46,6 @@ struct TargetResourceContainer : public ResourceContainer {
~TargetResourceContainer() override = default;
};
struct OverlayManifestInfo {
std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
};
struct OverlayData {
struct ResourceIdValue {
// The overlay resource id.

View File

@@ -30,13 +30,13 @@ namespace android::idmap2 {
#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
// use typedefs to let the compiler warn us about implicit casts
using ResourceId = uint32_t; // 0xpptteeee
using ResourceId = android::ResourceId; // 0xpptteeee
using PackageId = uint8_t; // pp in 0xpptteeee
using TypeId = uint8_t; // tt in 0xpptteeee
using EntryId = uint16_t; // eeee in 0xpptteeee
using DataType = uint8_t; // Res_value::dataType
using DataValue = uint32_t; // Res_value::data
using DataType = android::DataType; // Res_value::dataType
using DataValue = android::DataValue; // Res_value::data
struct TargetValue {
DataType data_type;

View File

@@ -0,0 +1,170 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/stat.h>
#include <fstream>
#include <optional>
#define LOG_TAG "SelfTargeting"
#include "androidfw/ResourceTypes.h"
#include "idmap2/BinaryStreamVisitor.h"
#include "idmap2/FabricatedOverlay.h"
#include "idmap2/Idmap.h"
#include "idmap2/Result.h"
using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask;
using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
using android::idmap2::BinaryStreamVisitor;
using android::idmap2::Idmap;
using android::idmap2::OverlayResourceContainer;
namespace android::self_targeting {
constexpr const mode_t kIdmapFilePermission = S_IRUSR | S_IWUSR; // u=rw-, g=---, o=---
extern "C" bool
CreateFrroFile(std::string& out_err_result, std::string& packageName, std::string& overlayName,
std::string& targetPackageName, std::optional<std::string>& targetOverlayable,
std::vector<FabricatedOverlayEntryParameters>& entries_params,
const std::string& frro_file_path) {
android::idmap2::FabricatedOverlay::Builder builder(packageName, overlayName,
targetPackageName);
if (targetOverlayable.has_value()) {
builder.SetOverlayable(targetOverlayable.value_or(std::string()));
}
for (const auto& entry_params : entries_params) {
const auto dataType = entry_params.data_type;
if (entry_params.data_binary_value.has_value()) {
builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value,
entry_params.configuration);
} else if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) {
builder.SetResourceValue(entry_params.resource_name, dataType,
entry_params.data_value, entry_params.configuration);
} else if (dataType == Res_value::TYPE_STRING) {
builder.SetResourceValue(entry_params.resource_name, dataType,
entry_params.data_string_value , entry_params.configuration);
} else {
out_err_result = base::StringPrintf("Unsupported data type %d", dataType);
return false;
}
}
const auto frro = builder.Build();
std::ofstream fout(frro_file_path);
if (fout.fail()) {
out_err_result = base::StringPrintf("open output stream fail %s", std::strerror(errno));
return false;
}
auto result = frro->ToBinaryStream(fout);
if (!result) {
unlink(frro_file_path.c_str());
out_err_result = base::StringPrintf("to stream fail %s", result.GetErrorMessage().c_str());
return false;
}
fout.close();
if (fout.fail()) {
unlink(frro_file_path.c_str());
out_err_result = base::StringPrintf("output stream fail %s", std::strerror(errno));
return false;
}
if (chmod(frro_file_path.c_str(), kIdmapFilePermission) == -1) {
out_err_result = base::StringPrintf("Failed to change the file permission %s",
frro_file_path.c_str());
return false;
}
return true;
}
extern "C" bool
CreateIdmapFile(std::string& out_err, const std::string& targetPath, const std::string& overlayPath,
const std::string& idmapPath, const std::string& overlayName) {
// idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap
// guarantees that existing memory maps will continue to be valid and unaffected. The file must
// be deleted before attempting to create the idmap, so that if idmap creation fails, the
// overlay will no longer be usable.
unlink(idmapPath.c_str());
const auto target = idmap2::TargetResourceContainer::FromPath(targetPath);
if (!target) {
out_err = base::StringPrintf("Failed to load target %s because of %s", targetPath.c_str(),
target.GetErrorMessage().c_str());
return false;
}
const auto overlay = OverlayResourceContainer::FromPath(overlayPath);
if (!overlay) {
out_err = base::StringPrintf("Failed to load overlay %s because of %s", overlayPath.c_str(),
overlay.GetErrorMessage().c_str());
return false;
}
// Overlay self target process. Only allow self-targeting types.
const auto fulfilled_policies = static_cast<PolicyBitmask>(
PolicyFlags::PUBLIC | PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION |
PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE | PolicyFlags::ODM_PARTITION |
PolicyFlags::OEM_PARTITION | PolicyFlags::ACTOR_SIGNATURE |
PolicyFlags::CONFIG_SIGNATURE);
const auto idmap = Idmap::FromContainers(**target, **overlay, overlayName,
fulfilled_policies, false /* enforce_overlayable */);
if (!idmap) {
out_err = base::StringPrintf("Failed to create idmap because of %s",
idmap.GetErrorMessage().c_str());
return false;
}
std::ofstream fout(idmapPath.c_str());
if (fout.fail()) {
out_err = base::StringPrintf("Failed to create idmap %s because of %s", idmapPath.c_str(),
strerror(errno));
return false;
}
BinaryStreamVisitor visitor(fout);
(*idmap)->accept(&visitor);
fout.close();
if (fout.fail()) {
unlink(idmapPath.c_str());
out_err = base::StringPrintf("Failed to write idmap %s because of %s", idmapPath.c_str(),
strerror(errno));
return false;
}
if (chmod(idmapPath.c_str(), kIdmapFilePermission) == -1) {
out_err = base::StringPrintf("Failed to change the file permission %s", idmapPath.c_str());
return false;
}
return true;
}
extern "C" bool
GetFabricatedOverlayInfo(std::string& out_err, const std::string& overlay_path,
OverlayManifestInfo& out_info) {
const auto overlay = idmap2::FabricatedOverlayContainer::FromPath(overlay_path);
if (!overlay) {
out_err = base::StringPrintf("Failed to write idmap %s because of %s",
overlay_path.c_str(), strerror(errno));
return false;
}
out_info = (*overlay)->GetManifestInfo();
return true;
}
} // namespace android::self_targeting

View File

@@ -0,0 +1,323 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.content.om;
import static android.content.Context.MODE_PRIVATE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY;
import android.annotation.NonNull;
import android.content.Context;
import android.content.om.OverlayInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.os.FabricatedOverlayInfo;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
import android.os.FileUtils;
import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This class provides the functionalities of registering an overlay, unregistering an overlay, and
* getting the list of overlays information.
*/
public class OverlayManagerImpl {
private static final String TAG = "OverlayManagerImpl";
private static final boolean DEBUG = false;
private static final String FRRO_EXTENSION = ".frro";
private static final String IDMAP_EXTENSION = ".idmap";
@VisibleForTesting(visibility = PRIVATE)
public static final String SELF_TARGET = ".self_target";
@NonNull
private final Context mContext;
private Path mBasePath;
/**
* Init a OverlayManagerImpl by the context.
*
* @param context the context to create overlay environment
*/
@VisibleForTesting(visibility = PACKAGE)
public OverlayManagerImpl(@NonNull Context context) {
mContext = Objects.requireNonNull(context);
if (!Process.myUserHandle().equals(context.getUser())) {
throw new SecurityException("Self-Targeting doesn't support multiple user now!");
}
}
private static void cleanExpiredOverlays(Path selfTargetingBasePath,
Path folderForCurrentBaseApk) {
try {
final String currentBaseFolder = folderForCurrentBaseApk.toString();
final String selfTargetingDir = selfTargetingBasePath.getFileName().toString();
Files.walkFileTree(
selfTargetingBasePath,
new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attrs)
throws IOException {
final String fileName = dir.getFileName().toString();
return fileName.equals(currentBaseFolder)
? FileVisitResult.SKIP_SUBTREE
: super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (!file.toFile().delete()) {
Log.w(TAG, "Failed to delete file " + file);
}
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
final String fileName = dir.getFileName().toString();
if (!fileName.equals(currentBaseFolder)
&& !fileName.equals(selfTargetingDir)) {
if (!dir.toFile().delete()) {
Log.w(TAG, "Failed to delete dir " + dir);
}
}
return super.postVisitDirectory(dir, exc);
}
});
} catch (IOException e) {
Log.w(TAG, "Unknown fail " + e);
}
}
/**
* Ensure the base dir for self-targeting is valid.
*/
@VisibleForTesting
public void ensureBaseDir() {
final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath();
final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName();
final File selfTargetingBaseFile = mContext.getDir(SELF_TARGET, MODE_PRIVATE);
Preconditions.checkArgument(
selfTargetingBaseFile.isDirectory()
&& selfTargetingBaseFile.exists()
&& selfTargetingBaseFile.canWrite()
&& selfTargetingBaseFile.canRead()
&& selfTargetingBaseFile.canExecute(),
"Can't work for this context");
cleanExpiredOverlays(selfTargetingBaseFile.toPath(), baseApkFolderName);
final File baseFile = new File(selfTargetingBaseFile, baseApkFolderName.toString());
if (!baseFile.exists()) {
if (!baseFile.mkdirs()) {
Log.w(TAG, "Failed to create directory " + baseFile);
}
// It fails to create frro and idmap files without this setting.
FileUtils.setPermissions(
baseFile,
FileUtils.S_IRWXU,
-1 /* uid unchanged */,
-1 /* gid unchanged */);
}
Preconditions.checkArgument(
baseFile.isDirectory()
&& baseFile.exists()
&& baseFile.canWrite()
&& baseFile.canRead()
&& baseFile.canExecute(), // 'list' capability
"Can't create a workspace for this context");
mBasePath = baseFile.toPath();
}
/**
* Check if the overlay name is valid or not.
*
* @param name the non-check overlay name
* @return the valid overlay name
*/
private static String checkOverlayNameValid(@NonNull String name) {
final String overlayName =
Preconditions.checkStringNotEmpty(
name, "overlayName should be neither empty nor null string");
final String checkOverlayNameResult =
FrameworkParsingPackageUtils.validateName(
overlayName, false /* requireSeparator */, true /* requireFilename */);
Preconditions.checkArgument(
checkOverlayNameResult == null,
TextUtils.formatSimple(
"Invalid overlayName \"%s\". The check result is %s.",
overlayName, checkOverlayNameResult));
return overlayName;
}
private void checkPackageName(@NonNull String packageName) {
Preconditions.checkStringNotEmpty(packageName);
Preconditions.checkArgument(
TextUtils.equals(mContext.getPackageName(), packageName),
TextUtils.formatSimple(
"UID %d doesn't own the package %s", Process.myUid(), packageName));
}
/**
* Save FabricatedOverlay instance as frro and idmap files.
*
* @param overlayInternal the FabricatedOverlayInternal to be saved.
*/
public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal)
throws IOException, PackageManager.NameNotFoundException {
ensureBaseDir();
Objects.requireNonNull(overlayInternal);
final List<FabricatedOverlayInternalEntry> entryList =
Objects.requireNonNull(overlayInternal.entries);
Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
checkPackageName(overlayInternal.packageName);
checkPackageName(overlayInternal.targetPackageName);
final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
final String targetPackage = Preconditions.checkStringNotEmpty(
applicationInfo.getBaseCodePath());
final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
createFrroFile(frroPath.toString(), overlayInternal);
try {
createIdmapFile(targetPackage, frroPath.toString(), idmapPath.toString(), overlayName);
} catch (IOException e) {
if (!frroPath.toFile().delete()) {
Log.w(TAG, "Failed to delete file " + frroPath);
}
throw e;
}
}
/**
* Remove the overlay with the specific name
*
* @param overlayName the specific name
*/
public void unregisterFabricatedOverlay(@NonNull String overlayName) {
ensureBaseDir();
checkOverlayNameValid(overlayName);
final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
if (!frroPath.toFile().delete()) {
Log.w(TAG, "Failed to delete file " + frroPath);
}
if (!idmapPath.toFile().delete()) {
Log.w(TAG, "Failed to delete file " + idmapPath);
}
}
/**
* Get the list of overlays information for the target package name.
*
* @param targetPackage the target package name
* @return the list of overlays information.
*/
@NonNull
public List<OverlayInfo> getOverlayInfosForTarget(@NonNull String targetPackage) {
ensureBaseDir();
final File base = mBasePath.toFile();
final File[] frroFiles = base.listFiles((dir, name) -> {
if (!name.endsWith(FRRO_EXTENSION)) {
return false;
}
final String idmapFileName = name.substring(0, name.length() - FRRO_EXTENSION.length())
+ IDMAP_EXTENSION;
final File idmapFile = new File(dir, idmapFileName);
return idmapFile.exists();
});
final ArrayList<OverlayInfo> overlayInfos = new ArrayList<>();
for (File file : frroFiles) {
final FabricatedOverlayInfo fabricatedOverlayInfo;
try {
fabricatedOverlayInfo = getFabricatedOverlayInfo(file.getAbsolutePath());
} catch (IOException e) {
Log.w(TAG, "can't load " + file);
continue;
}
if (!TextUtils.equals(targetPackage, fabricatedOverlayInfo.targetPackageName)) {
continue;
}
if (DEBUG) {
Log.i(TAG, "load " + file);
}
final OverlayInfo overlayInfo =
new OverlayInfo(
fabricatedOverlayInfo.packageName,
fabricatedOverlayInfo.overlayName,
fabricatedOverlayInfo.targetPackageName,
fabricatedOverlayInfo.targetOverlayable,
null,
file.getAbsolutePath(),
OverlayInfo.STATE_ENABLED,
UserHandle.myUserId(),
DEFAULT_PRIORITY,
true /* isMutable */,
true /* isFabricated */);
overlayInfos.add(overlayInfo);
}
return overlayInfos;
}
private static native void createFrroFile(
@NonNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal)
throws IOException;
private static native void createIdmapFile(
@NonNull String targetPath,
@NonNull String overlayPath,
@NonNull String idmapPath,
@NonNull String overlayName)
throws IOException;
private static native FabricatedOverlayInfo getFabricatedOverlayInfo(
@NonNull String overlayPath) throws IOException;
}

View File

@@ -215,6 +215,7 @@ cc_library_shared {
"android_content_res_ResourceTimer.cpp",
"android_security_Scrypt.cpp",
"com_android_internal_content_om_OverlayConfig.cpp",
"com_android_internal_content_om_OverlayManagerImpl.cpp",
"com_android_internal_expresslog_Counter.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",

View File

@@ -198,6 +198,7 @@ extern int register_android_security_Scrypt(JNIEnv *env);
extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
extern int register_com_android_internal_expresslog_Counter(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
@@ -1587,6 +1588,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_SharedMemory),
REG_JNI(register_android_os_incremental_IncrementalManager),
REG_JNI(register_com_android_internal_content_om_OverlayConfig),
REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
REG_JNI(register_com_android_internal_expresslog_Counter),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),

View File

@@ -0,0 +1,462 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <dlfcn.h>
#include <optional>
#define LOG_TAG "OverlayManagerImpl"
#include "android-base/no_destructor.h"
#include "androidfw/ResourceTypes.h"
#include "core_jni_helpers.h"
#include "jni.h"
namespace android {
static struct fabricated_overlay_internal_offsets_t {
jclass classObject;
jfieldID packageName;
jfieldID overlayName;
jfieldID targetPackageName;
jfieldID targetOverlayable;
jfieldID entries;
} gFabricatedOverlayInternalOffsets;
static struct fabricated_overlay_internal_entry_offsets_t {
jclass classObject;
jfieldID resourceName;
jfieldID dataType;
jfieldID data;
jfieldID stringData;
jfieldID binaryData;
jfieldID configuration;
} gFabricatedOverlayInternalEntryOffsets;
static struct parcel_file_descriptor_offsets_t {
jclass classObject;
jmethodID getFd;
} gParcelFileDescriptorOffsets;
static struct List_offsets_t {
jclass classObject;
jmethodID size;
jmethodID get;
} gListOffsets;
static struct fabricated_overlay_info_offsets_t {
jclass classObject;
jmethodID constructor;
jfieldID packageName;
jfieldID overlayName;
jfieldID targetPackageName;
jfieldID targetOverlayable;
jfieldID path;
} gFabricatedOverlayInfoOffsets;
namespace self_targeting {
constexpr const char kIOException[] = "java/io/IOException";
constexpr const char IllegalArgumentException[] = "java/lang/IllegalArgumentException";
class DynamicLibraryLoader {
public:
explicit DynamicLibraryLoader(JNIEnv* env) {
/* For SelfTargeting, there are 2 types of files to be handled. One is frro and the other is
* idmap. For creating frro/idmap files and reading frro files, it needs libandroid_runtime
* to do a shared link to libidmap2. However, libidmap2 contains the codes generated from
* google protocol buffer. When libandroid_runtime does a shared link to libidmap2, it will
* impact the memory for system_server and zygote(a.k.a. all applications).
*
* Not all applications need to either create/read frro files or create idmap files all the
* time. When the apps apply the SelfTargeting overlay effect, it only needs libandroifw
* that is loaded. To use dlopen(libidmap2.so) is to make sure that applications don't
* impact themselves' memory by loading libidmap2 until they need to create/read frro files
* or create idmap files.
*/
handle_ = dlopen("libidmap2.so", RTLD_NOW);
if (handle_ == nullptr) {
jniThrowNullPointerException(env);
return;
}
createIdmapFileFuncPtr_ =
reinterpret_cast<CreateIdmapFileFunc>(dlsym(handle_, "CreateIdmapFile"));
if (createIdmapFileFuncPtr_ == nullptr) {
jniThrowNullPointerException(env, "The symbol CreateIdmapFile is not found.");
return;
}
getFabricatedOverlayInfoFuncPtr_ = reinterpret_cast<GetFabricatedOverlayInfoFunc>(
dlsym(handle_, "GetFabricatedOverlayInfo"));
if (getFabricatedOverlayInfoFuncPtr_ == nullptr) {
jniThrowNullPointerException(env, "The symbol GetFabricatedOverlayInfo is not found.");
return;
}
createFrroFile_ = reinterpret_cast<CreateFrroFileFunc>(dlsym(handle_, "CreateFrroFile"));
if (createFrroFile_ == nullptr) {
jniThrowNullPointerException(env, "The symbol CreateFrroFile is not found.");
return;
}
}
bool callCreateFrroFile(std::string& out_error, const std::string& packageName,
const std::string& overlayName, const std::string& targetPackageName,
const std::optional<std::string>& targetOverlayable,
const std::vector<FabricatedOverlayEntryParameters>& entries_params,
const std::string& frro_file_path) {
return createFrroFile_(out_error, packageName, overlayName, targetPackageName,
targetOverlayable, entries_params, frro_file_path);
}
bool callCreateIdmapFile(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath, const std::string& idmapPath,
const std::string& overlayName) {
return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName);
}
bool callGetFabricatedOverlayInfo(std::string& out_error, const std::string& overlay_path,
OverlayManifestInfo& out_overlay_manifest_info) {
return getFabricatedOverlayInfoFuncPtr_(out_error, overlay_path, out_overlay_manifest_info);
}
explicit operator bool() const {
return handle_ != nullptr && createFrroFile_ != nullptr &&
createIdmapFileFuncPtr_ != nullptr && getFabricatedOverlayInfoFuncPtr_ != nullptr;
}
DynamicLibraryLoader(const DynamicLibraryLoader&) = delete;
DynamicLibraryLoader& operator=(const DynamicLibraryLoader&) = delete;
~DynamicLibraryLoader() {
if (handle_ != nullptr) {
dlclose(handle_);
}
}
private:
typedef bool (*CreateFrroFileFunc)(
std::string& out_error, const std::string& packageName, const std::string& overlayName,
const std::string& targetPackageName,
const std::optional<std::string>& targetOverlayable,
const std::vector<FabricatedOverlayEntryParameters>& entries_params,
const std::string& frro_file_path);
typedef bool (*CreateIdmapFileFunc)(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath,
const std::string& idmapPath,
const std::string& overlayName);
typedef bool (*GetFabricatedOverlayInfoFunc)(std::string& out_error,
const std::string& overlay_path,
OverlayManifestInfo& out_overlay_manifest_info);
void* handle_;
CreateFrroFileFunc createFrroFile_;
CreateIdmapFileFunc createIdmapFileFuncPtr_;
GetFabricatedOverlayInfoFunc getFabricatedOverlayInfoFuncPtr_;
};
static DynamicLibraryLoader& EnsureDynamicLibraryLoader(JNIEnv* env) {
static android::base::NoDestructor<DynamicLibraryLoader> loader(env);
return *loader;
}
static std::optional<std::string> getNullableString(JNIEnv* env, jobject object, jfieldID field) {
auto javaString = reinterpret_cast<jstring>(env->GetObjectField(object, field));
if (javaString == nullptr) {
return std::nullopt;
}
const ScopedUtfChars result(env, javaString);
if (result.c_str() == nullptr) {
return std::nullopt;
}
return std::optional<std::string>{result.c_str()};
}
static std::optional<android::base::borrowed_fd> getNullableFileDescriptor(JNIEnv* env,
jobject object,
jfieldID field) {
auto binaryData = env->GetObjectField(object, field);
if (binaryData == nullptr) {
return std::nullopt;
}
return env->CallIntMethod(binaryData, gParcelFileDescriptorOffsets.getFd);
}
static void CreateFrroFile(JNIEnv* env, jclass /*clazz*/, jstring jsFrroFilePath, jobject overlay) {
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
jniThrowNullPointerException(env, "libidmap2 is not loaded");
return;
}
if (overlay == nullptr) {
jniThrowNullPointerException(env, "overlay is null");
return;
}
auto jsPackageName =
(jstring)env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.packageName);
const ScopedUtfChars packageName(env, jsPackageName);
if (packageName.c_str() == nullptr) {
jniThrowNullPointerException(env, "packageName is null");
return;
}
auto jsOverlayName =
(jstring)env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.overlayName);
const ScopedUtfChars overlayName(env, jsOverlayName);
if (overlayName.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlayName is null");
return;
}
auto jsTargetPackageName =
(jstring)env->GetObjectField(overlay,
gFabricatedOverlayInternalOffsets.targetPackageName);
const ScopedUtfChars targetPackageName(env, jsTargetPackageName);
if (targetPackageName.c_str() == nullptr) {
jniThrowNullPointerException(env, "targetPackageName is null");
return;
}
auto overlayable =
getNullableString(env, overlay, gFabricatedOverlayInternalOffsets.targetOverlayable);
const ScopedUtfChars frroFilePath(env, jsFrroFilePath);
if (frroFilePath.c_str() == nullptr) {
jniThrowNullPointerException(env, "frroFilePath is null");
return;
}
jobject entries = env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.entries);
if (entries == nullptr) {
jniThrowNullPointerException(env, "overlay entries is null");
return;
}
const jint size = env->CallIntMethod(entries, gListOffsets.size);
ALOGV("frroFilePath = %s, packageName = %s, overlayName = %s, targetPackageName = %s,"
" targetOverlayable = %s, size = %d",
frroFilePath.c_str(), packageName.c_str(), overlayName.c_str(), targetPackageName.c_str(),
overlayable.value_or(std::string()).c_str(), size);
std::vector<FabricatedOverlayEntryParameters> entries_params;
for (jint i = 0; i < size; i++) {
jobject entry = env->CallObjectMethod(entries, gListOffsets.get, i);
auto jsResourceName = reinterpret_cast<jstring>(
env->GetObjectField(entry, gFabricatedOverlayInternalEntryOffsets.resourceName));
const ScopedUtfChars resourceName(env, jsResourceName);
const auto dataType =
env->GetIntField(entry, gFabricatedOverlayInternalEntryOffsets.dataType);
// In Java, the data type is int but the maximum value of data Type is less than 0xff.
if (dataType >= UCHAR_MAX) {
jniThrowException(env, IllegalArgumentException, "Unsupported data type");
return;
}
const auto data = env->GetIntField(entry, gFabricatedOverlayInternalEntryOffsets.data);
auto configuration =
getNullableString(env, entry, gFabricatedOverlayInternalEntryOffsets.configuration);
auto string_data =
getNullableString(env, entry, gFabricatedOverlayInternalEntryOffsets.stringData);
auto binary_data =
getNullableFileDescriptor(env, entry,
gFabricatedOverlayInternalEntryOffsets.binaryData);
entries_params.push_back(
FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType,
(DataValue)data,
string_data.value_or(std::string()), binary_data,
configuration.value_or(std::string())});
ALOGV("resourceName = %s, dataType = 0x%08x, data = 0x%08x, dataString = %s,"
" binaryData = %d, configuration = %s",
resourceName.c_str(), dataType, data, string_data.value_or(std::string()).c_str(),
binary_data.has_value(), configuration.value_or(std::string()).c_str());
}
std::string err_result;
if (!dlLoader.callCreateFrroFile(err_result, packageName.c_str(), overlayName.c_str(),
targetPackageName.c_str(), overlayable, entries_params,
frroFilePath.c_str())) {
jniThrowException(env, IllegalArgumentException, err_result.c_str());
return;
}
}
static void CreateIdmapFile(JNIEnv* env, jclass /* clazz */, jstring jsTargetPath,
jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName) {
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
jniThrowNullPointerException(env, "libidmap2 is not loaded");
return;
}
const ScopedUtfChars targetPath(env, jsTargetPath);
if (targetPath.c_str() == nullptr) {
jniThrowNullPointerException(env, "targetPath is null");
return;
}
const ScopedUtfChars overlayPath(env, jsOverlayPath);
if (overlayPath.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlayPath is null");
return;
}
const ScopedUtfChars idmapPath(env, jsIdmapPath);
if (idmapPath.c_str() == nullptr) {
jniThrowNullPointerException(env, "idmapPath is null");
return;
}
const ScopedUtfChars overlayName(env, jsOverlayName);
if (overlayName.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlayName is null");
return;
}
ALOGV("target_path = %s, overlay_path = %s, idmap_path = %s, overlay_name = %s",
targetPath.c_str(), overlayPath.c_str(), idmapPath.c_str(), overlayName.c_str());
std::string err_result;
if (!dlLoader.callCreateIdmapFile(err_result, targetPath.c_str(), overlayPath.c_str(),
idmapPath.c_str(), overlayName.c_str())) {
jniThrowException(env, kIOException, err_result.c_str());
return;
}
}
static jobject GetFabricatedOverlayInfo(JNIEnv* env, jclass /* clazz */, jstring jsOverlayPath) {
const ScopedUtfChars overlay_path(env, jsOverlayPath);
if (overlay_path.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlay_path is null");
return nullptr;
}
ALOGV("overlay_path = %s", overlay_path.c_str());
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
return nullptr;
}
std::string err_result;
OverlayManifestInfo overlay_manifest_info;
if (!dlLoader.callGetFabricatedOverlayInfo(err_result, overlay_path.c_str(),
overlay_manifest_info) != 0) {
jniThrowException(env, kIOException, err_result.c_str());
return nullptr;
}
jobject info = env->NewObject(gFabricatedOverlayInfoOffsets.classObject,
gFabricatedOverlayInfoOffsets.constructor);
jstring jsOverName = env->NewStringUTF(overlay_manifest_info.name.c_str());
jstring jsPackageName = env->NewStringUTF(overlay_manifest_info.package_name.c_str());
jstring jsTargetPackage = env->NewStringUTF(overlay_manifest_info.target_package.c_str());
jstring jsTargetOverlayable = env->NewStringUTF(overlay_manifest_info.target_name.c_str());
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.overlayName, jsOverName);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.packageName, jsPackageName);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.targetPackageName, jsTargetPackage);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.targetOverlayable, jsTargetOverlayable);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.path, jsOverlayPath);
return info;
}
} // namespace self_targeting
// JNI registration.
static const JNINativeMethod gOverlayManagerMethods[] = {
{"createFrroFile", "(Ljava/lang/String;Landroid/os/FabricatedOverlayInternal;)V",
reinterpret_cast<void*>(self_targeting::CreateFrroFile)},
{"createIdmapFile",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
reinterpret_cast<void*>(self_targeting::CreateIdmapFile)},
{"getFabricatedOverlayInfo", "(Ljava/lang/String;)Landroid/os/FabricatedOverlayInfo;",
reinterpret_cast<void*>(self_targeting::GetFabricatedOverlayInfo)},
};
int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env) {
jclass ListClass = FindClassOrDie(env, "java/util/List");
gListOffsets.classObject = MakeGlobalRefOrDie(env, ListClass);
gListOffsets.size = GetMethodIDOrDie(env, gListOffsets.classObject, "size", "()I");
gListOffsets.get =
GetMethodIDOrDie(env, gListOffsets.classObject, "get", "(I)Ljava/lang/Object;");
jclass fabricatedOverlayInternalClass =
FindClassOrDie(env, "android/os/FabricatedOverlayInternal");
gFabricatedOverlayInternalOffsets.classObject =
MakeGlobalRefOrDie(env, fabricatedOverlayInternalClass);
gFabricatedOverlayInternalOffsets.packageName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "packageName",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.overlayName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "overlayName",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.targetPackageName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "targetPackageName",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.targetOverlayable =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "targetOverlayable",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.entries =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "entries",
"Ljava/util/List;");
jclass fabricatedOverlayInternalEntryClass =
FindClassOrDie(env, "android/os/FabricatedOverlayInternalEntry");
gFabricatedOverlayInternalEntryOffsets.classObject =
MakeGlobalRefOrDie(env, fabricatedOverlayInternalEntryClass);
gFabricatedOverlayInternalEntryOffsets.resourceName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "resourceName",
"Ljava/lang/String;");
gFabricatedOverlayInternalEntryOffsets.dataType =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "dataType",
"I");
gFabricatedOverlayInternalEntryOffsets.data =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "data", "I");
gFabricatedOverlayInternalEntryOffsets.stringData =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "stringData",
"Ljava/lang/String;");
gFabricatedOverlayInternalEntryOffsets.binaryData =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "binaryData",
"Landroid/os/ParcelFileDescriptor;");
gFabricatedOverlayInternalEntryOffsets.configuration =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
"configuration", "Ljava/lang/String;");
jclass parcelFileDescriptorClass =
android::FindClassOrDie(env, "android/os/ParcelFileDescriptor");
gParcelFileDescriptorOffsets.classObject = MakeGlobalRefOrDie(env, parcelFileDescriptorClass);
gParcelFileDescriptorOffsets.getFd =
GetMethodIDOrDie(env, gParcelFileDescriptorOffsets.classObject, "getFd", "()I");
jclass fabricatedOverlayInfoClass = FindClassOrDie(env, "android/os/FabricatedOverlayInfo");
gFabricatedOverlayInfoOffsets.classObject = MakeGlobalRefOrDie(env, fabricatedOverlayInfoClass);
gFabricatedOverlayInfoOffsets.constructor =
GetMethodIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "<init>", "()V");
gFabricatedOverlayInfoOffsets.packageName =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "packageName",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.overlayName =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "overlayName",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.targetPackageName =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "targetPackageName",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.targetOverlayable =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "targetOverlayable",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.path =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "path",
"Ljava/lang/String;");
return RegisterMethodsOrDie(env, "com/android/internal/content/om/OverlayManagerImpl",
gOverlayManagerMethods, NELEM(gOverlayManagerMethods));
}
} // namespace android

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2022 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
android_test {
name: "SelfTargetingOverlayDeviceTests",
srcs: ["src/**/*.java"],
platform_apis: true,
static_libs: [
"androidx.test.rules",
"androidx.test.runner",
"androidx.test.ext.junit",
"truth-prebuilt",
],
optimize: {
enabled: false,
},
test_suites: ["device-tests"],
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.overlaytest.self_targeting">
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.overlaytest.self_targeting"
android:label="Self-Targeting resource overlay tests" />
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<color name="mycolor">#ff112233</color>
<string name="mystring">hello</string>
</resources>

View File

@@ -0,0 +1,411 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.overlaytest;
import static android.content.Context.MODE_PRIVATE;
import static com.android.internal.content.om.OverlayManagerImpl.SELF_TARGET;
import static org.junit.Assert.assertThrows;
import android.annotation.NonNull;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.om.OverlayInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.content.om.OverlayManagerImpl;
import com.android.overlaytest.self_targeting.R;
import com.google.common.truth.Expect;
import com.google.common.truth.Truth;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
/**
* This test class verify the interfaces of {@link
* com.android.internal.content.om.OverlayManagerImpl}.
*/
@RunWith(AndroidJUnit4.class)
public class OverlayManagerImplTest {
private static final String TAG = "OverlayManagerImplTest";
private static final String TARGET_COLOR_RES = "color/mycolor";
private static final String TARGET_STRING_RES = "string/mystring";
private static final String TARGET_DRAWABLE_RES = "drawable/mydrawable";
private Context mContext;
private OverlayManagerImpl mOverlayManagerImpl;
private String mOverlayName;
@Rule public TestName mTestName = new TestName();
@Rule public Expect expect = Expect.create();
private void clearDir() throws IOException {
final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
final Path basePath = context.getDir(SELF_TARGET, MODE_PRIVATE).toPath();
Files.walkFileTree(
basePath,
new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
if (!file.toFile().delete()) {
Log.w(TAG, "Failed to delete file " + file);
}
return super.visitFile(file, attrs);
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
if (!dir.toFile().delete()) {
Log.w(TAG, "Failed to delete dir " + dir);
}
return super.postVisitDirectory(dir, exc);
}
});
}
@Before
public void setUp() throws IOException {
clearDir();
mOverlayName = mTestName.getMethodName();
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mOverlayManagerImpl = new OverlayManagerImpl(mContext);
}
@After
public void tearDown() throws IOException {
clearDir();
}
private <T> void addOverlayEntry(
FabricatedOverlayInternal overlayInternal,
@NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) {
List<FabricatedOverlayInternalEntry> entries = new ArrayList<>();
for (Pair<String, Pair<String, T>> entryDefinition : entryDefinitions) {
FabricatedOverlayInternalEntry internalEntry = new FabricatedOverlayInternalEntry();
internalEntry.resourceName = entryDefinition.first;
internalEntry.configuration = entryDefinition.second.first;
if (entryDefinition.second.second instanceof ParcelFileDescriptor) {
internalEntry.binaryData = (ParcelFileDescriptor) entryDefinition.second.second;
} else if (entryDefinition.second.second instanceof String) {
internalEntry.stringData = (String) entryDefinition.second.second;
internalEntry.dataType = TypedValue.TYPE_STRING;
} else {
internalEntry.data = (int) entryDefinition.second.second;
internalEntry.dataType = TypedValue.TYPE_INT_COLOR_ARGB8;
}
entries.add(internalEntry);
overlayInternal.entries = entries;
}
}
private <T> FabricatedOverlayInternal createOverlayWithName(
@NonNull String overlayName,
@NonNull String targetPackageName,
@NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) {
final String packageName = mContext.getPackageName();
FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal();
overlayInternal.overlayName = overlayName;
overlayInternal.targetPackageName = targetPackageName;
overlayInternal.packageName = packageName;
addOverlayEntry(overlayInternal, entryDefinitions);
return overlayInternal;
}
@Test
public void registerOverlay_forAndroidPackage_shouldFail() {
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
"android",
List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
assertThrows(
"Wrong target package name",
IllegalArgumentException.class,
() -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
}
@Test
public void getOverlayInfosForTarget_defaultShouldBeZero() {
List<OverlayInfo> overlayInfos =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
Truth.assertThat(overlayInfos.size()).isEqualTo(0);
}
@Test
public void unregisterNonExistingOverlay_shouldBeOk() {
mOverlayManagerImpl.unregisterFabricatedOverlay("NotExisting");
}
@Test
public void registerOverlay_createColorOverlay_shouldBeSavedInAndLoadFromFile()
throws IOException, PackageManager.NameNotFoundException {
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
final List<OverlayInfo> overlayInfos =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
final int firstNumberOfOverlays = overlayInfos.size();
expect.that(firstNumberOfOverlays).isEqualTo(1);
final OverlayInfo overlayInfo = overlayInfos.get(0);
expect.that(overlayInfo).isNotNull();
Truth.assertThat(expect.hasFailures()).isFalse();
expect.that(overlayInfo.isFabricated()).isTrue();
expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
}
@Test
public void registerOverlay_createStringOverlay_shouldBeSavedInAndLoadFromFile()
throws IOException, PackageManager.NameNotFoundException {
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
mContext.getPackageName(),
List.of(Pair.create(TARGET_STRING_RES, Pair.create(null, "HELLO"))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
final List<OverlayInfo> overlayInfos =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
final int firstNumberOfOverlays = overlayInfos.size();
expect.that(firstNumberOfOverlays).isEqualTo(1);
final OverlayInfo overlayInfo = overlayInfos.get(0);
expect.that(overlayInfo).isNotNull();
Truth.assertThat(expect.hasFailures()).isFalse();
expect.that(overlayInfo.isFabricated()).isTrue();
expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
}
@Test
public void registerOverlay_createFileOverlay_shouldBeSavedInAndLoadFromFile()
throws IOException, PackageManager.NameNotFoundException {
ParcelFileDescriptor parcelFileDescriptor = mContext.getResources()
.openRawResourceFd(R.raw.overlay_drawable).getParcelFileDescriptor();
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
mContext.getPackageName(),
List.of(Pair.create(TARGET_DRAWABLE_RES,
Pair.create(null, parcelFileDescriptor))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
final List<OverlayInfo> overlayInfos =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
final int firstNumberOfOverlays = overlayInfos.size();
expect.that(firstNumberOfOverlays).isEqualTo(1);
final OverlayInfo overlayInfo = overlayInfos.get(0);
expect.that(overlayInfo).isNotNull();
Truth.assertThat(expect.hasFailures()).isFalse();
expect.that(overlayInfo.isFabricated()).isTrue();
expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName);
expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName());
expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName());
expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId());
}
@Test
public void registerOverlay_notExistedResource_shouldFailWithoutSavingAnyFile()
throws IOException {
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
mContext.getPackageName(),
List.of(Pair.create("color/not_existed", Pair.create(null, "HELLO"))));
assertThrows(IOException.class,
() -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
final List<OverlayInfo> overlayInfos =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
final int firstNumberOfOverlays = overlayInfos.size();
expect.that(firstNumberOfOverlays).isEqualTo(0);
final int[] fileCounts = new int[1];
Files.walkFileTree(
mContext.getDir(SELF_TARGET, MODE_PRIVATE).toPath(),
new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
fileCounts[0]++;
return super.visitFile(file, attrs);
}
});
expect.that(fileCounts[0]).isEqualTo(0);
}
@Test
public void registerMultipleOverlays_shouldMatchTheNumberOfOverlays()
throws IOException, PackageManager.NameNotFoundException {
final String secondOverlayName = mOverlayName + "2nd";
final int initNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
final int firstNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
overlayInternal =
createOverlayWithName(
secondOverlayName,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
final int secondNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
mOverlayManagerImpl.unregisterFabricatedOverlay(mOverlayName);
mOverlayManagerImpl.unregisterFabricatedOverlay(secondOverlayName);
final int finalNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
expect.that(initNumberOfOverlays).isEqualTo(0);
expect.that(firstNumberOfOverlays).isEqualTo(1);
expect.that(secondNumberOfOverlays).isEqualTo(2);
expect.that(finalNumberOfOverlays).isEqualTo(0);
}
@Test
public void unregisterOverlay_withIllegalOverlayName_shouldFail() {
assertThrows(
IllegalArgumentException.class,
() -> mOverlayManagerImpl.unregisterFabricatedOverlay("../../etc/password"));
}
@Test
public void registerTheSameOverlay_shouldNotIncreaseTheNumberOfOverlays()
throws IOException, PackageManager.NameNotFoundException {
final int initNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
FabricatedOverlayInternal overlayInternal =
createOverlayWithName(
mOverlayName,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
final int firstNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
overlayInternal =
createOverlayWithName(
mOverlayName,
mContext.getPackageName(),
List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE))));
mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal);
final int secondNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
mOverlayManagerImpl.unregisterFabricatedOverlay(mOverlayName);
final int finalNumberOfOverlays =
mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size();
expect.that(initNumberOfOverlays).isEqualTo(0);
expect.that(firstNumberOfOverlays).isEqualTo(1);
expect.that(secondNumberOfOverlays).isEqualTo(1);
expect.that(finalNumberOfOverlays).isEqualTo(0);
}
@Test
public void registerOverlay_packageNotOwnedBySelf_shouldFail() {
FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal();
overlayInternal.packageName = "com.android.systemui";
overlayInternal.overlayName = mOverlayName;
overlayInternal.targetOverlayable = "non-existed-target-overlayable";
overlayInternal.targetPackageName = mContext.getPackageName();
addOverlayEntry(
overlayInternal,
List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
assertThrows(
"The context doesn't own the package",
IllegalArgumentException.class,
() -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
}
@Test
public void ensureBaseDir_forOtherPackage_shouldFail()
throws PackageManager.NameNotFoundException {
final Context fakeContext =
mContext.createPackageContext("com.android.systemui", 0 /* flags */);
final OverlayManagerImpl overlayManagerImpl = new OverlayManagerImpl(fakeContext);
assertThrows(IllegalArgumentException.class, overlayManagerImpl::ensureBaseDir);
}
@Test
public void newOverlayManagerImpl_forOtherUser_shouldFail() {
Context fakeContext =
new ContextWrapper(mContext) {
@Override
public UserHandle getUser() {
return UserHandle.of(100);
}
@Override
public int getUserId() {
return 100;
}
};
assertThrows(SecurityException.class, () -> new OverlayManagerImpl(fakeContext));
}
}

View File

@@ -1830,6 +1830,28 @@ inline ResTable_overlayable_policy_header::PolicyFlags& operator |=(
return first;
}
using ResourceId = uint32_t; // 0xpptteeee
using DataType = uint8_t; // Res_value::dataType
using DataValue = uint32_t; // Res_value::data
struct OverlayManifestInfo {
std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
};
struct FabricatedOverlayEntryParameters {
std::string resource_name;
DataType data_type;
DataValue data_value;
std::string data_string_value;
std::optional<android::base::borrowed_fd> data_binary_value;
std::string configuration;
};
class AssetManager2;
/**