xref: /aosp_15_r20/external/robolectric/resources/src/main/java/org/robolectric/res/android/Idmap.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.res.android;
2 
3 // transliterated from
4 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/Idmap.cpp and
5 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/Idmap.h
6 
7 import static org.robolectric.res.android.Util.ATRACE_CALL;
8 import static org.robolectric.res.android.Util.SIZEOF_CPTR;
9 import static org.robolectric.res.android.Util.SIZEOF_INT;
10 import static org.robolectric.res.android.Util.dtohl;
11 import static org.robolectric.res.android.Util.dtohs;
12 import static org.robolectric.res.android.Util.logError;
13 
14 import java.util.HashMap;
15 import java.util.Map;
16 import org.robolectric.res.android.ResourceTypes.IdmapEntry_header;
17 import org.robolectric.res.android.ResourceTypes.Idmap_header;
18 
19 // #define ATRACE_TAG ATRACE_TAG_RESOURCES
20 //
21 // #include "androidfw/Idmap.h"
22 //
23 // #include "android-base/logging.h"
24 // #include "android-base/stringprintf.h"
25 // #include "utils/ByteOrder.h"
26 // #include "utils/Trace.h"
27 //
28 // #ifdef _WIN32
29 // #ifdef ERROR
30 // #undef ERROR
31 // #endif
32 // #endif
33 //
34 // #include "androidfw/ResourceTypes.h"
35 //
36 // using ::android::base::StringPrintf;
37 //
38 // namespace android {
39 class Idmap {
40 
is_valid_package_id(short id)41   static boolean is_valid_package_id(short id) {
42     return id != 0 && id <= 255;
43   }
44 
is_valid_type_id(short id)45   static boolean is_valid_type_id(short id) {
46     // Type IDs and package IDs have the same constraints in the IDMAP.
47     return is_valid_package_id(id);
48   }
49 
50   // Represents a loaded/parsed IDMAP for a Runtime Resource Overlay (RRO).
51   // An RRO and its target APK have different resource IDs assigned to their resources. Overlaying
52   // a resource is done by resource name. An IDMAP is a generated mapping between the resource IDs
53   // of the RRO and the target APK for each resource with the same name.
54   // A LoadedIdmap can be set alongside the overlay's LoadedArsc to allow the overlay ApkAssets to
55   // masquerade as the target ApkAssets resources.
56   static class LoadedIdmap {
57     Idmap_header header_ = null;
58     String overlay_apk_path_;
59     final Map<Byte, IdmapEntry_header> type_map_ = new HashMap<>();
60 
LoadedIdmap(Idmap_header header_)61     public LoadedIdmap(Idmap_header header_) {
62       this.header_ = header_;
63     }
64 
65     // Performs a lookup of the expected entry ID for the given IDMAP entry header.
66     // Returns true if the mapping exists and fills `output_entry_id` with the result.
Lookup( IdmapEntry_header header, int input_entry_id, final Ref<Integer> output_entry_id)67     static boolean Lookup(
68         IdmapEntry_header header, int input_entry_id, final Ref<Integer> output_entry_id) {
69       if (input_entry_id < dtohs(header.entry_id_offset)) {
70         // After applying the offset, the entry is not present.
71         return false;
72       }
73 
74       input_entry_id -= dtohs(header.entry_id_offset);
75       if (input_entry_id >= dtohs(header.entry_count)) {
76         // The entry is not present.
77         return false;
78       }
79 
80       int result = dtohl(header.entries[input_entry_id]);
81       if (result == 0xffffffff) {
82         return false;
83       }
84       output_entry_id.set(result);
85       return true;
86     }
87 
is_word_aligned(int offset)88     static boolean is_word_aligned(int offset) {
89       return (offset & 0x03) == 0;
90     }
91 
92     @SuppressWarnings("DoNotCallSuggester")
IsValidIdmapHeader(StringPiece data)93     static boolean IsValidIdmapHeader(StringPiece data) {
94       throw new UnsupportedOperationException();
95       //   if (!is_word_aligned(data.data())) {
96       //     LOG(ERROR) << "Idmap header is not word aligned.";
97       //     return false;
98       //   }
99       //
100       //   if (data.size() < sizeof(Idmap_header)) {
101       //     LOG(ERROR) << "Idmap header is too small.";
102       //     return false;
103       //   }
104       //
105       //   const Idmap_header* header = reinterpret_cast<const Idmap_header*>(data.data());
106       //   if (dtohl(header->magic) != kIdmapMagic) {
107       //     LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected
108       // 0x%08x)",
109       //                                dtohl(header->magic), kIdmapMagic);
110       //     return false;
111       //   }
112       //
113       //   if (dtohl(header->version) != kIdmapCurrentVersion) {
114       //     // We are strict about versions because files with this format are auto-generated and
115       // don't need
116       //     // backwards compatibility.
117       //     LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
118       //                                dtohl(header->version), kIdmapCurrentVersion);
119       //     return false;
120       //   }
121       //
122       //   if (!is_valid_package_id(dtohs(header->target_package_id))) {
123       //     LOG(ERROR) << StringPrintf("Target package ID in Idmap is invalid: 0x%02x",
124       //                                dtohs(header->target_package_id));
125       //     return false;
126       //   }
127       //
128       //   if (dtohs(header->type_count) > 255) {
129       //     LOG(ERROR) << StringPrintf("Idmap has too many type mappings (was %d, max 255)",
130       //                                (int)dtohs(header->type_count));
131       //     return false;
132       //   }
133       //   return true;
134     }
135 
136     // LoadedIdmap::LoadedIdmap(const Idmap_header* header) : header_(header) {
137     //   size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
138     //                           arraysize(header_->overlay_path));
139     //   overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length);
140     // }
141     // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
Load(StringPiece idmap_data)142     LoadedIdmap Load(StringPiece idmap_data) {
143       ATRACE_CALL();
144       if (!IsValidIdmapHeader(idmap_data)) {
145         return emptyBraces();
146       }
147 
148       // Idmap_header header = reinterpret_cast<const Idmap_header*>(idmap_data.data());
149       Idmap_header header = idmap_data.asIdmap_header();
150 
151       // Can't use make_unique because LoadedImpl constructor is private.
152       LoadedIdmap loaded_idmap = new LoadedIdmap(header);
153 
154       // const byte* data_ptr = reinterpret_cast<const byte*>(idmap_data.data()) + sizeof(*header);
155       StringPiece data_ptr =
156           new StringPiece(idmap_data.myBuf(), idmap_data.myOffset() + SIZEOF_CPTR);
157       // int data_size = idmap_data.size() - sizeof(*header);
158       int data_size = idmap_data.size() - SIZEOF_CPTR;
159 
160       int type_maps_encountered = 0;
161       while (data_size >= IdmapEntry_header.SIZEOF) {
162         if (!is_word_aligned(data_ptr.myOffset())) {
163           logError("Type mapping in Idmap is not word aligned");
164           return emptyBraces();
165         }
166 
167         // Validate the type IDs.
168         // IdmapEntry_header entry_header = reinterpret_cast<const IdmapEntry_header*>(data_ptr);
169         IdmapEntry_header entry_header =
170             new IdmapEntry_header(data_ptr.myBuf(), data_ptr.myOffset());
171         if (!is_valid_type_id(dtohs(entry_header.target_type_id))
172             || !is_valid_type_id(dtohs(entry_header.overlay_type_id))) {
173           logError(
174               String.format(
175                   "Invalid type map (0x%02x -> 0x%02x)",
176                   dtohs(entry_header.target_type_id), dtohs(entry_header.overlay_type_id)));
177           return emptyBraces();
178         }
179 
180         // Make sure there is enough space for the entries declared in the header.
181         if ((data_size - SIZEOF_CPTR) / SIZEOF_INT < dtohs(entry_header.entry_count)) {
182           logError(
183               String.format(
184                   "Idmap too small for the number of entries (%d)",
185                   (int) dtohs(entry_header.entry_count)));
186           return emptyBraces();
187         }
188 
189         // Only add a non-empty overlay.
190         if (dtohs(entry_header.entry_count) != 0) {
191           // loaded_idmap.type_map_[static_cast<byte>(dtohs(entry_header.overlay_type_id))] =
192           //     entry_header;
193           loaded_idmap.type_map_.put((byte) dtohs(entry_header.overlay_type_id), entry_header);
194         }
195 
196         // int entry_size_bytes =
197         //     sizeof(*entry_header) + (dtohs(entry_header.entry_count) * SIZEOF_INT);
198         int entry_size_bytes = SIZEOF_CPTR + (dtohs(entry_header.entry_count) * SIZEOF_INT);
199         data_ptr = new StringPiece(data_ptr.myBuf(), data_ptr.myOffset() + entry_size_bytes);
200         data_size -= entry_size_bytes;
201         type_maps_encountered++;
202       }
203 
204       // Verify that we parsed all the type maps.
205       if (type_maps_encountered != dtohs(header.type_count)) {
206         logError(
207             "Parsed "
208                 + type_maps_encountered
209                 + " type maps but expected "
210                 + (int) dtohs(header.type_count));
211         return emptyBraces();
212       }
213       // return std.move(loaded_idmap);
214       return loaded_idmap;
215     }
216 
emptyBraces()217     private LoadedIdmap emptyBraces() {
218       return new LoadedIdmap(null);
219     }
220 
221     // Returns the package ID for which this overlay should apply.
TargetPackageId()222     int TargetPackageId() {
223       return dtohs(header_.target_package_id);
224     }
225 
226     // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was
227     // generated.
OverlayApkPath()228     String OverlayApkPath() {
229       return overlay_apk_path_;
230     }
231 
232     // Returns the mapping of target entry ID to overlay entry ID for the given target type.
GetEntryMapForType(byte type_id)233     IdmapEntry_header GetEntryMapForType(byte type_id) {
234       // auto iter = type_map_.find(type_id);
235       // if (iter != type_map_.end()) {
236       //   return iter.second;
237       // }
238       // return null;
239       return type_map_.get(type_id);
240     }
241     //
242     // }  // namespace android
243   }
244 }
245