1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 4 import static android.os.Build.VERSION_CODES.KITKAT_WATCH; 5 import static android.os.Build.VERSION_CODES.LOLLIPOP; 6 import static android.os.Build.VERSION_CODES.M; 7 import static android.os.Build.VERSION_CODES.N; 8 import static android.os.Build.VERSION_CODES.N_MR1; 9 import static android.os.Build.VERSION_CODES.O; 10 import static android.os.Build.VERSION_CODES.O_MR1; 11 import static android.os.Build.VERSION_CODES.P; 12 import static android.os.Build.VERSION_CODES.Q; 13 14 import static org.robolectric.RuntimeEnvironment.castNativePtr; 15 import static org.robolectric.shadow.api.Shadow.directlyOn; 16 import static org.robolectric.shadow.api.Shadow.invokeConstructor; 17 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 18 19 import android.annotation.SuppressLint; 20 import android.content.res.ApkAssets; 21 import android.content.res.AssetFileDescriptor; 22 import android.content.res.AssetManager; 23 import android.content.res.Resources; 24 import android.content.res.TypedArray; 25 import android.content.res.XmlResourceParser; 26 import android.os.Build; 27 import android.os.Build.VERSION_CODES; 28 import android.os.ParcelFileDescriptor; 29 import android.util.AttributeSet; 30 import android.util.SparseArray; 31 import android.util.TypedValue; 32 import com.google.common.collect.Ordering; 33 import dalvik.system.VMRuntime; 34 import java.io.ByteArrayInputStream; 35 import java.io.File; 36 import java.io.FileInputStream; 37 import java.io.FileNotFoundException; 38 import java.io.FileOutputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.net.MalformedURLException; 42 import java.net.URL; 43 import java.nio.file.Files; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Set; 52 import java.util.concurrent.CopyOnWriteArraySet; 53 import java.util.zip.ZipEntry; 54 import java.util.zip.ZipInputStream; 55 import javax.annotation.Nonnull; 56 import org.robolectric.RuntimeEnvironment; 57 import org.robolectric.android.XmlResourceParserImpl; 58 import org.robolectric.annotation.HiddenApi; 59 import org.robolectric.annotation.Implementation; 60 import org.robolectric.annotation.Implements; 61 import org.robolectric.annotation.RealObject; 62 import org.robolectric.annotation.Resetter; 63 import org.robolectric.res.AttrData; 64 import org.robolectric.res.AttributeResource; 65 import org.robolectric.res.EmptyStyle; 66 import org.robolectric.res.FileTypedResource; 67 import org.robolectric.res.Fs; 68 import org.robolectric.res.FsFile; 69 import org.robolectric.res.ResName; 70 import org.robolectric.res.ResType; 71 import org.robolectric.res.ResourceIds; 72 import org.robolectric.res.ResourceTable; 73 import org.robolectric.res.Style; 74 import org.robolectric.res.StyleData; 75 import org.robolectric.res.StyleResolver; 76 import org.robolectric.res.ThemeStyleSet; 77 import org.robolectric.res.TypedResource; 78 import org.robolectric.res.android.Asset; 79 import org.robolectric.res.android.Registries; 80 import org.robolectric.res.android.ResTable_config; 81 import org.robolectric.res.builder.XmlBlock; 82 import org.robolectric.shadow.api.Shadow; 83 import org.robolectric.shadows.ShadowAssetManager.Picker; 84 import org.robolectric.util.Logger; 85 import org.robolectric.util.ReflectionHelpers; 86 87 @SuppressLint("NewApi") 88 @Implements(value = AssetManager.class, /* this one works for P too... maxSdk = VERSION_CODES.O_MR1,*/ 89 looseSignatures = true, shadowPicker = Picker.class) 90 public class ShadowLegacyAssetManager extends ShadowAssetManager { 91 92 public static final Ordering<String> ATTRIBUTE_TYPE_PRECIDENCE = 93 Ordering.explicit( 94 "reference", 95 "color", 96 "boolean", 97 "integer", 98 "fraction", 99 "dimension", 100 "float", 101 "enum", 102 "flag", 103 "flags", 104 "string"); 105 106 static boolean strictErrors = false; 107 108 private static final int STYLE_NUM_ENTRIES = 6; 109 private static final int STYLE_TYPE = 0; 110 private static final int STYLE_DATA = 1; 111 private static final int STYLE_ASSET_COOKIE = 2; 112 private static final int STYLE_RESOURCE_ID = 3; 113 private static final int STYLE_CHANGING_CONFIGURATIONS = 4; 114 private static final int STYLE_DENSITY = 5; 115 116 private static long nextInternalThemeId = 1000; 117 private static final Map<Long, NativeTheme> nativeThemes = new HashMap<>(); 118 119 @RealObject 120 protected AssetManager realObject; 121 122 private ResourceTable resourceTable; 123 124 class NativeTheme { 125 private ThemeStyleSet themeStyleSet; 126 127 public NativeTheme(ThemeStyleSet themeStyleSet) { 128 this.themeStyleSet = themeStyleSet; 129 } 130 131 public ShadowLegacyAssetManager getShadowAssetManager() { 132 return ShadowLegacyAssetManager.this; 133 } 134 } 135 136 ResTable_config config = new ResTable_config(); 137 private Set<FsFile> assetDirs = new CopyOnWriteArraySet<>(); 138 139 private void convertAndFill(AttributeResource attribute, TypedValue outValue, ResTable_config config, boolean resolveRefs) { 140 if (attribute.isNull()) { 141 outValue.type = TypedValue.TYPE_NULL; 142 outValue.data = TypedValue.DATA_NULL_UNDEFINED; 143 return; 144 } else if (attribute.isEmpty()) { 145 outValue.type = TypedValue.TYPE_NULL; 146 outValue.data = TypedValue.DATA_NULL_EMPTY; 147 return; 148 } 149 150 // short-circuit Android caching of loaded resources cuz our string positions don't remain stable... 151 outValue.assetCookie = Converter.getNextStringCookie(); 152 outValue.changingConfigurations = 0; 153 154 // TODO: Handle resource and style references 155 if (attribute.isStyleReference()) { 156 return; 157 } 158 159 while (attribute.isResourceReference()) { 160 Integer resourceId; 161 ResName resName = attribute.getResourceReference(); 162 if (attribute.getReferenceResId() != null) { 163 resourceId = attribute.getReferenceResId(); 164 } else { 165 resourceId = resourceTable.getResourceId(resName); 166 } 167 168 if (resourceId == null) { 169 throw new Resources.NotFoundException("unknown resource " + resName); 170 } 171 outValue.type = TypedValue.TYPE_REFERENCE; 172 if (!resolveRefs) { 173 // Just return the resourceId if resolveRefs is false. 174 outValue.data = resourceId; 175 return; 176 } 177 178 outValue.resourceId = resourceId; 179 180 TypedResource dereferencedRef = resourceTable.getValue(resName, config); 181 if (dereferencedRef == null) { 182 Logger.strict("couldn't resolve %s from %s", resName.getFullyQualifiedName(), attribute); 183 return; 184 } else { 185 if (dereferencedRef.isFile()) { 186 outValue.type = TypedValue.TYPE_STRING; 187 outValue.data = 0; 188 outValue.assetCookie = Converter.getNextStringCookie(); 189 outValue.string = dereferencedRef.asString(); 190 return; 191 } else if (dereferencedRef.getData() instanceof String) { 192 attribute = new AttributeResource(attribute.resName, dereferencedRef.asString(), resName.packageName); 193 if (attribute.isResourceReference()) { 194 continue; 195 } 196 if (resolveRefs) { 197 Converter.getConverter(dereferencedRef.getResType()).fillTypedValue(attribute.value, outValue); 198 return; 199 } 200 } 201 } 202 break; 203 } 204 205 if (attribute.isNull()) { 206 outValue.type = TypedValue.TYPE_NULL; 207 return; 208 } 209 210 TypedResource attrTypeData = getAttrTypeData(attribute.resName); 211 if (attrTypeData != null) { 212 AttrData attrData = (AttrData) attrTypeData.getData(); 213 String format = attrData.getFormat(); 214 String[] types = format.split("\\|"); 215 Arrays.sort(types, ATTRIBUTE_TYPE_PRECIDENCE); 216 for (String type : types) { 217 if ("reference".equals(type)) continue; // already handled above 218 Converter converter = Converter.getConverterFor(attrData, type); 219 220 if (converter != null) { 221 if (converter.fillTypedValue(attribute.value, outValue)) { 222 return; 223 } 224 } 225 } 226 } else { 227 /** 228 * In cases where the runtime framework doesn't know this attribute, e.g: viewportHeight (added in 21) on a 229 * KitKat runtine, then infer the attribute type from the value. 230 * 231 * TODO: When we are able to pass the SDK resources from the build environment then we can remove this 232 * and replace the NullResourceLoader with simple ResourceProvider that only parses attribute type information. 233 */ 234 ResType resType = ResType.inferFromValue(attribute.value); 235 Converter.getConverter(resType).fillTypedValue(attribute.value, outValue); 236 } 237 } 238 239 240 public TypedResource getAttrTypeData(ResName resName) { 241 return resourceTable.getValue(resName, config); 242 } 243 244 @Implementation 245 protected void __constructor__() { 246 resourceTable = RuntimeEnvironment.getAppResourceTable(); 247 248 249 if (RuntimeEnvironment.getApiLevel() >= P) { 250 invokeConstructor(AssetManager.class, realObject); 251 } 252 253 } 254 255 @Implementation 256 protected void __constructor__(boolean isSystem) { 257 resourceTable = isSystem ? RuntimeEnvironment.getSystemResourceTable() : RuntimeEnvironment.getAppResourceTable(); 258 259 260 if (RuntimeEnvironment.getApiLevel() >= P) { 261 invokeConstructor(AssetManager.class, realObject, from(boolean.class, isSystem)); 262 } 263 264 } 265 266 @Implementation(minSdk = P) 267 protected static long nativeCreate() { 268 // Return a fake pointer, must not be 0. 269 return 1; 270 } 271 272 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 273 protected void init() { 274 // no op 275 } 276 277 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 278 protected void init(boolean isSystem) { 279 // no op 280 } 281 282 protected ResourceTable getResourceTable() { 283 return resourceTable; 284 } 285 286 @HiddenApi @Implementation 287 public CharSequence getResourceText(int ident) { 288 TypedResource value = getAndResolve(ident, config, true); 289 if (value == null) return null; 290 return (CharSequence) value.getData(); 291 } 292 293 @HiddenApi @Implementation 294 public CharSequence getResourceBagText(int ident, int bagEntryId) { 295 throw new UnsupportedOperationException(); // todo 296 } 297 298 @HiddenApi @Implementation(maxSdk = O_MR1) 299 protected int getStringBlockCount() { 300 return 0; 301 } 302 303 @HiddenApi @Implementation 304 public String[] getResourceStringArray(final int id) { 305 CharSequence[] resourceTextArray = getResourceTextArray(id); 306 if (resourceTextArray == null) return null; 307 String[] strings = new String[resourceTextArray.length]; 308 for (int i = 0; i < strings.length; i++) { 309 strings[i] = resourceTextArray[i].toString(); 310 } 311 return strings; 312 } 313 314 @HiddenApi @Implementation 315 public int getResourceIdentifier(String name, String defType, String defPackage) { 316 Integer resourceId = resourceTable.getResourceId(ResName.qualifyResName(name, defPackage, defType)); 317 return resourceId == null ? 0 : resourceId; 318 } 319 320 @HiddenApi @Implementation 321 public boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) { 322 TypedResource value = getAndResolve(ident, config, resolveRefs); 323 if (value == null) return false; 324 325 getConverter(value).fillTypedValue(value.getData(), outValue); 326 return true; 327 } 328 329 private Converter getConverter(TypedResource value) { 330 if (value instanceof FileTypedResource.Image 331 || (value instanceof FileTypedResource 332 && ((FileTypedResource) value).getFsFile().getName().endsWith(".xml"))) { 333 return new Converter.FromFilePath(); 334 } 335 return Converter.getConverter(value.getResType()); 336 } 337 338 @HiddenApi @Implementation 339 public CharSequence[] getResourceTextArray(int resId) { 340 TypedResource value = getAndResolve(resId, config, true); 341 if (value == null) return null; 342 List<TypedResource> items = getConverter(value).getItems(value); 343 CharSequence[] charSequences = new CharSequence[items.size()]; 344 for (int i = 0; i < items.size(); i++) { 345 TypedResource typedResource = resolve(items.get(i), config, resId); 346 charSequences[i] = getConverter(typedResource).asCharSequence(typedResource); 347 } 348 return charSequences; 349 } 350 351 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 352 public boolean getThemeValue(int themePtr, int ident, TypedValue outValue, boolean resolveRefs) { 353 return getThemeValue((long) themePtr, ident, outValue, resolveRefs); 354 } 355 356 @HiddenApi @Implementation(minSdk = LOLLIPOP) 357 public boolean getThemeValue(long themePtr, int ident, TypedValue outValue, boolean resolveRefs) { 358 ResName resName = resourceTable.getResName(ident); 359 360 ThemeStyleSet themeStyleSet = getNativeTheme(themePtr).themeStyleSet; 361 AttributeResource attrValue = themeStyleSet.getAttrValue(resName); 362 while(attrValue != null && attrValue.isStyleReference()) { 363 ResName attrResName = attrValue.getStyleReference(); 364 if (attrValue.resName.equals(attrResName)) { 365 Logger.info("huh... circular reference for %s?", attrResName.getFullyQualifiedName()); 366 return false; 367 } 368 attrValue = themeStyleSet.getAttrValue(attrResName); 369 } 370 if (attrValue != null) { 371 convertAndFill(attrValue, outValue, config, resolveRefs); 372 return true; 373 } 374 return false; 375 } 376 377 @HiddenApi @Implementation(maxSdk = O_MR1) 378 protected Object ensureStringBlocks() { 379 return null; 380 } 381 382 @Implementation 383 protected final InputStream open(String fileName) throws IOException { 384 return findAssetFile(fileName).getInputStream(); 385 } 386 387 @Implementation 388 protected final InputStream open(String fileName, int accessMode) throws IOException { 389 return findAssetFile(fileName).getInputStream(); 390 } 391 392 @Implementation 393 protected final AssetFileDescriptor openFd(String fileName) throws IOException { 394 File file = new File(findAssetFile(fileName).getPath()); 395 if (file.getPath().startsWith("jar")) { 396 file = getFileFromZip(file); 397 } 398 ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 399 return new AssetFileDescriptor(parcelFileDescriptor, 0, file.length()); 400 } 401 402 private FsFile findAssetFile(String fileName) throws IOException { 403 for (FsFile assetDir : getAllAssetDirs()) { 404 FsFile assetFile = assetDir.join(fileName); 405 if (assetFile.exists()) { 406 return assetFile; 407 } 408 } 409 410 throw new FileNotFoundException("Asset file " + fileName + " not found"); 411 } 412 413 /** 414 * Extract an asset from a zipped up assets provided by the build system, this is required because there is no 415 * way to get a FileDescriptor from a zip entry. This is a temporary measure for Bazel which can be removed 416 * once binary resources are supported. 417 */ 418 private static File getFileFromZip(File file) { 419 File fileFromZip = null; 420 String pathString = file.getPath(); 421 String zipFile = pathString.substring(pathString.lastIndexOf(":") + 1, pathString.indexOf("!")); 422 String filePathInsideZip = pathString.split("!", 0)[1].substring(1); 423 byte[] buffer = new byte[1024]; 424 try { 425 File outputDir = Files.createTempDirectory("robolectric_assets").toFile(); 426 ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile)); 427 ZipEntry ze = zis.getNextEntry(); 428 while (ze != null) { 429 String currentFilename = ze.getName(); 430 if (!currentFilename.equals(filePathInsideZip)) { 431 ze = zis.getNextEntry(); 432 continue; 433 } 434 fileFromZip = new File(outputDir + File.separator + currentFilename); 435 new File(fileFromZip.getParent()).mkdirs(); 436 FileOutputStream fos = new FileOutputStream(fileFromZip); 437 int len; 438 while ((len = zis.read(buffer)) > 0) { 439 fos.write(buffer, 0, len); 440 } 441 fos.close(); 442 break; 443 } 444 zis.closeEntry(); 445 zis.close(); 446 } catch (IOException e) { 447 throw new RuntimeException(e); 448 } 449 return fileFromZip; 450 } 451 452 @Implementation 453 protected final String[] list(String path) throws IOException { 454 List<String> assetFiles = new ArrayList<>(); 455 456 for (FsFile assetsDir : getAllAssetDirs()) { 457 FsFile file; 458 if (path.isEmpty()) { 459 file = assetsDir; 460 } else { 461 file = assetsDir.join(path); 462 } 463 464 if (file.isDirectory()) { 465 Collections.addAll(assetFiles, file.listFileNames()); 466 } 467 } 468 return assetFiles.toArray(new String[assetFiles.size()]); 469 } 470 471 @HiddenApi @Implementation(maxSdk = O_MR1) 472 protected Number openAsset(String fileName, int mode) throws FileNotFoundException { 473 return 0; 474 } 475 476 @HiddenApi @Implementation(maxSdk = O_MR1) 477 protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException { 478 return null; 479 } 480 481 @HiddenApi @Implementation 482 public final InputStream openNonAsset(int cookie, String fileName, int accessMode) throws IOException { 483 final ResName resName = qualifyFromNonAssetFileName(fileName); 484 485 final FileTypedResource typedResource = 486 (FileTypedResource) resourceTable.getValue(resName, config); 487 488 if (typedResource == null) { 489 throw new IOException("Unable to find resource for " + fileName); 490 } 491 492 InputStream stream; 493 if (accessMode == AssetManager.ACCESS_STREAMING) { 494 stream = typedResource.getFsFile().getInputStream(); 495 } else { 496 stream = new ByteArrayInputStream(typedResource.getFsFile().getBytes()); 497 } 498 499 if (RuntimeEnvironment.getApiLevel() >= P) { 500 Asset asset = Asset.newFileAsset(typedResource); 501 long assetPtr = Registries.NATIVE_ASSET_REGISTRY.register(asset); 502 // Camouflage the InputStream as an AssetInputStream so subsequent instanceof checks pass. 503 stream = ShadowAssetInputStream.createAssetInputStream(stream, assetPtr, realObject); 504 } 505 506 return stream; 507 } 508 509 @HiddenApi @Implementation(maxSdk = O_MR1) 510 protected Number openNonAssetNative(int cookie, String fileName, int accessMode) 511 throws FileNotFoundException { 512 throw new IllegalStateException(); 513 } 514 515 private ResName qualifyFromNonAssetFileName(String fileName) { 516 // Resources from a jar belong to the "android" namespace, except when they come from "resource_files.zip" 517 // when they are application resources produced by Bazel. 518 if (fileName.startsWith("jar:") && !fileName.contains("resource_files.zip")) { 519 // Must remove "jar:" prefix, or else qualifyFromFilePath fails on Windows 520 return ResName.qualifyFromFilePath("android", fileName.replaceFirst("jar:", "")); 521 } else { 522 return ResName.qualifyFromFilePath(RuntimeEnvironment.application.getPackageName(), fileName); 523 } 524 } 525 526 @HiddenApi @Implementation 527 public final AssetFileDescriptor openNonAssetFd(int cookie, String fileName) throws IOException { 528 throw new IllegalStateException(); 529 } 530 531 @HiddenApi @Implementation(maxSdk = O_MR1) 532 protected ParcelFileDescriptor openNonAssetFdNative(int cookie, String fileName, long[] outOffsets) 533 throws IOException { 534 throw new IllegalStateException(); 535 } 536 537 @HiddenApi @Implementation(maxSdk = O_MR1) 538 protected Number openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException { 539 throw new IllegalStateException(); 540 } 541 542 @Implementation 543 protected final XmlResourceParser openXmlResourceParser(int cookie, String fileName) 544 throws IOException { 545 XmlBlock xmlBlock = XmlBlock.create(Fs.fileFromPath(fileName), resourceTable.getPackageName()); 546 if (xmlBlock == null) { 547 throw new Resources.NotFoundException(fileName); 548 } 549 return getXmlResourceParser(resourceTable, xmlBlock, resourceTable.getPackageName()); 550 } 551 552 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 553 protected final long seekAsset(int asset, long offset, int whence) { 554 return seekAsset((long) asset, offset, whence); 555 } 556 557 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 558 protected long seekAsset(long asset, long offset, int whence) { 559 return 0; 560 } 561 562 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 563 protected final long getAssetLength(int asset) { 564 return getAssetLength((long) asset); 565 } 566 567 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 568 protected long getAssetLength(long asset) { 569 return 0; 570 } 571 572 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 573 protected final long getAssetRemainingLength(int asset) { 574 return getAssetRemainingLength((long) asset); 575 } 576 577 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 578 protected long getAssetRemainingLength(long assetHandle) { 579 return 0; 580 } 581 582 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 583 protected final void destroyAsset(int asset) { 584 destroyAsset((long) asset); 585 } 586 587 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 588 protected void destroyAsset(long asset) { 589 // no op 590 } 591 592 protected XmlResourceParser loadXmlResourceParser(int resId, String type) throws Resources.NotFoundException { 593 ResName resName = getResName(resId); 594 ResName resolvedResName = resolveResName(resName, config); 595 if (resolvedResName == null) { 596 throw new RuntimeException("couldn't resolve " + resName.getFullyQualifiedName()); 597 } 598 resName = resolvedResName; 599 600 XmlBlock block = resourceTable.getXml(resName, config); 601 if (block == null) { 602 throw new Resources.NotFoundException(resName.getFullyQualifiedName()); 603 } 604 605 ResourceTable resourceProvider = ResourceIds.isFrameworkResource(resId) ? RuntimeEnvironment.getSystemResourceTable() : RuntimeEnvironment.getCompileTimeResourceTable(); 606 607 return getXmlResourceParser(resourceProvider, block, resName.packageName); 608 } 609 610 private XmlResourceParser getXmlResourceParser(ResourceTable resourceProvider, XmlBlock block, String packageName) { 611 return new XmlResourceParserImpl(block.getDocument(), block.getFilename(), block.getPackageName(), 612 packageName, resourceProvider); 613 } 614 615 @HiddenApi @Implementation 616 public int addAssetPath(String path) { 617 assetDirs.add(getFsFileFromPath(path)); 618 return 1; 619 } 620 621 @HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M) 622 final protected int addAssetPathNative(String path) { 623 return addAssetPathNative(path, false); 624 } 625 626 @HiddenApi @Implementation(minSdk = N, maxSdk = O_MR1) 627 protected int addAssetPathNative(String path, boolean appAsLib) { 628 return 0; 629 } 630 631 @HiddenApi @Implementation(minSdk = P) 632 public void setApkAssets(Object apkAssetsObject, Object invalidateCachesObject) { 633 ApkAssets[] apkAssets = (ApkAssets[]) apkAssetsObject; 634 boolean invalidateCaches = (boolean) invalidateCachesObject; 635 636 for (ApkAssets apkAsset : apkAssets) { 637 assetDirs.add(getFsFileFromPath(apkAsset.getAssetPath())); 638 } 639 directlyOn(realObject, AssetManager.class).setApkAssets(apkAssets, invalidateCaches); 640 } 641 642 private FsFile getFsFileFromPath(String property) { 643 if (property.startsWith("jar")) { 644 try { 645 URL url = new URL(property); 646 return Fs.fromURL(url); 647 } catch (MalformedURLException e) { 648 throw new RuntimeException(e); 649 } 650 } else { 651 return Fs.fileFromPath(property); 652 } 653 } 654 655 @HiddenApi @Implementation 656 public boolean isUpToDate() { 657 return true; 658 } 659 660 @HiddenApi @Implementation(maxSdk = M) 661 public void setLocale(String locale) { 662 } 663 664 @Implementation 665 protected String[] getLocales() { 666 return new String[0]; // todo 667 } 668 669 @HiddenApi @Implementation(maxSdk = N_MR1) 670 final public void setConfiguration(int mcc, int mnc, String locale, 671 int orientation, int touchscreen, int density, int keyboard, 672 int keyboardHidden, int navigation, int screenWidth, int screenHeight, 673 int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, 674 int screenLayout, int uiMode, int sdkVersion) { 675 setConfiguration(mcc, mnc, locale, 676 orientation, touchscreen, density, keyboard, 677 keyboardHidden, navigation, screenWidth, screenHeight, 678 smallestScreenWidthDp, screenWidthDp, screenHeightDp, 679 screenLayout, uiMode, 0, sdkVersion); 680 } 681 682 @HiddenApi @Implementation(minSdk = VERSION_CODES.O) 683 public void setConfiguration(int mcc, int mnc, String locale, 684 int orientation, int touchscreen, int density, int keyboard, 685 int keyboardHidden, int navigation, int screenWidth, int screenHeight, 686 int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, 687 int screenLayout, int uiMode, int colorMode, int majorVersion) { 688 // AssetManager* am = assetManagerForJavaObject(env, clazz); 689 690 ResTable_config config = new ResTable_config(); 691 692 // Constants duplicated from Java class android.content.res.Configuration. 693 final int kScreenLayoutRoundMask = 0x300; 694 final int kScreenLayoutRoundShift = 8; 695 696 config.mcc = mcc; 697 config.mnc = mnc; 698 config.orientation = orientation; 699 config.touchscreen = touchscreen; 700 config.density = density; 701 config.keyboard = keyboard; 702 config.inputFlags = keyboardHidden; 703 config.navigation = navigation; 704 config.screenWidth = screenWidth; 705 config.screenHeight = screenHeight; 706 config.smallestScreenWidthDp = smallestScreenWidthDp; 707 config.screenWidthDp = screenWidthDp; 708 config.screenHeightDp = screenHeightDp; 709 config.screenLayout = screenLayout; 710 config.uiMode = uiMode; 711 // config.colorMode = colorMode; // todo 712 config.sdkVersion = majorVersion; 713 config.minorVersion = 0; 714 715 // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer 716 // in C++. We must extract the round qualifier out of the Java screenLayout and put it 717 // into screenLayout2. 718 config.screenLayout2 = 719 (byte)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); 720 721 if (locale != null) { 722 config.setBcp47Locale(locale); 723 } 724 // am->setConfiguration(config, locale8); 725 726 this.config = config; 727 } 728 729 @HiddenApi @Implementation(maxSdk = O_MR1) 730 public int[] getArrayIntResource(int resId) { 731 TypedResource value = getAndResolve(resId, config, true); 732 if (value == null) return null; 733 List<TypedResource> items = getConverter(value).getItems(value); 734 int[] ints = new int[items.size()]; 735 for (int i = 0; i < items.size(); i++) { 736 TypedResource typedResource = resolve(items.get(i), config, resId); 737 ints[i] = getConverter(typedResource).asInt(typedResource); 738 } 739 return ints; 740 } 741 742 @HiddenApi @Implementation(minSdk = P) 743 protected int[] getResourceIntArray(int resId) { 744 return getArrayIntResource(resId); 745 } 746 747 @HiddenApi @Implementation(maxSdk = O_MR1) 748 protected String[] getArrayStringResource(int arrayResId) { 749 return new String[0]; 750 } 751 752 @HiddenApi @Implementation(maxSdk = O_MR1) 753 protected int[] getArrayStringInfo(int arrayResId) { 754 return new int[0]; 755 } 756 757 @HiddenApi @Implementation(maxSdk = O_MR1) 758 protected Number newTheme() { 759 return null; 760 } 761 762 protected TypedArray getTypedArrayResource(Resources resources, int resId) { 763 TypedResource value = getAndResolve(resId, config, true); 764 if (value == null) { 765 return null; 766 } 767 List<TypedResource> items = getConverter(value).getItems(value); 768 return getTypedArray(resources, items, resId); 769 } 770 771 private TypedArray getTypedArray(Resources resources, List<TypedResource> typedResources, int resId) { 772 final CharSequence[] stringData = new CharSequence[typedResources.size()]; 773 final int totalLen = typedResources.size() * STYLE_NUM_ENTRIES; 774 final int[] data = new int[totalLen]; 775 776 for (int i = 0; i < typedResources.size(); i++) { 777 final int offset = i * STYLE_NUM_ENTRIES; 778 TypedResource typedResource = typedResources.get(i); 779 780 // Classify the item. 781 int type = getResourceType(typedResource); 782 if (type == -1) { 783 // This type is unsupported; leave empty. 784 continue; 785 } 786 787 final TypedValue typedValue = new TypedValue(); 788 789 if (type == TypedValue.TYPE_REFERENCE) { 790 final String reference = typedResource.asString(); 791 ResName refResName = AttributeResource.getResourceReference(reference, 792 typedResource.getXmlContext().getPackageName(), null); 793 typedValue.resourceId = resourceTable.getResourceId(refResName); 794 typedValue.data = typedValue.resourceId; 795 typedResource = resolve(typedResource, config, typedValue.resourceId); 796 797 if (typedResource != null) { 798 // Reclassify to a non-reference type. 799 type = getResourceType(typedResource); 800 if (type == TypedValue.TYPE_ATTRIBUTE) { 801 type = TypedValue.TYPE_REFERENCE; 802 } else if (type == -1) { 803 // This type is unsupported; leave empty. 804 continue; 805 } 806 } 807 } 808 809 if (type == TypedValue.TYPE_ATTRIBUTE) { 810 final String reference = typedResource.asString(); 811 final ResName attrResName = AttributeResource.getStyleReference(reference, 812 typedResource.getXmlContext().getPackageName(), "attr"); 813 typedValue.data = resourceTable.getResourceId(attrResName); 814 } 815 816 if (typedResource != null && type != TypedValue.TYPE_NULL && type != TypedValue.TYPE_ATTRIBUTE) { 817 getConverter(typedResource).fillTypedValue(typedResource.getData(), typedValue); 818 } 819 820 data[offset + STYLE_TYPE] = type; 821 data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId; 822 data[offset + STYLE_DATA] = typedValue.data; 823 data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie; 824 data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations; 825 data[offset + STYLE_DENSITY] = typedValue.density; 826 stringData[i] = typedResource == null ? null : typedResource.asString(); 827 } 828 829 int[] indices = new int[typedResources.size() + 1]; /* keep zeroed out */ 830 return ShadowTypedArray.create(resources, null, data, indices, typedResources.size(), stringData); 831 } 832 833 private int getResourceType(TypedResource typedResource) { 834 if (typedResource == null) { 835 return -1; 836 } 837 final ResType resType = typedResource.getResType(); 838 int type; 839 if (typedResource.getData() == null || resType == ResType.NULL) { 840 type = TypedValue.TYPE_NULL; 841 } else if (typedResource.isReference()) { 842 type = TypedValue.TYPE_REFERENCE; 843 } else if (resType == ResType.STYLE) { 844 type = TypedValue.TYPE_ATTRIBUTE; 845 } else if (resType == ResType.CHAR_SEQUENCE || resType == ResType.DRAWABLE) { 846 type = TypedValue.TYPE_STRING; 847 } else if (resType == ResType.INTEGER) { 848 type = TypedValue.TYPE_INT_DEC; 849 } else if (resType == ResType.FLOAT || resType == ResType.FRACTION) { 850 type = TypedValue.TYPE_FLOAT; 851 } else if (resType == ResType.BOOLEAN) { 852 type = TypedValue.TYPE_INT_BOOLEAN; 853 } else if (resType == ResType.DIMEN) { 854 type = TypedValue.TYPE_DIMENSION; 855 } else if (resType == ResType.COLOR) { 856 type = TypedValue.TYPE_INT_COLOR_ARGB8; 857 } else if (resType == ResType.TYPED_ARRAY || resType == ResType.CHAR_SEQUENCE_ARRAY) { 858 type = TypedValue.TYPE_REFERENCE; 859 } else { 860 type = -1; 861 } 862 return type; 863 } 864 865 @HiddenApi @Implementation 866 public Number createTheme() { 867 synchronized (nativeThemes) { 868 long nativePtr = nextInternalThemeId++; 869 nativeThemes.put(nativePtr, new NativeTheme(new ThemeStyleSet())); 870 return castNativePtr(nativePtr); 871 } 872 } 873 874 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 875 protected static void dumpTheme(long theme, int priority, String tag, String prefix) { 876 throw new UnsupportedOperationException("not yet implemented"); 877 } 878 879 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 880 public void releaseTheme(int themePtr) { 881 // no op 882 } 883 884 private static NativeTheme getNativeTheme(long themePtr) { 885 NativeTheme nativeTheme; 886 synchronized (nativeThemes) { 887 nativeTheme = nativeThemes.get(themePtr); 888 } 889 if (nativeTheme == null) { 890 throw new RuntimeException("no theme " + themePtr + " found in AssetManager"); 891 } 892 return nativeTheme; 893 } 894 895 @HiddenApi @Implementation(minSdk = LOLLIPOP) 896 public void releaseTheme(long themePtr) { 897 synchronized (nativeThemes) { 898 nativeThemes.remove(themePtr); 899 } 900 } 901 902 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 903 protected void deleteTheme(int theme) { 904 deleteTheme((long) theme); 905 } 906 907 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 908 protected void deleteTheme(long theme) { 909 // no op 910 } 911 912 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 913 public static void applyThemeStyle(int themePtr, int styleRes, boolean force) { 914 applyThemeStyle((long) themePtr, styleRes, force); 915 } 916 917 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 918 public static void applyThemeStyle(long themePtr, int styleRes, boolean force) { 919 NativeTheme nativeTheme = getNativeTheme(themePtr); 920 Style style = nativeTheme.getShadowAssetManager().resolveStyle(styleRes, null); 921 nativeTheme.themeStyleSet.apply(style, force); 922 } 923 924 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 925 public static void copyTheme(int destPtr, int sourcePtr) { 926 copyTheme((long) destPtr, (long) sourcePtr); 927 } 928 929 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 930 public static void copyTheme(long destPtr, long sourcePtr) { 931 NativeTheme destNativeTheme = getNativeTheme(destPtr); 932 NativeTheme sourceNativeTheme = getNativeTheme(sourcePtr); 933 destNativeTheme.themeStyleSet = sourceNativeTheme.themeStyleSet.copy(); 934 } 935 936 @HiddenApi @Implementation(minSdk = P) 937 protected static void nativeThemeCopy(long destPtr, long sourcePtr) { 938 copyTheme(destPtr, sourcePtr); 939 } 940 941 // BEGIN-INTERNAL 942 @HiddenApi @Implementation(minSdk = Q) 943 protected static void nativeThemeCopy(long dstAssetManagerPtr, long dstThemePtr, 944 long srcAssetManagerPtr, long srcThemePtr) { 945 copyTheme(dstThemePtr, srcThemePtr); 946 } 947 // END-INTERNAL 948 949 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 950 protected static boolean applyStyle(int themeToken, int defStyleAttr, int defStyleRes, 951 int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) { 952 return applyStyle((long)themeToken, defStyleAttr, defStyleRes, (long)xmlParserToken, attrs, 953 outValues, outIndices); 954 } 955 956 @HiddenApi @Implementation(minSdk = O, maxSdk = O_MR1) 957 protected static void applyStyle(long themeToken, int defStyleAttr, int defStyleRes, 958 long xmlParserToken, int[] inAttrs, int length, long outValuesAddress, 959 long outIndicesAddress) { 960 ShadowVMRuntime shadowVMRuntime = Shadow.extract(VMRuntime.getRuntime()); 961 int[] outValues = (int[])shadowVMRuntime.getObjectForAddress(outValuesAddress); 962 int[] outIndices = (int[])shadowVMRuntime.getObjectForAddress(outIndicesAddress); 963 applyStyle(themeToken, defStyleAttr, defStyleRes, xmlParserToken, inAttrs, 964 outValues, outIndices); 965 } 966 967 @HiddenApi @Implementation(minSdk = P) 968 protected void applyStyleToTheme(long themePtr, int resId, boolean force) { 969 applyThemeStyle(themePtr, resId, force); 970 } 971 972 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1) 973 protected static boolean applyStyle(long themeToken, int defStyleAttr, int defStyleRes, 974 long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) { 975 // no-op 976 return false; 977 } 978 979 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 980 protected static boolean resolveAttrs(long themeToken, 981 int defStyleAttr, int defStyleRes, int[] inValues, 982 int[] attrs, int[] outValues, int[] outIndices) { 983 // no-op 984 return false; 985 } 986 987 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 988 protected boolean retrieveAttributes( 989 int xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) { 990 return retrieveAttributes((long)xmlParserToken, attrs, outValues, outIndices); 991 } 992 993 @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 994 protected boolean retrieveAttributes(long xmlParserToken, int[] attrs, int[] outValues, 995 int[] outIndices) { 996 return false; 997 } 998 999 @HiddenApi @Implementation(maxSdk = KITKAT_WATCH) 1000 protected static int loadThemeAttributeValue(int themeHandle, int ident, 1001 TypedValue outValue, boolean resolve) { 1002 return loadThemeAttributeValue((long) themeHandle, ident, outValue, resolve); 1003 } 1004 1005 @HiddenApi @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 1006 protected static int loadThemeAttributeValue(long themeHandle, int ident, 1007 TypedValue outValue, boolean resolve) { 1008 // no-op 1009 return 0; 1010 } 1011 1012 ///////////////////////// 1013 1014 Style resolveStyle(int resId, Style themeStyleSet) { 1015 return resolveStyle(getResName(resId), themeStyleSet); 1016 } 1017 1018 private Style resolveStyle(@Nonnull ResName themeStyleName, Style themeStyleSet) { 1019 TypedResource themeStyleResource = resourceTable.getValue(themeStyleName, config); 1020 if (themeStyleResource == null) return null; 1021 StyleData themeStyleData = (StyleData) themeStyleResource.getData(); 1022 if (themeStyleSet == null) { 1023 themeStyleSet = new ThemeStyleSet(); 1024 } 1025 return new StyleResolver(resourceTable, legacyShadowOf(AssetManager.getSystem()).getResourceTable(), 1026 themeStyleData, themeStyleSet, themeStyleName, config); 1027 } 1028 1029 private TypedResource getAndResolve(int resId, ResTable_config config, boolean resolveRefs) { 1030 TypedResource value = resourceTable.getValue(resId, config); 1031 if (resolveRefs) { 1032 value = resolve(value, config, resId); 1033 } 1034 return value; 1035 } 1036 1037 TypedResource resolve(TypedResource value, ResTable_config config, int resId) { 1038 return resolveResourceValue(value, config, resId); 1039 } 1040 1041 protected ResName resolveResName(ResName resName, ResTable_config config) { 1042 TypedResource value = resourceTable.getValue(resName, config); 1043 return resolveResource(value, config, resName); 1044 } 1045 1046 // todo: DRY up #resolveResource vs #resolveResourceValue 1047 private ResName resolveResource(TypedResource value, ResTable_config config, ResName resName) { 1048 while (value != null && value.isReference()) { 1049 String s = value.asString(); 1050 if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) { 1051 value = null; 1052 } else { 1053 String refStr = s.substring(1).replace("+", ""); 1054 resName = ResName.qualifyResName(refStr, resName); 1055 value = resourceTable.getValue(resName, config); 1056 } 1057 } 1058 1059 return resName; 1060 } 1061 1062 private TypedResource resolveResourceValue(TypedResource value, ResTable_config config, ResName resName) { 1063 while (value != null && value.isReference()) { 1064 String s = value.asString(); 1065 if (AttributeResource.isNull(s) || AttributeResource.isEmpty(s)) { 1066 value = null; 1067 } else { 1068 String refStr = s.substring(1).replace("+", ""); 1069 resName = ResName.qualifyResName(refStr, resName); 1070 value = resourceTable.getValue(resName, config); 1071 } 1072 } 1073 1074 return value; 1075 } 1076 1077 protected TypedResource resolveResourceValue(TypedResource value, ResTable_config config, int resId) { 1078 ResName resName = getResName(resId); 1079 return resolveResourceValue(value, config, resName); 1080 } 1081 1082 private TypedValue buildTypedValue(AttributeSet set, int resId, int defStyleAttr, Style themeStyleSet, int defStyleRes) { 1083 /* 1084 * When determining the final value of a particular attribute, there are four inputs that come into play: 1085 * 1086 * 1. Any attribute values in the given AttributeSet. 1087 * 2. The style resource specified in the AttributeSet (named "style"). 1088 * 3. The default style specified by defStyleAttr and defStyleRes 1089 * 4. The base values in this theme. 1090 */ 1091 Style defStyleFromAttr = null; 1092 Style defStyleFromRes = null; 1093 Style styleAttrStyle = null; 1094 1095 if (defStyleAttr != 0) { 1096 // Load the theme attribute for the default style attributes. E.g., attr/buttonStyle 1097 ResName defStyleName = getResName(defStyleAttr); 1098 1099 // Load the style for the default style attribute. E.g. "@style/Widget.Robolectric.Button"; 1100 AttributeResource defStyleAttribute = themeStyleSet.getAttrValue(defStyleName); 1101 if (defStyleAttribute != null) { 1102 while (defStyleAttribute.isStyleReference()) { 1103 AttributeResource other = themeStyleSet.getAttrValue(defStyleAttribute.getStyleReference()); 1104 if (other == null) { 1105 throw new RuntimeException("couldn't dereference " + defStyleAttribute); 1106 } 1107 defStyleAttribute = other; 1108 } 1109 1110 if (defStyleAttribute.isResourceReference()) { 1111 ResName defStyleResName = defStyleAttribute.getResourceReference(); 1112 defStyleFromAttr = resolveStyle(defStyleResName, themeStyleSet); 1113 } 1114 } 1115 } 1116 1117 if (set != null && set.getStyleAttribute() != 0) { 1118 ResName styleAttributeResName = getResName(set.getStyleAttribute()); 1119 while (styleAttributeResName.type.equals("attr")) { 1120 AttributeResource attrValue = themeStyleSet.getAttrValue(styleAttributeResName); 1121 if (attrValue == null) { 1122 throw new RuntimeException( 1123 "no value for " + styleAttributeResName.getFullyQualifiedName() 1124 + " in " + themeStyleSet); 1125 } 1126 if (attrValue.isResourceReference()) { 1127 styleAttributeResName = attrValue.getResourceReference(); 1128 } else if (attrValue.isStyleReference()) { 1129 styleAttributeResName = attrValue.getStyleReference(); 1130 } 1131 } 1132 styleAttrStyle = resolveStyle(styleAttributeResName, themeStyleSet); 1133 } 1134 1135 if (defStyleRes != 0) { 1136 ResName resName = getResName(defStyleRes); 1137 if (resName.type.equals("attr")) { 1138 // todo: this should be a style resId, not an attr 1139 System.out.println("WARN: " + resName.getFullyQualifiedName() + " should be a style resId"); 1140 // AttributeResource attributeValue = findAttributeValue(defStyleRes, set, styleAttrStyle, defStyleFromAttr, defStyleFromAttr, themeStyleSet); 1141 // if (attributeValue != null) { 1142 // if (attributeValue.isStyleReference()) { 1143 // resName = themeStyleSet.getAttrValue(attributeValue.getStyleReference()).getResourceReference(); 1144 // } else if (attributeValue.isResourceReference()) { 1145 // resName = attributeValue.getResourceReference(); 1146 // } 1147 // } 1148 } else if (resName.type.equals("style")) { 1149 defStyleFromRes = resolveStyle(resName, themeStyleSet); 1150 } 1151 } 1152 1153 AttributeResource attribute = findAttributeValue(resId, set, styleAttrStyle, defStyleFromAttr, defStyleFromRes, themeStyleSet); 1154 while (attribute != null && attribute.isStyleReference()) { 1155 ResName otherAttrName = attribute.getStyleReference(); 1156 if (attribute.resName.equals(otherAttrName)) { 1157 Logger.info("huh... circular reference for %s?", attribute.resName.getFullyQualifiedName()); 1158 return null; 1159 } 1160 ResName resName = resourceTable.getResName(resId); 1161 1162 AttributeResource otherAttr = themeStyleSet.getAttrValue(otherAttrName); 1163 if (otherAttr == null) { 1164 strictError("no such attr %s in %s while resolving value for %s", attribute.value, themeStyleSet, resName.getFullyQualifiedName()); 1165 attribute = null; 1166 } else { 1167 attribute = new AttributeResource(resName, otherAttr.value, otherAttr.contextPackageName); 1168 } 1169 } 1170 1171 if (attribute == null || attribute.isNull()) { 1172 return null; 1173 } else { 1174 TypedValue typedValue = new TypedValue(); 1175 convertAndFill(attribute, typedValue, config, true); 1176 return typedValue; 1177 } 1178 } 1179 1180 private void strictError(String message, Object... args) { 1181 if (strictErrors) { 1182 throw new RuntimeException(String.format(message, args)); 1183 } else { 1184 Logger.strict(message, args); 1185 } 1186 } 1187 1188 TypedArray attrsToTypedArray(Resources resources, AttributeSet set, int[] attrs, int defStyleAttr, long nativeTheme, int defStyleRes) { 1189 CharSequence[] stringData = new CharSequence[attrs.length]; 1190 int[] data = new int[attrs.length * STYLE_NUM_ENTRIES]; 1191 int[] indices = new int[attrs.length + 1]; 1192 int nextIndex = 0; 1193 1194 Style themeStyleSet = nativeTheme == 0 1195 ? new EmptyStyle() 1196 : getNativeTheme(nativeTheme).themeStyleSet; 1197 1198 for (int i = 0; i < attrs.length; i++) { 1199 int offset = i * STYLE_NUM_ENTRIES; 1200 1201 TypedValue typedValue = buildTypedValue(set, attrs[i], defStyleAttr, themeStyleSet, defStyleRes); 1202 if (typedValue != null) { 1203 //noinspection PointlessArithmeticExpression 1204 data[offset + STYLE_TYPE] = typedValue.type; 1205 data[offset + STYLE_DATA] = typedValue.type == TypedValue.TYPE_STRING ? i : typedValue.data; 1206 data[offset + STYLE_ASSET_COOKIE] = typedValue.assetCookie; 1207 data[offset + STYLE_RESOURCE_ID] = typedValue.resourceId; 1208 data[offset + STYLE_CHANGING_CONFIGURATIONS] = typedValue.changingConfigurations; 1209 data[offset + STYLE_DENSITY] = typedValue.density; 1210 stringData[i] = typedValue.string; 1211 1212 indices[nextIndex + 1] = i; 1213 nextIndex++; 1214 } 1215 } 1216 1217 indices[0] = nextIndex; 1218 1219 TypedArray typedArray = ShadowTypedArray.create(resources, attrs, data, indices, nextIndex, stringData); 1220 if (set != null) { 1221 ShadowTypedArray shadowTypedArray = Shadow.extract(typedArray); 1222 shadowTypedArray.positionDescription = set.getPositionDescription(); 1223 } 1224 return typedArray; 1225 } 1226 1227 private AttributeResource findAttributeValue(int resId, AttributeSet attributeSet, Style styleAttrStyle, Style defStyleFromAttr, Style defStyleFromRes, @Nonnull Style themeStyleSet) { 1228 if (attributeSet != null) { 1229 for (int i = 0; i < attributeSet.getAttributeCount(); i++) { 1230 if (attributeSet.getAttributeNameResource(i) == resId) { 1231 String attributeValue; 1232 try { 1233 attributeValue = attributeSet.getAttributeValue(i); 1234 } catch (IndexOutOfBoundsException e) { 1235 // type is TypedValue.TYPE_NULL, ignore... 1236 continue; 1237 } 1238 if (attributeValue != null) { 1239 String defaultPackageName = ResourceIds.isFrameworkResource(resId) ? "android" : RuntimeEnvironment.application.getPackageName(); 1240 ResName resName = ResName.qualifyResName(attributeSet.getAttributeName(i), defaultPackageName, "attr"); 1241 Integer referenceResId = null; 1242 if (AttributeResource.isResourceReference(attributeValue)) { 1243 referenceResId = attributeSet.getAttributeResourceValue(i, -1); 1244 // binary AttributeSet references have a string value of @resId rather than fully qualified resource name 1245 if (referenceResId != 0) { 1246 ResName refResName = resourceTable.getResName(referenceResId); 1247 if (refResName != null) { 1248 attributeValue = "@" + refResName.getFullyQualifiedName(); 1249 } 1250 } 1251 } 1252 return new AttributeResource(resName, attributeValue, "fixme!!!", referenceResId); 1253 } 1254 } 1255 } 1256 } 1257 1258 ResName attrName = resourceTable.getResName(resId); 1259 if (attrName == null) return null; 1260 1261 if (styleAttrStyle != null) { 1262 AttributeResource attribute = styleAttrStyle.getAttrValue(attrName); 1263 if (attribute != null) { 1264 return attribute; 1265 } 1266 } 1267 1268 // else if attr in defStyleFromAttr, use its value 1269 if (defStyleFromAttr != null) { 1270 AttributeResource attribute = defStyleFromAttr.getAttrValue(attrName); 1271 if (attribute != null) { 1272 return attribute; 1273 } 1274 } 1275 1276 if (defStyleFromRes != null) { 1277 AttributeResource attribute = defStyleFromRes.getAttrValue(attrName); 1278 if (attribute != null) { 1279 return attribute; 1280 } 1281 } 1282 1283 // else if attr in theme, use its value 1284 return themeStyleSet.getAttrValue(attrName); 1285 } 1286 1287 @Override 1288 Collection<FsFile> getAllAssetDirs() { 1289 return assetDirs; 1290 } 1291 1292 @Nonnull private ResName getResName(int id) { 1293 ResName resName = resourceTable.getResName(id); 1294 if (resName == null) { 1295 throw new Resources.NotFoundException("Resource ID #0x" + Integer.toHexString(id)); 1296 } 1297 return resName; 1298 } 1299 1300 @Implementation 1301 protected String getResourceName(int resid) { 1302 return getResName(resid).getFullyQualifiedName(); 1303 } 1304 1305 @Implementation 1306 protected String getResourcePackageName(int resid) { 1307 return getResName(resid).packageName; 1308 } 1309 1310 @Implementation 1311 protected String getResourceTypeName(int resid) { 1312 return getResName(resid).type; 1313 } 1314 1315 @Implementation 1316 protected String getResourceEntryName(int resid) { 1317 return getResName(resid).name; 1318 } 1319 1320 @Implementation(maxSdk = O_MR1) 1321 protected int getArraySize(int id) { 1322 return 0; 1323 } 1324 1325 @Implementation(maxSdk = O_MR1) 1326 protected int retrieveArray(int id, int[] outValues) { 1327 return 0; 1328 } 1329 1330 @Implementation(maxSdk = O_MR1) 1331 protected Number getNativeStringBlock(int block) { 1332 throw new IllegalStateException(); 1333 } 1334 1335 @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1) 1336 protected final SparseArray<String> getAssignedPackageIdentifiers() { 1337 return new SparseArray<>(); 1338 } 1339 1340 @Implementation(maxSdk = O_MR1) 1341 protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) { 1342 return 0; 1343 } 1344 1345 @Implementation(maxSdk = O_MR1) 1346 protected int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue, boolean resolve) { 1347 return 0; 1348 } 1349 1350 // static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { 1351 @Implementation(minSdk = P) 1352 protected static void nativeAssetDestroy(long asset_ptr) { 1353 ShadowArscAssetManager9.nativeAssetDestroy(asset_ptr); 1354 } 1355 1356 // static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { 1357 @Implementation(minSdk = P) 1358 protected static int nativeAssetReadChar(long asset_ptr) { 1359 return ShadowArscAssetManager9.nativeAssetReadChar(asset_ptr); 1360 } 1361 1362 // static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer, 1363 // jint offset, jint len) { 1364 @Implementation(minSdk = P) 1365 protected static int nativeAssetRead(long asset_ptr, byte[] java_buffer, int offset, int len) 1366 throws IOException { 1367 return ShadowArscAssetManager9.nativeAssetRead(asset_ptr, java_buffer, offset, len); 1368 } 1369 1370 // static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset, 1371 // jint whence) { 1372 @Implementation(minSdk = P) 1373 protected static long nativeAssetSeek(long asset_ptr, long offset, int whence) { 1374 return ShadowArscAssetManager9.nativeAssetSeek(asset_ptr, offset, whence); 1375 } 1376 1377 // static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { 1378 @Implementation(minSdk = P) 1379 protected static long nativeAssetGetLength(long asset_ptr) { 1380 return ShadowArscAssetManager9.nativeAssetGetLength(asset_ptr); 1381 } 1382 1383 // static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) { 1384 @Implementation(minSdk = P) 1385 protected static long nativeAssetGetRemainingLength(long asset_ptr) { 1386 return ShadowArscAssetManager9.nativeAssetGetRemainingLength(asset_ptr); 1387 } 1388 1389 // BEGIN-INTERNAL 1390 @Implementation(minSdk = Build.VERSION_CODES.Q) 1391 protected static String[] nativeCreateIdmapsForStaticOverlaysTargetingAndroid() { 1392 return new String[0]; 1393 } 1394 // END-INTERNAL 1395 1396 @Resetter 1397 public static void reset() { 1398 // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for resetters... 1399 if (useLegacy()) { 1400 if (RuntimeEnvironment.getApiLevel() >= P) { 1401 ReflectionHelpers.setStaticField(AssetManager.class, "sSystemApkAssetsSet", null); 1402 ReflectionHelpers.setStaticField(AssetManager.class, "sSystemApkAssets", null); 1403 } 1404 ReflectionHelpers.setStaticField(AssetManager.class, "sSystem", null); 1405 } 1406 } 1407 1408 } 1409