blob: d9ff35b49e0a51c1606fba1991768dcbc52e1000 [file] [log] [blame]
/*
* Copyright (C) 2016 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.
*/
#ifndef ANDROIDFW_ASSETMANAGER2_H_
#define ANDROIDFW_ASSETMANAGER2_H_
#include <utils/RefBase.h>
#include <array>
#include <limits>
#include <set>
#include <span>
#include <unordered_map>
#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include "androidfw/ApkAssets.h"
#include "androidfw/Asset.h"
#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
namespace android {
class Theme;
using ApkAssetsCookie = int32_t;
enum : ApkAssetsCookie {
kInvalidCookie = -1,
};
// Holds a bag that has been merged with its parent, if one exists.
struct ResolvedBag {
// A single key-value entry in a bag.
struct Entry {
// The key, as described in ResTable_map::name.
uint32_t key;
Res_value value;
// The resource ID of the origin style associated with the given entry.
uint32_t style;
// Which ApkAssets this entry came from.
ApkAssetsCookie cookie;
ResStringPool* key_pool;
ResStringPool* type_pool;
};
// Denotes the configuration axis that this bag varies with.
// If a configuration changes with respect to one of these axis,
// the bag should be reloaded.
uint32_t type_spec_flags;
// The number of entries in this bag. Access them by indexing into `entries`.
uint32_t entry_count;
// The array of entries for this bag. An empty array is a neat trick to force alignment
// of the Entry structs that follow this structure and avoids a bunch of casts.
Entry entries[0];
};
struct FindEntryResult;
// AssetManager2 is the main entry point for accessing assets and resources.
// AssetManager2 provides caching of resources retrieved via the underlying ApkAssets.
class AssetManager2 {
friend Theme;
public:
struct ResourceName {
const char* package = nullptr;
size_t package_len = 0u;
const char* type = nullptr;
const char16_t* type16 = nullptr;
size_t type_len = 0u;
const char* entry = nullptr;
const char16_t* entry16 = nullptr;
size_t entry_len = 0u;
};
using ApkAssetsPtr = sp<const ApkAssets>;
using ApkAssetsWPtr = wp<const ApkAssets>;
using ApkAssetsList = std::span<const ApkAssetsPtr>;
AssetManager2();
explicit AssetManager2(AssetManager2&& other) = default;
AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
struct ScopedOperation {
DISALLOW_COPY_AND_ASSIGN(ScopedOperation);
friend AssetManager2;
const AssetManager2& am_;
ScopedOperation(const AssetManager2& am);
public:
~ScopedOperation();
};
[[nodiscard]] ScopedOperation StartOperation() const;
// Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
// are not owned by the AssetManager, and must have a longer lifetime.
//
// Only pass invalidate_caches=false when it is known that the structure
// change in ApkAssets is due to a safe addition of resources with completely
// new resource IDs.
bool SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches = true);
bool SetApkAssets(std::initializer_list<ApkAssetsPtr> apk_assets, bool invalidate_caches = true);
const ApkAssetsPtr& GetApkAssets(ApkAssetsCookie cookie) const;
int GetApkAssetsCount() const {
return int(apk_assets_.size());
}
// Returns the string pool for the given asset cookie.
// Use the string pool returned here with a valid Res_value object of type Res_value::TYPE_STRING.
const ResStringPool* GetStringPoolForCookie(ApkAssetsCookie cookie) const;
// Returns the DynamicRefTable for the given package ID.
// This may be nullptr if the APK represented by `cookie` has no resource table.
const DynamicRefTable* GetDynamicRefTableForPackage(uint32_t package_id) const;
// Returns the DynamicRefTable for the ApkAssets represented by the cookie.
// This may be nullptr if the APK represented by `cookie` has no resource table.
std::shared_ptr<const DynamicRefTable> GetDynamicRefTableForCookie(ApkAssetsCookie cookie) const;
// Retrieve the assigned package id of the package if loaded into this AssetManager
uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
// Returns a string representation of the overlayable API of a package.
bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const;
const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage(
uint32_t package_id) const;
// Returns whether the resources.arsc of any loaded apk assets is allocated in RAM (not mmapped).
bool ContainsAllocatedTable() const;
// Sets/resets the configuration for this AssetManager. This will cause all
// caches that are related to the configuration change to be invalidated.
void SetConfigurations(std::vector<ResTable_config> configurations);
inline const std::vector<ResTable_config>& GetConfigurations() const {
return configurations_;
}
inline void SetDefaultLocale(uint32_t default_locale) {
default_locale_ = default_locale;
}
// Returns all configurations for which there are resources defined, or an I/O error if reading
// resource data failed.
//
// This includes resource configurations in all the ApkAssets set for this AssetManager.
// If `exclude_system` is set to true, resource configurations from system APKs
// ('android' package, other libraries) will be excluded from the list.
// If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap'
// will be excluded from the list.
base::expected<std::set<ResTable_config>, IOError> GetResourceConfigurations(
bool exclude_system = false, bool exclude_mipmap = false) const;
// Returns all the locales for which there are resources defined. This includes resource
// locales in all the ApkAssets set for this AssetManager.
// If `exclude_system` is set to true, resource locales from system APKs
// ('android' package, other libraries) will be excluded from the list.
// If `merge_equivalent_languages` is set to true, resource locales will be canonicalized
// and de-duped in the resulting list.
std::set<std::string> GetResourceLocales(bool exclude_system = false,
bool merge_equivalent_languages = false) const;
// Searches the set of APKs loaded by this AssetManager and opens the first one found located
// in the assets/ directory.
// `mode` controls how the file is opened.
//
// NOTE: The loaded APKs are searched in reverse order.
std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const;
// Opens a file within the assets/ directory of the APK specified by `cookie`.
// `mode` controls how the file is opened.
std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
Asset::AccessMode mode) const;
// Opens the directory specified by `dirname`. The result is an AssetDir that is the combination
// of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded.
// The entries are sorted by their ASCII name.
std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const;
// Searches the set of APKs loaded by this AssetManager and opens the first one found.
// `mode` controls how the file is opened.
// `out_cookie` is populated with the cookie of the APK this file was found in.
//
// NOTE: The loaded APKs are searched in reverse order.
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode,
ApkAssetsCookie* out_cookie = nullptr) const;
// Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened.
// This is typically used to open a specific AndroidManifest.xml, or a binary XML file
// referenced by a resource lookup with GetResource().
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
Asset::AccessMode mode) const;
// Returns the resource id of parent style of the specified theme.
//
// Returns a null error if the name is missing/corrupt, or an I/O error if reading resource data
// failed.
base::expected<uint32_t, NullOrIOError> GetParentThemeResourceId(uint32_t resid) const;
// Returns the resource name of the specified resource ID.
//
// Utf8 strings are preferred, and only if they are unavailable are the Utf16 variants populated.
//
// Returns a null error if the name is missing/corrupt, or an I/O error if reading resource data
// failed.
base::expected<ResourceName, NullOrIOError> GetResourceName(uint32_t resid) const;
// Finds the resource ID assigned to `resource_name`.
//
// `resource_name` must be of the form '[package:][type/]entry'.
// If no package is specified in `resource_name`, then `fallback_package` is used as the package.
// If no type is specified in `resource_name`, then `fallback_type` is used as the type.
//
// Returns a null error if no resource by that name was found, or an I/O error if reading resource
// data failed.
base::expected<uint32_t, NullOrIOError> GetResourceId(
const std::string& resource_name, const std::string& fallback_type = {},
const std::string& fallback_package = {}) const;
struct SelectedValue {
friend AssetManager2;
friend Theme;
SelectedValue() = default;
SelectedValue(const ResolvedBag* bag, const ResolvedBag::Entry& entry)
: cookie(entry.cookie),
data(entry.value.data),
type(entry.value.dataType),
flags(bag->type_spec_flags),
resid(0U),
config() {
}
// The cookie representing the ApkAssets in which the value resides.
ApkAssetsCookie cookie = kInvalidCookie;
// The data for this value, as interpreted according to `type`.
Res_value::data_type data;
// Type of the data value.
uint8_t type;
// The bitmask of configuration axis that this resource varies with.
// See ResTable_config::CONFIG_*.
uint32_t flags;
// The resource ID from which this value was resolved.
uint32_t resid;
// The configuration for which the resolved value was defined.
ResTable_config config;
private:
SelectedValue(uint8_t value_type, Res_value::data_type value_data, ApkAssetsCookie cookie,
uint32_t type_flags, uint32_t resid, const ResTable_config& config) :
cookie(cookie), data(value_data), type(value_type), flags(type_flags),
resid(resid), config(config) {};
};
// Retrieves the best matching resource value with ID `resid`.
//
// If `may_be_bag` is false, this function logs if the resource was a map/bag type and returns a
// null result. If `density_override` is non-zero, the configuration to match against is
// overridden with that density.
//
// Returns a null error if a best match could not be found, or an I/O error if reading resource
// data failed.
base::expected<SelectedValue, NullOrIOError> GetResource(uint32_t resid, bool may_be_bag = false,
uint16_t density_override = 0U) const;
// Resolves the resource referenced in `value` if the type is Res_value::TYPE_REFERENCE.
//
// If the data type is not Res_value::TYPE_REFERENCE, no work is done. Configuration flags of the
// values pointed to by the reference are OR'd into `value.flags`. If `cache_value` is true, then
// the resolved value will be cached and used when attempting to resolve the resource id specified
// in `value`.
//
// Returns a null error if the resource could not be resolved, or an I/O error if reading
// resource data failed.
base::expected<std::monostate, NullOrIOError> ResolveReference(SelectedValue& value,
bool cache_value = false) const;
// Retrieves the best matching bag/map resource with ID `resid`.
//
// This method will resolve all parent references for this bag and merge keys with the child.
// To iterate over the keys, use the following idiom:
//
// base::expected<const ResolvedBag*, NullOrIOError> bag = asset_manager->GetBag(id);
// if (bag.has_value()) {
// for (auto iter = begin(*bag); iter != end(*bag); ++iter) {
// ...
// }
// }
//
// Returns a null error if a best match could not be found, or an I/O error if reading resource
// data failed.
base::expected<const ResolvedBag*, NullOrIOError> GetBag(uint32_t resid) const;
// Retrieves the best matching bag/map resource of the resource referenced in `value`.
//
// If `value.type` is not Res_value::TYPE_REFERENCE, a null result is returned.
// Configuration flags of the bag pointed to by the reference are OR'd into `value.flags`.
//
// Returns a null error if a best match could not be found, or an I/O error if reading resource
// data failed.
base::expected<const ResolvedBag*, NullOrIOError> ResolveBag(SelectedValue& value) const;
// Returns the android::ResTable_typeSpec flags of the resource ID.
//
// Returns a null error if the resource could not be resolved, or an I/O error if reading
// resource data failed.
base::expected<uint32_t, NullOrIOError> GetResourceTypeSpecFlags(uint32_t resid) const;
base::expected<const std::vector<uint32_t>*, NullOrIOError> GetBagResIdStack(
uint32_t resid) const;
// Resets the resource resolution structures in preparation for the next resource retrieval.
void ResetResourceResolution() const;
// Enables or disables resource resolution logging. Clears stored steps when disabled.
void SetResourceResolutionLoggingEnabled(bool enabled);
// Returns formatted log of last resource resolution path, or empty if no resource has been
// resolved yet.
std::string GetLastResourceResolution() const;
// Creates a new Theme from this AssetManager.
std::unique_ptr<Theme> NewTheme();
void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
package_property_t excluded_property_flags = 0U) const;
void DumpToLog() const;
private:
DISALLOW_COPY_AND_ASSIGN(AssetManager2);
// A collection of configurations and their associated ResTable_type that match the current
// AssetManager configuration.
struct FilteredConfigGroup {
std::vector<const TypeSpec::TypeEntry*> type_entries;
};
// Represents an single package.
struct ConfiguredPackage {
// A pointer to the immutable, loaded package info.
const LoadedPackage* loaded_package_;
// A mutable AssetManager-specific list of configurations that match the AssetManager's
// current configuration. This is used as an optimization to avoid checking every single
// candidate configuration when looking up resources.
ByteBucketArray<FilteredConfigGroup> filtered_configs_;
};
// Represents a Runtime Resource Overlay that overlays resources in the logical package.
struct ConfiguredOverlay {
// The set of package groups that overlay this package group.
IdmapResMap overlay_res_maps_;
// The cookie of the overlay assets.
ApkAssetsCookie cookie;
};
// Represents a logical package, which can be made up of many individual packages. Each package
// in a PackageGroup shares the same package name and package ID.
struct PackageGroup {
// The set of packages that make-up this group.
std::vector<ConfiguredPackage> packages_;
// The cookies associated with each package in the group. They share the same order as
// packages_.
std::vector<ApkAssetsCookie> cookies_;
// Runtime Resource Overlays that overlay resources in this package group.
std::vector<ConfiguredOverlay> overlays_;
// A library reference table that contains build-package ID to runtime-package ID mappings.
std::shared_ptr<DynamicRefTable> dynamic_ref_table = std::make_shared<DynamicRefTable>();
};
// Finds the best entry for `resid` from the set of ApkAssets. The entry can be a simple
// Res_value, or a complex map/bag type. Returns a null result if a best entry cannot be found.
//
// `density_override` overrides the density of the current configuration when doing a search.
//
// When `stop_at_first_match` is true, the first match found is selected and the search
// terminates. This is useful for methods that just look up the name of a resource and don't
// care about the value. In this case, the value of `FindEntryResult::type_flags` is incomplete
// and should not be used.
//
// When `ignore_configuration` is true, FindEntry will return always select the first entry in
// for the type seen regardless of its configuration.
//
// NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly
// bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds.
base::expected<FindEntryResult, NullOrIOError> FindEntry(uint32_t resid,
uint16_t density_override,
bool stop_at_first_match,
bool ignore_configuration) const;
base::expected<FindEntryResult, NullOrIOError> FindEntryInternal(
const PackageGroup& package_group, uint8_t type_idx, uint16_t entry_idx,
const ResTable_config& desired_config, bool stop_at_first_match,
bool ignore_configuration) const;
// Assigns package IDs to all shared library ApkAssets.
// Should be called whenever the ApkAssets are changed.
void BuildDynamicRefTable(ApkAssetsList assets);
// Purge all resources that are cached and vary by the configuration axis denoted by the
// bitmask `diff`.
void InvalidateCaches(uint32_t diff);
// Triggers the re-construction of lists of types that match the set configuration.
// This should always be called when mutating the AssetManager's configuration or ApkAssets set.
void RebuildFilterList();
// Retrieves the APK paths of overlays that overlay non-system packages.
std::set<ApkAssetsPtr> GetNonSystemOverlays() const;
// AssetManager2::GetBag(resid) wraps this function to track which resource ids have already
// been seen while traversing bag parents.
base::expected<const ResolvedBag*, NullOrIOError> GetBag(
uint32_t resid, std::vector<uint32_t>& child_resids) const;
// Finish an operation that was running with the current asset manager, and clean up the
// promoted apk assets when the last operation ends.
void FinishOperation() const;
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
// The second pair element is the promoted version of the assets, that is held for the duration
// of the currently running operation. FinishOperation() clears all promoted assets to make sure
// they can be released when the system needs that.
mutable std::vector<std::pair<ApkAssetsWPtr, ApkAssetsPtr>> apk_assets_;
// DynamicRefTables for shared library package resolution.
// These are ordered according to apk_assets_. The mappings may change depending on what is
// in apk_assets_, therefore they must be stored in the AssetManager and not in the
// immutable ApkAssets class.
std::vector<PackageGroup> package_groups_;
// An array mapping package ID to index into package_groups. This keeps the lookup fast
// without taking too much memory.
std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
uint32_t default_locale_;
// The current configurations set for this AssetManager. When this changes, cached resources
// may need to be purged.
std::vector<ResTable_config> configurations_;
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
mutable std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
// Cached set of bag resid stacks for each bag. These are cached because they might be requested
// a number of times for each view during View inspection.
mutable std::unordered_map<uint32_t, std::vector<uint32_t>> cached_bag_resid_stacks_;
// Cached set of resolved resource values.
mutable std::unordered_map<uint32_t, SelectedValue> cached_resolved_values_;
// Tracking the number of the started operations running with the current AssetManager.
// Finishing the last one clears all promoted apk assets.
mutable int number_of_running_scoped_operations_ = 0;
// Whether or not to save resource resolution steps
bool resource_resolution_logging_enabled_ = false;
struct Resolution {
struct Step {
enum class Type {
INITIAL,
BETTER_MATCH,
OVERLAID,
OVERLAID_INLINE,
SKIPPED,
NO_ENTRY,
};
// Marks what kind of override this step was.
Type type;
ApkAssetsCookie cookie = kInvalidCookie;
// Built name of configuration for this step.
String8 config_name;
};
// Last resolved resource ID.
uint32_t resid;
// Last resolved resource result cookie.
ApkAssetsCookie cookie = kInvalidCookie;
// Last resolved resource type.
StringPoolRef type_string_ref;
// Last resolved resource entry.
StringPoolRef entry_string_ref;
// Steps taken to resolve last resource.
std::vector<Step> steps;
// The configuration name of the best resource found.
String8 best_config_name;
// The package name of the best resource found.
String8 best_package_name;
};
// Record of the last resolved resource's resolution path.
mutable Resolution last_resolution_;
};
class Theme {
friend class AssetManager2;
public:
~Theme();
// Applies the style identified by `resid` to this theme.
//
// This can be called multiple times with different styles. By default, any theme attributes that
// are already defined before this call are not overridden. If `force` is set to true, this
// behavior is changed and all theme attributes from the style at `resid` are applied.
//
// Returns a null error if the style could not be applied, or an I/O error if reading resource
// data failed.
base::expected<std::monostate, NullOrIOError> ApplyStyle(uint32_t resid, bool force = false);
// Clears the existing theme, sets the new asset manager to use for this theme, and applies the
// styles in `style_ids` through repeated invocations of `ApplyStyle`.
void Rebase(AssetManager2* am, const uint32_t* style_ids, const uint8_t* force,
size_t style_count);
// Sets this Theme to be a copy of `source` if `source` has the same AssetManager as this Theme.
//
// If `source` does not have the same AssetManager as this theme, only attributes from ApkAssets
// loaded into both AssetManagers will be copied to this theme.
//
// Returns an I/O error if reading resource data failed.
base::expected<std::monostate, IOError> SetTo(const Theme& source);
void Clear();
// Retrieves the value of attribute ID `resid` in the theme.
//
// NOTE: This function does not do reference traversal. If you want to follow references to other
// resources to get the "real" value to use, you need to call ResolveReference() after this
// function.
std::optional<AssetManager2::SelectedValue> GetAttribute(uint32_t resid) const;
// This is like AssetManager2::ResolveReference(), but also takes care of resolving attribute
// references to the theme.
base::expected<std::monostate, NullOrIOError> ResolveAttributeReference(
AssetManager2::SelectedValue& value) const;
AssetManager2* GetAssetManager() {
return asset_manager_;
}
const AssetManager2* GetAssetManager() const {
return asset_manager_;
}
// Returns a bit mask of configuration changes that will impact this
// theme (and thus require completely reloading it).
uint32_t GetChangingConfigurations() const {
return type_spec_flags_;
}
void Dump() const;
struct Entry;
private:
DISALLOW_COPY_AND_ASSIGN(Theme);
explicit Theme(AssetManager2* asset_manager);
AssetManager2* asset_manager_ = nullptr;
uint32_t type_spec_flags_ = 0u;
std::vector<uint32_t> keys_;
std::vector<Entry> entries_;
};
inline const ResolvedBag::Entry* begin(const ResolvedBag* bag) {
return bag->entries;
}
inline const ResolvedBag::Entry* end(const ResolvedBag* bag) {
return bag->entries + bag->entry_count;
}
} // namespace android
#endif /* ANDROIDFW_ASSETMANAGER2_H_ */