308 lines
11 KiB
C++
308 lines
11 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file android_main.cxx
|
|
* @author rdb
|
|
* @date 2013-01-12
|
|
*/
|
|
|
|
#include "config_android.h"
|
|
#include "config_putil.h"
|
|
#include "virtualFileMountAndroidAsset.h"
|
|
#include "virtualFileSystem.h"
|
|
#include "filename.h"
|
|
#include "thread.h"
|
|
#include "urlSpec.h"
|
|
|
|
#include "android_native_app_glue.h"
|
|
|
|
#include "config_display.h"
|
|
// #define OPENGLES_1 #include "config_androiddisplay.h"
|
|
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
|
|
using std::string;
|
|
|
|
// struct android_app* panda_android_app = NULL;
|
|
|
|
extern int main(int argc, const char **argv);
|
|
|
|
/**
|
|
* This function is called by native_app_glue to initialize the program. It
|
|
* simply stores the android_app object and calls main() normally.
|
|
*
|
|
* Note that this does not run in the main thread, but in a thread created
|
|
* specifically for this activity by android_native_app_glue.
|
|
*/
|
|
void android_main(struct android_app* app) {
|
|
panda_android_app = app;
|
|
|
|
// Attach the app thread to the Java VM.
|
|
JNIEnv *env;
|
|
ANativeActivity* activity = app->activity;
|
|
int status = activity->vm->AttachCurrentThread(&env, nullptr);
|
|
if (status < 0 || env == nullptr) {
|
|
android_cat.error() << "Failed to attach thread to JVM!\n";
|
|
return;
|
|
}
|
|
|
|
jclass activity_class = env->GetObjectClass(activity->clazz);
|
|
|
|
// Get the current Java thread name. This just helps with debugging.
|
|
jmethodID methodID = env->GetStaticMethodID(activity_class, "getCurrentThreadName", "()Ljava/lang/String;");
|
|
jstring jthread_name = (jstring) env->CallStaticObjectMethod(activity_class, methodID);
|
|
|
|
string thread_name;
|
|
if (jthread_name != nullptr) {
|
|
const char *c_str = env->GetStringUTFChars(jthread_name, nullptr);
|
|
thread_name.assign(c_str);
|
|
env->ReleaseStringUTFChars(jthread_name, c_str);
|
|
}
|
|
|
|
// Before we make any Panda calls, we must make the thread known to Panda.
|
|
// This will also cause the JNIEnv pointer to be stored on the thread.
|
|
// Note that we must keep a reference to this thread around.
|
|
PT(Thread) current_thread = Thread::bind_thread(thread_name, "android_app");
|
|
|
|
android_cat.info()
|
|
<< "New native activity started on " << *current_thread << "\n";
|
|
|
|
// Were we given an optional location to write the stdout/stderr streams?
|
|
methodID = env->GetMethodID(activity_class, "getIntentOutputUri", "()Ljava/lang/String;");
|
|
jstring joutput_uri = (jstring) env->CallObjectMethod(activity->clazz, methodID);
|
|
if (joutput_uri != nullptr) {
|
|
const char *output_uri = env->GetStringUTFChars(joutput_uri, nullptr);
|
|
|
|
if (output_uri != nullptr && output_uri[0] != 0) {
|
|
URLSpec spec(output_uri);
|
|
|
|
if (spec.get_scheme() == "file") {
|
|
string path = spec.get_path();
|
|
int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY);
|
|
if (fd != -1) {
|
|
android_cat.info()
|
|
<< "Writing standard output to file " << path << "\n";
|
|
|
|
dup2(fd, 1);
|
|
dup2(fd, 2);
|
|
} else {
|
|
android_cat.error()
|
|
<< "Failed to open output path " << path << "\n";
|
|
}
|
|
} else if (spec.get_scheme() == "tcp") {
|
|
string host = spec.get_server();
|
|
int fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
struct sockaddr_in serv_addr = {0};
|
|
serv_addr.sin_family = AF_INET;
|
|
serv_addr.sin_port = htons(spec.get_port());
|
|
serv_addr.sin_addr.s_addr = inet_addr(host.c_str());
|
|
if (connect(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) {
|
|
android_cat.info()
|
|
<< "Writing standard output to socket "
|
|
<< spec.get_server_and_port() << "\n";
|
|
dup2(fd, 1);
|
|
dup2(fd, 2);
|
|
} else {
|
|
android_cat.error()
|
|
<< "Failed to open output socket "
|
|
<< spec.get_server_and_port() << "\n";
|
|
}
|
|
close(fd);
|
|
} else {
|
|
android_cat.error()
|
|
<< "Unsupported scheme in output URI: " << output_uri << "\n";
|
|
}
|
|
env->ReleaseStringUTFChars(joutput_uri, output_uri);
|
|
}
|
|
}
|
|
|
|
// Fetch the data directory.
|
|
jmethodID get_appinfo = env->GetMethodID(activity_class, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
|
|
|
|
jobject appinfo = env->CallObjectMethod(activity->clazz, get_appinfo);
|
|
jclass appinfo_class = env->GetObjectClass(appinfo);
|
|
|
|
// Fetch the path to the data directory.
|
|
jfieldID datadir_field = env->GetFieldID(appinfo_class, "dataDir", "Ljava/lang/String;");
|
|
jstring datadir = (jstring) env->GetObjectField(appinfo, datadir_field);
|
|
const char *data_path = env->GetStringUTFChars(datadir, nullptr);
|
|
|
|
if (data_path != nullptr) {
|
|
Filename::_internal_data_dir = data_path;
|
|
android_cat.info() << "Path to data: " << data_path << "\n";
|
|
|
|
env->ReleaseStringUTFChars(datadir, data_path);
|
|
}
|
|
|
|
// Fetch the path to the library directory.
|
|
if (ExecutionEnvironment::get_dtool_name().empty()) {
|
|
jfieldID libdir_field = env->GetFieldID(appinfo_class, "nativeLibraryDir", "Ljava/lang/String;");
|
|
jstring libdir = (jstring) env->GetObjectField(appinfo, libdir_field);
|
|
const char *lib_path = env->GetStringUTFChars(libdir, nullptr);
|
|
|
|
if (lib_path != nullptr) {
|
|
string dtool_name = string(lib_path) + "/libp3dtool.so";
|
|
ExecutionEnvironment::set_dtool_name(dtool_name);
|
|
android_cat.info() << "Path to dtool: " << dtool_name << "\n";
|
|
|
|
env->ReleaseStringUTFChars(libdir, lib_path);
|
|
}
|
|
}
|
|
|
|
// Get the cache directory. Set the model-path to this location.
|
|
methodID = env->GetMethodID(activity_class, "getCacheDirString", "()Ljava/lang/String;");
|
|
jstring jcache_dir = (jstring) env->CallObjectMethod(activity->clazz, methodID);
|
|
|
|
if (jcache_dir != nullptr) {
|
|
const char *cache_dir;
|
|
cache_dir = env->GetStringUTFChars(jcache_dir, nullptr);
|
|
android_cat.info() << "Path to cache: " << cache_dir << "\n";
|
|
|
|
ConfigVariableFilename model_cache_dir("model-cache-dir", Filename());
|
|
model_cache_dir.set_value(cache_dir);
|
|
env->ReleaseStringUTFChars(jcache_dir, cache_dir);
|
|
}
|
|
|
|
// Get the path to the APK.
|
|
methodID = env->GetMethodID(activity_class, "getPackageCodePath", "()Ljava/lang/String;");
|
|
jstring code_path = (jstring) env->CallObjectMethod(activity->clazz, methodID);
|
|
|
|
const char* apk_path;
|
|
apk_path = env->GetStringUTFChars(code_path, nullptr);
|
|
|
|
// We're going to set this as binary name, which is better than the
|
|
// default (which refers to the zygote). Or should we set it to the
|
|
// native library? How do we get the path to that?
|
|
android_cat.info() << "Path to APK: " << apk_path << "\n";
|
|
ExecutionEnvironment::set_binary_name(apk_path);
|
|
|
|
// Mount the assets directory.
|
|
Filename apk_fn(apk_path);
|
|
PT(VirtualFileMountAndroidAsset) asset_mount;
|
|
asset_mount = new VirtualFileMountAndroidAsset(app->activity->assetManager, apk_fn);
|
|
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
|
|
|
|
//Filename asset_dir(apk_fn.get_dirname(), "assets");
|
|
Filename asset_dir("/android_asset");
|
|
vfs->mount(asset_mount, asset_dir, 0);
|
|
|
|
// Release the apk_path.
|
|
env->ReleaseStringUTFChars(code_path, apk_path);
|
|
|
|
// Now add the asset directory to the model-path.
|
|
//TODO: prevent it from adding the directory multiple times.
|
|
get_model_path().append_directory(asset_dir);
|
|
|
|
// Now load the configuration files.
|
|
std::vector<ConfigPage *> pages;
|
|
ConfigPageManager *cp_mgr;
|
|
AAssetDir *etc = AAssetManager_openDir(app->activity->assetManager, "etc");
|
|
if (etc != nullptr) {
|
|
cp_mgr = ConfigPageManager::get_global_ptr();
|
|
const char *filename = AAssetDir_getNextFileName(etc);
|
|
while (filename != nullptr) {
|
|
// Does it match any of the configured prc patterns?
|
|
for (size_t i = 0; i < cp_mgr->get_num_prc_patterns(); ++i) {
|
|
GlobPattern pattern = cp_mgr->get_prc_pattern(i);
|
|
if (pattern.matches(filename)) {
|
|
Filename prc_fn("etc", filename);
|
|
std::istream *in = asset_mount->open_read_file(prc_fn);
|
|
if (in != nullptr) {
|
|
ConfigPage *page = cp_mgr->make_explicit_page(Filename("/android_asset", prc_fn));
|
|
page->read_prc(*in);
|
|
pages.push_back(page);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
filename = AAssetDir_getNextFileName(etc);
|
|
}
|
|
AAssetDir_close(etc);
|
|
}
|
|
|
|
// Also read the intent filename.
|
|
methodID = env->GetMethodID(activity_class, "getIntentDataPath", "()Ljava/lang/String;");
|
|
jstring filename = (jstring) env->CallObjectMethod(activity->clazz, methodID);
|
|
const char *filename_str = nullptr;
|
|
if (filename != nullptr) {
|
|
filename_str = env->GetStringUTFChars(filename, nullptr);
|
|
android_cat.info() << "Got intent filename: " << filename_str << "\n";
|
|
|
|
Filename fn(filename_str);
|
|
if (!fn.exists()) {
|
|
// Show a toast with the failure message.
|
|
android_show_toast(activity, string("Unable to access ") + filename_str, 1);
|
|
}
|
|
}
|
|
|
|
// Create bogus argc and argv for calling the main function.
|
|
const char *argv[] = {"pview", nullptr, nullptr};
|
|
int argc = 1;
|
|
|
|
if (filename_str != nullptr) {
|
|
argv[1] = filename_str;
|
|
++argc;
|
|
}
|
|
|
|
while (!app->destroyRequested) {
|
|
// Call the main function. This will not return until the app is done.
|
|
android_cat.info() << "Calling main()\n";
|
|
main(argc, argv);
|
|
|
|
if (app->destroyRequested) {
|
|
// The app closed responding to a destroy request.
|
|
break;
|
|
}
|
|
|
|
// Ask Android to clean up the activity.
|
|
android_cat.info() << "Exited from main(), finishing activity\n";
|
|
ANativeActivity_finish(activity);
|
|
|
|
// We still need to keep an event loop going until Android gives us leave
|
|
// to end the process.
|
|
int looper_id;
|
|
int events;
|
|
struct android_poll_source *source;
|
|
while ((looper_id = ALooper_pollAll(-1, nullptr, &events, (void**)&source)) >= 0) {
|
|
// Process this event, but intercept application command events.
|
|
if (looper_id == LOOPER_ID_MAIN) {
|
|
int8_t cmd = android_app_read_cmd(app);
|
|
android_app_pre_exec_cmd(app, cmd);
|
|
android_app_post_exec_cmd(app, cmd);
|
|
|
|
// I don't think we can get a resume command after we call finish(),
|
|
// but let's handle it just in case.
|
|
if (cmd == APP_CMD_RESUME ||
|
|
cmd == APP_CMD_DESTROY) {
|
|
break;
|
|
}
|
|
} else if (source != nullptr) {
|
|
source->process(app, source);
|
|
}
|
|
}
|
|
}
|
|
|
|
android_cat.info() << "Destroy requested, exiting from android_main\n";
|
|
|
|
for (ConfigPage *page : pages) {
|
|
cp_mgr->delete_explicit_page(page);
|
|
}
|
|
vfs->unmount(asset_mount);
|
|
|
|
if (filename_str != nullptr) {
|
|
env->ReleaseStringUTFChars(filename, filename_str);
|
|
}
|
|
|
|
close(1);
|
|
close(2);
|
|
|
|
// Detach the thread before exiting.
|
|
activity->vm->DetachCurrentThread();
|
|
}
|