Merge "Add OverlayManagerImpl to create frro, idmap and get overlayInfos"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a526f37aae
@@ -71,6 +71,7 @@ cc_library {
|
||||
host_supported: true,
|
||||
srcs: [
|
||||
"libidmap2/**/*.cpp",
|
||||
"self_targeting/*.cpp",
|
||||
],
|
||||
export_include_dirs: ["include"],
|
||||
target: {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
170
cmds/idmap2/self_targeting/SelfTargeting.cpp
Normal file
170
cmds/idmap2/self_targeting/SelfTargeting.cpp
Normal 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
|
||||
462
core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
Normal file
462
core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
Normal 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
|
||||
39
core/tests/overlaytests/device_self_targeting/Android.bp
Normal file
39
core/tests/overlaytests/device_self_targeting/Android.bp
Normal 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"],
|
||||
}
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user