1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.M; 4 import static android.os.Build.VERSION_CODES.N_MR1; 5 import static android.os.Build.VERSION_CODES.O; 6 import static android.os.Build.VERSION_CODES.O_MR1; 7 import static android.os.Build.VERSION_CODES.P; 8 import static org.robolectric.res.android.Asset.SEEK_CUR; 9 import static org.robolectric.res.android.Asset.SEEK_SET; 10 import static org.robolectric.res.android.AttributeResolution.kThrowOnBadId; 11 import static org.robolectric.res.android.Errors.BAD_INDEX; 12 import static org.robolectric.res.android.Errors.NO_ERROR; 13 import static org.robolectric.res.android.Util.ALOGV; 14 import static org.robolectric.res.android.Util.isTruthy; 15 import static org.robolectric.util.reflector.Reflector.reflector; 16 17 import android.content.res.AssetManager; 18 import android.os.Build.VERSION_CODES; 19 import android.os.ParcelFileDescriptor; 20 import android.util.SparseArray; 21 import android.util.TypedValue; 22 import com.google.common.annotations.VisibleForTesting; 23 import com.google.common.base.Preconditions; 24 import com.google.common.base.Strings; 25 import dalvik.system.VMRuntime; 26 import java.io.FileDescriptor; 27 import java.io.FileNotFoundException; 28 import java.io.IOException; 29 import java.nio.file.Files; 30 import java.nio.file.Path; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import org.robolectric.RuntimeEnvironment; 37 import org.robolectric.annotation.HiddenApi; 38 import org.robolectric.annotation.Implementation; 39 import org.robolectric.annotation.Implements; 40 import org.robolectric.annotation.RealObject; 41 import org.robolectric.annotation.Resetter; 42 import org.robolectric.res.Fs; 43 import org.robolectric.res.android.Asset; 44 import org.robolectric.res.android.Asset.AccessMode; 45 import org.robolectric.res.android.AssetDir; 46 import org.robolectric.res.android.AssetPath; 47 import org.robolectric.res.android.AttributeResolution; 48 import org.robolectric.res.android.CppAssetManager; 49 import org.robolectric.res.android.DataType; 50 import org.robolectric.res.android.DynamicRefTable; 51 import org.robolectric.res.android.Ref; 52 import org.robolectric.res.android.Registries; 53 import org.robolectric.res.android.ResStringPool; 54 import org.robolectric.res.android.ResTable; 55 import org.robolectric.res.android.ResTable.ResourceName; 56 import org.robolectric.res.android.ResTable.bag_entry; 57 import org.robolectric.res.android.ResTableTheme; 58 import org.robolectric.res.android.ResTable_config; 59 import org.robolectric.res.android.ResXMLParser; 60 import org.robolectric.res.android.ResXMLTree; 61 import org.robolectric.res.android.ResourceTypes.Res_value; 62 import org.robolectric.res.android.String8; 63 import org.robolectric.shadow.api.Shadow; 64 import org.robolectric.shadows.ShadowAssetManager.Picker; 65 66 // native method impls transliterated from 67 // https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_util_AssetManager.cpp 68 @Implements(value = AssetManager.class, maxSdk = VERSION_CODES.O_MR1, shadowPicker = Picker.class) 69 @SuppressWarnings("NewApi") 70 public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase { 71 72 private static final int STYLE_NUM_ENTRIES = 6; 73 private static final int STYLE_TYPE = 0; 74 private static final int STYLE_DATA = 1; 75 private static final int STYLE_ASSET_COOKIE = 2; 76 private static final int STYLE_RESOURCE_ID = 3; 77 private static final int STYLE_CHANGING_CONFIGURATIONS = 4; 78 private static final int STYLE_DENSITY = 5; 79 80 @RealObject 81 protected AssetManager realObject; 82 83 private CppAssetManager cppAssetManager; 84 85 @Resetter reset()86 public static void reset() { 87 // todo: ShadowPicker doesn't discriminate properly between concrete shadow classes for 88 // resetters... 89 if (RuntimeEnvironment.getApiLevel() < P) { 90 reflector(_AssetManager_.class).setSystem(null); 91 // NATIVE_THEME_REGISTRY.clear(); 92 // nativeXMLParserRegistry.clear(); // todo: shouldn't these be freed explicitly? [yes! xw] 93 // NATIVE_ASSET_REGISTRY.clear(); 94 } 95 } 96 97 @Implementation list(String path)98 protected String[] list(String path) throws IOException { 99 CppAssetManager am = assetManagerForJavaObject(); 100 101 String fileName8 = path; 102 if (fileName8 == null) { 103 return null; 104 } 105 106 AssetDir dir = am.openDir(fileName8); 107 108 if (dir == null) { 109 throw new FileNotFoundException(fileName8); 110 } 111 112 113 int N = dir.getFileCount(); 114 115 String[] array = new String[dir.getFileCount()]; 116 117 for (int i=0; i<N; i++) { 118 String8 name = dir.getFileName(i); 119 array[i] = name.string(); 120 } 121 122 return array; 123 } 124 125 // @HiddenApi @Implementation(minSdk = VERSION_CODES.P) 126 // public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) { 127 // throw new UnsupportedOperationException("implement me"); 128 // } 129 130 @HiddenApi @Implementation(maxSdk = N_MR1) setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int sdkVersion)131 final public void setConfiguration(int mcc, int mnc, String locale, 132 int orientation, int touchscreen, int density, int keyboard, 133 int keyboardHidden, int navigation, int screenWidth, int screenHeight, 134 int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, 135 int screenLayout, int uiMode, int sdkVersion) { 136 setConfiguration(mcc, mnc, locale, 137 orientation, touchscreen, density, keyboard, 138 keyboardHidden, navigation, screenWidth, screenHeight, 139 smallestScreenWidthDp, screenWidthDp, screenHeightDp, 140 screenLayout, uiMode, 0, sdkVersion); 141 } 142 143 @HiddenApi @Implementation(minSdk = O) setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode, int sdkVersion)144 public void setConfiguration(int mcc, int mnc, String locale, 145 int orientation, int touchscreen, int density, int keyboard, 146 int keyboardHidden, int navigation, int screenWidth, int screenHeight, 147 int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, 148 int screenLayout, int uiMode, int colorMode, int sdkVersion) { 149 CppAssetManager am = assetManagerForJavaObject(); 150 if (am == null) { 151 return; 152 } 153 154 ResTable_config config = new ResTable_config(); 155 // memset(&config, 0, sizeof(config)); 156 157 // const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL; 158 159 // Constants duplicated from Java class android.content.res.Configuration. 160 int kScreenLayoutRoundMask = 0x300; 161 int kScreenLayoutRoundShift = 8; 162 163 config.mcc = mcc; 164 config.mnc = mnc; 165 config.orientation = orientation; 166 config.touchscreen = touchscreen; 167 config.density = density; 168 config.keyboard = keyboard; 169 config.inputFlags = keyboardHidden; 170 config.navigation = navigation; 171 config.screenWidth = screenWidth; 172 config.screenHeight = screenHeight; 173 config.smallestScreenWidthDp = smallestScreenWidthDp; 174 config.screenWidthDp = screenWidthDp; 175 config.screenHeightDp = screenHeightDp; 176 config.screenLayout = screenLayout; 177 config.uiMode = uiMode; 178 config.colorMode = (byte) colorMode; 179 config.sdkVersion = sdkVersion; 180 config.minorVersion = 0; 181 182 // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer 183 // in C++. We must extract the round qualifier out of the Java screenLayout and put it 184 // into screenLayout2. 185 config.screenLayout2 = 186 (byte) ((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift); 187 188 am.setConfiguration(config, locale); 189 190 // if (locale != null) env->ReleaseStringUTFChars(locale, locale8); 191 } 192 193 @HiddenApi @Implementation dumpTheme(long theme, int priority, String tag, String prefix)194 protected static void dumpTheme(long theme, int priority, String tag, String prefix) { 195 throw new UnsupportedOperationException("not yet implemented"); 196 } 197 198 @Implementation getResourceName(int resid)199 protected String getResourceName(int resid) { 200 CppAssetManager am = assetManagerForJavaObject(); 201 202 ResourceName name = new ResourceName(); 203 if (!am.getResources().getResourceName(resid, true, name)) { 204 return null; 205 } 206 207 StringBuilder str = new StringBuilder(); 208 if (name.packageName != null) { 209 str.append(name.packageName.trim()); 210 } 211 if (name.type != null) { 212 if (str.length() > 0) { 213 char div = ':'; 214 str.append(div); 215 } 216 str.append(name.type); 217 } 218 if (name.name != null) { 219 if (str.length() > 0) { 220 char div = '/'; 221 str.append(div); 222 } 223 str.append(name.name); 224 } 225 return str.toString(); 226 } 227 228 @Implementation getResourcePackageName(int resid)229 protected String getResourcePackageName(int resid) { 230 CppAssetManager cppAssetManager = assetManagerForJavaObject(); 231 232 ResourceName name = new ResourceName(); 233 if (!cppAssetManager.getResources().getResourceName(resid, true, name)) { 234 return null; 235 } 236 237 return name.packageName.trim(); 238 } 239 240 @Implementation getResourceTypeName(int resid)241 protected String getResourceTypeName(int resid) { 242 CppAssetManager cppAssetManager = assetManagerForJavaObject(); 243 244 ResourceName name = new ResourceName(); 245 if (!cppAssetManager.getResources().getResourceName(resid, true, name)) { 246 return null; 247 } 248 249 return name.type; 250 } 251 252 @Implementation getResourceEntryName(int resid)253 protected String getResourceEntryName(int resid) { 254 CppAssetManager cppAssetManager = assetManagerForJavaObject(); 255 256 ResourceName name = new ResourceName(); 257 if (!cppAssetManager.getResources().getResourceName(resid, true, name)) { 258 return null; 259 } 260 261 return name.name; 262 } 263 264 //////////// native method implementations 265 266 // public native final String[] list(String path) 267 // throws IOException; 268 269 // @HiddenApi @Implementation(minSdk = VERSION_CODES.P) 270 // public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) { 271 // throw new UnsupportedOperationException("implement me"); 272 // } 273 // 274 275 @HiddenApi 276 @Implementation(maxSdk = M) addAssetPathNative(String path)277 final protected int addAssetPathNative(String path) { 278 return addAssetPathNative(path, false); 279 } 280 281 @HiddenApi @Implementation(minSdk = VERSION_CODES.N) addAssetPathNative(String path, boolean appAsLib)282 protected int addAssetPathNative(String path, boolean appAsLib) { 283 if (Strings.isNullOrEmpty(path)) { 284 return 0; 285 } 286 287 CppAssetManager am = assetManagerForJavaObject(); 288 if (am == null) { 289 return 0; 290 } 291 final Ref<Integer> cookie = new Ref<>(null); 292 boolean res = am.addAssetPath(new String8(path), cookie, appAsLib); 293 return (res) ? cookie.get() : 0; 294 } 295 296 @HiddenApi @Implementation getResourceIdentifier(String name, String defType, String defPackage)297 public int getResourceIdentifier(String name, String defType, String defPackage) { 298 if (Strings.isNullOrEmpty(name)) { 299 return 0; 300 } 301 CppAssetManager am = assetManagerForJavaObject(); 302 if (am == null) { 303 return 0; 304 } 305 306 int ident = am.getResources().identifierForName(name, defType, defPackage); 307 308 return ident; 309 } 310 311 @HiddenApi 312 @Implementation openAsset(String fileName, int mode)313 protected long openAsset(String fileName, int mode) throws FileNotFoundException { 314 CppAssetManager am = assetManagerForJavaObject(); 315 316 ALOGV("openAsset in %s", am); 317 318 String fileName8 = fileName; 319 if (fileName8 == null) { 320 throw new IllegalArgumentException("Empty file name"); 321 } 322 323 if (mode != AccessMode.ACCESS_UNKNOWN.mode() && mode != AccessMode.ACCESS_RANDOM.mode() 324 && mode != AccessMode.ACCESS_STREAMING.mode() && mode != AccessMode.ACCESS_BUFFER.mode()) { 325 throw new IllegalArgumentException("Bad access mode"); 326 } 327 328 Asset a = am.open(fileName8, AccessMode.fromInt(mode)); 329 330 if (a == null) { 331 throw new FileNotFoundException(fileName8); 332 } 333 334 // printf("Created Asset Stream: %p\n", a); 335 336 return Registries.NATIVE_ASSET_REGISTRY.register(a); 337 } 338 339 @HiddenApi @Implementation openAssetFd(String fileName, long[] outOffsets)340 protected ParcelFileDescriptor openAssetFd(String fileName, long[] outOffsets) throws IOException { 341 CppAssetManager am = assetManagerForJavaObject(); 342 343 ALOGV("openAssetFd in %s", am); 344 345 String fileName8 = fileName; 346 if (fileName8 == null) { 347 return null; 348 } 349 350 Asset a = am.open(fileName8, Asset.AccessMode.ACCESS_RANDOM); 351 352 if (a == null) { 353 throw new FileNotFoundException(fileName8); 354 } 355 356 return returnParcelFileDescriptor(a, outOffsets); 357 } 358 359 @HiddenApi 360 @Implementation openNonAssetNative(int cookie, String fileName, int accessMode)361 protected long openNonAssetNative(int cookie, String fileName, int accessMode) 362 throws FileNotFoundException { 363 CppAssetManager am = assetManagerForJavaObject(); 364 if (am == null) { 365 return 0; 366 } 367 ALOGV("openNonAssetNative in %s (Java object %s)\n", am, AssetManager.class); 368 String fileName8 = fileName; 369 if (fileName8 == null) { 370 return -1; 371 } 372 AccessMode mode = AccessMode.fromInt(accessMode); 373 if (mode != Asset.AccessMode.ACCESS_UNKNOWN && mode != Asset.AccessMode.ACCESS_RANDOM 374 && mode != Asset.AccessMode.ACCESS_STREAMING && mode != Asset.AccessMode.ACCESS_BUFFER) { 375 throw new IllegalArgumentException("Bad access mode"); 376 } 377 Asset a = isTruthy(cookie) 378 ? am.openNonAsset(cookie, fileName8, mode) 379 : am.openNonAsset(fileName8, mode, null); 380 if (a == null) { 381 throw new FileNotFoundException(fileName8); 382 } 383 long assetId = Registries.NATIVE_ASSET_REGISTRY.register(a); 384 // todo: something better than this [xw] 385 a.onClose = () -> destroyAsset(assetId); 386 // printf("Created Asset Stream: %p\n", a); 387 return assetId; 388 } 389 390 @HiddenApi @Implementation openNonAssetFdNative(int cookie, String fileName, long[] outOffsets)391 protected ParcelFileDescriptor openNonAssetFdNative(int cookie, 392 String fileName, long[] outOffsets) throws IOException { 393 CppAssetManager am = assetManagerForJavaObject(); 394 395 ALOGV("openNonAssetFd in %s (Java object %s)", am, this); 396 397 if (fileName == null) { 398 return null; 399 } 400 401 Asset a = isTruthy(cookie) 402 ? am.openNonAsset(cookie, fileName, Asset.AccessMode.ACCESS_RANDOM) 403 : am.openNonAsset(fileName, Asset.AccessMode.ACCESS_RANDOM, null); 404 405 if (a == null) { 406 throw new FileNotFoundException(fileName); 407 } 408 409 //printf("Created Asset Stream: %p\n", a); 410 411 return returnParcelFileDescriptor(a, outOffsets); 412 } 413 414 @HiddenApi 415 @Implementation destroyAsset(long asset)416 protected void destroyAsset(long asset) { 417 Registries.NATIVE_ASSET_REGISTRY.unregister(asset); 418 } 419 420 @HiddenApi 421 @Implementation readAssetChar(long asset)422 protected int readAssetChar(long asset) { 423 Asset a = getAsset(asset); 424 byte[] b = new byte[1]; 425 int res = a.read(b, 1); 426 return res == 1 ? b[0] & 0xff : -1; 427 } 428 429 @HiddenApi 430 @Implementation readAsset(long asset, byte[] bArray, int off, int len)431 protected int readAsset(long asset, byte[] bArray, int off, int len) throws IOException { 432 Asset a = getAsset(asset); 433 434 if (a == null || bArray == null) { 435 throw new NullPointerException("asset"); 436 } 437 438 if (len == 0) { 439 return 0; 440 } 441 442 int bLen = bArray.length; 443 if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) { 444 throw new IndexOutOfBoundsException(); 445 } 446 447 byte[] b = bArray; 448 int res = a.read(b, off, len); 449 450 if (res > 0) return res; 451 452 if (res < 0) { 453 throw new IOException(); 454 } 455 return -1; 456 } 457 458 @HiddenApi 459 @Implementation seekAsset(long asset, long offset, int whence)460 protected long seekAsset(long asset, long offset, int whence) { 461 Asset a = getAsset(asset); 462 return a.seek(offset, whence < 0 ? SEEK_SET : SEEK_CUR); 463 } 464 465 @HiddenApi 466 @Implementation getAssetLength(long asset)467 protected long getAssetLength(long asset) { 468 Asset a = getAsset(asset); 469 return a.getLength(); 470 } 471 472 @HiddenApi 473 @Implementation getAssetRemainingLength(long assetHandle)474 protected long getAssetRemainingLength(long assetHandle) { 475 Asset a = getAsset(assetHandle); 476 477 if (a == null) { 478 throw new NullPointerException("asset"); 479 } 480 481 return a.getRemainingLength(); 482 } 483 getAsset(long asset)484 private Asset getAsset(long asset) { 485 return Registries.NATIVE_ASSET_REGISTRY.getNativeObject(asset); 486 } 487 488 @HiddenApi @Implementation loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve)489 protected int loadResourceValue(int ident, short density, TypedValue outValue, boolean resolve) { 490 if (outValue == null) { 491 throw new NullPointerException("outValue"); 492 //return 0; 493 } 494 CppAssetManager am = assetManagerForJavaObject(); 495 if (am == null) { 496 return 0; 497 } 498 final ResTable res = am.getResources(); 499 500 final Ref<Res_value> value = new Ref<>(null); 501 final Ref<ResTable_config> config = new Ref<>(null); 502 final Ref<Integer> typeSpecFlags = new Ref<>(null); 503 int block = res.getResource(ident, value, false, density, typeSpecFlags, config); 504 if (kThrowOnBadId) { 505 if (block == BAD_INDEX) { 506 throw new IllegalStateException("Bad resource!"); 507 //return 0; 508 } 509 } 510 final Ref<Integer> ref = new Ref<>(ident); 511 if (resolve) { 512 block = res.resolveReference(value, block, ref, typeSpecFlags, config); 513 if (kThrowOnBadId) { 514 if (block == BAD_INDEX) { 515 throw new IllegalStateException("Bad resource!"); 516 //return 0; 517 } 518 } 519 } 520 if (block >= 0) { 521 //return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); 522 return copyValue(outValue, res, value.get(), ref.get(), block, typeSpecFlags.get(), 523 config.get()); 524 525 } 526 return block; 527 } 528 copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block, int typeSpecFlags)529 private static int copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block, 530 int typeSpecFlags) { 531 return copyValue(outValue, table, value, ref, block, typeSpecFlags, null); 532 } 533 copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block, int typeSpecFlags, ResTable_config config)534 private static int copyValue(TypedValue outValue, ResTable table, Res_value value, int ref, int block, 535 int typeSpecFlags, ResTable_config config) { 536 outValue.type = value.dataType; 537 outValue.assetCookie = table.getTableCookie(block); 538 outValue.data = value.data; 539 outValue.string = null; 540 outValue.resourceId = ref; 541 outValue.changingConfigurations = typeSpecFlags; 542 543 if (config != null) { 544 outValue.density = config.density; 545 } 546 return block; 547 } 548 getResourceBagValues(int ident, ResTable res)549 public static Map<String, Integer> getResourceBagValues(int ident, ResTable res) { 550 // Now lock down the resource object and start pulling stuff from it. 551 res.lock(); 552 553 HashMap<String, Integer> map; 554 try { 555 final Ref<bag_entry[]> entryRef = new Ref<>(null); 556 final Ref<Integer> typeSpecFlags = new Ref<>(0); 557 int entryCount = res.getBagLocked(ident, entryRef, typeSpecFlags); 558 559 map = new HashMap<>(); 560 bag_entry[] bag_entries = entryRef.get(); 561 for (int i=0; i < entryCount; i++) { 562 bag_entry entry = bag_entries[i]; 563 ResourceName resourceName = new ResourceName(); 564 if (res.getResourceName(entry.map.name.ident, true, resourceName)) { 565 map.put(resourceName.name, entry.map.value.data); 566 } 567 } 568 } finally { 569 res.unlock(); 570 } 571 572 return map; 573 } 574 575 /** Returns true if the resource was found, filling in mRetStringBlock and mRetData. */ 576 @Implementation 577 @HiddenApi loadResourceBagValue( int ident, int bagEntryId, TypedValue outValue, boolean resolve)578 protected int loadResourceBagValue( 579 int ident, int bagEntryId, TypedValue outValue, boolean resolve) { 580 CppAssetManager am = assetManagerForJavaObject(); 581 if (am == null) { 582 return 0; 583 } 584 final ResTable res = am.getResources(); 585 return loadResourceBagValueInternal(ident, bagEntryId, outValue, resolve, res); 586 } 587 getResourceBagValue(int ident, int bagEntryId, ResTable resTable)588 public static String getResourceBagValue(int ident, int bagEntryId, ResTable resTable) { 589 TypedValue outValue = new TypedValue(); 590 int blockId = ShadowArscAssetManager 591 .loadResourceBagValueInternal(ident, bagEntryId, outValue, true, resTable); 592 if (outValue.type == TypedValue.TYPE_STRING) { 593 return resTable.getTableStringBlock(blockId).stringAt(outValue.data); 594 } else { 595 return outValue.coerceToString().toString(); 596 } 597 } 598 loadResourceBagValueInternal(int ident, int bagEntryId, TypedValue outValue, boolean resolve, ResTable res)599 private static int loadResourceBagValueInternal(int ident, int bagEntryId, TypedValue outValue, 600 boolean resolve, ResTable res) { 601 // Now lock down the resource object and start pulling stuff from it. 602 res.lock(); 603 604 int block = -1; 605 final Ref<Res_value> valueRef = new Ref<>(null); 606 final Ref<bag_entry[]> entryRef = new Ref<>(null); 607 final Ref<Integer> typeSpecFlags = new Ref<>(0); 608 int entryCount = res.getBagLocked(ident, entryRef, typeSpecFlags); 609 610 bag_entry[] bag_entries = entryRef.get(); 611 for (int i=0; i < entryCount; i++) { 612 bag_entry entry = bag_entries[i]; 613 if (bagEntryId == entry.map.name.ident) { 614 block = entry.stringBlock; 615 valueRef.set(entry.map.value); 616 } 617 } 618 619 res.unlock(); 620 621 if (block < 0) { 622 return block; 623 } 624 625 final Ref<Integer> ref = new Ref<>(ident); 626 if (resolve) { 627 block = res.resolveReference(valueRef, block, ref, typeSpecFlags); 628 if (kThrowOnBadId) { 629 if (block == BAD_INDEX) { 630 throw new IllegalStateException("Bad resource!"); 631 } 632 } 633 } 634 if (block >= 0) { 635 return copyValue(outValue, res, valueRef.get(), ref.get(), block, typeSpecFlags.get()); 636 } 637 638 return block; 639 } 640 641 // /*package*/ static final int STYLE_NUM_ENTRIES = 6; 642 // /*package*/ static final int STYLE_TYPE = 0; 643 // /*package*/ static final int STYLE_DATA = 1; 644 // /*package*/ static final int STYLE_ASSET_COOKIE = 2; 645 // /*package*/ static final int STYLE_RESOURCE_ID = 3; 646 // 647 // /* Offset within typed data array for native changingConfigurations. */ 648 // static final int STYLE_CHANGING_CONFIGURATIONS = 4; 649 650 // /*package*/ static final int STYLE_DENSITY = 5; 651 652 /* lowercase hexadecimal notation. */ 653 //# define PRIx8 "x" 654 // # define PRIx16 "x" 655 // # define PRIx32 "x" 656 657 @HiddenApi @Implementation(minSdk = O, maxSdk = O_MR1) applyStyle(long themeToken, int defStyleAttr, int defStyleRes, long xmlParserToken, int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress)658 protected static void applyStyle(long themeToken, int defStyleAttr, int defStyleRes, 659 long xmlParserToken, int[] inAttrs, int length, long outValuesAddress, 660 long outIndicesAddress) { 661 ShadowVMRuntime shadowVMRuntime = Shadow.extract(VMRuntime.getRuntime()); 662 int[] outValues = (int[])shadowVMRuntime.getObjectForAddress(outValuesAddress); 663 int[] outIndices = (int[])shadowVMRuntime.getObjectForAddress(outIndicesAddress); 664 applyStyle(themeToken, defStyleAttr, defStyleRes, xmlParserToken, inAttrs, 665 outValues, outIndices); 666 } 667 668 @HiddenApi 669 @Implementation(maxSdk = N_MR1) applyStyle( long themeToken, int defStyleAttr, int defStyleRes, long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)670 protected static boolean applyStyle( 671 long themeToken, 672 int defStyleAttr, 673 int defStyleRes, 674 long xmlParserToken, 675 int[] attrs, 676 int[] outValues, 677 int[] outIndices) { 678 ResTableTheme theme = Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeToken); 679 ResXMLParser xmlParser = xmlParserToken == 0 680 ? null 681 : Registries.NATIVE_RES_XML_PARSERS.getNativeObject(xmlParserToken); 682 AttributeResolution.ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes, 683 attrs, attrs.length, outValues, outIndices); 684 return true; 685 } 686 687 @Implementation @HiddenApi resolveAttrs(long themeToken, int defStyleAttr, int defStyleRes, int[] inValues, int[] attrs, int[] outValues, int[] outIndices)688 protected static boolean resolveAttrs(long themeToken, 689 int defStyleAttr, int defStyleRes, int[] inValues, 690 int[] attrs, int[] outValues, int[] outIndices) { 691 if (themeToken == 0) { 692 throw new NullPointerException("theme token"); 693 } 694 if (attrs == null) { 695 throw new NullPointerException("attrs"); 696 } 697 if (outValues == null) { 698 throw new NullPointerException("out values"); 699 } 700 701 final int NI = attrs.length; 702 final int NV = outValues.length; 703 if (NV < (NI*STYLE_NUM_ENTRIES)) { 704 throw new IndexOutOfBoundsException("out values too small"); 705 } 706 707 int[] src = attrs; 708 // if (src == null) { 709 // return JNI_FALSE; 710 // } 711 712 int[] srcValues = inValues; 713 final int NSV = srcValues == null ? 0 : inValues.length; 714 715 int[] baseDest = outValues; 716 int destOffset = 0; 717 if (baseDest == null) { 718 return false; 719 } 720 721 int[] indices = null; 722 if (outIndices != null) { 723 if (outIndices.length > NI) { 724 indices = outIndices; 725 } 726 } 727 728 ResTableTheme theme = Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeToken); 729 730 boolean result = AttributeResolution.ResolveAttrs(theme, defStyleAttr, defStyleRes, 731 srcValues, NSV, 732 src, NI, 733 baseDest, 734 indices); 735 736 if (indices != null) { 737 // env.ReleasePrimitiveArrayCritical(outIndices, indices, 0); 738 } 739 // env.ReleasePrimitiveArrayCritical(outValues, baseDest, 0); 740 // env.ReleasePrimitiveArrayCritical(inValues, srcValues, 0); 741 // env.ReleasePrimitiveArrayCritical(attrs, src, 0); 742 743 return result; 744 } 745 746 @HiddenApi 747 @Implementation retrieveAttributes( long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices)748 protected boolean retrieveAttributes( 749 long xmlParserToken, int[] attrs, int[] outValues, int[] outIndices) { 750 if (xmlParserToken == 0) { 751 throw new NullPointerException("xmlParserToken"); 752 // return JNI_FALSE; 753 } 754 if (attrs == null) { 755 throw new NullPointerException("attrs"); 756 // return JNI_FALSE; 757 } 758 if (outValues == null) { 759 throw new NullPointerException("out values"); 760 // return JNI_FALSE; 761 } 762 763 CppAssetManager am = assetManagerForJavaObject(); 764 // if (am == null) { 765 // return JNI_FALSE; 766 // } 767 ResTable res = am.getResources(); 768 // ResXMLParser xmlParser = (ResXMLParser*)xmlParserToken; 769 ResXMLParser xmlParser = Registries.NATIVE_RES_XML_PARSERS.getNativeObject(xmlParserToken); 770 771 // const int NI = env.GetArrayLength(attrs); 772 // const int NV = env.GetArrayLength(outValues); 773 final int NI = attrs.length; 774 final int NV = outValues.length; 775 if (NV < (NI*STYLE_NUM_ENTRIES)) { 776 throw new IndexOutOfBoundsException("out values too small"); 777 // return JNI_FALSE; 778 } 779 780 // int[] src = (int[])env.GetPrimitiveArrayCritical(attrs, 0); 781 // if (src == null) { 782 // return JNI_FALSE; 783 // } 784 int[] src = attrs; 785 786 // int[] baseDest = (int[])env.GetPrimitiveArrayCritical(outValues, 0); 787 int[] baseDest = outValues; 788 if (baseDest == null) { 789 // env.ReleasePrimitiveArrayCritical(attrs, src, 0); 790 // return JNI_FALSE; 791 return false; 792 } 793 794 int[] indices = null; 795 if (outIndices != null) { 796 if (outIndices.length > NI) { 797 // indices = (int[])env.GetPrimitiveArrayCritical(outIndices, 0); 798 indices = outIndices; 799 } 800 } 801 boolean result = AttributeResolution.RetrieveAttributes(res, xmlParser, src, NI, baseDest, indices); 802 803 if (indices != null) { 804 // indices[0] = indicesIdx; 805 // env.ReleasePrimitiveArrayCritical(outIndices, indices, 0); 806 } 807 808 // env.ReleasePrimitiveArrayCritical(outValues, baseDest, 0); 809 // env.ReleasePrimitiveArrayCritical(attrs, src, 0); 810 811 return result; 812 } 813 814 @HiddenApi @Implementation getArraySize(int id)815 protected int getArraySize(int id) { 816 CppAssetManager am = assetManagerForJavaObject(); 817 if (am == null) { 818 return 0; 819 } 820 final ResTable res = am.getResources(); 821 822 res.lock(); 823 final Ref<bag_entry[]> defStyleEnt = new Ref<>(null); 824 int bagOff = res.getBagLocked(id, defStyleEnt, null); 825 res.unlock(); 826 827 return bagOff; 828 829 } 830 831 @Implementation @HiddenApi retrieveArray(int id, int[] outValues)832 protected int retrieveArray(int id, int[] outValues) { 833 if (outValues == null) { 834 throw new NullPointerException("out values"); 835 } 836 837 CppAssetManager am = assetManagerForJavaObject(); 838 if (am == null) { 839 return 0 /*JNI_FALSE */; 840 } 841 ResTable res = am.getResources(); 842 final Ref<ResTable_config> config = new Ref<>(new ResTable_config()); 843 Res_value value; 844 int block; 845 846 int NV = outValues.length; 847 848 // int[] baseDest = (int[])env->GetPrimitiveArrayCritical(outValues, 0); 849 int[] baseDest = outValues; 850 int[] dest = baseDest; 851 // if (dest == null) { 852 // throw new NullPointerException(env, "java/lang/OutOfMemoryError", ""); 853 // return JNI_FALSE; 854 // } 855 856 // Now lock down the resource object and start pulling stuff from it. 857 res.lock(); 858 859 final Ref<bag_entry[]> arrayEnt = new Ref<>(null); 860 final Ref<Integer> arrayTypeSetFlags = new Ref<>(0); 861 int bagOff = res.getBagLocked(id, arrayEnt, arrayTypeSetFlags); 862 // final ResTable::bag_entry* endArrayEnt = arrayEnt + 863 // (bagOff >= 0 ? bagOff : 0); 864 865 int destOffset = 0; 866 final Ref<Integer> typeSetFlags = new Ref<>(0); 867 while (destOffset < NV && destOffset < bagOff * STYLE_NUM_ENTRIES /*&& arrayEnt < endArrayEnt*/) { 868 bag_entry curArrayEnt = arrayEnt.get()[destOffset / STYLE_NUM_ENTRIES]; 869 870 block = curArrayEnt.stringBlock; 871 typeSetFlags.set(arrayTypeSetFlags.get()); 872 config.get().density = 0; 873 value = curArrayEnt.map.value; 874 875 final Ref<Integer> resid = new Ref<>(0); 876 if (value.dataType != DataType.NULL.code()) { 877 // Take care of resolving the found resource to its final value. 878 //printf("Resolving attribute reference\n"); 879 final Ref<Res_value> resValueRef = new Ref<>(value); 880 int newBlock = res.resolveReference(resValueRef, block, resid, 881 typeSetFlags, config); 882 value = resValueRef.get(); 883 if (kThrowOnBadId) { 884 if (newBlock == BAD_INDEX) { 885 throw new IllegalStateException("Bad resource!"); 886 } 887 } 888 if (newBlock >= 0) block = newBlock; 889 } 890 891 // Deal with the special @null value -- it turns back to TYPE_NULL. 892 if (value.dataType == DataType.REFERENCE.code() && value.data == 0) { 893 value = Res_value.NULL_VALUE; 894 } 895 896 //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); 897 898 // Write the final value back to Java. 899 dest[destOffset + STYLE_TYPE] = value.dataType; 900 dest[destOffset + STYLE_DATA] = value.data; 901 dest[destOffset + STYLE_ASSET_COOKIE] = res.getTableCookie(block); 902 dest[destOffset + STYLE_RESOURCE_ID] = resid.get(); 903 dest[destOffset + STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags.get(); 904 dest[destOffset + STYLE_DENSITY] = config.get().density; 905 // dest += STYLE_NUM_ENTRIES; 906 destOffset+= STYLE_NUM_ENTRIES; 907 // arrayEnt++; 908 } 909 910 destOffset /= STYLE_NUM_ENTRIES; 911 912 res.unlock(); 913 914 // env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); 915 916 return destOffset; 917 918 } 919 920 @HiddenApi 921 @Implementation getNativeStringBlock(int block)922 protected long getNativeStringBlock(int block) { 923 CppAssetManager am = assetManagerForJavaObject(); 924 if (am == null) { 925 return 0; 926 } 927 928 return am.getResources().getTableStringBlock(block).getNativePtr(); 929 } 930 931 @Implementation getAssignedPackageIdentifiers()932 protected SparseArray<String> getAssignedPackageIdentifiers() { 933 CppAssetManager am = assetManagerForJavaObject(); 934 final ResTable res = am.getResources(); 935 936 SparseArray<String> sparseArray = new SparseArray<>(); 937 final int N = res.getBasePackageCount(); 938 for (int i = 0; i < N; i++) { 939 final String name = res.getBasePackageName(i); 940 sparseArray.put(res.getBasePackageId(i), name); 941 } 942 return sparseArray; 943 } 944 945 @HiddenApi 946 @Implementation newTheme()947 protected long newTheme() { 948 CppAssetManager am = assetManagerForJavaObject(); 949 if (am == null) { 950 return 0; 951 } 952 ResTableTheme theme = new ResTableTheme(am.getResources()); 953 return Registries.NATIVE_THEME_REGISTRY.register(theme); 954 } 955 956 @HiddenApi 957 @Implementation deleteTheme(long theme)958 protected void deleteTheme(long theme) { 959 Registries.NATIVE_THEME_REGISTRY.unregister(theme); 960 } 961 962 @HiddenApi 963 @Implementation(maxSdk = O_MR1) applyThemeStyle(long themePtr, int styleRes, boolean force)964 public static void applyThemeStyle(long themePtr, int styleRes, boolean force) { 965 Registries.NATIVE_THEME_REGISTRY.getNativeObject(themePtr).applyStyle(styleRes, force); 966 } 967 968 @HiddenApi 969 @Implementation(maxSdk = O_MR1) copyTheme(long destPtr, long sourcePtr)970 public static void copyTheme(long destPtr, long sourcePtr) { 971 ResTableTheme dest = Registries.NATIVE_THEME_REGISTRY.getNativeObject(destPtr); 972 ResTableTheme src = Registries.NATIVE_THEME_REGISTRY.getNativeObject(sourcePtr); 973 dest.setTo(src); 974 } 975 976 @HiddenApi 977 @Implementation loadThemeAttributeValue( long themeHandle, int ident, TypedValue outValue, boolean resolve)978 protected static int loadThemeAttributeValue( 979 long themeHandle, int ident, TypedValue outValue, boolean resolve) { 980 ResTableTheme theme = Preconditions.checkNotNull(Registries.NATIVE_THEME_REGISTRY.getNativeObject(themeHandle)); 981 ResTable res = theme.getResTable(); 982 983 final Ref<Res_value> value = new Ref<>(null); 984 // XXX value could be different in different configs! 985 final Ref<Integer> typeSpecFlags = new Ref<>(0); 986 int block = theme.GetAttribute(ident, value, typeSpecFlags); 987 final Ref<Integer> ref = new Ref<>(0); 988 if (resolve) { 989 block = res.resolveReference(value, block, ref, typeSpecFlags); 990 if (kThrowOnBadId) { 991 if (block == BAD_INDEX) { 992 throw new IllegalStateException("Bad resource!"); 993 } 994 } 995 } 996 return block >= 0 ? copyValue(outValue, res, value.get(), ref.get(), block, typeSpecFlags.get(), null) : block; 997 } 998 999 // /*package*/@HiddenApi @Implementation public static final @NativeConfig 1000 // int getThemeChangingConfigurations(long theme); 1001 1002 @HiddenApi 1003 @Implementation openXmlAssetNative(int cookie, String fileName)1004 protected long openXmlAssetNative(int cookie, String fileName) throws FileNotFoundException { 1005 CppAssetManager am = assetManagerForJavaObject(); 1006 if (am == null) { 1007 return 0; 1008 } 1009 1010 ALOGV("openXmlAsset in %s (Java object %s)\n", am, ShadowArscAssetManager.class); 1011 1012 String fileName8 = fileName; 1013 if (fileName8 == null) { 1014 return 0; 1015 } 1016 1017 int assetCookie = cookie; 1018 Asset a; 1019 if (isTruthy(assetCookie)) { 1020 a = am.openNonAsset(assetCookie, fileName8, AccessMode.ACCESS_BUFFER); 1021 } else { 1022 final Ref<Integer> assetCookieRef = new Ref<>(assetCookie); 1023 a = am.openNonAsset(fileName8, AccessMode.ACCESS_BUFFER, assetCookieRef); 1024 assetCookie = assetCookieRef.get(); 1025 } 1026 1027 if (a == null) { 1028 throw new FileNotFoundException(fileName8); 1029 } 1030 1031 final DynamicRefTable dynamicRefTable = 1032 am.getResources().getDynamicRefTableForCookie(assetCookie); 1033 ResXMLTree block = new ResXMLTree(dynamicRefTable); 1034 int err = block.setTo(a.getBuffer(true), (int) a.getLength(), true); 1035 a.close(); 1036 // delete a; 1037 1038 if (err != NO_ERROR) { 1039 throw new FileNotFoundException("Corrupt XML binary file"); 1040 } 1041 1042 return Registries.NATIVE_RES_XML_TREES.register(block); 1043 } 1044 1045 @HiddenApi 1046 @Implementation getArrayStringResource(int arrayResId)1047 protected String[] getArrayStringResource(int arrayResId) { 1048 CppAssetManager am = assetManagerForJavaObject(); 1049 if (am == null) { 1050 return null; 1051 } 1052 final ResTable res = am.getResources(); 1053 1054 final Ref<bag_entry[]> startOfBag = new Ref<>(null); 1055 final int N = res.lockBag(arrayResId, startOfBag); 1056 if (N < 0) { 1057 return null; 1058 } 1059 1060 String[] array = new String[N]; 1061 1062 final Ref<Res_value> valueRef = new Ref<>(null); 1063 final bag_entry[] bag = startOfBag.get(); 1064 int strLen = 0; 1065 for (int i=0; ((int)i)<N; i++) { 1066 valueRef.set(bag[i].map.value); 1067 String str = null; 1068 1069 // Take care of resolving the found resource to its final value. 1070 int block = res.resolveReference(valueRef, bag[i].stringBlock, null); 1071 if (kThrowOnBadId) { 1072 if (block == BAD_INDEX) { 1073 throw new IllegalStateException("Bad resource!"); 1074 } 1075 } 1076 if (valueRef.get().dataType == DataType.STRING.code()) { 1077 final ResStringPool pool = res.getTableStringBlock(block); 1078 str = pool.stringAt(valueRef.get().data); 1079 1080 // assume we can skip utf8 vs utf 16 handling 1081 1082 // final char* str8 = pool.string8At(value.data, &strLen); 1083 // if (str8 != NULL) { 1084 // str = env.NewStringUTF(str8); 1085 // } else { 1086 // final char16_t* str16 = pool.stringAt(value.data, &strLen); 1087 // str = env.NewString(reinterpret_cast<final jchar*>(str16), 1088 // strLen); 1089 // } 1090 // 1091 // // If one of our NewString{UTF} calls failed due to memory, an 1092 // // exception will be pending. 1093 // if (env.ExceptionCheck()) { 1094 // res.unlockBag(startOfBag); 1095 // return NULL; 1096 // } 1097 if (str == null) { 1098 res.unlockBag(startOfBag); 1099 return null; 1100 } 1101 1102 array[i] = str; 1103 1104 // str is not NULL at that point, otherwise ExceptionCheck would have been true. 1105 // If we have a large amount of strings in our array, we might 1106 // overflow the local reference table of the VM. 1107 // env.DeleteLocalRef(str); 1108 } 1109 } 1110 res.unlockBag(startOfBag); 1111 return array; 1112 } 1113 1114 @HiddenApi 1115 @Implementation getArrayStringInfo(int arrayResId)1116 protected int[] getArrayStringInfo(int arrayResId) { 1117 CppAssetManager am = assetManagerForJavaObject(); 1118 ResTable res = am.getResources(); 1119 1120 final Ref<bag_entry[]> startOfBag = new Ref<>(null); 1121 final int N = res.lockBag(arrayResId, startOfBag); 1122 if (N < 0) { 1123 return null; 1124 } 1125 1126 int[] array = new int[N * 2]; 1127 1128 final Ref<Res_value> value = new Ref<>(null); 1129 bag_entry[] bag = startOfBag.get(); 1130 for (int i = 0, j = 0; i<N; i++) { 1131 int stringIndex = -1; 1132 int stringBlock = 0; 1133 value.set(bag[i].map.value); 1134 1135 // Take care of resolving the found resource to its final value. 1136 stringBlock = res.resolveReference(value, bag[i].stringBlock, null); 1137 if (value.get().dataType == DataType.STRING.code()) { 1138 stringIndex = value.get().data; 1139 } 1140 1141 if (kThrowOnBadId) { 1142 if (stringBlock == BAD_INDEX) { 1143 throw new IllegalStateException("Bad resource!"); 1144 } 1145 } 1146 1147 //todo: It might be faster to allocate a C array to contain 1148 // the blocknums and indices, put them in there and then 1149 // do just one SetIntArrayRegion() 1150 //env->SetIntArrayRegion(array, j, 1, &stringBlock); 1151 array[j] = stringBlock; 1152 //env->SetIntArrayRegion(array, j + 1, 1, &stringIndex); 1153 array[j+1] = stringIndex; 1154 j += 2; 1155 } 1156 res.unlockBag(startOfBag); 1157 return array; 1158 } 1159 1160 @HiddenApi @Implementation getArrayIntResource(int arrayResId)1161 public int[] getArrayIntResource(int arrayResId) { 1162 CppAssetManager am = assetManagerForJavaObject(); 1163 if (am == null) { 1164 return null; 1165 } 1166 final ResTable res = am.getResources(); 1167 1168 // final ResTable::bag_entry* startOfBag; 1169 final Ref<bag_entry[]> startOfBag = new Ref<>(null); 1170 final int N = res.lockBag(arrayResId, startOfBag); 1171 if (N < 0) { 1172 return null; 1173 } 1174 1175 int[] array = new int[N]; 1176 if (array == null) { 1177 res.unlockBag(startOfBag); 1178 return null; 1179 } 1180 1181 final Ref<Res_value> valueRef = new Ref<>(null); 1182 bag_entry[] bag = startOfBag.get(); 1183 for (int i=0; i<N; i++) { 1184 valueRef.set(bag[i].map.value); 1185 1186 // Take care of resolving the found resource to its final value. 1187 int block = res.resolveReference(valueRef, bag[i].stringBlock, null, null, null); 1188 if (kThrowOnBadId) { 1189 if (block == BAD_INDEX) { 1190 res.unlockBag(startOfBag); // seems like this is missing from android_util_AssetManager.cpp? 1191 throw new IllegalStateException("Bad resource!"); 1192 // return array; 1193 } 1194 } 1195 Res_value value = valueRef.get(); 1196 if (value.dataType >= DataType.TYPE_FIRST_INT 1197 && value.dataType <= DataType.TYPE_LAST_INT) { 1198 int intVal = value.data; 1199 // env->SetIntArrayRegion(array, i, 1, &intVal); 1200 array[i] = intVal; 1201 } 1202 } 1203 res.unlockBag(startOfBag); 1204 return array; 1205 } 1206 1207 private static CppAssetManager systemCppAssetManager; 1208 1209 @HiddenApi 1210 @Implementation init(boolean isSystem)1211 protected void init(boolean isSystem) { 1212 // if (isSystem) { 1213 // verifySystemIdmaps(); 1214 // } 1215 1216 Path androidFrameworkJarPath = RuntimeEnvironment.getAndroidFrameworkJarPath(); 1217 Preconditions.checkNotNull(androidFrameworkJarPath); 1218 1219 if (isSystem) { 1220 synchronized (ShadowArscAssetManager.class) { 1221 if (systemCppAssetManager == null) { 1222 systemCppAssetManager = new CppAssetManager(); 1223 systemCppAssetManager.addDefaultAssets(androidFrameworkJarPath); 1224 } 1225 } 1226 this.cppAssetManager = systemCppAssetManager; 1227 } else { 1228 this.cppAssetManager = new CppAssetManager(); 1229 cppAssetManager.addDefaultAssets(androidFrameworkJarPath); 1230 } 1231 1232 ALOGV("Created AssetManager %s for Java object %s\n", cppAssetManager, 1233 ShadowArscAssetManager.class); 1234 } 1235 1236 @VisibleForTesting getConfiguration()1237 ResTable_config getConfiguration() { 1238 final Ref<ResTable_config> config = new Ref<>(new ResTable_config()); 1239 assetManagerForJavaObject().getConfiguration(config); 1240 return config.get(); 1241 } 1242 1243 // private native final void destroy(); 1244 1245 // @HiddenApi 1246 // @Implementation 1247 // public int addOverlayPathNative(String idmapPath) { 1248 // if (Strings.isNullOrEmpty(idmapPath)) { 1249 // return 0; 1250 // } 1251 // 1252 // CppAssetManager am = assetManagerForJavaObject(); 1253 // if (am == null) { 1254 // return 0; 1255 // } 1256 // final Ref<Integer> cookie = new Ref<>(null); 1257 // boolean res = am.addOverlayPath(new String8(idmapPath), cookie); 1258 // return (res) ? cookie.get() : 0; 1259 // } 1260 1261 @HiddenApi @Implementation getStringBlockCount()1262 protected int getStringBlockCount() { 1263 CppAssetManager am = assetManagerForJavaObject(); 1264 if (am == null) { 1265 return 0; 1266 } 1267 return am.getResources().getTableCount(); 1268 } 1269 1270 assetManagerForJavaObject()1271 synchronized private CppAssetManager assetManagerForJavaObject() { 1272 if (cppAssetManager == null) { 1273 throw new NullPointerException(); 1274 } 1275 return cppAssetManager; 1276 } 1277 returnParcelFileDescriptor(Asset a, long[] outOffsets)1278 static ParcelFileDescriptor returnParcelFileDescriptor(Asset a, long[] outOffsets) 1279 throws FileNotFoundException { 1280 final Ref<Long> startOffset = new Ref<Long>(-1L); 1281 final Ref<Long> length = new Ref<Long>(-1L);; 1282 FileDescriptor fd = a.openFileDescriptor(startOffset, length); 1283 1284 if (fd == null) { 1285 throw new FileNotFoundException( 1286 "This file can not be opened as a file descriptor; it is probably compressed"); 1287 } 1288 1289 long[] offsets = outOffsets; 1290 if (offsets == null) { 1291 // fd.close(); 1292 return null; 1293 } 1294 1295 offsets[0] = startOffset.get(); 1296 offsets[1] = length.get(); 1297 1298 // FileDescriptor fileDesc = jniCreateFileDescriptor(fd); 1299 // if (fileDesc == null) { 1300 // close(fd); 1301 // return null; 1302 // } 1303 1304 // TODO: consider doing this 1305 // return new ParcelFileDescriptor(fileDesc); 1306 return ParcelFileDescriptor.open(a.getFile(), ParcelFileDescriptor.MODE_READ_ONLY); 1307 } 1308 1309 @Override getAllAssetDirs()1310 Collection<Path> getAllAssetDirs() { 1311 ArrayList<Path> paths = new ArrayList<>(); 1312 for (AssetPath assetPath : cppAssetManager.getAssetPaths()) { 1313 if (Files.isRegularFile(assetPath.file)) { 1314 paths.add(Fs.forJar(assetPath.file).getPath("assets")); 1315 } else { 1316 paths.add(assetPath.file); 1317 } 1318 } 1319 return paths; 1320 } 1321 1322 @VisibleForTesting 1323 @Override getNativePtr()1324 long getNativePtr() { 1325 return reflector(_AssetManager_.class, realObject).getNativePtr(); 1326 } 1327 1328 @Override getAssetPaths()1329 List<AssetPath> getAssetPaths() { 1330 return assetManagerForJavaObject().getAssetPaths(); 1331 } 1332 1333 } 1334