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