• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res.android;
2 
3 import static org.robolectric.res.android.Errors.BAD_INDEX;
4 import static org.robolectric.res.android.Errors.NO_ERROR;
5 import static org.robolectric.res.android.ResTable.Res_GETENTRY;
6 import static org.robolectric.res.android.ResTable.Res_GETPACKAGE;
7 import static org.robolectric.res.android.ResTable.Res_GETTYPE;
8 import static org.robolectric.res.android.ResTable.getOrDefault;
9 import static org.robolectric.res.android.ResourceTypes.Res_value.TYPE_ATTRIBUTE;
10 import static org.robolectric.res.android.ResourceTypes.Res_value.TYPE_NULL;
11 import static org.robolectric.res.android.Util.ALOGE;
12 import static org.robolectric.res.android.Util.ALOGI;
13 import static org.robolectric.res.android.Util.ALOGV;
14 import static org.robolectric.res.android.Util.ALOGW;
15 
16 import java.util.ArrayList;
17 import java.util.Collections;
18 import java.util.List;
19 import org.robolectric.res.android.ResTable.PackageGroup;
20 import org.robolectric.res.android.ResTable.ResourceName;
21 import org.robolectric.res.android.ResTable.Type;
22 import org.robolectric.res.android.ResTable.bag_entry;
23 import org.robolectric.res.android.ResourceTypes.Res_value;
24 
25 // transliterated from
26 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp and
27 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/ResourceTypes.h
28 
29 public class ResTableTheme {
30 
31   private final List<AppliedStyle> styles = new ArrayList<>();
32   private static boolean styleDebug = false;
33   private static final type_info EMPTY_TYPE_INFO = new type_info();
34   private static final theme_entry EMPTY_THEME_ENTRY = new theme_entry();
35 
36   private class AppliedStyle {
37     private final int styleResId;
38     private final boolean forced;
39 
AppliedStyle(int styleResId, boolean forced)40     public AppliedStyle(int styleResId, boolean forced) {
41       this.styleResId = styleResId;
42       this.forced = forced;
43     }
44 
45     @Override
toString()46     public String toString() {
47       ResourceName resourceName = new ResourceName();
48       boolean found = mTable.getResourceName(styleResId, true, resourceName);
49       return (found ? resourceName : "unknown") + (forced ? " (forced)" : "");
50     }
51   }
52 
53   @Override
toString()54   public String toString() {
55     if (styles.isEmpty()) {
56       return "theme with no applied styles";
57     } else {
58       return "theme with applied styles: " + styles + "";
59     }
60   }
61 
62   private ResTable mTable;
63   private boolean kDebugTableTheme = false;
64   private boolean kDebugTableNoisy = false;
65   private package_info[] mPackages = new package_info[Res_MAXPACKAGE];
66   private Ref<Integer> mTypeSpecFlags = new Ref<>(0);
67 
ResTableTheme(ResTable resources)68   public ResTableTheme(ResTable resources) {
69     this.mTable = resources;
70   }
71 
getResTable()72   public ResTable getResTable() {
73     return this.mTable;
74   }
75 
GetAttribute(int resID, Ref<Res_value> valueRef, final Ref<Integer> outTypeSpecFlags)76   public int GetAttribute(int resID, Ref<Res_value> valueRef,
77       final Ref<Integer> outTypeSpecFlags) {
78     int cnt = 20;
79 
80     if (outTypeSpecFlags != null) outTypeSpecFlags.set(0);
81 
82     do {
83       final int p = mTable.getResourcePackageIndex(resID);
84       final int t = Res_GETTYPE(resID);
85       final int e = Res_GETENTRY(resID);
86 
87       if (kDebugTableTheme) {
88         ALOGI("Looking up attr 0x%08x in theme %s", resID, this);
89       }
90 
91       if (p >= 0) {
92         final package_info pi = mPackages[p];
93         if (kDebugTableTheme) {
94           ALOGI("Found package: %s", pi);
95         }
96         if (pi != null) {
97           if (kDebugTableTheme) {
98             ALOGI("Desired type index is %d in avail %d", t, Res_MAXTYPE + 1);
99           }
100           if (t <= Res_MAXTYPE) {
101             type_info ti = pi.types[t];
102             if (ti == null) {
103               ti = EMPTY_TYPE_INFO;
104             }
105             if (kDebugTableTheme) {
106               ALOGI("Desired entry index is %d in avail %d", e, ti.numEntries);
107             }
108             if (e < ti.numEntries) {
109               theme_entry te = ti.entries[e];
110               if (te == null) {
111                 te = EMPTY_THEME_ENTRY;
112               }
113               if (outTypeSpecFlags != null) {
114                 outTypeSpecFlags.set(outTypeSpecFlags.get() | te.typeSpecFlags);
115               }
116               if (kDebugTableTheme) {
117                 ALOGI("Theme value: type=0x%x, data=0x%08x",
118                     te.value.dataType, te.value.data);
119               }
120               final int type = te.value.dataType;
121               if (type == TYPE_ATTRIBUTE) {
122                 if (cnt > 0) {
123                   cnt--;
124                   resID = te.value.data;
125                   continue;
126                 }
127                 ALOGW("Too many attribute references, stopped at: 0x%08x\n", resID);
128                 return BAD_INDEX;
129               } else if (type != TYPE_NULL
130                   || te.value.data == Res_value.DATA_NULL_EMPTY) {
131                 valueRef.set(te.value);
132                 return te.stringBlock;
133               }
134               return BAD_INDEX;
135             }
136           }
137         }
138       }
139       break;
140 
141     } while (true);
142 
143     return BAD_INDEX;
144 
145   }
146 
resolveAttributeReference(Ref<Res_value> inOutValue, int blockIndex, Ref<Integer> outLastRef, final Ref<Integer> inoutTypeSpecFlags, Ref<ResTable_config> inoutConfig)147   public int resolveAttributeReference(Ref<Res_value> inOutValue,
148       int blockIndex, Ref<Integer> outLastRef,
149       final Ref<Integer> inoutTypeSpecFlags, Ref<ResTable_config> inoutConfig) {
150     //printf("Resolving type=0x%x\n", inOutValue->dataType);
151     if (inOutValue.get().dataType == TYPE_ATTRIBUTE) {
152       final Ref<Integer> newTypeSpecFlags = new Ref<>(0);
153       blockIndex = GetAttribute(inOutValue.get().data, inOutValue, newTypeSpecFlags);
154       if (kDebugTableTheme) {
155         ALOGI("Resolving attr reference: blockIndex=%d, type=0x%x, data=0x%x\n",
156             (int)blockIndex, (int)inOutValue.get().dataType, inOutValue.get().data);
157       }
158       if (inoutTypeSpecFlags != null) inoutTypeSpecFlags.set(inoutTypeSpecFlags.get() | newTypeSpecFlags.get());
159       //printf("Retrieved attribute new type=0x%x\n", inOutValue->dataType);
160       if (blockIndex < 0) {
161         return blockIndex;
162       }
163     }
164     return mTable.resolveReference(inOutValue, blockIndex, outLastRef,
165         inoutTypeSpecFlags, inoutConfig);
166   }
167 
applyStyle(int resID, boolean force)168   public int applyStyle(int resID, boolean force) {
169     AppliedStyle newAppliedStyle = new AppliedStyle(resID, force);
170     if (styleDebug) {
171       System.out.println("Apply " + newAppliedStyle + " to " + this);
172     }
173     styles.add(newAppliedStyle);
174 
175     final Ref<bag_entry[]> bag = new Ref<>(null);
176     final Ref<Integer> bagTypeSpecFlags = new Ref<>(0);
177     mTable.lock();
178     final int N = mTable.getBagLocked(resID, bag, bagTypeSpecFlags);
179     if (kDebugTableNoisy) {
180       ALOGV("Applying style 0x%08x to theme %s, count=%d", resID, this, N);
181     }
182     if (N < 0) {
183       mTable.unlock();
184       return N;
185     }
186 
187     mTypeSpecFlags.set(mTypeSpecFlags.get() | bagTypeSpecFlags.get());
188 
189     int curPackage = 0xffffffff;
190     int curPackageIndex = 0;
191     package_info curPI = null;
192     int curType = 0xffffffff;
193     int numEntries = 0;
194     theme_entry[] curEntries = null;
195 
196     final int end = N;
197     int bagIndex = 0;
198     while (bagIndex < end) {
199       bag_entry bag_entry = bag.get()[bagIndex];
200       final int attrRes = bag_entry.map.name.ident;
201       final int p = Res_GETPACKAGE(attrRes);
202       final int t = Res_GETTYPE(attrRes);
203       final int e = Res_GETENTRY(attrRes);
204 
205       if (curPackage != p) {
206         final int pidx = mTable.getResourcePackageIndex(attrRes);
207         if (pidx < 0) {
208           ALOGE("Style contains key with bad package: 0x%08x\n", attrRes);
209           bagIndex++;
210           continue;
211         }
212         curPackage = p;
213         curPackageIndex = pidx;
214         curPI = mPackages[pidx];
215         if (curPI == null) {
216           curPI = new package_info();
217           mPackages[pidx] = curPI;
218         }
219         curType = 0xffffffff;
220       }
221       if (curType != t) {
222         if (t > Res_MAXTYPE) {
223           ALOGE("Style contains key with bad type: 0x%08x\n", attrRes);
224           bagIndex++;
225           continue;
226         }
227         curType = t;
228         curEntries = curPI.types[t] != null ? curPI.types[t].entries: null;
229         if (curEntries == null) {
230           final PackageGroup grp = mTable.mPackageGroups.get(curPackageIndex);
231           final List<Type> typeList = getOrDefault(grp.types, t, Collections.emptyList());
232           int cnt = typeList.isEmpty() ? 0 : typeList.get(0).entryCount;
233           curEntries = new theme_entry[cnt];
234           // memset(curEntries, Res_value::TYPE_NULL, buff_size);
235           curPI.types[t] = new type_info();
236           curPI.types[t].numEntries = cnt;
237           curPI.types[t].entries = curEntries;
238         }
239         numEntries = curPI.types[t].numEntries;
240       }
241       if (e >= numEntries) {
242         ALOGE("Style contains key with bad entry: 0x%08x\n", attrRes);
243         bagIndex++;
244         continue;
245       }
246 
247       if (curEntries[e] == null) {
248         curEntries[e] = new theme_entry();
249       }
250       theme_entry curEntry = curEntries[e];
251 
252       if (styleDebug) {
253         ResourceName outName = new ResourceName();
254         mTable.getResourceName(attrRes, true, outName);
255         System.out.println("  " + outName + "(" + attrRes + ")" + " := " + bag_entry.map.value);
256       }
257 
258       if (kDebugTableNoisy) {
259         ALOGV("Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x",
260             attrRes, bag.get()[bagIndex].map.value.dataType, bag.get()[bagIndex].map.value.data,
261             curEntry.value.dataType);
262       }
263       if (force || (curEntry.value.dataType == TYPE_NULL
264           && curEntry.value.data != Res_value.DATA_NULL_EMPTY)) {
265         curEntry.stringBlock = bag_entry.stringBlock;
266         curEntry.typeSpecFlags |= bagTypeSpecFlags.get();
267         curEntry.value = new Res_value(bag_entry.map.value);
268       }
269 
270       bagIndex++;
271     }
272 
273     mTable.unlock();
274 
275     if (kDebugTableTheme) {
276       ALOGI("Applying style 0x%08x (force=%s)  theme %s...\n", resID, force, this);
277       dumpToLog();
278     }
279 
280     return NO_ERROR;
281 
282   }
283 
dumpToLog()284   private void dumpToLog() {
285 
286   }
287 
setTo(ResTableTheme other)288   public int setTo(ResTableTheme other) {
289     styles.clear();
290     styles.addAll(other.styles);
291 
292     if (kDebugTableTheme) {
293       ALOGI("Setting theme %s from theme %s...\n", this, other);
294       dumpToLog();
295       other.dumpToLog();
296     }
297 
298     if (mTable == other.mTable) {
299       for (int i=0; i<Res_MAXPACKAGE; i++) {
300         if (mPackages[i] != null) {
301           mPackages[i] = null;
302         }
303         if (other.mPackages[i] != null) {
304           mPackages[i] = copy_package(other.mPackages[i]);
305         } else {
306           mPackages[i] = null;
307         }
308       }
309     } else {
310       // @todo: need to really implement this, not just copy
311       // the system package (which is still wrong because it isn't
312       // fixing up resource references).
313       for (int i=0; i<Res_MAXPACKAGE; i++) {
314         if (mPackages[i] != null) {
315           mPackages[i] = null;
316         }
317         // todo: C++ code presumably assumes index 0 is system, and only system
318         //if (i == 0 && other.mPackages[i] != null) {
319         if (other.mPackages[i] != null) {
320           mPackages[i] = copy_package(other.mPackages[i]);
321         } else {
322           mPackages[i] = null;
323         }
324       }
325     }
326 
327     mTypeSpecFlags = other.mTypeSpecFlags;
328 
329     if (kDebugTableTheme) {
330       ALOGI("Final theme:");
331       dumpToLog();
332     }
333 
334     return NO_ERROR;
335   }
336 
copy_package(package_info pi)337   private static package_info copy_package(package_info pi) {
338     package_info newpi = new package_info();
339     for (int j = 0; j <= Res_MAXTYPE; j++) {
340       if (pi.types[j] == null) {
341         newpi.types[j] = null;
342         continue;
343       }
344       int cnt = pi.types[j].numEntries;
345       newpi.types[j] = new type_info();
346       newpi.types[j].numEntries = cnt;
347       theme_entry[] te = pi.types[j].entries;
348       if (te != null) {
349         theme_entry[] newte = new theme_entry[cnt];
350         newpi.types[j].entries = newte;
351 //        memcpy(newte, te, cnt*sizeof(theme_entry));
352         for (int i = 0; i < newte.length; i++) {
353           newte[i] = te[i] == null ? null : new theme_entry(te[i]); // deep copy
354         }
355       } else {
356         newpi.types[j].entries = null;
357       }
358     }
359     return newpi;
360   }
361 
362   static class theme_entry {
363     int stringBlock;
364     int typeSpecFlags;
365     Res_value value = new Res_value();
366 
theme_entry()367     theme_entry() {}
368 
369     /** copy constructor. Performs a deep copy */
theme_entry(theme_entry src)370     public theme_entry(theme_entry src) {
371       if (src != null) {
372         stringBlock = src.stringBlock;
373         typeSpecFlags = src.typeSpecFlags;
374         value = new Res_value(src.value);
375       }
376     }
377   };
378 
379   static class type_info {
380     int numEntries;
381     theme_entry[] entries;
382 
type_info()383     type_info() {}
384 
385     /** copy constructor. Performs a deep copy */
type_info(type_info src)386     type_info(type_info src) {
387       numEntries = src.numEntries;
388       entries = new theme_entry[src.entries.length];
389       for (int i=0; i < src.entries.length; i++) {
390         if (src.entries[i] == null) {
391           entries[i] = null;
392         } else {
393           entries[i] = new theme_entry(src.entries[i]);
394         }
395       }
396     }
397   };
398 
399   static class package_info {
400     type_info[] types = new type_info[Res_MAXTYPE + 1];
401 
package_info()402     package_info() {}
403 
404     /** copy constructor. Performs a deep copy */
package_info(package_info src)405     package_info(package_info src) {
406       for (int i=0; i < src.types.length; i++) {
407         if (src.types[i] == null) {
408           types[i] = null;
409         } else {
410           types[i] = new type_info(src.types[i]);
411         }
412       }
413     }
414   };
415 
416   static final int Res_MAXPACKAGE = 255;
417   static final int Res_MAXTYPE = 255;
418 }
419