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