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