Add OverlayManagerImpl to create frro, idmap and get overlayInfos
Add OverlayManagerImpl to let applications create frro and idmap files
for overlaying itself's android resources.
* OverlayManagerImpl.java
* check the FabricatedOverlayInternal.
* com_android_internal_content_om_OverlayManagerImpl.cpp
* convert and read Java objects to native data.
* call the APIs in libidmap2 to create frro and idmap files.
Add OverlayManagerImpl.cpp into AndroidRuntime by modifying Android.bp
* Add register_com_android_internal_content_om_OverlayManagerImpl
register JNI methods
* add com_android_internal_content_om_OverlayManagerImpl.cpp
* It's a adapter to delegate the tasks to SelfTargeting.cpp
* dynamic link with libidmap2 by using dlopen and dlsym to call
functions in libidmap2.
Add SelfTargeting.cpp into libidmap2
* createFrroFile
* createIdmapFile
* getFabricatedOverlayInfo
for interoperability between libandroid_runtime and libidmap2.
* move OverlayManifestInfo from libidmap2 to libandroidfw
* add FabricatedOverlayEntryParameters into libandroidfw
Bug: 205919743
Test: build
Test: atest \
OverlayHostTests \
OverlayDeviceTests \
SelfTargetingOverlayDeviceTests \
OverlayRemountedTest \
FrameworksServicesTests:com.android.server.om \
CtsContentTestCases:android.content.om.cts \
idmap2_tests
Change-Id: I5425f3229e9a3858e57427ef84e6abaf32e89b6e
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 7a08cbd..5f06c97 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -71,6 +71,7 @@
host_supported: true,
srcs: [
"libidmap2/**/*.cpp",
+ "self_targeting/*.cpp",
],
export_include_dirs: ["include"],
target: {
diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h
index 2452ff0..4d28321 100644
--- a/cmds/idmap2/include/idmap2/ResourceContainer.h
+++ b/cmds/idmap2/include/idmap2/ResourceContainer.h
@@ -46,14 +46,6 @@
~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.
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index 2214a83..c2b0abe 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -30,13 +30,13 @@
#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;
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
new file mode 100644
index 0000000..20aa7d3
--- /dev/null
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -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
+
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
new file mode 100644
index 0000000..76e068d
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -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;
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d8b91c7..f140e79 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -215,6 +215,7 @@
"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",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index f549cd8..9e563de 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -198,6 +198,7 @@
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 @@
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),
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
new file mode 100644
index 0000000..df55e42
--- /dev/null
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -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
diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp
new file mode 100644
index 0000000..82998db
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/Android.bp
@@ -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"],
+}
diff --git a/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml b/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml
new file mode 100644
index 0000000..c121bf2
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml
@@ -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>
diff --git a/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp b/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp
new file mode 100644
index 0000000..aa7d642
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp
Binary files differ
diff --git a/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp b/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp
new file mode 100644
index 0000000..9126ae3
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp
Binary files differ
diff --git a/core/tests/overlaytests/device_self_targeting/res/values/values.xml b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
new file mode 100644
index 0000000..f0b4a6f
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/res/values/values.xml
@@ -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>
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
new file mode 100644
index 0000000..ca58410
--- /dev/null
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -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));
+ }
+}
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c740832..b2b95b7 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1830,6 +1830,28 @@
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;
/**