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(IdmapEntry_header header, int input_entry_id,
68         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 
IsValidIdmapHeader(StringPiece data)92     static boolean IsValidIdmapHeader(StringPiece data) {
93       throw new UnsupportedOperationException();
94 //   if (!is_word_aligned(data.data())) {
95 //     LOG(ERROR) << "Idmap header is not word aligned.";
96 //     return false;
97 //   }
98 //
99 //   if (data.size() < sizeof(Idmap_header)) {
100 //     LOG(ERROR) << "Idmap header is too small.";
101 //     return false;
102 //   }
103 //
104 //   const Idmap_header* header = reinterpret_cast<const Idmap_header*>(data.data());
105 //   if (dtohl(header->magic) != kIdmapMagic) {
106 //     LOG(ERROR) << StringPrintf("Invalid Idmap file: bad magic value (was 0x%08x, expected 0x%08x)",
107 //                                dtohl(header->magic), kIdmapMagic);
108 //     return false;
109 //   }
110 //
111 //   if (dtohl(header->version) != kIdmapCurrentVersion) {
112 //     // We are strict about versions because files with this format are auto-generated and don't need
113 //     // backwards compatibility.
114 //     LOG(ERROR) << StringPrintf("Version mismatch in Idmap (was 0x%08x, expected 0x%08x)",
115 //                                dtohl(header->version), kIdmapCurrentVersion);
116 //     return false;
117 //   }
118 //
119 //   if (!is_valid_package_id(dtohs(header->target_package_id))) {
120 //     LOG(ERROR) << StringPrintf("Target package ID in Idmap is invalid: 0x%02x",
121 //                                dtohs(header->target_package_id));
122 //     return false;
123 //   }
124 //
125 //   if (dtohs(header->type_count) > 255) {
126 //     LOG(ERROR) << StringPrintf("Idmap has too many type mappings (was %d, max 255)",
127 //                                (int)dtohs(header->type_count));
128 //     return false;
129 //   }
130 //   return true;
131     }
132 
133 // LoadedIdmap::LoadedIdmap(const Idmap_header* header) : header_(header) {
134 //   size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
135 //                           arraysize(header_->overlay_path));
136 //   overlay_apk_path_.assign(reinterpret_cast<const char*>(header_->overlay_path), length);
137 // }
138     // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
Load(StringPiece idmap_data)139     LoadedIdmap Load(StringPiece idmap_data) {
140       ATRACE_CALL();
141       if (!IsValidIdmapHeader(idmap_data)) {
142         return emptyBraces();
143       }
144 
145       // Idmap_header header = reinterpret_cast<const Idmap_header*>(idmap_data.data());
146       Idmap_header header = idmap_data.asIdmap_header();
147 
148       // Can't use make_unique because LoadedImpl constructor is private.
149       LoadedIdmap loaded_idmap = new LoadedIdmap(header);
150 
151   // const byte* data_ptr = reinterpret_cast<const byte*>(idmap_data.data()) + sizeof(*header);
152       StringPiece data_ptr = new StringPiece(idmap_data.myBuf(),
153           idmap_data.myOffset() + SIZEOF_CPTR);
154       // int data_size = idmap_data.size() - sizeof(*header);
155       int data_size = idmap_data.size() - SIZEOF_CPTR;
156 
157       int type_maps_encountered = 0;
158       while (data_size >= IdmapEntry_header.SIZEOF) {
159         if (!is_word_aligned(data_ptr.myOffset())) {
160           logError("Type mapping in Idmap is not word aligned");
161           return emptyBraces();
162         }
163 
164         // Validate the type IDs.
165     // IdmapEntry_header entry_header = reinterpret_cast<const IdmapEntry_header*>(data_ptr);
166         IdmapEntry_header entry_header = new IdmapEntry_header(data_ptr.myBuf(), data_ptr.myOffset());
167         if (!is_valid_type_id(dtohs(entry_header.target_type_id)) || !is_valid_type_id(dtohs(entry_header.overlay_type_id))) {
168           logError(String.format("Invalid type map (0x%02x -> 0x%02x)",
169               dtohs(entry_header.target_type_id),
170               dtohs(entry_header.overlay_type_id)));
171           return emptyBraces();
172         }
173 
174         // Make sure there is enough space for the entries declared in the header.
175         if ((data_size - SIZEOF_CPTR) / SIZEOF_INT <
176             (dtohs(entry_header.entry_count))) {
177           logError(String.format("Idmap too small for the number of entries (%d)",
178               (int) dtohs(entry_header.entry_count)));
179           return emptyBraces();
180         }
181 
182         // Only add a non-empty overlay.
183         if (dtohs(entry_header.entry_count) != 0) {
184           // loaded_idmap.type_map_[static_cast<byte>(dtohs(entry_header.overlay_type_id))] =
185           //     entry_header;
186           loaded_idmap.type_map_.put((byte) dtohs(entry_header.overlay_type_id),
187               entry_header);
188         }
189 
190         // int entry_size_bytes =
191         //     sizeof(*entry_header) + (dtohs(entry_header.entry_count) * SIZEOF_INT);
192         int entry_size_bytes =
193             SIZEOF_CPTR + (dtohs(entry_header.entry_count) * SIZEOF_INT);
194         data_ptr = new StringPiece(data_ptr.myBuf(), data_ptr.myOffset() + entry_size_bytes);
195         data_size -= entry_size_bytes;
196         type_maps_encountered++;
197       }
198 
199       // Verify that we parsed all the type maps.
200       if (type_maps_encountered != dtohs(header.type_count)) {
201         logError("Parsed " + type_maps_encountered + " type maps but expected "
202             + (int) dtohs(header.type_count));
203         return emptyBraces();
204       }
205       // return std.move(loaded_idmap);
206       return loaded_idmap;
207     }
208 
emptyBraces()209     private LoadedIdmap emptyBraces() {
210       return new LoadedIdmap(null);
211     }
212 
213     // Returns the package ID for which this overlay should apply.
TargetPackageId()214     int TargetPackageId() {
215       return dtohs(header_.target_package_id);
216     }
217 
218     // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
OverlayApkPath()219     String OverlayApkPath() {
220       return overlay_apk_path_;
221     }
222 
223     // Returns the mapping of target entry ID to overlay entry ID for the given target type.
GetEntryMapForType(byte type_id)224     IdmapEntry_header GetEntryMapForType(byte type_id) {
225       // auto iter = type_map_.find(type_id);
226       // if (iter != type_map_.end()) {
227       //   return iter.second;
228       // }
229       // return null;
230       return type_map_.get(type_id);
231     }
232 //
233 // }  // namespace android
234   }
235 }