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