1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "androidfw/AttributeResolution.h"
18
19 #include <cstdint>
20
21 #include <log/log.h>
22
23 #include "androidfw/AssetManager2.h"
24 #include "androidfw/AttributeFinder.h"
25
26 constexpr bool kDebugStyles = false;
27
28 namespace android {
29
30 // Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie)31 static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
32 return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1);
33 }
34
35 class XmlAttributeFinder
36 : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> {
37 public:
XmlAttributeFinder(const ResXMLParser * parser)38 explicit XmlAttributeFinder(const ResXMLParser* parser)
39 : BackTrackingAttributeFinder(
40 0, parser != nullptr ? parser->getAttributeCount() : 0),
41 parser_(parser) {}
42
GetAttribute(size_t index) const43 inline uint32_t GetAttribute(size_t index) const {
44 return parser_->getAttributeNameResID(index);
45 }
46
47 private:
48 const ResXMLParser* parser_;
49 };
50
51 class BagAttributeFinder
52 : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> {
53 public:
BagAttributeFinder(const ResolvedBag * bag)54 explicit BagAttributeFinder(const ResolvedBag* bag)
55 : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr,
56 bag != nullptr ? bag->entries + bag->entry_count : nullptr) {
57 }
58
GetAttribute(const ResolvedBag::Entry * entry) const59 inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const {
60 return entry->key;
61 }
62 };
63
ResolveAttrs(Theme * theme,uint32_t def_style_attr,uint32_t def_style_res,uint32_t * src_values,size_t src_values_length,uint32_t * attrs,size_t attrs_length,uint32_t * out_values,uint32_t * out_indices)64 bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
65 uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
66 size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
67 if (kDebugStyles) {
68 ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme,
69 def_style_attr, def_style_res);
70 }
71
72 AssetManager2* assetmanager = theme->GetAssetManager();
73 ResTable_config config;
74 Res_value value;
75
76 int indices_idx = 0;
77
78 // Load default style from attribute, if specified...
79 uint32_t def_style_flags = 0u;
80 if (def_style_attr != 0) {
81 Res_value value;
82 if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
83 if (value.dataType == Res_value::TYPE_REFERENCE) {
84 def_style_res = value.data;
85 }
86 }
87 }
88
89 // Retrieve the default style bag, if requested.
90 const ResolvedBag* default_style_bag = nullptr;
91 if (def_style_res != 0) {
92 default_style_bag = assetmanager->GetBag(def_style_res);
93 if (default_style_bag != nullptr) {
94 def_style_flags |= default_style_bag->type_spec_flags;
95 }
96 }
97
98 BagAttributeFinder def_style_attr_finder(default_style_bag);
99
100 // Now iterate through all of the attributes that the client has requested,
101 // filling in each with whatever data we can find.
102 for (size_t ii = 0; ii < attrs_length; ii++) {
103 const uint32_t cur_ident = attrs[ii];
104
105 if (kDebugStyles) {
106 ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
107 }
108
109 ApkAssetsCookie cookie = kInvalidCookie;
110 uint32_t type_set_flags = 0;
111
112 value.dataType = Res_value::TYPE_NULL;
113 value.data = Res_value::DATA_NULL_UNDEFINED;
114 config.density = 0;
115
116 // Try to find a value for this attribute... we prioritize values
117 // coming from, first XML attributes, then XML style, then default
118 // style, and finally the theme.
119
120 // Retrieve the current input value if available.
121 if (src_values_length > 0 && src_values[ii] != 0) {
122 value.dataType = Res_value::TYPE_ATTRIBUTE;
123 value.data = src_values[ii];
124 if (kDebugStyles) {
125 ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
126 }
127 } else {
128 const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident);
129 if (entry != def_style_attr_finder.end()) {
130 cookie = entry->cookie;
131 type_set_flags = def_style_flags;
132 value = entry->value;
133 if (kDebugStyles) {
134 ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
135 }
136 }
137 }
138
139 uint32_t resid = 0;
140 if (value.dataType != Res_value::TYPE_NULL) {
141 // Take care of resolving the found resource to its final value.
142 ApkAssetsCookie new_cookie =
143 theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
144 if (new_cookie != kInvalidCookie) {
145 cookie = new_cookie;
146 }
147 if (kDebugStyles) {
148 ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
149 }
150 } else if (value.data != Res_value::DATA_NULL_EMPTY) {
151 // If we still don't have a value for this attribute, try to find it in the theme!
152 ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
153 if (new_cookie != kInvalidCookie) {
154 if (kDebugStyles) {
155 ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
156 }
157 new_cookie =
158 assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
159 if (new_cookie != kInvalidCookie) {
160 cookie = new_cookie;
161 }
162 if (kDebugStyles) {
163 ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
164 }
165 }
166 }
167
168 // Deal with the special @null value -- it turns back to TYPE_NULL.
169 if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
170 if (kDebugStyles) {
171 ALOGI("-> Setting to @null!");
172 }
173 value.dataType = Res_value::TYPE_NULL;
174 value.data = Res_value::DATA_NULL_UNDEFINED;
175 cookie = kInvalidCookie;
176 }
177
178 if (kDebugStyles) {
179 ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data);
180 }
181
182 // Write the final value back to Java.
183 out_values[STYLE_TYPE] = value.dataType;
184 out_values[STYLE_DATA] = value.data;
185 out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
186 out_values[STYLE_RESOURCE_ID] = resid;
187 out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
188 out_values[STYLE_DENSITY] = config.density;
189
190 if (out_indices != nullptr &&
191 (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) {
192 indices_idx++;
193 out_indices[indices_idx] = ii;
194 }
195
196 out_values += STYLE_NUM_ENTRIES;
197 }
198
199 if (out_indices != nullptr) {
200 out_indices[0] = indices_idx;
201 }
202 return true;
203 }
204
ApplyStyle(Theme * theme,ResXMLParser * xml_parser,uint32_t def_style_attr,uint32_t def_style_resid,const uint32_t * attrs,size_t attrs_length,uint32_t * out_values,uint32_t * out_indices)205 void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
206 uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
207 uint32_t* out_values, uint32_t* out_indices) {
208 if (kDebugStyles) {
209 ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
210 def_style_attr, def_style_resid, xml_parser);
211 }
212
213 AssetManager2* assetmanager = theme->GetAssetManager();
214 ResTable_config config;
215 Res_value value;
216
217 int indices_idx = 0;
218
219 // Load default style from attribute, if specified...
220 uint32_t def_style_flags = 0u;
221 if (def_style_attr != 0) {
222 Res_value value;
223 if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
224 if (value.dataType == Res_value::TYPE_REFERENCE) {
225 def_style_resid = value.data;
226 }
227 }
228 }
229
230 // Retrieve the style resource ID associated with the current XML tag's style attribute.
231 uint32_t style_resid = 0u;
232 uint32_t style_flags = 0u;
233 if (xml_parser != nullptr) {
234 ssize_t idx = xml_parser->indexOfStyle();
235 if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
236 if (value.dataType == value.TYPE_ATTRIBUTE) {
237 // Resolve the attribute with out theme.
238 if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) {
239 value.dataType = Res_value::TYPE_NULL;
240 }
241 }
242
243 if (value.dataType == value.TYPE_REFERENCE) {
244 style_resid = value.data;
245 }
246 }
247 }
248
249 // Retrieve the default style bag, if requested.
250 const ResolvedBag* default_style_bag = nullptr;
251 if (def_style_resid != 0) {
252 default_style_bag = assetmanager->GetBag(def_style_resid);
253 if (default_style_bag != nullptr) {
254 def_style_flags |= default_style_bag->type_spec_flags;
255 }
256 }
257
258 BagAttributeFinder def_style_attr_finder(default_style_bag);
259
260 // Retrieve the style class bag, if requested.
261 const ResolvedBag* xml_style_bag = nullptr;
262 if (style_resid != 0) {
263 xml_style_bag = assetmanager->GetBag(style_resid);
264 if (xml_style_bag != nullptr) {
265 style_flags |= xml_style_bag->type_spec_flags;
266 }
267 }
268
269 BagAttributeFinder xml_style_attr_finder(xml_style_bag);
270
271 // Retrieve the XML attributes, if requested.
272 XmlAttributeFinder xml_attr_finder(xml_parser);
273
274 // Now iterate through all of the attributes that the client has requested,
275 // filling in each with whatever data we can find.
276 for (size_t ii = 0; ii < attrs_length; ii++) {
277 const uint32_t cur_ident = attrs[ii];
278
279 if (kDebugStyles) {
280 ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
281 }
282
283 ApkAssetsCookie cookie = kInvalidCookie;
284 uint32_t type_set_flags = 0u;
285
286 value.dataType = Res_value::TYPE_NULL;
287 value.data = Res_value::DATA_NULL_UNDEFINED;
288 config.density = 0;
289 uint32_t value_source_resid = 0;
290
291 // Try to find a value for this attribute... we prioritize values
292 // coming from, first XML attributes, then XML style, then default
293 // style, and finally the theme.
294
295 // Walk through the xml attributes looking for the requested attribute.
296 const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
297 if (xml_attr_idx != xml_attr_finder.end()) {
298 // We found the attribute we were looking for.
299 xml_parser->getAttributeValue(xml_attr_idx, &value);
300 if (kDebugStyles) {
301 ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType, value.data);
302 }
303 value_source_resid = xml_parser->getSourceResourceId();
304 }
305
306 if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
307 // Walk through the style class values looking for the requested attribute.
308 const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
309 if (entry != xml_style_attr_finder.end()) {
310 // We found the attribute we were looking for.
311 cookie = entry->cookie;
312 type_set_flags = style_flags;
313 value = entry->value;
314 value_source_resid = entry->style;
315 if (kDebugStyles) {
316 ALOGI("-> From style: type=0x%x, data=0x%08x, style=0x%08x", value.dataType, value.data,
317 entry->style);
318 }
319 }
320 }
321
322 if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
323 // Walk through the default style values looking for the requested attribute.
324 const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
325 if (entry != def_style_attr_finder.end()) {
326 // We found the attribute we were looking for.
327 cookie = entry->cookie;
328 type_set_flags = def_style_flags;
329 value = entry->value;
330 if (kDebugStyles) {
331 ALOGI("-> From def style: type=0x%x, data=0x%08x, style=0x%08x", value.dataType, value.data,
332 entry->style);
333 }
334 value_source_resid = entry->style;
335 }
336 }
337
338 uint32_t resid = 0u;
339 if (value.dataType != Res_value::TYPE_NULL) {
340 // Take care of resolving the found resource to its final value.
341 ApkAssetsCookie new_cookie =
342 theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
343 if (new_cookie != kInvalidCookie) {
344 cookie = new_cookie;
345 }
346
347 if (kDebugStyles) {
348 ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
349 }
350 } else if (value.data != Res_value::DATA_NULL_EMPTY) {
351 // If we still don't have a value for this attribute, try to find it in the theme!
352 ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
353 // TODO: set value_source_resid for the style in the theme that was used.
354 if (new_cookie != kInvalidCookie) {
355 if (kDebugStyles) {
356 ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
357 }
358 new_cookie =
359 assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
360 if (new_cookie != kInvalidCookie) {
361 cookie = new_cookie;
362 }
363
364 if (kDebugStyles) {
365 ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
366 }
367 }
368 }
369
370 // Deal with the special @null value -- it turns back to TYPE_NULL.
371 if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
372 if (kDebugStyles) {
373 ALOGI("-> Setting to @null!");
374 }
375 value.dataType = Res_value::TYPE_NULL;
376 value.data = Res_value::DATA_NULL_UNDEFINED;
377 cookie = kInvalidCookie;
378 }
379
380 if (kDebugStyles) {
381 ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.dataType, value.data);
382 }
383
384 // Write the final value back to Java.
385 out_values[STYLE_TYPE] = value.dataType;
386 out_values[STYLE_DATA] = value.data;
387 out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
388 out_values[STYLE_RESOURCE_ID] = resid;
389 out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
390 out_values[STYLE_DENSITY] = config.density;
391 out_values[STYLE_SOURCE_RESOURCE_ID] = value_source_resid;
392
393 if (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) {
394 indices_idx++;
395
396 // out_indices must NOT be nullptr.
397 out_indices[indices_idx] = ii;
398 }
399 out_values += STYLE_NUM_ENTRIES;
400 }
401
402 // out_indices must NOT be nullptr.
403 out_indices[0] = indices_idx;
404 }
405
RetrieveAttributes(AssetManager2 * assetmanager,ResXMLParser * xml_parser,uint32_t * attrs,size_t attrs_length,uint32_t * out_values,uint32_t * out_indices)406 bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs,
407 size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
408 ResTable_config config;
409 Res_value value;
410
411 int indices_idx = 0;
412
413 // Retrieve the XML attributes, if requested.
414 const size_t xml_attr_count = xml_parser->getAttributeCount();
415 size_t ix = 0;
416 uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix);
417
418 // Now iterate through all of the attributes that the client has requested,
419 // filling in each with whatever data we can find.
420 for (size_t ii = 0; ii < attrs_length; ii++) {
421 const uint32_t cur_ident = attrs[ii];
422 ApkAssetsCookie cookie = kInvalidCookie;
423 uint32_t type_set_flags = 0u;
424
425 value.dataType = Res_value::TYPE_NULL;
426 value.data = Res_value::DATA_NULL_UNDEFINED;
427 config.density = 0;
428
429 // Try to find a value for this attribute...
430 // Skip through XML attributes until the end or the next possible match.
431 while (ix < xml_attr_count && cur_ident > cur_xml_attr) {
432 ix++;
433 cur_xml_attr = xml_parser->getAttributeNameResID(ix);
434 }
435 // Retrieve the current XML attribute if it matches, and step to next.
436 if (ix < xml_attr_count && cur_ident == cur_xml_attr) {
437 xml_parser->getAttributeValue(ix, &value);
438 ix++;
439 cur_xml_attr = xml_parser->getAttributeNameResID(ix);
440 }
441
442 uint32_t resid = 0u;
443 if (value.dataType != Res_value::TYPE_NULL) {
444 // Take care of resolving the found resource to its final value.
445 ApkAssetsCookie new_cookie =
446 assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid);
447 if (new_cookie != kInvalidCookie) {
448 cookie = new_cookie;
449 }
450 }
451
452 // Deal with the special @null value -- it turns back to TYPE_NULL.
453 if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
454 value.dataType = Res_value::TYPE_NULL;
455 value.data = Res_value::DATA_NULL_UNDEFINED;
456 cookie = kInvalidCookie;
457 }
458
459 // Write the final value back to Java.
460 out_values[STYLE_TYPE] = value.dataType;
461 out_values[STYLE_DATA] = value.data;
462 out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
463 out_values[STYLE_RESOURCE_ID] = resid;
464 out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
465 out_values[STYLE_DENSITY] = config.density;
466
467 if (out_indices != nullptr &&
468 (value.dataType != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY)) {
469 indices_idx++;
470 out_indices[indices_idx] = ii;
471 }
472
473 out_values += STYLE_NUM_ENTRIES;
474 }
475
476 if (out_indices != nullptr) {
477 out_indices[0] = indices_idx;
478 }
479 return true;
480 }
481
482 } // namespace android
483