1 /*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define ATRACE_TAG ATRACE_TAG_RESOURCES
18
19 #include <mutex>
20
21 #include "signal.h"
22
23 #include "android-base/logging.h"
24 #include "android-base/macros.h"
25 #include "android-base/stringprintf.h"
26 #include "android-base/unique_fd.h"
27 #include "androidfw/ApkAssets.h"
28 #include "utils/misc.h"
29 #include "utils/Trace.h"
30
31 #include "android_content_res_ApkAssets.h"
32 #include "core_jni_helpers.h"
33 #include "jni.h"
34 #include "nativehelper/ScopedUtfChars.h"
35
36 using ::android::base::unique_fd;
37
38 namespace android {
39
40 static struct overlayableinfo_offsets_t {
41 jclass classObject;
42 jmethodID constructor;
43 } gOverlayableInfoOffsets;
44
45 static struct assetfiledescriptor_offsets_t {
46 jfieldID mFd;
47 jfieldID mStartOffset;
48 jfieldID mLength;
49 } gAssetFileDescriptorOffsets;
50
51 static struct assetsprovider_offsets_t {
52 jclass classObject;
53 jmethodID loadAssetFd;
54 jmethodID toString;
55 } gAssetsProviderOffsets;
56
57 static struct {
58 jmethodID detachFd;
59 } gParcelFileDescriptorOffsets;
60
61 // Keep in sync with f/b/android/content/res/ApkAssets.java
62 using format_type_t = jint;
63 enum : format_type_t {
64 // The path used to load the apk assets represents an APK file.
65 FORMAT_APK = 0,
66
67 // The path used to load the apk assets represents an idmap file.
68 FORMAT_IDMAP = 1,
69
70 // The path used to load the apk assets represents an resources.arsc file.
71 FORMAT_ARSC = 2,
72
73 // The path used to load the apk assets represents the a directory.
74 FORMAT_DIRECTORY = 3,
75 };
76
ApkAssetsFromLong(jlong ptr)77 Guarded<std::unique_ptr<const ApkAssets>>& ApkAssetsFromLong(jlong ptr) {
78 return *reinterpret_cast<Guarded<std::unique_ptr<const ApkAssets>>*>(ptr);
79 }
80
CreateGuardedApkAssets(std::unique_ptr<const ApkAssets> assets)81 static jlong CreateGuardedApkAssets(std::unique_ptr<const ApkAssets> assets) {
82 auto guarded_assets = new Guarded<std::unique_ptr<const ApkAssets>>(std::move(assets));
83 return reinterpret_cast<jlong>(guarded_assets);
84 }
85
DeleteGuardedApkAssets(Guarded<std::unique_ptr<const ApkAssets>> & apk_assets)86 static void DeleteGuardedApkAssets(Guarded<std::unique_ptr<const ApkAssets>>& apk_assets) {
87 delete &apk_assets;
88 }
89
90 class LoaderAssetsProvider : public AssetsProvider {
91 public:
Create(JNIEnv * env,jobject assets_provider)92 static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
93 return (!assets_provider) ? EmptyAssetsProvider::Create()
94 : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
95 env, assets_provider));
96 }
97
ForEachFile(const std::string &,const std::function<void (const StringPiece &,FileType)> &) const98 bool ForEachFile(const std::string& /* root_path */,
99 const std::function<void(const StringPiece&, FileType)>& /* f */) const {
100 return true;
101 }
102
GetPath() const103 std::optional<std::string_view> GetPath() const override {
104 return {};
105 }
106
GetDebugName() const107 const std::string& GetDebugName() const override {
108 return debug_name_;
109 }
110
IsUpToDate() const111 bool IsUpToDate() const override {
112 return true;
113 }
114
~LoaderAssetsProvider()115 ~LoaderAssetsProvider() override {
116 const auto env = AndroidRuntime::getJNIEnv();
117 CHECK(env != nullptr) << "Current thread not attached to a Java VM."
118 << " Failed to close LoaderAssetsProvider.";
119 env->DeleteGlobalRef(assets_provider_);
120 }
121
122 protected:
OpenInternal(const std::string & path,Asset::AccessMode mode,bool * file_exists) const123 std::unique_ptr<Asset> OpenInternal(const std::string& path,
124 Asset::AccessMode mode,
125 bool* file_exists) const override {
126 const auto env = AndroidRuntime::getJNIEnv();
127 CHECK(env != nullptr) << "Current thread not attached to a Java VM."
128 << " ResourcesProvider assets cannot be retrieved on current thread.";
129
130 jstring java_string = env->NewStringUTF(path.c_str());
131 if (env->ExceptionCheck()) {
132 env->ExceptionDescribe();
133 env->ExceptionClear();
134 return nullptr;
135 }
136
137 // Check if the AssetsProvider provides a value for the path.
138 jobject asset_fd = env->CallObjectMethod(assets_provider_,
139 gAssetsProviderOffsets.loadAssetFd,
140 java_string, static_cast<jint>(mode));
141 env->DeleteLocalRef(java_string);
142 if (env->ExceptionCheck()) {
143 env->ExceptionDescribe();
144 env->ExceptionClear();
145 return nullptr;
146 }
147
148 if (!asset_fd) {
149 if (file_exists) {
150 *file_exists = false;
151 }
152 return nullptr;
153 }
154
155 const jlong mOffset = env->GetLongField(asset_fd, gAssetFileDescriptorOffsets.mStartOffset);
156 const jlong mLength = env->GetLongField(asset_fd, gAssetFileDescriptorOffsets.mLength);
157 jobject mFd = env->GetObjectField(asset_fd, gAssetFileDescriptorOffsets.mFd);
158 env->DeleteLocalRef(asset_fd);
159
160 if (!mFd) {
161 jniThrowException(env, "java/lang/NullPointerException", nullptr);
162 env->ExceptionDescribe();
163 env->ExceptionClear();
164 return nullptr;
165 }
166
167 // Gain ownership of the file descriptor.
168 const jint fd = env->CallIntMethod(mFd, gParcelFileDescriptorOffsets.detachFd);
169 env->DeleteLocalRef(mFd);
170 if (env->ExceptionCheck()) {
171 env->ExceptionDescribe();
172 env->ExceptionClear();
173 return nullptr;
174 }
175
176 if (file_exists) {
177 *file_exists = true;
178 }
179
180 return AssetsProvider::CreateAssetFromFd(base::unique_fd(fd),
181 nullptr /* path */,
182 static_cast<off64_t>(mOffset),
183 static_cast<off64_t>(mLength));
184 }
185
186 private:
187 DISALLOW_COPY_AND_ASSIGN(LoaderAssetsProvider);
188
LoaderAssetsProvider(JNIEnv * env,jobject assets_provider)189 explicit LoaderAssetsProvider(JNIEnv* env, jobject assets_provider) {
190 assets_provider_ = env->NewGlobalRef(assets_provider);
191 auto string_result = static_cast<jstring>(env->CallObjectMethod(
192 assets_provider_, gAssetsProviderOffsets.toString));
193 ScopedUtfChars str(env, string_result);
194 debug_name_ = std::string(str.c_str(), str.size());
195 }
196
197 // The global reference to the AssetsProvider
198 jobject assets_provider_;
199 std::string debug_name_;
200 };
201
NativeLoad(JNIEnv * env,jclass,const format_type_t format,jstring java_path,const jint property_flags,jobject assets_provider)202 static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
203 jstring java_path, const jint property_flags, jobject assets_provider) {
204 ScopedUtfChars path(env, java_path);
205 if (path.c_str() == nullptr) {
206 return 0;
207 }
208
209 ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
210
211 auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
212 std::unique_ptr<ApkAssets> apk_assets;
213 switch (format) {
214 case FORMAT_APK: {
215 auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
216 ZipAssetsProvider::Create(path.c_str(),
217 property_flags));
218 apk_assets = ApkAssets::Load(std::move(assets), property_flags);
219 break;
220 }
221 case FORMAT_IDMAP:
222 apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
223 break;
224 case FORMAT_ARSC:
225 apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
226 std::move(loader_assets),
227 property_flags);
228 break;
229 case FORMAT_DIRECTORY: {
230 auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
231 DirectoryAssetsProvider::Create(path.c_str()));
232 apk_assets = ApkAssets::Load(std::move(assets), property_flags);
233 break;
234 }
235 default:
236 const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
237 jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
238 return 0;
239 }
240
241 if (apk_assets == nullptr) {
242 const std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
243 jniThrowException(env, "java/io/IOException", error_msg.c_str());
244 return 0;
245 }
246 return CreateGuardedApkAssets(std::move(apk_assets));
247 }
248
NativeLoadFromFd(JNIEnv * env,jclass,const format_type_t format,jobject file_descriptor,jstring friendly_name,const jint property_flags,jobject assets_provider)249 static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
250 jobject file_descriptor, jstring friendly_name,
251 const jint property_flags, jobject assets_provider) {
252 ScopedUtfChars friendly_name_utf8(env, friendly_name);
253 if (friendly_name_utf8.c_str() == nullptr) {
254 return 0;
255 }
256
257 ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str());
258
259 int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
260 if (fd < 0) {
261 jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
262 return 0;
263 }
264
265 unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
266 if (dup_fd < 0) {
267 jniThrowIOException(env, errno);
268 return 0;
269 }
270
271 auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
272 std::unique_ptr<const ApkAssets> apk_assets;
273 switch (format) {
274 case FORMAT_APK: {
275 auto assets =
276 MultiAssetsProvider::Create(std::move(loader_assets),
277 ZipAssetsProvider::Create(std::move(dup_fd),
278 friendly_name_utf8.c_str(),
279 property_flags));
280 apk_assets = ApkAssets::Load(std::move(assets), property_flags);
281 break;
282 }
283 case FORMAT_ARSC:
284 apk_assets = ApkAssets::LoadTable(
285 AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
286 std::move(loader_assets), property_flags);
287 break;
288 default:
289 const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
290 jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
291 return 0;
292 }
293
294 if (apk_assets == nullptr) {
295 std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
296 friendly_name_utf8.c_str(), fd);
297 jniThrowException(env, "java/io/IOException", error_msg.c_str());
298 return 0;
299 }
300 return CreateGuardedApkAssets(std::move(apk_assets));
301 }
302
NativeLoadFromFdOffset(JNIEnv * env,jclass,const format_type_t format,jobject file_descriptor,jstring friendly_name,const jlong offset,const jlong length,const jint property_flags,jobject assets_provider)303 static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
304 jobject file_descriptor, jstring friendly_name,
305 const jlong offset, const jlong length,
306 const jint property_flags, jobject assets_provider) {
307 ScopedUtfChars friendly_name_utf8(env, friendly_name);
308 if (friendly_name_utf8.c_str() == nullptr) {
309 return 0;
310 }
311
312 ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str());
313
314 if (offset < 0) {
315 jniThrowException(env, "java/lang/IllegalArgumentException",
316 "offset cannot be negative");
317 return 0;
318 }
319
320 if (length < 0) {
321 jniThrowException(env, "java/lang/IllegalArgumentException",
322 "length cannot be negative");
323 return 0;
324 }
325
326 int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
327 if (fd < 0) {
328 jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
329 return 0;
330 }
331
332 unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
333 if (dup_fd < 0) {
334 jniThrowIOException(env, errno);
335 return 0;
336 }
337
338 auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
339 std::unique_ptr<const ApkAssets> apk_assets;
340 switch (format) {
341 case FORMAT_APK: {
342 auto assets =
343 MultiAssetsProvider::Create(std::move(loader_assets),
344 ZipAssetsProvider::Create(std::move(dup_fd),
345 friendly_name_utf8.c_str(),
346 property_flags,
347 static_cast<off64_t>(offset),
348 static_cast<off64_t>(
349 length)));
350 apk_assets = ApkAssets::Load(std::move(assets), property_flags);
351 break;
352 }
353 case FORMAT_ARSC:
354 apk_assets = ApkAssets::LoadTable(
355 AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
356 static_cast<off64_t>(offset),
357 static_cast<off64_t>(length)),
358 std::move(loader_assets), property_flags);
359 break;
360 default:
361 const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
362 jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
363 return 0;
364 }
365
366 if (apk_assets == nullptr) {
367 std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
368 friendly_name_utf8.c_str(), fd);
369 jniThrowException(env, "java/io/IOException", error_msg.c_str());
370 return 0;
371 }
372 return CreateGuardedApkAssets(std::move(apk_assets));
373 }
374
NativeLoadEmpty(JNIEnv * env,jclass,jint flags,jobject assets_provider)375 static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
376 auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
377 return CreateGuardedApkAssets(std::move(apk_assets));
378 }
379
NativeDestroy(JNIEnv *,jclass,jlong ptr)380 static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
381 DeleteGuardedApkAssets(ApkAssetsFromLong(ptr));
382 }
383
NativeGetAssetPath(JNIEnv * env,jclass,jlong ptr)384 static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
385 auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
386 auto apk_assets = scoped_apk_assets->get();
387 if (auto path = apk_assets->GetPath()) {
388 return env->NewStringUTF(path->data());
389 }
390 return nullptr;
391 }
392
NativeGetDebugName(JNIEnv * env,jclass,jlong ptr)393 static jstring NativeGetDebugName(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
394 auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
395 auto apk_assets = scoped_apk_assets->get();
396 return env->NewStringUTF(apk_assets->GetDebugName().c_str());
397 }
398
NativeGetStringBlock(JNIEnv *,jclass,jlong ptr)399 static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
400 auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
401 auto apk_assets = scoped_apk_assets->get();
402 return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
403 }
404
NativeIsUpToDate(JNIEnv *,jclass,jlong ptr)405 static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
406 auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
407 auto apk_assets = scoped_apk_assets->get();
408 return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
409 }
410
NativeOpenXml(JNIEnv * env,jclass,jlong ptr,jstring file_name)411 static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
412 ScopedUtfChars path_utf8(env, file_name);
413 if (path_utf8.c_str() == nullptr) {
414 return 0;
415 }
416
417 auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
418 auto apk_assets = scoped_apk_assets->get();
419 std::unique_ptr<Asset> asset = apk_assets->GetAssetsProvider()->Open(
420 path_utf8.c_str(),Asset::AccessMode::ACCESS_RANDOM);
421 if (asset == nullptr) {
422 jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
423 return 0;
424 }
425
426 const auto buffer = asset->getIncFsBuffer(true /* aligned */);
427 const size_t length = asset->getLength();
428 if (!buffer.convert<uint8_t>().verify(length)) {
429 jniThrowException(env, "java/io/FileNotFoundException",
430 "File not fully present due to incremental installation");
431 return 0;
432 }
433
434 // DynamicRefTable is only needed when looking up resource references. Opening an XML file
435 // directly from an ApkAssets has no notion of proper resource references.
436 auto xml_tree = util::make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/);
437 status_t err = xml_tree->setTo(buffer.unsafe_ptr(), length, true);
438 if (err != NO_ERROR) {
439 jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
440 return 0;
441 }
442 return reinterpret_cast<jlong>(xml_tree.release());
443 }
444
NativeGetOverlayableInfo(JNIEnv * env,jclass,jlong ptr,jstring overlayable_name)445 static jobject NativeGetOverlayableInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr,
446 jstring overlayable_name) {
447 auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
448 auto apk_assets = scoped_apk_assets->get();
449
450 const auto& packages = apk_assets->GetLoadedArsc()->GetPackages();
451 if (packages.empty()) {
452 jniThrowException(env, "java/io/IOException", "Error reading overlayable from APK");
453 return 0;
454 }
455
456 // TODO(b/119899133): Convert this to a search for the info rather than assuming it's at index 0
457 const auto& overlayable_map = packages[0]->GetOverlayableMap();
458 if (overlayable_map.empty()) {
459 return nullptr;
460 }
461
462 auto overlayable_name_native = std::string(env->GetStringUTFChars(overlayable_name, NULL));
463 auto actor = overlayable_map.find(overlayable_name_native);
464 if (actor == overlayable_map.end()) {
465 return nullptr;
466 }
467
468 jstring actor_string = env->NewStringUTF(actor->second.c_str());
469 if (env->ExceptionCheck() || actor_string == nullptr) {
470 jniThrowException(env, "java/io/IOException", "Error reading overlayable from APK");
471 return 0;
472 }
473
474 return env->NewObject(
475 gOverlayableInfoOffsets.classObject,
476 gOverlayableInfoOffsets.constructor,
477 overlayable_name,
478 actor_string
479 );
480 }
481
NativeDefinesOverlayable(JNIEnv * env,jclass,jlong ptr)482 static jboolean NativeDefinesOverlayable(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
483 auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
484 auto apk_assets = scoped_apk_assets->get();
485
486 const auto& packages = apk_assets->GetLoadedArsc()->GetPackages();
487 if (packages.empty()) {
488 // Must throw to prevent bypass by returning false
489 jniThrowException(env, "java/io/IOException", "Error reading overlayable from APK");
490 return 0;
491 }
492
493 const auto& overlayable_infos = packages[0]->GetOverlayableMap();
494 return overlayable_infos.empty() ? JNI_FALSE : JNI_TRUE;
495 }
496
497 // JNI registration.
498 static const JNINativeMethod gApkAssetsMethods[] = {
499 {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
500 (void*)NativeLoad},
501 {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty},
502 {"nativeLoadFd",
503 "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
504 (void*)NativeLoadFromFd},
505 {"nativeLoadFdOffsets",
506 "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J",
507 (void*)NativeLoadFromFdOffset},
508 {"nativeDestroy", "(J)V", (void*)NativeDestroy},
509 {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
510 {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
511 {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
512 {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
513 {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
514 {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
515 (void*)NativeGetOverlayableInfo},
516 {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
517 };
518
register_android_content_res_ApkAssets(JNIEnv * env)519 int register_android_content_res_ApkAssets(JNIEnv* env) {
520 jclass overlayableInfoClass = FindClassOrDie(env, "android/content/om/OverlayableInfo");
521 gOverlayableInfoOffsets.classObject = MakeGlobalRefOrDie(env, overlayableInfoClass);
522 gOverlayableInfoOffsets.constructor = GetMethodIDOrDie(env, gOverlayableInfoOffsets.classObject,
523 "<init>", "(Ljava/lang/String;Ljava/lang/String;)V");
524
525 jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
526 gAssetFileDescriptorOffsets.mFd =
527 GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;");
528 gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
529 gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
530
531 jclass assetsProvider = FindClassOrDie(env, "android/content/res/loader/AssetsProvider");
532 gAssetsProviderOffsets.classObject = MakeGlobalRefOrDie(env, assetsProvider);
533 gAssetsProviderOffsets.loadAssetFd = GetMethodIDOrDie(
534 env, gAssetsProviderOffsets.classObject, "loadAssetFd",
535 "(Ljava/lang/String;I)Landroid/content/res/AssetFileDescriptor;");
536 gAssetsProviderOffsets.toString = GetMethodIDOrDie(
537 env, gAssetsProviderOffsets.classObject, "toString", "()Ljava/lang/String;");
538
539 jclass parcelFd = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
540 gParcelFileDescriptorOffsets.detachFd = GetMethodIDOrDie(env, parcelFd, "detachFd", "()I");
541 return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
542 arraysize(gApkAssetsMethods));
543 }
544
545 } // namespace android
546