diff --git a/docs/lcb/notes/feature-jobjectarray.md b/docs/lcb/notes/feature-jobjectarray.md new file mode 100644 index 00000000000..5da64bbd044 --- /dev/null +++ b/docs/lcb/notes/feature-jobjectarray.md @@ -0,0 +1,10 @@ +# LiveCode Builder Standard Library + +## Java Utilities + +Syntax for converting between a JObjectArray and a List of JObjects have been +added to the Java utility library. + +* The alias `JObjectArray` has been added to aid readability +* `ListToJObjectArray` - converts a List of JObjects to a JObjectArray +* `ListFromJObjectArray` - converts a JObjectArray to a List of JObjects diff --git a/engine/src/java/com/runrev/android/LiveCodeActivity.java b/engine/src/java/com/runrev/android/LiveCodeActivity.java index 4e773a0f7d7..0e587f8edaa 100644 --- a/engine/src/java/com/runrev/android/LiveCodeActivity.java +++ b/engine/src/java/com/runrev/android/LiveCodeActivity.java @@ -25,6 +25,7 @@ import android.util.*; import android.content.pm.PackageManager; import android.graphics.*; +import java.util.*; // This is the main activity exported by the application. This is // split into two parts, a customizable sub-class that gets dynamically @@ -37,6 +38,8 @@ public class LiveCodeActivity extends Activity public static FrameLayout s_main_layout; public static Engine s_main_view; + private HashMap m_activity_result_listeners; + ////////// public LiveCodeActivity() @@ -86,6 +89,9 @@ public void onGlobalLayout() s_main_view.updateKeyboardVisible(); } }); + + m_activity_result_listeners = new HashMap(); + } @Override @@ -203,11 +209,38 @@ else if (keyCode == KeyEvent.KEYCODE_SEARCH && event.getRepeatCount() == 0) //////////////////////////////////////////////////////////////////////////////// + public interface OnActivityResultListener + { + public void onActivityResult (int p_request_code, int p_result_code, Intent p_data); + } + + public void setOnActivityResultListener(OnActivityResultListener p_listener, int p_request_code) + { + Integer t_request_code = Integer.valueOf(p_request_code); + m_activity_result_listeners.put(t_request_code, p_listener); + } + + public void removeOnActivityResultListener(int p_request_code) + { + Integer t_request_code = Integer.valueOf(p_request_code); + m_activity_result_listeners.remove(t_request_code); + } + // @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { - s_main_view.onActivityResult(requestCode, resultCode, data); + // activity resuult listeners override any engine request code handlers + Integer t_request_code = Integer.valueOf(requestCode); + OnActivityResultListener t_listener = m_activity_result_listeners.get(t_request_code); + if (t_listener != null) + { + t_listener.onActivityResult(requestCode, resultCode, data); + } + else + { + s_main_view.onActivityResult(requestCode, resultCode, data); + } } // Callback sent when the app requests permissions on runtime (Android API 23+) diff --git a/extensions/modules/android-utils/android-utils.lcb b/extensions/modules/android-utils/android-utils.lcb index 74de3e3b2eb..53e8518128b 100644 --- a/extensions/modules/android-utils/android-utils.lcb +++ b/extensions/modules/android-utils/android-utils.lcb @@ -26,7 +26,7 @@ use com.livecode.java use com.livecode.canvas use com.livecode.library.widgetutils -metadata version is "1.0.0" +metadata version is "1.1.0" metadata author is "LiveCode" metadata title is "Android Utilities" metadata os is "android" @@ -90,6 +90,152 @@ public handler ApplicationContext() returns JObject return _JNI_GetEngineContext(_JNI_GetAndroidEngine()) end handler +private variable sResultListeners as Array + +public handler type OnActivityResultHandler(\ + in pRequestCode as JInt, \ + in pResultCode as JInt, \ + in pIntent as optional JObject) returns nothing + +handler _OnActivityResultListener(\ + in pRequestCode as JObject, \ + in pResultCode as JObject, \ + in pIntent as optional JObject) returns nothing + + variable tContext as JObject + put ApplicationContext() into tContext + + variable tRequestCode as JInt + put _JNI_NumberIntValue(pRequestCode) into tRequestCode + variable tResultCode as JInt + put _JNI_NumberIntValue(pResultCode) into tResultCode + + _JNI_LiveCodeActivityRemoveOnActivityResultListener(tContext, tResultCode) + + variable tRequestCodeString as String + put tRequestCode formatted as string into tRequestCodeString + + if tRequestCodeString is among the keys of sResultListeners then + variable tHandler as OnActivityResultHandler + put sResultListeners[tRequestCodeString] into tHandler + delete sResultListeners[tRequestCodeString] + tHandler(tRequestCode, tResultCode, pIntent) + end if +end handler + +__safe foreign handler _JNI_LiveCodeActivityOnActivityResultListener( \ + in pCallbacks as Array) returns JObject \ + binds to "java:com.runrev.android.LiveCodeActivity$OnActivityResultListener>interface()" + +__safe foreign handler _JNI_LiveCodeActivitySetOnActivityResultListener( \ + in pEngine as JObject, \ + in pListener as JObject, \ + in pRequestCode as JInt) returns nothing \ + binds to "java:com.runrev.android.LiveCodeActivity>setOnActivityResultListener(Lcom/runrev/android/LiveCodeActivity$OnActivityResultListener;I)V" + +__safe foreign handler _JNI_LiveCodeActivityRemoveOnActivityResultListener( \ + in pEngine as JObject, \ + in pRequestCode as JInt) returns nothing \ + binds to "java:com.runrev.android.LiveCodeActivity>removeOnActivityResultListener(I)V" + +__safe foreign handler _JNI_ActivityStartActivityForResult( \ + in pActivity as JObject, \ + in pIntent as JObject, \ + in pRequestCode as JInt) \ + returns nothing \ + binds to "java:android.app.Activity>startActivityForResult(Landroid/content/Intent;I)V" + +__safe foreign handler _JNI_NumberIntValue(in pNumber as JObject) returns JInt \ + binds to "java:java.lang.Number>intValue()I" + +/** +Summary: Start an activity by Intent + +Example: + + constant kIntentACTION_SEND is "android.intent.action.SEND" + constant kIntentEXTRA_TEXT is "android.intent.extra.TEXT" + constant kActivityRESULT_CANCELED is 0 + constant kShareStringRequestCode is 123 + + __safe foreign handler _JNI_IntentNew(in pAction as JString) \ + returns JObject \ + binds to "java:android.content.Intent>new(Ljava/lang/String;)" + + __safe foreign handler _JNI_IntentSetType(in pIntent as JObject, \ + in pType as JString) \ + returns JObject \ + binds to "java:android.content.Intent>setType(Ljava/lang/String;)Landroid/content/Intent;" + + __safe foreign handler _JNI_IntentPutExtraString(in pIntent as JObject, \ + in pType as JString, \ + in pValue as JString) \ + returns JObject \ + binds to "java:android.content.Intent>putExtra(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;" + + handler _ShareStringResultListener( \ + in pRequestCode as JInt, \ + in pResultCode as JInt, \ + in pIntent as optional JObject) returns nothing + + if pResultCode is kActivityRESULT_CANCELED then + post "shareStringCancelled" + else + post "shareStringComplete" + end if + end handler + + public handler ShareString(in pString as String) returns nothing + variable tIntent as JObject + put _JNI_IntentNew(StringToJString(kIntentACTION_SEND)) into tIntent + + _JNI_IntentSetType(tIntent, StringToJString("text/plain")) + + _JNI_IntentPutExtraString(tIntent, StringToJString(kIntentEXTRA_TEXT), \ + StringToJString(pString)) + + StartActivityForResult(tIntent, kShareStringRequestCode, _ShareStringResultListener) + end handler + +Parameters: +pIntent: An Intent JObject to use to start an activity +pRequestCode: A positive integer used to identify the request when handling +`onActivityResult`. +pHandler: A handler that conforms to the `OnActivityResultHandler` type + +Description: +Start an activity by Intent and receive a callback to the specified handler when +the LiveCode activity receives the result via the `onActivityResult` method. + +The callback must conform to the `OnActivityResultHandler` type which returns +nothing and has parameters: + +- in pRequestCode as JInt +- in pResultCode as JInt +- in pIntent as optional JObject + +*/ + +public handler StartActivityForResult( \ + in pIntent as JObject, \ + in pRequestCode as JInt, \ + in pHandler as OnActivityResultHandler) returns nothing + + variable tContext as JObject + put ApplicationContext() into tContext + + variable tListener as JObject + put _JNI_LiveCodeActivityOnActivityResultListener(\ + {"onActivityResult" : _OnActivityResultListener }) into tListener + + _JNI_LiveCodeActivitySetOnActivityResultListener(tContext, tListener, pRequestCode) + + put pHandler into sResultListeners[pRequestCode formatted as string] + + _JNI_ActivityStartActivityForResult(tContext, pIntent, pRequestCode) +end handler + + private foreign handler MCAndroidCheckRuntimePermission(in pPermission as String) \ returns CBool binds to "" diff --git a/extensions/modules/android-utils/notes/feature-startactivityforresult.md b/extensions/modules/android-utils/notes/feature-startactivityforresult.md new file mode 100644 index 00000000000..8016642e3e6 --- /dev/null +++ b/extensions/modules/android-utils/notes/feature-startactivityforresult.md @@ -0,0 +1,6 @@ +# Starting Activities and Handling Results + +A new handler has been added to facilitate starting activities by Intent and +handling results. The `StartActivityForResult` handler registers a listener to +handle the `onActivityResult` method of the engine activity and when handled +calls a callback handler with the request code, result code and Intent object. \ No newline at end of file diff --git a/libfoundation/include/foundation.h b/libfoundation/include/foundation.h index 5acf8aa4798..cde02d3735f 100755 --- a/libfoundation/include/foundation.h +++ b/libfoundation/include/foundation.h @@ -2008,7 +2008,10 @@ MC_DLLEXPORT bool MCJavaConvertStringRefToJString(MCStringRef p_string, MCJavaOb MC_DLLEXPORT bool MCJavaConvertJByteArrayToDataRef(MCJavaObjectRef p_object, MCDataRef &r_data); // Convert a Data Ref to a Java object wrapping a jByteArray MC_DLLEXPORT bool MCJavaConvertDataRefToJByteArray(MCDataRef p_data, MCJavaObjectRef &r_object); - + +MC_DLLEXPORT bool MCJavaConvertProperListRefToJObjectArray(MCProperListRef p_list, MCStringRef p_class_name, MCJavaObjectRef &r_obj_array); +MC_DLLEXPORT bool MCJavaConvertJObjectArrayToProperListRef(MCJavaObjectRef p_obj_array, MCProperListRef &r_list); + //////////////////////////////////////////////////////////////////////////////// // // BOOLEAN DEFINITIONS diff --git a/libfoundation/src/foundation-java-private.cpp b/libfoundation/src/foundation-java-private.cpp index 044d647996f..c222a4440c3 100644 --- a/libfoundation/src/foundation-java-private.cpp +++ b/libfoundation/src/foundation-java-private.cpp @@ -536,6 +536,46 @@ static bool __MCJavaProperListFromJObjectArray(jobjectArray p_obj_array, MCPrope return MCProperListCopy(*t_list, r_list); } +bool MCJavaPrivateProperListFromJObjectArray(MCJavaObjectRef p_obj_array, MCProperListRef& r_list) +{ + jobjectArray t_obj_array = static_cast(MCJavaObjectGetObject(static_cast(p_obj_array))); + if (t_obj_array == nullptr) + { + return false; + } + + return __MCJavaProperListFromJObjectArray(t_obj_array, r_list); +} + +static jclass MCJavaPrivateFindClass(MCNameRef p_class_name); + +bool MCJavaPrivateProperListToJObjectArray(MCProperListRef p_list, MCNameRef p_class_name, MCJavaObjectRef &r_obj_array) +{ + MCJavaDoAttachCurrentThread(); + + jclass t_class = MCJavaPrivateFindClass(p_class_name); + + uindex_t t_size = MCProperListGetLength(p_list); + jobjectArray t_obj_array = s_env -> NewObjectArray(t_size, t_class, nullptr); + + if (t_obj_array == nullptr) + return false; + + for (uint32_t i = 0; i < t_size; i++) + { + MCValueRef t_value; + t_value = MCProperListFetchElementAtIndex(p_list, i); + if (MCValueGetTypeInfo(t_value) == MCJavaGetObjectTypeInfo()) + { + jobject t_object = static_cast(MCJavaObjectGetObject(static_cast(t_value))); + + s_env -> SetObjectArrayElement(t_obj_array, i, t_object); + } + } + + return MCJavaObjectCreate(t_obj_array, r_obj_array); +} + void* MCJavaPrivateGlobalRef(void *p_object) { MCJavaDoAttachCurrentThread(); @@ -2044,4 +2084,15 @@ void* MCJavaPrivateGlobalRef(void *p_object) { return p_object; } + +bool MCJavaPrivateProperListFromJObjectArray(MCJavaObjectRef p_obj_array, MCProperListRef& r_list) +{ + return false; +} + +bool MCJavaPrivateProperListToJObjectArray(MCProperListRef p_list, MCNameRef p_class_name, MCJavaObjectRef &r_obj_array) +{ + return false; +} + #endif diff --git a/libfoundation/src/foundation-java-private.h b/libfoundation/src/foundation-java-private.h index cb0e3fa5fce..381faa008eb 100644 --- a/libfoundation/src/foundation-java-private.h +++ b/libfoundation/src/foundation-java-private.h @@ -69,6 +69,9 @@ void MCJavaPrivateDestroyObject(MCJavaObjectRef p_object); bool MCJavaPrivateCheckSignature(MCTypeInfoRef p_signature, MCStringRef p_args, MCStringRef p_return, int p_call_type); bool MCJavaPrivateGetJObjectClassName(MCJavaObjectRef p_object, MCStringRef &r_name); +bool MCJavaPrivateProperListFromJObjectArray(MCJavaObjectRef p_obj_array, MCProperListRef& r_list); +bool MCJavaPrivateProperListToJObjectArray(MCProperListRef p_list, MCNameRef p_class_name, MCJavaObjectRef &r_obj_array); + void* MCJavaPrivateGlobalRef(void *p_object); bool MCJavaPrivateErrorThrow(MCTypeInfoRef p_error); diff --git a/libfoundation/src/foundation-java.cpp b/libfoundation/src/foundation-java.cpp index 9804a5c4d47..55fcd8508bf 100644 --- a/libfoundation/src/foundation-java.cpp +++ b/libfoundation/src/foundation-java.cpp @@ -220,3 +220,22 @@ MC_DLLEXPORT bool MCJavaGetJObjectClassName(MCJavaObjectRef p_object, MCStringRe return MCJavaPrivateGetJObjectClassName(p_object, r_name); } + +MC_DLLEXPORT bool MCJavaConvertProperListRefToJObjectArray(MCProperListRef p_list, MCStringRef p_class_name, MCJavaObjectRef &r_obj_array) +{ + if (!s_java_initialised) + return MCJavaPrivateErrorThrow(kMCJavaJRENotSupportedErrorTypeInfo); + + MCNewAutoNameRef t_class_name; + return MCNameCreate(p_class_name, &t_class_name) && \ + MCJavaPrivateProperListToJObjectArray(p_list, *t_class_name, r_obj_array); +} + +MC_DLLEXPORT bool MCJavaConvertJObjectArrayToProperListRef(MCJavaObjectRef p_obj_array, MCProperListRef &r_list) +{ + if (!s_java_initialised) + return MCJavaPrivateErrorThrow(kMCJavaJRENotSupportedErrorTypeInfo); + + return MCJavaPrivateProperListFromJObjectArray(p_obj_array, r_list); +} + diff --git a/libscript/src/java.lcb b/libscript/src/java.lcb index 06a971cfede..ecbb1d6c96b 100644 --- a/libscript/src/java.lcb +++ b/libscript/src/java.lcb @@ -46,11 +46,14 @@ public type JDouble is Float64 public foreign type JObject binds to "MCJavaObjectTypeInfo" public type JString is JObject public type JByteArray is JObject +public type JObjectArray is JObject foreign handler MCJavaStringFromJString(in pString as JObject, out rString as String) returns nothing binds to "" foreign handler MCJavaStringToJString(in pString as String, out rString as JObject) returns nothing binds to "" foreign handler MCJavaDataFromJByteArray(in pByteArray as JObject, out rData as Data) returns nothing binds to "" foreign handler MCJavaDataToJByteArray(in pData as Data, out rByteArray as JObject) returns nothing binds to "" +foreign handler MCJavaListToJObjectArray(in pList as List, in pClassName as String, out rObjectArray as JObjectArray) returns nothing binds to "" +foreign handler MCJavaListFromJObjectArray(in pObjectArray as JObjectArray, out rList as List) returns nothing binds to "" foreign handler MCJavaGetClassName(in pObject as JObject, out rName as String) returns nothing binds to "" foreign handler MCJavaUnwrapJObject(in pObject as JObject, out rPointer as Pointer) returns nothing binds to "" @@ -245,4 +248,80 @@ public handler PointerToJObject(in pPointer as Pointer) returns JObject return tObj end handler +/** +Summary: Convert a java object array into a List of java objects + +Parameters: +pObj: The JObjectArray to convert + +Example: + __safe foreign handler _JNI_IntentGetStringArrayExtra(in pIntent as JObject, \ + in pType as JString) \ + returns JObjectArray \ + binds to "java:android.content.Intent>getStringArrayExtra(Ljava/lang/String;)[Ljava/lang/String;" + + handler OnActivityResultListener(\ + in pRequestCode as JObject, \ + in pResultCode as JObject, \ + in pIntent as optional JObject) returns nothing + + if pResultCode is -1 then + variable tArray as JObjectArray + put _JNI_IntentGetStringArrayExtra(pIntent, \ + StringToJString("android.intent.extra.MIME_TYPES")) into tArray + variable tList as List + put ListFromJObjectArray(tArray) into tList + + variable tStringList as List + variable tString as JObject + repeat for each element tString in tList + push StringFromJObject(tString) onto back of tStringList + end repeat + + post "mimeTypes" with [tStringList] + end if + + end handler + +Description: +Use to convert into a List of JObjects. + +*/ +public handler ListFromJObjectArray(in pObj as JObjectArray) returns optional List + variable tList as optional List + unsafe + MCJavaListFromJObjectArray(pObj, tList) + end unsafe + return tList +end handler + +/** +Summary: Convert a List of JObjects into a JObjectArray + +Parameters: +pList: The List to convert +pClass: The class name of the JObjects in the List + +Returns: +A JObjectArray where each element is a JObject of class pClass + +Example: + variable tArray as JObjectArray + put ListToJObjectArray(\ + [StringToJString("foo"), StringToJString("bar")], \ + "java/lang/String") into tArray + +Description: +Use to convert where each element is a JObject +into a JObjectArray. Note all elements of must conform +to the class . +*/ +public handler ListToJObjectArray(in pList as List, in pClass as String) returns optional JObjectArray + variable tArray as optional JObjectArray + unsafe + MCJavaListToJObjectArray(pList, pClass, tArray) + end unsafe + return tArray +end handler + end module diff --git a/libscript/src/module-java.cpp b/libscript/src/module-java.cpp index 4cd3e89a373..b9994e1931e 100644 --- a/libscript/src/module-java.cpp +++ b/libscript/src/module-java.cpp @@ -42,6 +42,8 @@ MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotConvertStringToJStringErrorTypeInfo; MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotConvertJStringToStringErrorTypeInfo; MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotConvertDataToJByteArrayErrorTypeInfo; MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotConvertJByteArrayToDataErrorTypeInfo; +MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotConvertListToJObjectArrayErrorTypeInfo; +MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotConvertJObjectArrayToListErrorTypeInfo; MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotGetObjectClassNameErrorTypeInfo; MC_DLLEXPORT MCTypeInfoRef kMCJavaCouldNotCreateJObjectErrorTypeInfo; @@ -83,6 +85,24 @@ extern "C" MC_DLLEXPORT_DEF void MCJavaDataToJByteArray(MCDataRef p_data, MCJava MCJavaErrorThrow(kMCJavaCouldNotConvertDataToJByteArrayErrorTypeInfo); } +extern "C" MC_DLLEXPORT_DEF void MCJavaListFromJObjectArray(MCJavaObjectRef p_object, MCProperListRef &r_list) +{ + if (!TryToInitializeJava()) + return; + + if (!MCJavaConvertJObjectArrayToProperListRef(p_object, r_list)) + MCJavaErrorThrow(kMCJavaCouldNotConvertJObjectArrayToListErrorTypeInfo); +} + +extern "C" MC_DLLEXPORT_DEF void MCJavaListToJObjectArray(MCProperListRef p_list, MCStringRef p_class_name, MCJavaObjectRef &r_object) +{ + if (!TryToInitializeJava()) + return; + + if (!MCJavaConvertProperListRefToJObjectArray(p_list, p_class_name, r_object)) + MCJavaErrorThrow(kMCJavaCouldNotConvertListToJObjectArrayErrorTypeInfo); +} + extern "C" MC_DLLEXPORT_DEF void MCJavaGetClassName(MCJavaObjectRef p_obj, MCStringRef &r_string) { if (!TryToInitializeJava()) @@ -119,6 +139,12 @@ bool MCJavaErrorsInitialize() if (!MCNamedErrorTypeInfoCreate(MCNAME("com.livecode.java.ConvertToDataError"), MCNAME("java"), MCSTR("Could not convert Java byte array to Data"), kMCJavaCouldNotConvertJByteArrayToDataErrorTypeInfo)) return false; + if (!MCNamedErrorTypeInfoCreate(MCNAME("com.livecode.java.ConvertFromListError"), MCNAME("java"), MCSTR("Could not convert Java object array to List"), kMCJavaCouldNotConvertListToJObjectArrayErrorTypeInfo)) + return false; + + if (!MCNamedErrorTypeInfoCreate(MCNAME("com.livecode.java.ConvertToListError"), MCNAME("java"), MCSTR("Could not convert Java object array to List"), kMCJavaCouldNotConvertJObjectArrayToListErrorTypeInfo)) + return false; + if (!MCNamedErrorTypeInfoCreate(MCNAME("com.livecode.java.FetchJavaClassNameError"), MCNAME("java"), MCSTR("Could not get Java object class name"), kMCJavaCouldNotGetObjectClassNameErrorTypeInfo)) return false; @@ -134,6 +160,8 @@ void MCJavaErrorsFinalize() MCValueRelease(kMCJavaCouldNotConvertJStringToStringErrorTypeInfo); MCValueRelease(kMCJavaCouldNotConvertDataToJByteArrayErrorTypeInfo); MCValueRelease(kMCJavaCouldNotConvertJByteArrayToDataErrorTypeInfo); + MCValueRelease(kMCJavaCouldNotConvertListToJObjectArrayErrorTypeInfo); + MCValueRelease(kMCJavaCouldNotConvertJObjectArrayToListErrorTypeInfo); MCValueRelease(kMCJavaCouldNotGetObjectClassNameErrorTypeInfo); MCValueRelease(kMCJavaCouldNotCreateJObjectErrorTypeInfo); }