Browse Source

More android support

rdb 13 years ago
parent
commit
bc85e1a4f2

+ 49 - 0
panda/src/android/NativeIStream.java

@@ -0,0 +1,49 @@
+// Filename: NativeIStream.java
+// Created by:  rdb (22Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+package org.panda3d.android;
+
+import java.io.InputStream;
+
+////////////////////////////////////////////////////////////////////
+//       Class : NativeIStream
+// Description : An implementation of InputStream that gets its
+//               data from a C++ istream pointer, passed as long.
+////////////////////////////////////////////////////////////////////
+public class NativeIStream extends InputStream {
+    private long streamPtr = 0;
+
+    public NativeIStream(long ptr) {
+        streamPtr = ptr;
+    }
+
+    @Override
+    public int read() {
+        return nativeGet(streamPtr);
+    }
+
+    @Override
+    public int read(byte[] buffer, int offset, int length) {
+        return nativeRead(streamPtr, buffer, offset, length);
+    }
+
+    @Override
+    public long skip(long n) {
+        return nativeIgnore(streamPtr, n);
+    }
+
+    private static native int nativeGet(long ptr);
+    private static native int nativeRead(long ptr, byte[] buffer, int offset, int length);
+    private static native long nativeIgnore(long ptr, long offset);
+}

+ 58 - 0
panda/src/android/PandaActivity.java

@@ -0,0 +1,58 @@
+// Filename: PandaActivity.java
+// Created by:  rdb (22Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+package org.panda3d.android;
+
+import android.app.NativeActivity;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import org.panda3d.android.NativeIStream;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PandaActivity
+// Description : The entry point for a Panda-based activity.  Loads
+//               the Panda libraries and also provides some utility
+//               functions.
+////////////////////////////////////////////////////////////////////
+public class PandaActivity extends NativeActivity {
+    protected static BitmapFactory.Options readBitmapSize(long istreamPtr) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        options.inScaled = false;
+        NativeIStream stream = new NativeIStream(istreamPtr);
+        BitmapFactory.decodeStream(stream, null, options);
+        return options;
+    }
+
+    protected static Bitmap readBitmap(long istreamPtr, int sampleSize) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        //options.inPreferredConfig = Bitmap.Config.RGBA_8888;
+        options.inScaled = false;
+        options.inSampleSize = sampleSize;
+        NativeIStream stream = new NativeIStream(istreamPtr);
+        return BitmapFactory.decodeStream(stream, null, options);
+    }
+
+    static {
+        System.loadLibrary("gnustl_shared");
+        System.loadLibrary("p3dtool");
+        System.loadLibrary("p3dtoolconfig");
+        System.loadLibrary("pandaexpress");
+        System.loadLibrary("panda");
+        System.loadLibrary("p3android");
+        System.loadLibrary("p3framework");
+        System.loadLibrary("pandaegg");
+        System.loadLibrary("pandagles");
+    }
+}

+ 76 - 0
panda/src/android/android_main.cxx

@@ -0,0 +1,76 @@
+// Filename: android_main.cxx
+// Created by:  rdb (12Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "config_android.h"
+#include "config_util.h"
+#include "virtualFileMountAndroidAsset.h"
+#include "virtualFileSystem.h"
+
+#include "config_display.h"
+//#define OPENGLES_1
+//#include "config_androiddisplay.h"
+
+#include <android_native_app_glue.h>
+
+//struct android_app* panda_android_app = NULL;
+
+extern int main(int argc, char **argv);
+
+////////////////////////////////////////////////////////////////////
+//     Function: android_main
+//  Description: This function is called by native_app_glue to
+//               initialize the program.  It simply stores the
+//               android_app object and calls main() normally.
+////////////////////////////////////////////////////////////////////
+void android_main(struct android_app* app) {
+  panda_android_app = app;
+
+  // Attach the current thread to the JVM.
+  JNIEnv *env;
+  ANativeActivity* activity = app->activity;
+  int status = activity->vm->AttachCurrentThread(&env, NULL);
+  if (status < 0 || env == NULL) {
+    android_cat.error() << "Failed to attach thread to JVM!\n";
+    return;
+  }
+
+  // Get the path to the APK.
+  jclass clazz = env->GetObjectClass(activity->clazz);
+  jmethodID methodID = env->GetMethodID(clazz, "getPackageCodePath", "()Ljava/lang/String;");
+  jstring code_path = (jstring) env->CallObjectMethod(activity->clazz, methodID);
+
+  const char* apk_path;
+  apk_path = env->GetStringUTFChars(code_path, NULL);
+  android_cat.info() << "Path to APK: " << apk_path << "\n";
+
+  // Mount the assets directory.
+  PT(VirtualFileMountAndroidAsset) asset_mount;
+  asset_mount = new VirtualFileMountAndroidAsset(app->activity->assetManager, apk_path);
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  vfs->mount(asset_mount, "/android_asset", 0);
+
+  // Release the apk_path.
+  env->ReleaseStringUTFChars(code_path, apk_path);
+
+  // Now add the asset directory to the model-path.
+  get_model_path().append_directory("/android_asset");
+
+  // Create bogus argc and argv, then call our main function.
+  char *argv[] = {NULL};
+  int argc = 0;
+  main(argc, argv);
+
+  // Detach the thread before exiting.
+  activity->vm->DetachCurrentThread();
+}

+ 88 - 0
panda/src/android/config_android.cxx

@@ -0,0 +1,88 @@
+// Filename: config_android.cxx
+// Created by:  rdb (12Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "config_android.h"
+#include "pnmFileTypeAndroid.h"
+#include "pnmFileTypeRegistry.h"
+#include "dconfig.h"
+#include "pandaSystem.h"
+
+NotifyCategoryDef(android, "");
+
+struct android_app *panda_android_app = NULL;
+
+jclass    jni_PandaActivity;
+jmethodID jni_PandaActivity_readBitmapSize;
+jmethodID jni_PandaActivity_readBitmap;
+
+jclass   jni_BitmapFactory_Options;
+jfieldID jni_BitmapFactory_Options_outWidth;
+jfieldID jni_BitmapFactory_Options_outHeight;
+
+////////////////////////////////////////////////////////////////////
+//     Function: init_libandroid
+//  Description: Initializes the library.  This must be called at
+//               least once before any of the functions or classes
+//               in this library can be used.  Normally, this is
+//               called by JNI_OnLoad.
+////////////////////////////////////////////////////////////////////
+void
+init_libandroid() {
+  PNMFileTypeRegistry *tr = PNMFileTypeRegistry::get_global_ptr();
+  PNMFileTypeAndroid::init_type();
+  PNMFileTypeAndroid::register_with_read_factory();
+  tr->register_type(new PNMFileTypeAndroid);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: JNI_OnLoad
+//  Description: Called by Java when loading this library.
+//               Initializes the global class references and the
+//               method IDs.
+////////////////////////////////////////////////////////////////////
+jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
+  init_libandroid();
+
+  JNIEnv *env = get_jni_env();
+  assert(env != NULL);
+
+  jni_PandaActivity = env->FindClass("org/panda3d/android/PandaActivity");
+  jni_PandaActivity = (jclass) env->NewGlobalRef(jni_PandaActivity);
+
+  jni_PandaActivity_readBitmapSize = env->GetStaticMethodID(jni_PandaActivity,
+                   "readBitmapSize", "(J)Landroid/graphics/BitmapFactory$Options;");
+
+  jni_PandaActivity_readBitmap = env->GetStaticMethodID(jni_PandaActivity,
+                   "readBitmap", "(JI)Landroid/graphics/Bitmap;");
+
+  jni_BitmapFactory_Options = env->FindClass("android/graphics/BitmapFactory$Options");
+  jni_BitmapFactory_Options = (jclass) env->NewGlobalRef(jni_BitmapFactory_Options);
+
+  jni_BitmapFactory_Options_outWidth = env->GetFieldID(jni_BitmapFactory_Options, "outWidth", "I");
+  jni_BitmapFactory_Options_outHeight = env->GetFieldID(jni_BitmapFactory_Options, "outHeight", "I");
+
+  return JNI_VERSION_1_4;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: JNI_OnUnload
+//  Description: Called by Java when unloading this library.
+//               Destroys the global class references.
+////////////////////////////////////////////////////////////////////
+void JNI_OnUnload(JavaVM *jvm, void *reserved) {
+  JNIEnv *env = get_jni_env();
+
+  env->DeleteGlobalRef(jni_PandaActivity);
+  env->DeleteGlobalRef(jni_BitmapFactory_Options);
+}

+ 39 - 0
panda/src/android/config_android.h

@@ -0,0 +1,39 @@
+// Filename: config_android.h
+// Created by:  rdb (12Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef CONFIG_ANDROID_H
+#define CONFIG_ANDROID_H
+
+#include "pandabase.h"
+#include "notifyCategoryProxy.h"
+#include "configVariableString.h"
+#include "configVariableBool.h"
+#include "configVariableInt.h"
+
+#include <jni.h>
+
+NotifyCategoryDeclNoExport(android);
+extern void init_libandroid();
+
+extern struct android_app* panda_android_app;
+
+extern jclass    jni_PandaActivity;
+extern jmethodID jni_PandaActivity_readBitmapHeader;
+extern jmethodID jni_PandaActivity_readBitmap;
+
+extern jclass   jni_BitmapFactory_Options;
+extern jfieldID jni_BitmapFactory_Options_outWidth;
+extern jfieldID jni_BitmapFactory_Options_outHeight;
+
+#endif

+ 71 - 0
panda/src/android/jni_NativeIStream.cxx

@@ -0,0 +1,71 @@
+// Filename: jni_NativeIStream.cxx
+// Created by:  rdb (22Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include <jni.h>
+
+#include <istream>
+
+////////////////////////////////////////////////////////////////////
+//     Function: NativeIStream::nativeGet
+//       Access: Private, Static
+//  Description: Reads a single character from the istream.
+//               Should return -1 on EOF.
+////////////////////////////////////////////////////////////////////
+extern "C" jint
+Java_org_panda3d_android_NativeIStream_nativeGet(JNIEnv *env, jclass clazz, jlong ptr) {
+  std::istream *stream = (std::istream *) ptr;
+
+  int ch = stream->get();
+  return stream->good() ? ch : -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NativeIStream::nativeRead
+//       Access: Private, Static
+//  Description: Reads an array of bytes from the istream.  Returns
+//               the actual number of bytes that were read.
+//               Should return -1 on EOF.
+////////////////////////////////////////////////////////////////////
+extern "C" jint
+Java_org_panda3d_android_NativeIStream_nativeRead(JNIEnv *env, jclass clazz, jlong ptr, jbyteArray byte_array, jint offset, jint length) {
+  std::istream *stream = (std::istream *) ptr;
+  jbyte *buffer = (jbyte *) env->GetPrimitiveArrayCritical(byte_array, NULL);
+  if (buffer == NULL) {
+    return -1;
+  }
+
+  stream->read((char*) buffer + offset, length);
+  env->ReleasePrimitiveArrayCritical(byte_array, buffer, 0);
+
+  // We have to return -1 on EOF, otherwise it will keep trying to read.
+  size_t count = stream->gcount();
+  if (count == 0 && stream->eof()) {
+    return -1;
+  } else {
+    return count;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NativeIStream::nativeIgnore
+//       Access: Private, Static
+//  Description: Skips ahead N bytes in the stream.  Returns the
+//               actual number of skipped bytes.
+////////////////////////////////////////////////////////////////////
+extern "C" jlong
+Java_org_panda3d_android_NativeIStream_nativeIgnore(JNIEnv *env, jclass clazz, jlong ptr, jlong offset) {
+  std::istream *stream = (std::istream *) ptr;
+  stream->ignore(offset);
+  return stream->gcount();
+}

+ 4 - 0
panda/src/android/p3android_composite1.cxx

@@ -0,0 +1,4 @@
+#include "config_android.cxx"
+#include "jni_NativeIStream.cxx"
+#include "pnmFileTypeAndroid.cxx"
+#include "pnmFileTypeAndroidReader.cxx"

+ 127 - 0
panda/src/android/pnmFileTypeAndroid.cxx

@@ -0,0 +1,127 @@
+// Filename: pnmFileTypeAndroid.cxx
+// Created by:  rdb (11Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pnmFileTypeAndroid.h"
+
+#ifdef ANDROID
+
+#include "config_pnmimagetypes.h"
+
+#include "pnmFileTypeRegistry.h"
+#include "bamReader.h"
+
+static const char * const extensions_android[] = {
+  "jpg", "jpeg", "gif", "png",//"webp" (android 4.0+)
+};
+static const int num_extensions_android = sizeof(extensions_android) / sizeof(const char *);
+
+TypeHandle PNMFileTypeAndroid::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+PNMFileTypeAndroid::
+PNMFileTypeAndroid() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::get_name
+//       Access: Public, Virtual
+//  Description: Returns a few words describing the file type.
+////////////////////////////////////////////////////////////////////
+string PNMFileTypeAndroid::
+get_name() const {
+  return "Android Bitmap";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::get_num_extensions
+//       Access: Public, Virtual
+//  Description: Returns the number of different possible filename
+//               extensions associated with this particular file type.
+////////////////////////////////////////////////////////////////////
+int PNMFileTypeAndroid::
+get_num_extensions() const {
+  return num_extensions_android;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::get_extension
+//       Access: Public, Virtual
+//  Description: Returns the nth possible filename extension
+//               associated with this particular file type, without a
+//               leading dot.
+////////////////////////////////////////////////////////////////////
+string PNMFileTypeAndroid::
+get_extension(int n) const {
+  nassertr(n >= 0 && n < num_extensions_android, string());
+  return extensions_android[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::has_magic_number
+//       Access: Public, Virtual
+//  Description: Returns true if this particular file type uses a
+//               magic number to identify it, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PNMFileTypeAndroid::
+has_magic_number() const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::make_reader
+//       Access: Public, Virtual
+//  Description: Allocates and returns a new PNMReader suitable for
+//               reading from this file type, if possible.  If reading
+//               from this file type is not supported, returns NULL.
+////////////////////////////////////////////////////////////////////
+PNMReader *PNMFileTypeAndroid::
+make_reader(istream *file, bool owns_file, const string &magic_number) {
+  init_pnm();
+  return new Reader(this, file, owns_file, magic_number);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::register_with_read_factory
+//       Access: Public, Static
+//  Description: Registers the current object as something that can be
+//               read from a Bam file.
+////////////////////////////////////////////////////////////////////
+void PNMFileTypeAndroid::
+register_with_read_factory() {
+  BamReader::get_factory()->
+    register_factory(get_class_type(), make_PNMFileTypeAndroid);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::make_PNMFileTypeAndroid
+//       Access: Protected, Static
+//  Description: This method is called by the BamReader when an object
+//               of this type is encountered in a Bam file; it should
+//               allocate and return a new object with all the data
+//               read.
+//
+//               In the case of the PNMFileType objects, since these
+//               objects are all shared, we just pull the object from
+//               the registry.
+////////////////////////////////////////////////////////////////////
+TypedWritable *PNMFileTypeAndroid::
+make_PNMFileTypeAndroid(const FactoryParams &params) {
+  return PNMFileTypeRegistry::get_global_ptr()->get_type_by_handle(get_class_type());
+}
+
+#endif  // ANDROID

+ 93 - 0
panda/src/android/pnmFileTypeAndroid.h

@@ -0,0 +1,93 @@
+// Filename: pnmFileTypeAndroid.h
+// Created by:  rdb (11Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PNMFILETYPEANDROID_H
+#define PNMFILETYPEANDROID_H
+
+#ifdef ANDROID
+
+#include "pandabase.h"
+
+#include "pnmFileType.h"
+#include "pnmReader.h"
+#include "pnmWriter.h"
+
+#include <jni.h>
+
+////////////////////////////////////////////////////////////////////
+//       Class : PNMFileTypeAndroid
+// Description : Wrapper class around the Android Bitmap mechanism
+//               to allow loading images on Android without needing
+//               libpng or libjpeg.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_PNMIMAGETYPES PNMFileTypeAndroid : public PNMFileType {
+public:
+  PNMFileTypeAndroid();
+
+  virtual string get_name() const;
+
+  virtual int get_num_extensions() const;
+  virtual string get_extension(int n) const;
+
+  virtual bool has_magic_number() const;
+
+  virtual PNMReader *make_reader(istream *file, bool owns_file = true,
+                                 const string &magic_number = string());
+
+public:
+  class Reader : public PNMReader {
+  public:
+    Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number);
+    virtual ~Reader();
+
+    virtual void prepare_read();
+    virtual int read_data(xel *array, xelval *alpha);
+
+  private:
+    // It is assumed that the Reader is only used within a single thread.
+    JNIEnv *_env;
+    jobject _bitmap;
+    int _sample_size;
+    uint32_t _stride;
+    int32_t _format;
+  };
+
+  // The TypedWritable interface follows.
+public:
+  static void register_with_read_factory();
+
+protected:
+  static TypedWritable *make_PNMFileTypeAndroid(const FactoryParams &params);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PNMFileType::init_type();
+    register_type(_type_handle, "PNMFileTypeAndroid",
+                  PNMFileType::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#endif  // ANDROID
+
+#endif

+ 303 - 0
panda/src/android/pnmFileTypeAndroidReader.cxx

@@ -0,0 +1,303 @@
+// Filename: pnmFileTypeAndroidReader.cxx
+// Created by:  rdb (22Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pnmFileTypeAndroid.h"
+
+#ifdef ANDROID
+
+#include "config_pnmimagetypes.h"
+#include "config_express.h"
+
+#include <android/bitmap.h>
+#include <jni.h>
+
+// These tables linearly map 4-bit, 5-bit or 6-bit to 8-bit values.
+static uint8_t scale_table_4[16];
+static uint8_t scale_table_5[32];
+static uint8_t scale_table_6[64];
+
+static void init_scale_tables() {
+  static bool initialized = false;
+  if (!initialized) {
+    int i;
+    for (i = 0; i < 16; ++i) {
+      scale_table_4[i] = 255 * i / 15;
+    }
+    for (i = 0; i < 32; ++i) {
+      scale_table_5[i] = 255 * i / 31;
+    }
+    for (i = 0; i < 64; ++i) {
+      scale_table_6[i] = 255 * i / 63;
+    }
+    initialized = true;
+  }
+}
+
+static void conv_rgb565(uint16_t in, xel &out) {
+  out.r = scale_table_5[(in >> 11) & 31];
+  out.g = scale_table_6[(in >> 5) & 63];
+  out.b = scale_table_5[in & 31];
+}
+
+static void conv_rgba4444(uint16_t in, xel &rgb, xelval &alpha) {
+  rgb.r = scale_table_4[(in >> 12) & 0xF];
+  rgb.g = scale_table_4[(in >> 8) & 0xF];
+  rgb.b = scale_table_4[(in >> 4) & 0xF];
+  alpha = scale_table_4[in & 0xF];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::Reader::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+PNMFileTypeAndroid::Reader::
+Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) :
+  PNMReader(type, file, owns_file), _bitmap(NULL)
+{
+  // Hope we can putback() more than one character.
+  for (string::reverse_iterator mi = magic_number.rbegin();
+       mi != magic_number.rend(); ++mi) {
+    _file->putback(*mi);
+  };
+  if (_file->fail()) {
+    android_cat.error()
+      << "Unable to put back magic number.\n";
+    _is_valid = false;
+    return;
+  }
+
+  streampos pos = _file->tellg();
+  _env = get_jni_env();
+  jobject opts = _env->CallStaticObjectMethod(jni_PandaActivity,
+                                              jni_PandaActivity_readBitmapSize,
+                                              (jlong) _file);
+  _file->seekg(pos);
+  if (_file->tellg() != pos) {
+    android_cat.error()
+      << "Unable to seek back to beginning.\n";
+    _is_valid = false;
+    return;
+  }
+
+  _x_size = _env->GetIntField(opts, jni_BitmapFactory_Options_outWidth);
+  _y_size = _env->GetIntField(opts, jni_BitmapFactory_Options_outHeight);
+
+  if (_x_size < 0 || _y_size < 0) {
+    android_cat.error()
+      << "Failed to read header of " << *this << "\n";
+    _is_valid = false;
+  }
+
+  // Apparently we have to know this even though we don't yet.
+  _num_channels = 4;
+  _maxval = 255;
+
+  if (android_cat.is_debug()) {
+    android_cat.debug()
+      << "Reading " << *this << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::Reader::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+PNMFileTypeAndroid::Reader::
+~Reader() {
+  if (_bitmap != NULL) {
+    _env->DeleteGlobalRef(_bitmap);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::Reader::prepare_read
+//       Access: Public, Virtual
+//  Description: This method will be called before read_data() or
+//               read_row() is called.  It instructs the reader to
+//               initialize its data structures as necessary to
+//               actually perform the read operation.  
+//
+//               After this call, _x_size and _y_size should reflect
+//               the actual size that will be filled by read_data()
+//               (as possibly modified by set_read_size()).
+////////////////////////////////////////////////////////////////////
+void PNMFileTypeAndroid::Reader::
+prepare_read() {
+  _sample_size = 2;
+  _orig_x_size = _x_size;
+  _orig_y_size = _y_size;
+
+  if (_has_read_size && _read_x_size != 0 && _read_y_size != 0) {
+    int x_reduction = _orig_x_size / _read_x_size;
+    int y_reduction = _orig_y_size / _read_y_size;
+
+    _sample_size = max(min(x_reduction, y_reduction), 1);
+  }
+
+  _bitmap = _env->CallStaticObjectMethod(jni_PandaActivity,
+                                         jni_PandaActivity_readBitmap,
+                                         (jlong) _file, _sample_size);
+
+  if (_bitmap == NULL) {
+    android_cat.error()
+      << "Failed to read " << *this << "\n";
+    _is_valid = false;
+    return;
+  }
+
+  _bitmap = _env->NewGlobalRef(_bitmap);
+
+  AndroidBitmapInfo info;
+  if (AndroidBitmap_getInfo(_env, _bitmap, &info) < 0) {
+    android_cat.error()
+      << "Failed to get info of " << *this << "\n";
+    _is_valid = false;
+    return;
+  }
+
+  _x_size = info.width;
+  _y_size = info.height;
+  _format = info.format;
+  _stride = info.stride;
+
+  // Note: we could be setting maxval more appropriately,
+  // but this only causes texture.cxx to end up rescaling it later.
+  // Best to do the scaling ourselves, using efficient tables.
+  _maxval = 255;
+
+  switch (info.format) {
+    case ANDROID_BITMAP_FORMAT_RGBA_8888:
+      _num_channels = 4;
+      android_cat.debug()
+        << "Bitmap has format RGBA_8888\n";
+      break;
+    case ANDROID_BITMAP_FORMAT_RGB_565:
+      _num_channels = 3;
+      android_cat.debug()
+        << "Bitmap has format RGB_565\n";
+      break;
+    case ANDROID_BITMAP_FORMAT_RGBA_4444:
+      _num_channels = 4;
+      android_cat.debug()
+        << "Bitmap has format RGBA_4444\n";
+      break;
+    case ANDROID_BITMAP_FORMAT_A_8:
+      _num_channels = 1;
+      android_cat.debug()
+        << "Bitmap has format A_8\n";
+      break;
+    default:
+      android_cat.error()
+        << "Unsupported bitmap format!\n";
+      _num_channels = 0;
+      _is_valid = false;
+      break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PNMFileTypeAndroid::Reader::read_data
+//       Access: Public, Virtual
+//  Description: Reads in an entire image all at once, storing it in
+//               the pre-allocated _x_size * _y_size array and alpha
+//               pointers.  (If the image type has no alpha channel,
+//               alpha is ignored.)  Returns the number of rows
+//               correctly read.
+//
+//               Derived classes need not override this if they
+//               instead provide supports_read_row() and read_row(),
+//               below.
+////////////////////////////////////////////////////////////////////
+int PNMFileTypeAndroid::Reader::
+read_data(xel *rgb, xelval *alpha) {
+  if (!_is_valid) {
+    return 0;
+  }
+  void *ptr;
+  if (AndroidBitmap_lockPixels(_env, _bitmap, &ptr) < 0) {
+    android_cat.error()
+      << "Failed to lock bitmap for reading.\n";
+    return 0;
+  }
+
+  switch (_format) {
+    case ANDROID_BITMAP_FORMAT_RGBA_8888: {
+      nassertr(_stride == _x_size * 4, 0);
+      uint8_t *data = (uint8_t *) ptr;
+      for (int y = 0; y < _y_size; ++y) {
+        for (int x = 0; x < _x_size; ++x) {
+          rgb[x].r = data[0];
+          rgb[x].g = data[1];
+          rgb[x].b = data[2];
+          alpha[x] = data[3];
+          data += 4;
+        }
+        rgb += _x_size;
+        alpha += _y_size;
+      }
+      break;
+    }
+    case ANDROID_BITMAP_FORMAT_RGB_565: {
+      nassertr(_stride == _x_size * 2, 0);
+      init_scale_tables();
+
+      uint16_t *data = (uint16_t *) ptr;
+      for (int y = 0; y < _y_size; ++y) {
+        for (int x = 0; x < _x_size; ++x) {
+          conv_rgb565(data[x], rgb[x]);
+        }
+        data += _x_size;
+        rgb += _x_size;
+      }
+      break;
+    }
+    case ANDROID_BITMAP_FORMAT_RGBA_4444: {
+      nassertr(_stride == _x_size * 2, 0);
+      init_scale_tables();
+
+      uint16_t *data = (uint16_t *) ptr;
+      for (int y = 0; y < _y_size; ++y) {
+        for (int x = 0; x < _x_size; ++x) {
+          conv_rgba4444(data[x], rgb[x], alpha[x]);
+        }
+        data += _x_size;
+        rgb += _x_size;
+        alpha += _x_size;
+      }
+      break;
+    }
+    case ANDROID_BITMAP_FORMAT_A_8: {
+      nassertr(_stride == _x_size, 0);
+      uint8_t *data = (uint8_t *) ptr;
+      for (int y = 0; y < _y_size; ++y) {
+        for (int x = 0; x < _x_size; ++x) {
+          alpha[x] = data[x];
+        }
+        data += _x_size;
+        alpha += _x_size;
+      }
+      break;
+    }
+    default:
+      AndroidBitmap_unlockPixels(_env, _bitmap);
+      return 0;
+  }
+
+  AndroidBitmap_unlockPixels(_env, _bitmap);
+  return _y_size;
+}
+
+#endif  // ANDROID

+ 100 - 0
panda/src/android/pview.cxx

@@ -0,0 +1,100 @@
+// Filename: pview.cxx
+// Created by:  rdb (12Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pandaFramework.h"
+#include "pandaSystem.h"
+#include "pystub.h"
+#include "texturePool.h"
+#include "multitexReducer.h"
+#include "sceneGraphReducer.h"
+#include "partGroup.h"
+#include "cardMaker.h"
+#include "bamCache.h"
+#include "virtualFileSystem.h"
+
+// By including checkPandaVersion.h, we guarantee that runtime
+// attempts to run pview will fail if it inadvertently links with the
+// wrong version of libdtool.so/.dll.
+
+#include "checkPandaVersion.h"
+
+int main(int argc, char **argv) {
+  // A call to pystub() to force libpystub.so to be linked in.
+  pystub();
+
+  PandaFramework framework;
+  framework.open_framework(argc, argv);
+  framework.set_window_title("Panda Viewer");
+
+  int hierarchy_match_flags = PartGroup::HMF_ok_part_extra |
+                              PartGroup::HMF_ok_anim_extra;
+
+  WindowFramework *window = framework.open_window();
+  if (window != (WindowFramework *)NULL) {
+    // We've successfully opened a window.
+
+    NodePath loading_np;
+
+    if (true) {
+      // Put up a "loading" message for the user's benefit.
+      NodePath aspect_2d = window->get_aspect_2d();
+      PT(TextNode) loading = new TextNode("loading");
+      loading_np = aspect_2d.attach_new_node(loading);
+      loading_np.set_scale(0.125f);
+      loading->set_text_color(1.0f, 1.0f, 1.0f, 1.0f);
+      loading->set_shadow_color(0.0f, 0.0f, 0.0f, 1.0f);
+      loading->set_shadow(0.04, 0.04);
+      loading->set_align(TextNode::A_center);
+      loading->set_text("Loading...");
+
+      // Allow a couple of frames to go by so the window will be fully
+      // created and the text will be visible.
+      Thread *current_thread = Thread::get_current_thread();
+      framework.do_frame(current_thread);
+      framework.do_frame(current_thread);
+    }
+
+    window->enable_keyboard();
+    window->setup_trackball();
+    framework.get_models().instance_to(window->get_render());
+    //if (argc < 2) {
+      // If we have no arguments, get that trusty old triangle out.
+      //window->load_default_model(framework.get_models());
+    //} else {
+    //  window->load_models(framework.get_models(), argc, argv);
+    //}
+
+    window->load_model(framework.get_models(), "panda-model.egg");
+    window->load_model(framework.get_models(), "panda-walk4.egg");
+
+    window->loop_animations(hierarchy_match_flags);
+
+    // Make sure the textures are preloaded.
+    framework.get_models().prepare_scene(window->get_graphics_output()->get_gsg());
+
+    loading_np.remove_node();
+
+    window->center_trackball(framework.get_models());
+    window->set_anim_controls(true);
+
+    framework.enable_default_keys();
+    framework.main_loop();
+    framework.report_frame_rate(nout);
+  } else {
+    assert(false);
+  }
+
+  framework.close_framework();
+  return (0);
+}

+ 24 - 0
panda/src/android/pview_manifest.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- BEGIN_INCLUDE(manifest) -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="org.panda3d.sdk"
+        android:versionCode="1"
+        android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="9" />
+    <application android:label="Panda Viewer" android:hasCode="true">
+        <activity android:name="org.panda3d.android.PandaActivity"
+                android:label="Panda Viewer" android:theme="@android:style/Theme.NoTitleBar"
+                android:configChanges="orientation|keyboardHidden">
+
+            <meta-data android:name="android.app.lib_name"
+                       android:value="pview" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest> 
+<!-- END_INCLUDE(manifest) -->

+ 2 - 2
panda/src/androiddisplay/androidGraphicsWindow.cxx

@@ -566,8 +566,8 @@ handle_motion_event(const AInputEvent *event) {
     _input_devices[0].button_up(MouseButton::one());
   }
 
-  float x = AMotionEvent_getX(event, 0);
-  float y = AMotionEvent_getY(event, 0);
+  float x = AMotionEvent_getX(event, 0) - _app->contentRect.left;
+  float y = AMotionEvent_getY(event, 0) - _app->contentRect.top;
 
   _input_devices[0].set_pointer_in_window(x, y);
 

+ 45 - 0
panda/src/express/config_express.cxx

@@ -22,6 +22,7 @@
 #include "virtualFile.h"
 #include "virtualFileComposite.h"
 #include "virtualFileMount.h"
+#include "virtualFileMountAndroidAsset.h"
 #include "virtualFileMountMultifile.h"
 #include "virtualFileMountRamdisk.h"
 #include "virtualFileMountSystem.h"
@@ -106,6 +107,9 @@ init_libexpress() {
   VirtualFile::init_type();
   VirtualFileComposite::init_type();
   VirtualFileMount::init_type();
+#ifdef ANDROID
+  VirtualFileMountAndroidAsset::init_type();
+#endif
   VirtualFileMountMultifile::init_type();
   VirtualFileMountRamdisk::init_type();
   VirtualFileMountSystem::init_type();
@@ -191,3 +195,44 @@ get_config_express() {
   static DConfig config_express;
   return config_express;
 }
+
+#ifdef ANDROID
+static JavaVM *panda_jvm = NULL;
+
+////////////////////////////////////////////////////////////////////
+//     Function: JNI_OnLoad
+//  Description: Called by Java when loading this library.
+////////////////////////////////////////////////////////////////////
+jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
+  panda_jvm = jvm;
+  return JNI_VERSION_1_4;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_java_vm
+//  Description: Returns a pointer to the JavaVM object.
+////////////////////////////////////////////////////////////////////
+JavaVM *get_java_vm() {
+  nassertr(panda_jvm != NULL, NULL);
+  return panda_jvm;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_jni_env
+//  Description: Returns a JNIEnv object for the current thread.
+//               If it doesn't already exist, attaches the JVM
+//               to this thread.
+////////////////////////////////////////////////////////////////////
+JNIEnv *get_jni_env() {
+  nassertr(panda_jvm != NULL, NULL);
+  JNIEnv *env = NULL;
+  int status = panda_jvm->GetEnv((void**) &env, JNI_VERSION_1_4);
+
+  if (status < 0 || env == NULL) {
+    express_cat.error() << "JVM is not available in this thread!\n";
+    return NULL;
+  }
+
+  return env;
+}
+#endif

+ 9 - 0
panda/src/express/config_express.h

@@ -28,6 +28,10 @@
 // Include this so interrogate can find it.
 #include "executionEnvironment.h"
 
+#ifdef ANDROID
+#include <jni.h>
+#endif
+
 ConfigureDecl(config_express, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
 NotifyCategoryDecl(express, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
 NotifyCategoryDecl(clock, EXPCL_PANDAEXPRESS, EXPTP_PANDAEXPRESS);
@@ -62,4 +66,9 @@ END_PUBLISH
 
 extern EXPCL_PANDAEXPRESS void init_libexpress();
 
+#ifdef ANDROID
+extern EXPCL_PANDAEXPRESS JavaVM *get_java_vm();
+extern EXPCL_PANDAEXPRESS JNIEnv *get_jni_env();
+#endif
+
 #endif /* __CONFIG_UTIL_H__ */

+ 1 - 0
panda/src/express/p3express_composite2.cxx

@@ -19,6 +19,7 @@
 #include "virtualFileComposite.cxx"
 #include "virtualFileList.cxx"
 #include "virtualFileMount.cxx"
+#include "virtualFileMountAndroidAsset.cxx"
 #include "virtualFileMountMultifile.cxx"
 #include "virtualFileMountRamdisk.cxx"
 #include "virtualFileMountSystem.cxx"

+ 35 - 0
panda/src/express/virtualFileMountAndroidAsset.I

@@ -0,0 +1,35 @@
+// Filename: virtualFileMountAndroidAsset.cxx
+// Created by:  rdb (21Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+VirtualFileMountAndroidAsset::
+VirtualFileMountAndroidAsset(AAssetManager *mgr, const string &apk_path) :
+  _asset_mgr(mgr), _apk_path(apk_path)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::AssetStream::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE VirtualFileMountAndroidAsset::AssetStream::
+AssetStream(AAsset *asset) :
+  istream(new AssetStreamBuf(asset)) {
+}

+ 420 - 0
panda/src/express/virtualFileMountAndroidAsset.cxx

@@ -0,0 +1,420 @@
+// Filename: virtualFileMountAndroidAsset.cxx
+// Created by:  rdb (21Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifdef ANDROID
+
+#include "virtualFileMountAndroidAsset.h"
+#include "virtualFileSystem.h"
+
+#ifndef NDEBUG
+#include <sys/stat.h>
+#endif
+
+TypeHandle VirtualFileMountAndroidAsset::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+VirtualFileMountAndroidAsset::
+~VirtualFileMountAndroidAsset() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::get_fd
+//       Access: Public
+//  Description: Returns a file descriptor that can be used to read
+//               the asset if it was stored uncompressed and
+//               unencrypted.  Returns a valid fd or -1.
+////////////////////////////////////////////////////////////////////
+int VirtualFileMountAndroidAsset::
+get_fd(const Filename &file, off_t *start, off_t *length) const {
+  AAsset* asset;
+  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
+
+  int fd = AAsset_openFileDescriptor(asset, start, length);
+  AAsset_close(asset);
+  return fd;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::has_file
+//       Access: Public, Virtual
+//  Description: Returns true if the indicated file exists within the
+//               mount system.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountAndroidAsset::
+has_file(const Filename &file) const {
+  return (file.empty() || is_directory(file) || is_regular_file(file));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::is_directory
+//       Access: Public, Virtual
+//  Description: Returns true if the indicated file exists within the
+//               mount system and is a directory.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountAndroidAsset::
+is_directory(const Filename &file) const {
+  // This is the only way - AAssetManager_openDir also works for files.
+  //AAssetDir *dir = AAssetManager_openDir(_asset_mgr, file.c_str());
+
+  //express_cat.error() << "is_directory " << file << " - " << dir << "\n";
+
+  //if (dir == NULL) {
+  //  return false;
+  //}
+  //AAssetDir_close(dir);
+
+  // openDir doesn't return NULL for ordinary files!
+  return !is_regular_file(file);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::is_regular_file
+//       Access: Public, Virtual
+//  Description: Returns true if the indicated file exists within the
+//               mount system and is a regular file.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountAndroidAsset::
+is_regular_file(const Filename &file) const {
+  // I'm afraid the only way to see if it exists is to try and open it.
+  AAsset* asset;
+  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
+
+  express_cat.error() << "is_regular_file " << file << " - " << asset << "\n";
+
+  if (asset == NULL) {
+    return false;
+  }
+  AAsset_close(asset);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::read_file
+//       Access: Public, Virtual
+//  Description: Fills up the indicated pvector with the contents of
+//               the file, if it is a regular file.  Returns true on
+//               success, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountAndroidAsset::
+read_file(const Filename &file, bool do_uncompress,
+          pvector<unsigned char> &result) const {
+  if (do_uncompress) {
+    // If the file is to be decompressed, we'd better just use the
+    // higher-level implementation, which includes support for
+    // on-the-fly decompression.
+    return VirtualFileMount::read_file(file, do_uncompress, result);
+  }
+
+  // But if we're just reading a straight file, let's just read
+  // it here to avoid all of the streambuf nonsense.
+  result.clear();
+
+  AAsset* asset;
+  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_STREAMING);
+  if (asset == (AAsset *)NULL) {
+    express_cat.info()
+      << "Unable to read " << file << "\n";
+  }
+
+  // Reserve enough space to hold the entire file.
+  off_t file_size = AAsset_getLength(asset);
+  if (file_size == 0) {
+    return true;
+  } else if (file_size > 0) {
+    result.reserve((size_t)file_size);
+  }
+
+  static const size_t buffer_size = 4096;
+  char buffer[buffer_size];
+
+  int count = AAsset_read(asset, buffer, buffer_size);
+  while (count > 0) {
+    thread_consider_yield();
+    result.insert(result.end(), buffer, buffer + count);
+    count = AAsset_read(asset, buffer, buffer_size);
+  }
+
+  AAsset_close(asset);
+  return (count == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::open_read_file
+//       Access: Public, Virtual
+//  Description: Opens the file for reading, if it exists.  Returns a
+//               newly allocated istream on success (which you should
+//               eventually delete when you are done reading).
+//               Returns NULL on failure.
+////////////////////////////////////////////////////////////////////
+istream *VirtualFileMountAndroidAsset::
+open_read_file(const Filename &file) const {
+  AAsset* asset;
+  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
+  if (asset == (AAsset *)NULL) {
+    return NULL;
+  }
+
+  AssetStream *stream = new AssetStream(asset);
+  return (istream *) stream;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::get_file_size
+//       Access: Published, Virtual
+//  Description: Returns the current size on disk (or wherever it is)
+//               of the already-open file.  Pass in the stream that
+//               was returned by open_read_file(); some
+//               implementations may require this stream to determine
+//               the size.
+////////////////////////////////////////////////////////////////////
+off_t VirtualFileMountAndroidAsset::
+get_file_size(const Filename &file, istream *in) const {
+  // If it's already open, get the AAsset pointer from the streambuf.
+  const AssetStreamBuf *buf = (const AssetStreamBuf *) in->rdbuf();
+  off_t length = AAsset_getLength(buf->_asset);
+  return length;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::get_file_size
+//       Access: Published, Virtual
+//  Description: Returns the current size on disk (or wherever it is)
+//               of the file before it has been opened.
+////////////////////////////////////////////////////////////////////
+off_t VirtualFileMountAndroidAsset::
+get_file_size(const Filename &file) const {
+  AAsset* asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
+  off_t length = AAsset_getLength(asset);
+  AAsset_close(asset);
+  return length;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::get_timestamp
+//       Access: Published, Virtual
+//  Description: Returns a time_t value that represents the time the
+//               file was last modified, to within whatever precision
+//               the operating system records this information (on a
+//               Windows95 system, for instance, this may only be
+//               accurate to within 2 seconds).
+//
+//               If the timestamp cannot be determined, either because
+//               it is not supported by the operating system or
+//               because there is some error (such as file not found),
+//               returns 0.
+////////////////////////////////////////////////////////////////////
+time_t VirtualFileMountAndroidAsset::
+get_timestamp(const Filename &file) const {
+  // There's no obvious way to get a timestamp from an Android asset.
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::get_system_info
+//       Access: Public, Virtual
+//  Description: Populates the SubfileInfo structure with the data
+//               representing where the file actually resides on disk,
+//               if this is knowable.  Returns true if the file might
+//               reside on disk, and the info is populated, or false
+//               if it might not (or it is not known where the file
+//               resides), in which case the info is meaningless.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountAndroidAsset::
+get_system_info(const Filename &file, SubfileInfo &info) {
+  off_t start, length;
+  int fd = get_fd(file, &start, &length);
+
+#ifndef NDEBUG
+  // Double-check that this fd actually points to the apk.
+  struct stat st1, st2;
+  nassertr(fstat(fd, &st1) == 0, false);
+  nassertr(stat(_apk_path.c_str(), &st2) == 0, false);
+  nassertr(st1.st_dev == st2.st_dev, false);
+  nassertr(st1.st_ino == st2.st_ino, false);
+#endif
+
+  // We don't actually need the file descriptor, so close it.
+  close(fd);
+
+  info = SubfileInfo(_apk_path, start, length); 
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::scan_directory
+//       Access: Public, Virtual
+//  Description: Fills the given vector up with the list of filenames
+//               that are local to this directory, if the filename is
+//               a directory.  Returns true if successful, or false if
+//               the file is not a directory or cannot be read.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountAndroidAsset::
+scan_directory(vector_string &contents, const Filename &dir) const {
+  AAssetDir *asset_dir = AAssetManager_openDir(_asset_mgr, dir.c_str());
+  if (asset_dir == NULL) {
+    return false;
+  }
+
+  // Note: this returns the full path.
+  const char *fullpath = AAssetDir_getNextFileName(asset_dir);
+
+  while (fullpath != NULL) {
+    express_cat.error() << fullpath << "\n"; // DEBUG
+    // Determine the basename and add it to the vector.
+    Filename fname (fullpath);
+    contents.push_back(fname.get_basename());
+    fullpath = AAssetDir_getNextFileName(asset_dir);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::AssetStream::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+VirtualFileMountAndroidAsset::AssetStream::
+~AssetStream() {
+  delete rdbuf();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::AssetStreamBuf::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+VirtualFileMountAndroidAsset::AssetStreamBuf::
+AssetStreamBuf(AAsset *asset) :
+  _asset(asset) {
+
+#ifdef PHAVE_IOSTREAM
+  char *buf = new char[4096];
+  char *ebuf = buf + 4096;
+  setg(buf, ebuf, ebuf);
+
+#else
+  allocate();
+  setg(base(), ebuf(), ebuf());
+#endif
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::AssetStreamBuf::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+VirtualFileMountAndroidAsset::AssetStreamBuf::
+~AssetStreamBuf() {
+  AAsset_close(_asset);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::AssetStreamBuf::seekoff
+//       Access: Public, Virtual
+//  Description: Implements seeking within the stream.
+////////////////////////////////////////////////////////////////////
+streampos VirtualFileMountAndroidAsset::AssetStreamBuf::
+seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
+  size_t n = egptr() - gptr();
+
+  int whence;
+  switch (dir) {
+  case ios_base::beg:
+    whence = SEEK_SET;
+    break;
+  case ios_base::cur:
+    if (off == 0) {
+      // Just requesting the current position,
+      // no need to void the buffer.
+      return AAsset_seek(_asset, 0, SEEK_CUR) - n;
+
+    } else if (gptr() + off >= eback() && gptr() + off < egptr()) {
+      // We can seek around within the buffer.
+      gbump(off);
+      return AAsset_seek(_asset, 0, SEEK_CUR) - n + off;
+    }
+    whence = SEEK_CUR;
+    break;
+  case ios_base::end:
+    whence = SEEK_END;
+    break;
+  default:
+    return pos_type(-1);
+  }
+  gbump(n);
+  return AAsset_seek(_asset, off, whence);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::AssetStreamBuf::seekpos
+//       Access: Public, Virtual
+//  Description: A variant on seekoff() to implement seeking within a
+//               stream.
+//
+//               The MSDN Library claims that it is only necessary to
+//               redefine seekoff(), and not seekpos() as well, as the
+//               default implementation of seekpos() is supposed to
+//               map to seekoff() exactly as I am doing here; but in
+//               fact it must do something else, because seeking
+//               didn't work on Windows until I redefined this
+//               function as well.
+////////////////////////////////////////////////////////////////////
+streampos VirtualFileMountAndroidAsset::AssetStreamBuf::
+seekpos(streampos pos, ios_openmode which) {
+  size_t n = egptr() - gptr();
+  gbump(n);
+  return AAsset_seek(_asset, pos, SEEK_SET);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountAndroidAsset::AssetStreamBuf::underflow
+//       Access: Protected, Virtual
+//  Description: Called by the system istream implementation when its
+//               internal buffer needs more characters.
+////////////////////////////////////////////////////////////////////
+int VirtualFileMountAndroidAsset::AssetStreamBuf::
+underflow() {
+  // Sometimes underflow() is called even if the buffer is not empty.
+  if (gptr() >= egptr()) {
+    // Mark the buffer filled (with buffer_size bytes).
+    size_t buffer_size = egptr() - eback();
+    gbump(-(int)buffer_size);
+
+    streamsize read_count;
+    read_count = AAsset_read(_asset, gptr(), buffer_size);
+
+    if (read_count != buffer_size) {
+      // Oops, we didn't read what we thought we would.
+      if (read_count == 0) {
+        gbump(buffer_size);
+        return EOF;
+      }
+
+      // Slide what we did read to the top of the buffer.
+      nassertr(read_count < buffer_size, EOF);
+      size_t delta = buffer_size - read_count;
+      memmove(gptr() + delta, gptr(), read_count);
+      gbump(delta);
+    }
+  }
+
+  return (unsigned char)*gptr();
+}
+
+#endif  // ANDROID

+ 107 - 0
panda/src/express/virtualFileMountAndroidAsset.h

@@ -0,0 +1,107 @@
+// Filename: virtualFileMountAndroidAsset.h
+// Created by:  rdb (21Jan13)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef VIRTUALFILEMOUNTANDROIDASSET_H
+#define VIRTUALFILEMOUNTANDROIDASSET_H
+
+#ifdef ANDROID
+
+#include "pandabase.h"
+
+#include "virtualFileMount.h"
+#include "multifile.h"
+#include "pointerTo.h"
+
+#include <android/asset_manager.h>
+
+////////////////////////////////////////////////////////////////////
+//       Class : VirtualFileMountAndroidAsset
+// Description : Maps a Multifile's contents into the
+//               VirtualFileSystem.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS VirtualFileMountAndroidAsset : public VirtualFileMount {
+PUBLISHED:
+  INLINE VirtualFileMountAndroidAsset(AAssetManager *mgr, const string &apk_path);
+  virtual ~VirtualFileMountAndroidAsset();
+
+public:
+  int get_fd(const Filename &file, off_t *start, off_t *end) const;
+
+  virtual bool has_file(const Filename &file) const;
+  virtual bool is_directory(const Filename &file) const;
+  virtual bool is_regular_file(const Filename &file) const;
+
+  virtual bool read_file(const Filename &file, bool do_uncompress,
+                         pvector<unsigned char> &result) const;
+
+  virtual istream *open_read_file(const Filename &file) const;
+  virtual off_t get_file_size(const Filename &file, istream *stream) const;
+  virtual off_t get_file_size(const Filename &file) const;
+  virtual time_t get_timestamp(const Filename &file) const;
+  virtual bool get_system_info(const Filename &file, SubfileInfo &info);
+
+  virtual bool scan_directory(vector_string &contents, 
+                              const Filename &dir) const;
+
+private:
+  AAssetManager *_asset_mgr;
+  string _apk_path;
+
+  class AssetStream : public istream {
+  public:
+    INLINE AssetStream(AAsset *asset);
+    virtual ~AssetStream();
+  };
+
+  class AssetStreamBuf : public streambuf {
+  public:
+    AssetStreamBuf(AAsset *asset);
+    virtual ~AssetStreamBuf();
+
+    virtual streampos seekoff(streamoff off, ios_seekdir dir, ios_openmode which);
+    virtual streampos seekpos(streampos pos, ios_openmode which);
+
+  protected:
+    virtual int underflow();
+
+  private:
+    AAsset *_asset;
+    off_t _offset;
+
+    friend class VirtualFileMountAndroidAsset;
+  };
+
+public:
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    VirtualFileMount::init_type();
+    register_type(_type_handle, "VirtualFileMountAndroidAsset",
+                  VirtualFileMount::get_class_type());
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "virtualFileMountAndroidAsset.I"
+
+#endif  // ANDROID
+
+#endif

+ 4 - 0
panda/src/gobj/vertexDataSaveFile.cxx

@@ -127,7 +127,11 @@ VertexDataSaveFile(const Filename &directory, const string &prefix,
     
     // Now try to lock the file, so we can be sure that no other
     // process is simultaneously writing to the same save file.
+#ifdef HAVE_LOCKF
     int result = lockf(_fd, F_TLOCK, 0);
+#else
+    int result = flock(_fd, LOCK_EX | LOCK_NB);
+#endif
     if (result == 0) {
       // We've got the file.  Truncate it first, for good measure, in
       // case there's an old version of the file we picked up.

+ 24 - 1
panda/src/pipeline/threadPosixImpl.cxx

@@ -22,6 +22,11 @@
 #include "config_pipeline.h"
 #include <sched.h>
 
+#ifdef ANDROID
+#include "config_express.h"
+#include <jni.h>
+#endif
+
 pthread_key_t ThreadPosixImpl::_pt_ptr_index = 0;
 bool ThreadPosixImpl::_got_pt_ptr_index = false;
 
@@ -217,6 +222,18 @@ root_func(void *data) {
       self->_status = S_running;
       self->_mutex.release();
     }
+
+#ifdef ANDROID
+    // Attach the Java VM to allow calling Java functions in this thread.
+    JavaVM *jvm = get_java_vm();
+    JNIEnv *env;
+    if (jvm == NULL || jvm->AttachCurrentThread(&env, NULL) != 0) {
+      thread_cat.error()
+        << "Failed to attach Java VM to thread "
+        << self->_parent_obj->get_name() << "!\n";
+      env = NULL;
+    }
+#endif
     
     self->_parent_obj->thread_main();
     
@@ -235,7 +252,13 @@ root_func(void *data) {
       self->_status = S_finished;
       self->_mutex.release();
     }
-    
+
+#ifdef ANDROID
+    if (env != NULL) {
+      jvm->DetachCurrentThread();
+    }
+#endif
+
     // Now drop the parent object reference that we grabbed in start().
     // This might delete the parent object, and in turn, delete the
     // ThreadPosixImpl object.