xwords/xwords4/android/jni/andutils.c
Eric House 28318eb38a shorten file path in logs
Couldn't figure out how to do it at compile-time on Android.
2023-10-29 08:09:37 -07:00

1054 lines
31 KiB
C

/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */
/*
* Copyright © 2009 - 2023 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <sys/time.h>
#include <time.h>
#include "andutils.h"
#include "paths.h"
#include "comtypes.h"
#include "xwstream.h"
#include "strutils.h"
#include "dbgutil.h"
void
and_assert( const char* test, int line, const char* file, const char* func )
{
RAW_LOG( "assertion \"%s\" failed: line %d in %s() in %s",
test, line, func, file );
XP_LOGF( "assertion \"%s\" failed: line %d in %s() in %s",
test, line, func, file );
/* give log a chance to write before __android_log_assert() kills the
process */
struct timespec req = {
.tv_nsec = 400000000, /* 4/10 second */
};
nanosleep( &req, NULL );
__android_log_assert( test, "ASSERT", "line %d in %s() in %s",
line, func, file );
}
#ifdef __LITTLE_ENDIAN
XP_U32
and_ntohl(XP_U32 ll)
{
XP_U32 result = 0L;
for ( int ii = 0; ii < 4; ++ii ) {
result <<= 8;
result |= ll & 0x000000FF;
ll >>= 8;
}
return result;
}
XP_U16
and_ntohs( XP_U16 ss )
{
XP_U16 result;
result = ss << 8;
result |= ss >> 8;
return result;
}
XP_U32
and_htonl( XP_U32 ll )
{
return and_ntohl( ll );
}
XP_U16
and_htons( XP_U16 ss )
{
return and_ntohs( ss );
}
#else
error error error
#endif
jfieldID
getFieldID( JNIEnv* env, jobject obj, const char* fieldName, const char* fieldSig )
{
jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls );
jfieldID fid = (*env)->GetFieldID( env, cls, fieldName, fieldSig );
XP_ASSERT( !!fid );
deleteLocalRef( env, cls );
return fid;
}
int
getInt( JNIEnv* env, jobject obj, const char* name )
{
jfieldID fid = getFieldID( env, obj, name, "I");
XP_ASSERT( !!fid );
int result = (*env)->GetIntField( env, obj, fid );
return result;
}
void
getInts( JNIEnv* env, void* cobj, jobject jobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
uint8_t* ptr = ((uint8_t*)cobj) + si->offset;
int val = getInt( env, jobj, si->name );
switch( si->siz ) {
case 4:
*(uint32_t*)ptr = val;
break;
case 2:
*(uint16_t*)ptr = val;
break;
case 1:
*ptr = val;
break;
}
/* XP_LOGF( "%s: wrote int %s of size %d with val %d at offset %d", */
/* __func__, si->name, si->siz, val, si->offset ); */
}
}
void
setInt( JNIEnv* env, jobject obj, const char* name, int value )
{
jfieldID fid = getFieldID( env, obj, name, "I");
XP_ASSERT( !!fid );
(*env)->SetIntField( env, obj, fid, value );
}
void
setInts( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
uint8_t* ptr = ((uint8_t*)cobj) + si->offset;
int val;
switch( si->siz ) {
case 4:
val = *(uint32_t*)ptr;
break;
case 2:
val = *(uint16_t*)ptr;
break;
case 1:
val = *ptr;
break;
default:
val = 0;
XP_ASSERT(0);
}
setInt( env, jobj, si->name, val );
/* XP_LOGFF( "read int %s of size %d with val %d/0x%x from offset %d", */
/* si->name, si->siz, val, val, si->offset ); */
}
}
bool
setBool( JNIEnv* env, jobject obj, const char* name, bool value )
{
bool success = false;
jfieldID fid = getFieldID( env, obj, name, "Z" );
if ( 0 != fid ) {
(*env)->SetBooleanField( env, obj, fid, value );
success = true;
}
return success;
}
void
setBools( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
XP_Bool val = *(XP_Bool*)(((uint8_t*)cobj)+si->offset);
setBool( env, jobj, si->name, val );
/* XP_LOGF( "%s: read bool %s with val %d from offset %d", __func__, */
/* si->name, val, si->offset ); */
}
}
bool
setString( JNIEnv* env, jobject container, const char* fieldName, const XP_UCHAR* value )
{
// XP_LOGFF( "(fieldName=%s, val=%s)", fieldName, value );
bool success = false;
/* jfieldID fid = getFieldID( env, obj, name, "Ljava/lang/String;" ); */
jstring str = (*env)->NewStringUTF( env, value );
setObjectField( env, container, fieldName, "Ljava/lang/String;", str );
success = true;
#ifdef DEBUG
XP_UCHAR buf[1024];
getString( env, container, fieldName, buf, VSIZE(buf) );
XP_ASSERT( !value || 0 == XP_STRCMP( buf, value ) );
#endif
// XP_LOGFF( "(%s, %s) => %s", fieldName, value, boolToStr(success) );
return success;
}
void
getStrings( JNIEnv* env, void* cobj, jobject jobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
XP_UCHAR* buf = (XP_UCHAR*)(((uint8_t*)cobj) + si->offset);
getString( env, jobj, si->name, buf, si->siz );
}
}
void
setStrings( JNIEnv* env, jobject jobj, void* cobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
// XP_LOGF( "calling setString(%s)", si->name );
XP_UCHAR* val = (XP_UCHAR*)(((uint8_t*)cobj) + si->offset);
setString( env, jobj, si->name, val );
}
}
void
getString( JNIEnv* env, jobject container, const char* name, XP_UCHAR* buf,
int bufLen )
{
jstring jstr = getObjectField( env, container, name, "Ljava/lang/String;" );
jsize len = 0;
if ( !!jstr ) { /* might be null */
len = (*env)->GetStringUTFLength( env, jstr );
XP_ASSERT( len < bufLen );
const char* chars = (*env)->GetStringUTFChars( env, jstr, NULL );
XP_MEMCPY( buf, chars, len );
(*env)->ReleaseStringUTFChars( env, jstr, chars );
deleteLocalRef( env, jstr );
}
buf[len] = '\0';
// XP_LOGFF( "(field: %s) => '%s'", name, buf );
}
XP_UCHAR*
getStringCopy( MPFORMAL JNIEnv* env, jstring jstr )
{
XP_UCHAR* result = NULL;
if ( NULL != jstr ) {
const char* chars = (*env)->GetStringUTFChars( env, jstr, NULL );
result = copyString( mpool, chars );
(*env)->ReleaseStringUTFChars( env, jstr, chars );
}
return result;
}
static jobject
getObjectFieldWithFID( JNIEnv* env, jobject obj, const char* fieldName,
const char* sig, jfieldID* fidp )
{
jfieldID fid = getFieldID( env, obj, fieldName, sig );
XP_ASSERT( !!fid );
if ( !!fidp ) {
*fidp = fid;
}
jobject result = (*env)->GetObjectField( env, obj, fid );
return result;
}
jobject
getObjectField( JNIEnv* env, jobject container, const char* name, const char* sig )
{
return getObjectFieldWithFID( env, container, name, sig, NULL );
}
void
setObjectField( JNIEnv* env, jobject obj, const char* name, const char* sig,
jobject val )
{
jfieldID fid = getFieldID( env, obj, name, sig );
XP_ASSERT( !!fid );
(*env)->SetObjectField( env, obj, fid, val );
deleteLocalRef( env, val );
}
bool
getBool( JNIEnv* env, jobject obj, const char* name )
{
bool result;
jfieldID fid = getFieldID( env, obj, name, "Z");
XP_ASSERT( !!fid );
result = (*env)->GetBooleanField( env, obj, fid );
return result;
}
void
getBools( JNIEnv* env, void* cobj, jobject jobj, const SetInfo* sis, XP_U16 nSis )
{
for ( int ii = 0; ii < nSis; ++ii ) {
const SetInfo* si = &sis[ii];
XP_Bool val = getBool( env, jobj, si->name );
*(XP_Bool*)(((uint8_t*)cobj)+si->offset) = val;
/* XP_LOGF( "%s: wrote bool %s with val %d at offset %d", __func__, */
/* si->name, val, si->offset ); */
}
}
jintArray
makeIntArray( JNIEnv* env, int count, const void* vals, size_t elemSize )
{
jintArray array = (*env)->NewIntArray( env, count );
XP_ASSERT( !!array );
jint* elems = (*env)->GetIntArrayElements( env, array, NULL );
XP_ASSERT( !!elems );
jint elem;
for ( int ii = 0; ii < count; ++ii ) {
switch( elemSize ) {
case sizeof(XP_U32):
elem = *(XP_U32*)vals;
break;
case sizeof(XP_U16):
elem = *(XP_U16*)vals;
break;
case sizeof(XP_U8):
elem = *(XP_U8*)vals;
break;
default:
XP_ASSERT(0);
break;
}
vals += elemSize;
elems[ii] = elem;
}
(*env)->ReleaseIntArrayElements( env, array, elems, 0 );
return array;
}
void
setIntArray( JNIEnv* env, jobject jowner, const char* fieldName,
int count, const void* vals, size_t elemSize )
{
jintArray jarr = makeIntArray( env, count, vals, elemSize );
setObjectField( env, jowner, fieldName, "[I", jarr );
}
jbyteArray
makeByteArray( JNIEnv* env, int siz, const jbyte* vals )
{
jbyteArray array = (*env)->NewByteArray( env, siz );
XP_ASSERT( !!array );
if ( !!vals ) {
jbyte* elems = (*env)->GetByteArrayElements( env, array, NULL );
XP_ASSERT( !!elems );
XP_MEMCPY( elems, vals, siz * sizeof(*elems) );
(*env)->ReleaseByteArrayElements( env, array, elems, 0 );
}
return array;
}
jbyteArray
streamToBArray( JNIEnv* env, XWStreamCtxt* stream )
{
int nBytes = stream_getSize( stream );
jbyteArray result = (*env)->NewByteArray( env, nBytes );
jbyte* jelems = (*env)->GetByteArrayElements( env, result, NULL );
stream_getBytes( stream, jelems, nBytes );
(*env)->ReleaseByteArrayElements( env, result, jelems, 0 );
return result;
}
void
setBoolArray( JNIEnv* env, jbooleanArray jarr, int count,
const jboolean* vals )
{
jboolean* elems = (*env)->GetBooleanArrayElements( env, jarr, NULL );
XP_ASSERT( !!elems );
XP_MEMCPY( elems, vals, count * sizeof(*elems) );
(*env)->ReleaseBooleanArrayElements( env, jarr, elems, 0 );
}
jbooleanArray
makeBooleanArray( JNIEnv* env, int siz, const jboolean* vals )
{
jbooleanArray array = (*env)->NewBooleanArray( env, siz );
XP_ASSERT( !!array );
if ( !!vals ) {
setBoolArray( env, array, siz, vals );
}
return array;
}
void
getIntsFromArray( JNIEnv* env, int dest[], jintArray arr, int count, bool del )
{
jint* ints = (*env)->GetIntArrayElements(env, arr, 0);
for ( int ii = 0; ii < count; ++ii ) {
dest[ii] = ints[ii];
}
(*env)->ReleaseIntArrayElements( env, arr, ints, 0 );
if ( del ) {
deleteLocalRef( env, arr );
}
}
void
setIntInArray( JNIEnv* env, jintArray arr, int index, int val )
{
jint* ints = (*env)->GetIntArrayElements( env, arr, 0 );
#ifdef DEBUG
jsize len = (*env)->GetArrayLength( env, arr );
XP_ASSERT( len > index );
#endif
ints[index] = val;
(*env)->ReleaseIntArrayElements( env, arr, ints, 0 );
}
jobjectArray
makeStringArray( JNIEnv* env, const int count, const XP_UCHAR* const* vals )
{
jobjectArray jarray;
{
jclass clas = (*env)->FindClass(env, "java/lang/String");
jstring empty = (*env)->NewStringUTF( env, "" );
jarray = (*env)->NewObjectArray( env, count, clas, empty );
deleteLocalRefs( env, clas, empty, DELETE_NO_REF );
}
for ( int ii = 0; !!vals && ii < count; ++ii ) {
jstring jstr = (*env)->NewStringUTF( env, vals[ii] );
(*env)->SetObjectArrayElement( env, jarray, ii, jstr );
deleteLocalRef( env, jstr );
}
return jarray;
}
void
setStringArray( JNIEnv* env, jobject jowner, const char* ownerField,
int count, const XP_UCHAR** vals )
{
jobjectArray jaddrs = makeStringArray( env, count, vals );
setObjectField( env, jowner, ownerField, "[Ljava/lang/String;", jaddrs );
}
jobjectArray
makeByteArrayArray( JNIEnv* env, int siz )
{
jclass clas = (*env)->FindClass( env, "[B" );
jobjectArray result = (*env)->NewObjectArray( env, siz, clas, NULL );
deleteLocalRef( env, clas );
return result;
}
jstring
streamToJString( JNIEnv* env, XWStreamCtxt* stream )
{
int len = stream_getSize( stream );
XP_UCHAR buf[1 + len];
stream_getBytes( stream, buf, len );
buf[len] = '\0';
jstring jstr = (*env)->NewStringUTF( env, buf );
return jstr;
}
jmethodID
getMethodID( JNIEnv* env, jobject obj, const char* proc, const char* sig )
{
XP_ASSERT( !!env );
jclass cls = (*env)->GetObjectClass( env, obj );
XP_ASSERT( !!cls );
#ifdef DEBUG
char buf[128] = {0};
/* int len = sizeof(buf); */
/* getClassName( env, obj, buf, &len ); */
#endif
jmethodID mid = (*env)->GetMethodID( env, cls, proc, sig );
if ( !mid ) {
XP_LOGFF( "no mid for proc %s, sig %s in object of class %s",
proc, sig, buf );
}
XP_ASSERT( !!mid );
deleteLocalRef( env, cls );
return mid;
}
void
setTypeSetFieldIn( JNIEnv* env, const CommsAddrRec* addr, jobject jTarget,
const char* fieldName )
{
jobject jtypset = addrTypesToJ( env, addr );
XP_ASSERT( !!jtypset );
setObjectField( env, jTarget, fieldName,
"L" PKG_PATH("jni/CommsAddrRec$CommsConnTypeSet") ";",
jtypset );
}
jobject
makeObject( JNIEnv* env, const char* className, const char* initSig, ... )
{
jclass clazz = (*env)->FindClass( env, className );
XP_ASSERT( !!clazz );
jmethodID mid = (*env)->GetMethodID( env, clazz, "<init>", initSig );
XP_ASSERT( !!mid );
va_list ap;
va_start( ap, initSig );
jobject result = (*env)->NewObjectV( env, clazz, mid, ap );
va_end( ap );
deleteLocalRef( env, clazz );
return result;
}
jobject
makeObjectEmptyConstr( JNIEnv* env, const char* className )
{
return makeObject( env, className, "()V" );
}
jobject
makeJAddr( JNIEnv* env, const CommsAddrRec* addr )
{
jobject jaddr = NULL;
if ( NULL != addr ) {
jaddr = makeObjectEmptyConstr( env, PKG_PATH("jni/CommsAddrRec") );
setJAddrRec( env, jaddr, addr );
}
return jaddr;
}
/* Copy C object data into Java object */
jobject
setJAddrRec( JNIEnv* env, jobject jaddr, const CommsAddrRec* addr )
{
XP_ASSERT( !!addr );
setTypeSetFieldIn( env, addr, jaddr, "conTypes" );
CommsConnType typ;
for ( XP_U32 st = 0; addr_iter( addr, &typ, &st ); ) {
switch ( typ ) {
case COMMS_CONN_NONE:
break;
#ifdef XWFEATURE_RELAY
case COMMS_CONN_RELAY:
setInt( env, jaddr, "ip_relay_port", addr->u.ip_relay.port );
setString( env, jaddr, "ip_relay_hostName", addr->u.ip_relay.hostName );
setString( env, jaddr, "ip_relay_invite", addr->u.ip_relay.invite );
setBool( env, jaddr, "ip_relay_seeksPublicRoom",
addr->u.ip_relay.seeksPublicRoom );
setBool( env, jaddr, "ip_relay_advertiseRoom",
addr->u.ip_relay.advertiseRoom );
break;
#endif
case COMMS_CONN_SMS:
setString( env, jaddr, "sms_phone", addr->u.sms.phone );
setInt( env, jaddr, "sms_port", addr->u.sms.port );
break;
case COMMS_CONN_BT:
setString( env, jaddr, "bt_hostName", addr->u.bt.hostName );
setString( env, jaddr, "bt_btAddr", addr->u.bt.btAddr.chars );
break;
case COMMS_CONN_P2P:
setString( env, jaddr, "p2p_addr", addr->u.p2p.mac_addr );
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT: {
XP_UCHAR buf[32];
formatMQTTDevID( &addr->u.mqtt.devID, buf, VSIZE(buf) );
setString( env, jaddr, "mqtt_devID", buf );
}
break;
default:
XP_ASSERT(0);
}
}
return jaddr;
}
jobject
addrTypesToJ( JNIEnv* env, const CommsAddrRec* addr )
{
XP_ASSERT( !!addr );
jobject result =
makeObjectEmptyConstr( env, PKG_PATH("jni/CommsAddrRec$CommsConnTypeSet") );
XP_ASSERT( !!result );
jmethodID mid2 = getMethodID( env, result, "add",
"(Ljava/lang/Object;)Z" );
XP_ASSERT( !!mid2 );
CommsConnType typ;
for ( XP_U32 st = 0; addr_iter( addr, &typ, &st ); ) {
jobject jtyp = intToJEnum( env, typ,
PKG_PATH("jni/CommsAddrRec$CommsConnType") );
XP_ASSERT( !!jtyp );
(*env)->CallBooleanMethod( env, result, mid2, jtyp );
deleteLocalRef( env, jtyp );
}
return result;
}
jobjectArray
makeAddrArray( JNIEnv* env, XP_U16 count, const CommsAddrRec* addrs )
{
jclass clas = (*env)->FindClass( env, PKG_PATH("jni/CommsAddrRec") );
jobjectArray result = (*env)->NewObjectArray( env, count, clas, NULL );
for ( int ii = 0; ii < count; ++ii ) {
jobject jaddr = makeJAddr( env, &addrs[ii] );
(*env)->SetObjectArrayElement( env, result, ii, jaddr );
deleteLocalRef( env, jaddr );
}
deleteLocalRef( env, clas );
return result;
}
/* Writes a java version of CommsAddrRec into a C one */
void
getJAddrRec( JNIEnv* env, CommsAddrRec* addr, jobject jaddr )
{
XP_MEMSET( addr, 0, sizeof(*addr) );
/* Iterate over types in the set in jaddr, and for each call
addr_addType() and then copy in the types. */
jobject jtypeset = getObjectField( env, jaddr, "conTypes",
"L" PKG_PATH("jni/CommsAddrRec$CommsConnTypeSet") ";" );
XP_ASSERT( !!jtypeset );
jmethodID mid = getMethodID( env, jtypeset, "getTypes",
"()[L" PKG_PATH("jni/CommsAddrRec$CommsConnType;") );
XP_ASSERT( !!mid );
jobject jtypesarr = (*env)->CallObjectMethod( env, jtypeset, mid );
XP_ASSERT( !!jtypesarr );
jsize len = (*env)->GetArrayLength( env, jtypesarr );
for ( int ii = 0; ii < len; ++ii ) {
jobject jtype = (*env)->GetObjectArrayElement( env, jtypesarr, ii );
jint asInt = jEnumToInt( env, jtype );
deleteLocalRef( env, jtype );
CommsConnType typ = (CommsConnType)asInt;
addr_addType( addr, typ );
switch ( typ ) {
case COMMS_CONN_RELAY:
#ifdef XWFEATURE_RELAY
addr->u.ip_relay.port = getInt( env, jaddr, "ip_relay_port" );
getString( env, jaddr, "ip_relay_hostName", addr->u.ip_relay.hostName,
VSIZE(addr->u.ip_relay.hostName) );
getString( env, jaddr, "ip_relay_invite", addr->u.ip_relay.invite,
VSIZE(addr->u.ip_relay.invite) );
addr->u.ip_relay.seeksPublicRoom =
getBool( env, jaddr, "ip_relay_seeksPublicRoom" );
addr->u.ip_relay.advertiseRoom =
getBool( env, jaddr, "ip_relay_advertiseRoom" );
#endif
break;
case COMMS_CONN_SMS:
getString( env, jaddr, "sms_phone", addr->u.sms.phone,
VSIZE(addr->u.sms.phone) );
// XP_LOGF( "%s: got SMS; phone=%s", __func__, addr->u.sms.phone );
addr->u.sms.port = getInt( env, jaddr, "sms_port" );
break;
case COMMS_CONN_BT:
getString( env, jaddr, "bt_hostName", addr->u.bt.hostName,
VSIZE(addr->u.bt.hostName) );
getString( env, jaddr, "bt_btAddr", addr->u.bt.btAddr.chars,
VSIZE(addr->u.bt.btAddr.chars) );
break;
case COMMS_CONN_P2P:
getString( env, jaddr, "p2p_addr", addr->u.p2p.mac_addr,
VSIZE(addr->u.p2p.mac_addr) );
break;
case COMMS_CONN_NFC:
break;
case COMMS_CONN_MQTT: {
XP_UCHAR buf[32];
getString( env, jaddr, "mqtt_devID", buf, VSIZE(buf) );
sscanf( buf, MQTTDevID_FMT, &addr->u.mqtt.devID );
}
break;
default:
XP_ASSERT(0);
}
}
deleteLocalRefs( env, jtypeset, jtypesarr, DELETE_NO_REF );
}
jint
jenumFieldToInt( JNIEnv* env, jobject j_gi, const char* field,
const char* fieldSig )
{
char sig[128];
snprintf( sig, sizeof(sig), "L%s;", fieldSig );
jobject jenum = getObjectField( env, j_gi, field, sig );
XP_ASSERT( !!jenum );
jint result = jEnumToInt( env, jenum );
deleteLocalRef( env, jenum );
return result;
}
void
intToJenumField( JNIEnv* env, jobject jobj, int val, const char* field,
const char* fieldSig )
{
char buf[128];
snprintf( buf, sizeof(buf), "L%s;", fieldSig );
jfieldID fid;
jobject jenum = getObjectFieldWithFID( env, jobj, field, buf, &fid );
if ( !jenum ) { /* won't exist in new object */
jenum = makeObjectEmptyConstr( env, fieldSig );
XP_ASSERT( !!jenum );
(*env)->SetObjectField( env, jobj, fid, jenum );
}
jobject jval = intToJEnum( env, val, fieldSig );
XP_ASSERT( !!jval );
(*env)->SetObjectField( env, jobj, fid, jval );
deleteLocalRef( env, jval );
} /* intToJenumField */
/* Cons up a new enum instance and set its value */
jobject
intToJEnum( JNIEnv* env, int val, const char* enumSig )
{
jobject jenum = NULL;
jclass clazz = (*env)->FindClass( env, enumSig );
XP_ASSERT( !!clazz );
char buf[128];
snprintf( buf, sizeof(buf), "()[L%s;", enumSig );
jmethodID mid = (*env)->GetStaticMethodID( env, clazz, "values", buf );
XP_ASSERT( !!mid );
jobject jvalues = (*env)->CallStaticObjectMethod( env, clazz, mid );
XP_ASSERT( !!jvalues );
XP_ASSERT( val < (*env)->GetArrayLength( env, jvalues ) );
/* get the value we want */
jenum = (*env)->GetObjectArrayElement( env, jvalues, val );
XP_ASSERT( !!jenum );
deleteLocalRefs( env, jvalues, clazz, DELETE_NO_REF );
return jenum;
} /* intToJEnum */
jint
jEnumToInt( JNIEnv* env, jobject jenum )
{
jmethodID mid = getMethodID( env, jenum, "ordinal", "()I" );
XP_ASSERT( !!mid );
return (*env)->CallIntMethod( env, jenum, mid );
}
static const SetInfo nli_ints[] = {
ARR_MEMBER( NetLaunchInfo, _conTypes ),
ARR_MEMBER( NetLaunchInfo, forceChannel ),
ARR_MEMBER( NetLaunchInfo, nPlayersT ),
ARR_MEMBER( NetLaunchInfo, nPlayersH ),
ARR_MEMBER( NetLaunchInfo, gameID ),
ARR_MEMBER( NetLaunchInfo, osVers ),
};
static const SetInfo nli_bools[] = {
ARR_MEMBER( NetLaunchInfo, isGSM ),
ARR_MEMBER( NetLaunchInfo, remotesAreRobots ),
};
static const SetInfo nli_strs[] = {
ARR_MEMBER( NetLaunchInfo, dict ),
ARR_MEMBER( NetLaunchInfo, isoCodeStr ),
ARR_MEMBER( NetLaunchInfo, gameName ),
ARR_MEMBER( NetLaunchInfo, room ),
ARR_MEMBER( NetLaunchInfo, btName ),
ARR_MEMBER( NetLaunchInfo, btAddress ),
ARR_MEMBER( NetLaunchInfo, phone ),
ARR_MEMBER( NetLaunchInfo, inviteID ),
ARR_MEMBER( NetLaunchInfo, mqttDevID ),
};
void
loadNLI( JNIEnv* env, NetLaunchInfo* nli, jobject jnli )
{
XP_MEMSET( nli, 0, sizeof(*nli) );
getInts( env, (void*)nli, jnli, AANDS(nli_ints) );
getBools( env, (void*)nli, jnli, AANDS(nli_bools) );
getStrings( env, (void*)nli, jnli, AANDS(nli_strs) );
}
void
setNLI( JNIEnv* env, jobject jnli, const NetLaunchInfo* nli )
{
setInts( env, jnli, (void*)nli, AANDS(nli_ints) );
setBools( env, jnli, (void*)nli, AANDS(nli_bools) );
setStrings( env, jnli, (void*)nli, AANDS(nli_strs) );
}
XWStreamCtxt*
and_empty_stream( MPFORMAL AndGameGlobals* globals )
{
XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globals->vtMgr,
globals, 0, NULL, NULL );
return stream;
}
XP_U32
getCurSeconds( JNIEnv* env )
{
jclass clazz = (*env)->FindClass( env, PKG_PATH("Utils") );
XP_ASSERT( !!clazz );
jmethodID mid = (*env)->GetStaticMethodID( env, clazz,
"getCurSeconds", "()J" );
jlong result = (*env)->CallStaticLongMethod( env, clazz, mid );
deleteLocalRef( env, clazz );
return result;
}
void deleteLocalRef( JNIEnv* env, jobject jobj )
{
if ( NULL != jobj ) {
(*env)->DeleteLocalRef( env, jobj );
}
}
void
deleteLocalRefs( JNIEnv* env, ... )
{
va_list ap;
va_start( ap, env );
for ( ; ; ) {
jobject jnext = va_arg( ap, jobject );
if ( DELETE_NO_REF == jnext ) {
break;
}
deleteLocalRef( env, jnext );
}
va_end( ap );
}
/* Passed to device methods related to MQTT messages */
void
msgAndTopicProc( void* closure, const XP_UCHAR* topic,
const XP_U8* msgBuf, XP_U16 msgLen )
{
MTPData* mtp = (MTPData*)closure;
JNIEnv* env = mtp->env;
if ( VSIZE(mtp->topics) <= mtp->count ) {
XP_LOGFF( "exausted space for topics; dropping" );
} else {
const XP_UCHAR* ptr = &mtp->storage[mtp->offset];
size_t siz = XP_SNPRINTF( (char*)ptr, VSIZE(mtp->storage) - mtp->offset,
"%s", topic );
if ( siz >= VSIZE(mtp->storage) - mtp->offset ) {
XP_LOGFF( "exausted space for data; dropping" );
} else {
mtp->topics[mtp->count] = ptr;
mtp->offset += 1 + XP_STRLEN(ptr);
mtp->jPackets[mtp->count] = makeByteArray( env, msgLen, (const jbyte*)msgBuf );
++mtp->count;
XP_LOGFF( "mtp->count now: %d", mtp->count );
}
}
}
jobject
wrapResults( MTPData* mtp )
{
JNIEnv* env = mtp->env;
jobject result =
makeObjectEmptyConstr( env, PKG_PATH("jni/XwJNI$TopicsAndPackets"));
jobjectArray jTopics = makeStringArray( env, mtp->count, mtp->topics );
setObjectField( env, result, "topics", "[Ljava/lang/String;", jTopics );
jobjectArray jPackets = makeByteArrayArray( env, mtp->count );
for ( int ii = 0; ii < mtp->count; ++ii ) {
(*env)->SetObjectArrayElement( env, jPackets, ii, mtp->jPackets[ii] );
deleteLocalRef( env, mtp->jPackets[ii] );
}
setObjectField( env, result, "packets", "[[B", jPackets );
return result;
}
#ifdef DEBUG
/* A bunch of threads are generating log statements. */
static void
passToJava( const char* tag, const char* msg )
{
JNIEnv* env = waitEnvFromGlobals();
if ( !!env ) {
jstring jtag = (*env)->NewStringUTF( env, tag );
jstring jbuf = (*env)->NewStringUTF( env, msg );
jclass clazz = (*env)->FindClass( env, PKG_PATH("Log") );
XP_ASSERT( !!clazz );
jmethodID mid = (*env)->GetStaticMethodID( env, clazz, "store",
"(Ljava/lang/String;Ljava/lang/String;)V" );
(*env)->CallStaticVoidMethod( env, clazz, mid, jtag, jbuf );
deleteLocalRefs( env, clazz, jtag, jbuf, DELETE_NO_REF );
} else {
// RAW_LOG( "env is NULL; dropping" );
}
}
static void
debugf( const char* format, va_list ap )
{
char buf[1024];
int len;
struct tm* timp;
struct timeval tv;
struct timezone tz;
gettimeofday( &tv, &tz );
timp = localtime( &tv.tv_sec );
len = snprintf( buf, sizeof(buf), "%.2d:%.2d:%.2d: ",
timp->tm_hour, timp->tm_min, timp->tm_sec );
if ( len < sizeof(buf) ) {
vsnprintf( buf + len, sizeof(buf)-len, format, ap );
}
const char* tag =
# if defined VARIANT_xw4GPlay || defined VARIANT_xw4fdroid || defined VARIANT_xw4Foss
"xw4"
# elif defined VARIANT_xw4d || defined VARIANT_xw4dGPlay
"x4bg"
# elif defined VARIANT_xw4dup || defined VARIANT_xw4dupGPlay
"x4du"
# else
ERROR
# endif
;
(void)__android_log_write( ANDROID_LOG_DEBUG, tag, buf );
passToJava( tag, buf );
}
void
raw_log( const char* func, const char* fmt, ... )
{
char buf[1024];
int len = snprintf( buf, VSIZE(buf) - 1, "in %s(): %s", func, fmt );
buf[len] = '\0';
va_list ap;
va_start( ap, fmt );
char buf2[1024];
len = vsnprintf( buf2, VSIZE(buf2) - 1, buf, ap );
va_end( ap );
(void)__android_log_write( ANDROID_LOG_DEBUG, "raw", buf2 );
}
void
android_debugf( const char* format, ... )
{
va_list ap;
va_start( ap, format );
debugf( format, ap );
va_end(ap);
}
void
android_debugff(const char* func, const char* file, const char* fmt, ...)
{
char buf[256];
const char* shortPath = 1 + strrchr(file, '/');
if ( !shortPath ) {
shortPath = file;
}
snprintf( buf, sizeof(buf), "%s:%s(): %s", shortPath, func, fmt );
va_list ap;
va_start( ap, fmt );
debugf( buf, ap );
va_end( ap );
}
/* Print an object's class name into buffer.
*
* NOTE: this must be called in advance of any jni error, because methods on
* env can't be called once there's an exception pending.
*/
#if 0
static void
getClassName( JNIEnv* env, jobject obj, char* out, int* outLen )
{
XP_ASSERT( !!obj );
jclass cls1 = (*env)->GetObjectClass( env, obj );
// First get the class object
jmethodID mid = (*env)->GetMethodID( env, cls1, "getClass",
"()Ljava/lang/Class;" );
jobject clsObj = (*env)->CallObjectMethod( env, obj, mid );
// Now get the class object's class descriptor
jclass cls2 = (*env)->GetObjectClass( env, clsObj );
// Find the getName() method on the class object
mid = (*env)->GetMethodID( env, cls2, "getName", "()Ljava/lang/String;" );
// Call the getName() to get a jstring object back
jstring strObj = (jstring)(*env)->CallObjectMethod( env, clsObj, mid );
jint slen = (*env)->GetStringUTFLength( env, strObj );
if ( slen < *outLen ) {
*outLen = slen;
(*env)->GetStringUTFRegion( env, strObj, 0, slen, out );
out[slen] = '\0';
} else {
*outLen = 0;
out[0] = '\0';
}
deleteLocalRefs( env, clsObj, cls1, cls2, strObj, DELETE_NO_REF );
LOG_RETURNF( "%s", out );
}
#endif
#endif
/* #ifdef DEBUG */
/* XP_U32 */
/* andy_rand( const char* caller ) */
/* { */
/* XP_U32 result = rand(); */
/* XP_LOGF( "%s: returning 0x%lx to %s", __func__, result, caller ); */
/* LOG_RETURNF( "%lx", result ); */
/* return result; */
/* } */
/* #endif */
#ifndef MEM_DEBUG
void
and_freep( void** ptrp )
{
if ( !!*ptrp ) {
free( *ptrp );
*ptrp = NULL;
}
}
#endif