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