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