• 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.bag_entry;
22 import org.robolectric.res.android.ResourceTypes.Res_value;
23 
24 // transliterated from
25 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp and
26 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/ResourceTypes.h
27 
28 public class ResTableTheme {
29 
30   private final List<AppliedStyle> styles = new ArrayList<>();
31   private static boolean styleDebug = false;
32   private static final type_info EMPTY_TYPE_INFO = new type_info();
33   private static final theme_entry EMPTY_THEME_ENTRY = new theme_entry();
34 
35   private class AppliedStyle {
36     private final int styleResId;
37     private final boolean forced;
38 
AppliedStyle(int styleResId, boolean forced)39     public AppliedStyle(int styleResId, boolean forced) {
40       this.styleResId = styleResId;
41       this.forced = forced;
42     }
43 
44     @Override
toString()45     public String toString() {
46       ResourceName resourceName = new ResourceName();
47       boolean found = mTable.getResourceName(styleResId, true, resourceName);
48       return (found ? resourceName : "unknown") + (forced ? " (forced)" : "");
49     }
50   }
51 
52   @Override
toString()53   public String toString() {
54     if (styles.isEmpty()) {
55       return "theme with no applied styles";
56     } else {
57       return "theme with applied styles: " + styles + "";
58     }
59   }
60 
61   private ResTable mTable;
62   private boolean kDebugTableTheme = false;
63   private boolean kDebugTableNoisy = false;
64   private package_info[] mPackages = new package_info[Res_MAXPACKAGE];
65   private Ref<Integer> mTypeSpecFlags = new Ref<>(0);
66 
ResTableTheme(ResTable resources)67   public ResTableTheme(ResTable resources) {
68     this.mTable = resources;
69   }
70 
getResTable()71   public ResTable getResTable() {
72     return this.mTable;
73   }
74 
GetAttribute(int resID, Ref<Res_value> valueRef, final Ref<Integer> outTypeSpecFlags)75   public int GetAttribute(int resID, Ref<Res_value> valueRef, final Ref<Integer> outTypeSpecFlags) {
76     int cnt = 20;
77 
78     if (outTypeSpecFlags != null) outTypeSpecFlags.set(0);
79 
80     do {
81       final int p = mTable.getResourcePackageIndex(resID);
82       final int t = Res_GETTYPE(resID);
83       final int e = Res_GETENTRY(resID);
84 
85       if (kDebugTableTheme) {
86         ALOGI("Looking up attr 0x%08x in theme %s", resID, this);
87       }
88 
89       if (p >= 0) {
90         final package_info pi = mPackages[p];
91         if (kDebugTableTheme) {
92           ALOGI("Found package: %s", pi);
93         }
94         if (pi != null) {
95           if (kDebugTableTheme) {
96             ALOGI("Desired type index is %d in avail %d", t, Res_MAXTYPE + 1);
97           }
98           if (t <= Res_MAXTYPE) {
99             type_info ti = pi.types[t];
100             if (ti == null) {
101               ti = EMPTY_TYPE_INFO;
102             }
103             if (kDebugTableTheme) {
104               ALOGI("Desired entry index is %d in avail %d", e, ti.numEntries);
105             }
106             if (e < ti.numEntries) {
107               theme_entry te = ti.entries[e];
108               if (te == null) {
109                 te = EMPTY_THEME_ENTRY;
110               }
111               if (outTypeSpecFlags != null) {
112                 outTypeSpecFlags.set(outTypeSpecFlags.get() | te.typeSpecFlags);
113               }
114               if (kDebugTableTheme) {
115                 ALOGI("Theme value: type=0x%x, data=0x%08x", te.value.dataType, te.value.data);
116               }
117               final int type = te.value.dataType;
118               if (type == TYPE_ATTRIBUTE) {
119                 if (cnt > 0) {
120                   cnt--;
121                   resID = te.value.data;
122                   continue;
123                 }
124                 ALOGW("Too many attribute references, stopped at: 0x%08x\n", resID);
125                 return BAD_INDEX;
126               } else if (type != TYPE_NULL || te.value.data == Res_value.DATA_NULL_EMPTY) {
127                 valueRef.set(te.value);
128                 return te.stringBlock;
129               }
130               return BAD_INDEX;
131             }
132           }
133         }
134       }
135       break;
136 
137     } while (true);
138 
139     return BAD_INDEX;
140   }
141 
resolveAttributeReference( Ref<Res_value> inOutValue, int blockIndex, Ref<Integer> outLastRef, final Ref<Integer> inoutTypeSpecFlags, Ref<ResTable_config> inoutConfig)142   public int resolveAttributeReference(
143       Ref<Res_value> inOutValue,
144       int blockIndex,
145       Ref<Integer> outLastRef,
146       final Ref<Integer> inoutTypeSpecFlags,
147       Ref<ResTable_config> inoutConfig) {
148     // printf("Resolving type=0x%x\n", inOutValue->dataType);
149     if (inOutValue.get().dataType == TYPE_ATTRIBUTE) {
150       final Ref<Integer> newTypeSpecFlags = new Ref<>(0);
151       blockIndex = GetAttribute(inOutValue.get().data, inOutValue, newTypeSpecFlags);
152       if (kDebugTableTheme) {
153         ALOGI(
154             "Resolving attr reference: blockIndex=%d, type=0x%x, data=0x%x\n",
155             (int) blockIndex, (int) inOutValue.get().dataType, inOutValue.get().data);
156       }
157       if (inoutTypeSpecFlags != null)
158         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(
165         inOutValue, blockIndex, outLastRef, 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 bagEntry = bag.get()[bagIndex];
200       final int attrRes = bagEntry.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<ResTable.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 + ")" + " := " + bagEntry.map.value);
256       }
257 
258       if (kDebugTableNoisy) {
259         ALOGV(
260             "Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x",
261             attrRes,
262             bag.get()[bagIndex].map.value.dataType,
263             bag.get()[bagIndex].map.value.data,
264             curEntry.value.dataType);
265       }
266       if (force
267           || (curEntry.value.dataType == TYPE_NULL
268               && curEntry.value.data != Res_value.DATA_NULL_EMPTY)) {
269         curEntry.stringBlock = bagEntry.stringBlock;
270         curEntry.typeSpecFlags |= bagTypeSpecFlags.get();
271         curEntry.value = new Res_value(bagEntry.map.value);
272       }
273 
274       bagIndex++;
275     }
276 
277     mTable.unlock();
278 
279     if (kDebugTableTheme) {
280       ALOGI("Applying style 0x%08x (force=%s)  theme %s...\n", resID, force, this);
281       dumpToLog();
282     }
283 
284     return NO_ERROR;
285   }
286 
dumpToLog()287   private void dumpToLog() {}
288 
289   @SuppressWarnings("DuplicateBranches") // The 'else' clause contains TODOs to remove duplication
setTo(ResTableTheme other)290   public int setTo(ResTableTheme other) {
291     styles.clear();
292     styles.addAll(other.styles);
293 
294     if (kDebugTableTheme) {
295       ALOGI("Setting theme %s from theme %s...\n", this, other);
296       dumpToLog();
297       other.dumpToLog();
298     }
299 
300     if (mTable == other.mTable) {
301       for (int i = 0; i < Res_MAXPACKAGE; i++) {
302         if (mPackages[i] != null) {
303           mPackages[i] = null;
304         }
305         if (other.mPackages[i] != null) {
306           mPackages[i] = copy_package(other.mPackages[i]);
307         } else {
308           mPackages[i] = null;
309         }
310       }
311     } else {
312       // @todo: need to really implement this, not just copy
313       // the system package (which is still wrong because it isn't
314       // fixing up resource references).
315       for (int i = 0; i < Res_MAXPACKAGE; i++) {
316         if (mPackages[i] != null) {
317           mPackages[i] = null;
318         }
319         // todo: C++ code presumably assumes index 0 is system, and only system
320         // if (i == 0 && other.mPackages[i] != null) {
321         if (other.mPackages[i] != null) {
322           mPackages[i] = copy_package(other.mPackages[i]);
323         } else {
324           mPackages[i] = null;
325         }
326       }
327     }
328 
329     mTypeSpecFlags = other.mTypeSpecFlags;
330 
331     if (kDebugTableTheme) {
332       ALOGI("Final theme:");
333       dumpToLog();
334     }
335 
336     return NO_ERROR;
337   }
338 
copy_package(package_info pi)339   private static package_info copy_package(package_info pi) {
340     package_info newpi = new package_info();
341     for (int j = 0; j <= Res_MAXTYPE; j++) {
342       if (pi.types[j] == null) {
343         newpi.types[j] = null;
344         continue;
345       }
346       int cnt = pi.types[j].numEntries;
347       newpi.types[j] = new type_info();
348       newpi.types[j].numEntries = cnt;
349       theme_entry[] te = pi.types[j].entries;
350       if (te != null) {
351         theme_entry[] newte = new theme_entry[cnt];
352         newpi.types[j].entries = newte;
353         //        memcpy(newte, te, cnt*sizeof(theme_entry));
354         for (int i = 0; i < newte.length; i++) {
355           newte[i] = te[i] == null ? null : new theme_entry(te[i]); // deep copy
356         }
357       } else {
358         newpi.types[j].entries = null;
359       }
360     }
361     return newpi;
362   }
363 
364   static class theme_entry {
365     int stringBlock;
366     int typeSpecFlags;
367     Res_value value = new Res_value();
368 
theme_entry()369     theme_entry() {}
370 
371     /** copy constructor. Performs a deep copy */
theme_entry(theme_entry src)372     public theme_entry(theme_entry src) {
373       if (src != null) {
374         stringBlock = src.stringBlock;
375         typeSpecFlags = src.typeSpecFlags;
376         value = new Res_value(src.value);
377       }
378     }
379   }
380   ;
381 
382   static class type_info {
383     int numEntries;
384     theme_entry[] entries;
385 
type_info()386     type_info() {}
387 
388     /** copy constructor. Performs a deep copy */
type_info(type_info src)389     type_info(type_info src) {
390       numEntries = src.numEntries;
391       entries = new theme_entry[src.entries.length];
392       for (int i = 0; i < src.entries.length; i++) {
393         if (src.entries[i] == null) {
394           entries[i] = null;
395         } else {
396           entries[i] = new theme_entry(src.entries[i]);
397         }
398       }
399     }
400   }
401   ;
402 
403   static class package_info {
404     type_info[] types = new type_info[Res_MAXTYPE + 1];
405 
package_info()406     package_info() {}
407 
408     /** copy constructor. Performs a deep copy */
package_info(package_info src)409     package_info(package_info src) {
410       for (int i = 0; i < src.types.length; i++) {
411         if (src.types[i] == null) {
412           types[i] = null;
413         } else {
414           types[i] = new type_info(src.types[i]);
415         }
416       }
417     }
418   }
419   ;
420 
421   static final int Res_MAXPACKAGE = 255;
422   static final int Res_MAXTYPE = 255;
423 }
424