#include "file_source.hpp" #include "attach_env.hpp" #include "mapbox.hpp" #include #include #include #include #include #include #include #include "asset_manager_file_source.hpp" namespace mbgl { namespace android { // FileSource // FileSource::FileSource(jni::JNIEnv& _env, const jni::String& accessToken, const jni::String& _cachePath) { std::string path = jni::Make(_env, _cachePath); mapbox::sqlite::setTempPath(path); mbgl::FileSourceManager::get()->registerFileSourceFactory( mbgl::FileSourceType::Asset, [](const mbgl::ResourceOptions&) { auto env{android::AttachEnv()}; std::unique_ptr assetFileSource; if (android::Mapbox::hasInstance(*env)) { auto assetManager = android::Mapbox::getAssetManager(*env); assetFileSource = std::make_unique(*env, assetManager); } return assetFileSource; }); resourceOptions.withAccessToken(accessToken ? jni::Make(_env, accessToken) : "") .withCachePath(path + DATABASE_FILE); // Create a core file sources // TODO: Split Android FileSource API to smaller interfaces resourceLoader = mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::ResourceLoader, resourceOptions); databaseSource = std::static_pointer_cast( mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Database, resourceOptions)); onlineSource = std::static_pointer_cast( mbgl::FileSourceManager::get()->getFileSource(mbgl::FileSourceType::Network, resourceOptions)); } FileSource::~FileSource() { } jni::Local FileSource::getAccessToken(jni::JNIEnv& env) { if (auto* token = onlineSource->getProperty(mbgl::ACCESS_TOKEN_KEY).getString()) { return jni::Make(env, *token); } ThrowNew(env, jni::FindClass(env, "java/lang/IllegalStateException"), "Online functionality is disabled."); return jni::Make(env, ""); } void FileSource::setAccessToken(jni::JNIEnv& env, const jni::String& token) { if (onlineSource) { onlineSource->setProperty(mbgl::ACCESS_TOKEN_KEY, token ? jni::Make(env, token) : ""); } else { ThrowNew(env, jni::FindClass(env, "java/lang/IllegalStateException"), "Online functionality is disabled."); } } void FileSource::setAPIBaseUrl(jni::JNIEnv& env, const jni::String& url) { if (onlineSource) { onlineSource->setProperty(mbgl::API_BASE_URL_KEY, jni::Make(env, url)); } else { ThrowNew(env, jni::FindClass(env, "java/lang/IllegalStateException"), "Online functionality is disabled."); } } void FileSource::setResourceTransform(jni::JNIEnv& env, const jni::Object& transformCallback) { // Core could be built without support for network resource provider. if (!onlineSource) { ThrowNew(env, jni::FindClass(env, "java/lang/IllegalStateException"), "Online functionality is disabled."); return; } if (transformCallback) { auto global = jni::NewGlobal(env, transformCallback); resourceTransform = std::make_unique>( *Scheduler::GetCurrent(), // Capture the ResourceTransformCallback object as a managed global into // the lambda. It is released automatically when we're setting a new ResourceTransform in // a subsequent call. // Note: we're converting it to shared_ptr because this lambda is converted to a std::function, // which requires copyability of its captured variables. [callback = std::make_shared(std::move(global))]( mbgl::Resource::Kind kind, const std::string& url_, ResourceTransform::FinishedCallback cb) { android::UniqueEnv _env = android::AttachEnv(); cb(FileSource::ResourceTransformCallback::onURL(*_env, *callback, int(kind), url_)); }); onlineSource->setResourceTransform( {[actorRef = resourceTransform->self()]( Resource::Kind kind, const std::string& url, ResourceTransform::FinishedCallback cb) { actorRef.invoke(&ResourceTransform::TransformCallback::operator(), kind, url, std::move(cb)); }}); } else { // Reset the callback resourceTransform.reset(); onlineSource->setResourceTransform({}); } } void FileSource::setResourceCachePath(jni::JNIEnv& env, const jni::String& path, const jni::Object& _callback) { if (!databaseSource) { ThrowNew(env, jni::FindClass(env, "java/lang/IllegalStateException"), "Offline functionality is disabled."); return; } if (pathChangeCallback) { FileSource::ResourcesCachePathChangeCallback::onError(env, _callback, jni::Make(env, "Another resources cache path change is in progress")); return; } std::string newPath = jni::Make(env, path); mapbox::sqlite::setTempPath(newPath); auto global = jni::NewGlobal(env, _callback); pathChangeCallback = Scheduler::GetCurrent()->bindOnce( [this, callback = std::make_shared(std::move(global)), newPath] { android::UniqueEnv _env = android::AttachEnv(); FileSource::ResourcesCachePathChangeCallback::onSuccess( *_env, *callback, jni::Make(*_env, newPath)); pathChangeCallback = {}; }); databaseSource->setDatabasePath(newPath + DATABASE_FILE, pathChangeCallback); } void FileSource::resume(jni::JNIEnv&) { if (!resourceLoader) { return; } if (!activationCounter) { activationCounter = optional(1) ; return; } activationCounter.value()++; if (activationCounter == 1) { resourceLoader->resume(); } } void FileSource::pause(jni::JNIEnv&) { if (!resourceLoader) { return; } if (activationCounter) { activationCounter.value()--; if (activationCounter == 0) { resourceLoader->pause(); } } } jni::jboolean FileSource::isResumed(jni::JNIEnv&) { if (activationCounter) { return (jboolean) (activationCounter > 0); } return (jboolean) false; } FileSource* FileSource::getNativePeer(jni::JNIEnv& env, const jni::Object& jFileSource) { static auto& javaClass = jni::Class::Singleton(env); static auto field = javaClass.GetField(env, "nativePtr"); return reinterpret_cast(jFileSource.Get(env, field)); } mbgl::ResourceOptions FileSource::getSharedResourceOptions(jni::JNIEnv& env, const jni::Object& jFileSource) { FileSource* fileSource = FileSource::getNativePeer(env, jFileSource); // Core could be compiled without support for any sources. if (fileSource) { return fileSource->resourceOptions.clone(); } return {}; } // FileSource::ResourcesCachePathChangeCallback // void FileSource::ResourcesCachePathChangeCallback::onSuccess(jni::JNIEnv& env, const jni::Object& callback, const jni::String& path) { static auto& javaClass = jni::Class::Singleton(env); static auto method = javaClass.GetMethod(env, "onSuccess"); callback.Call(env, method, path); } void FileSource::ResourcesCachePathChangeCallback::onError(jni::JNIEnv& env, const jni::Object& callback, const jni::String& message) { static auto& javaClass = jni::Class::Singleton(env); static auto method = javaClass.GetMethod(env, "onError"); callback.Call(env, method, message); } void FileSource::registerNative(jni::JNIEnv& env) { // Ensure the classes are cached. If they're requested for the // first time on a background thread, Android's class loader heuristics will fail. // https://developer.android.com/training/articles/perf-jni#faq_FindClass jni::Class::Singleton(env); jni::Class::Singleton(env); static auto& javaClass = jni::Class::Singleton(env); #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) // Register the peer jni::RegisterNativePeer(env, javaClass, "nativePtr", jni::MakePeer, "initialize", "finalize", METHOD(&FileSource::getAccessToken, "getAccessToken"), METHOD(&FileSource::setAccessToken, "setAccessToken"), METHOD(&FileSource::setAPIBaseUrl, "setApiBaseUrl"), METHOD(&FileSource::setResourceTransform, "setResourceTransform"), METHOD(&FileSource::setResourceCachePath, "setResourceCachePath"), METHOD(&FileSource::resume, "activate"), METHOD(&FileSource::pause, "deactivate"), METHOD(&FileSource::isResumed, "isActivated")); } // FileSource::ResourceTransformCallback // std::string FileSource::ResourceTransformCallback::onURL(jni::JNIEnv& env, const jni::Object& callback, int kind, std::string url_) { static auto& javaClass = jni::Class::Singleton(env); static auto method = javaClass.GetMethod(env, "onURL"); return jni::Make(env, callback.Call(env, method, kind, jni::Make(env, url_))); } } // namespace android } // namespace mbgl