Access method from android plugin in unity - android

This is pretty basic, but I'm not getting success on implementing it from the official Unity docs and YouTube video tutorials, so I asked.
I want to access methods from already made aar plug-in. Its checked as android plug-in by default. The plug-in has following code:
package alarm.company.com.unity;
public class myPlugin {
public String myTest(int i){
return "Hello your string number is: "+i;
}
}
the compiled aar plug-in is copied into the Unity folder.
Here's the c# code to access that method:
const string PluginName = "alarm.company.com.unity.myPlugin";
static AndroidJavaClass _pluginClass;
static AndroidJavaClass _pluginInstance;
public Text text;
// Start is called before the first frame update
void Start()
{
AndroidJavaClass plugin = new AndroidJavaClass(PluginName);
string s = plugin.CallStatic<string>("myTest", 9);
print(s);
text.text = s;
}
text is assigned and this script is put on the main camera. But I'm not getting any text back or the text object is not changed, while running in emulator. How can this be solved?

Unity Plugin development is a bit tricky to get setup at first but once you are able to it is pretty powerful.
Couple things.
1) You are using an AndroidJavaClass and trying to call a non static method in java. Your method in your plugin should be static if you are wanting to use that class.
2) Otherwise you will need to use an AndroidJavaObject to construct your class and then you should be able to call your function.
Hope this helps!
Example Using non Static Code:
Unity Code
using (AndroidJavaObject leetPlugin = new AndroidJavaObject("alarm.company.com.unity.myPlugin")){
string s = leetPlugin.Call("myTest", 9);
print(s);
text.text = s;
}
Android Code
public void myTest(int value) {
return "Your value is " + value;
}

Some advises when you work with plugins for unity:
Use gradle build system. It generates mainTemplate file in Plugins/Android. You can edit this file and set source java code to your java classes so you don't need to generate aar every time you want to build:
sourceSets
{
main
{
java
{
srcDirs = ['src']
srcDirs += ['E:/AndroidStudioProjects/PathToJavaClasses']
}
}
}
If you want to send something from java code to unity you should extend your activity from unity and call method UnitySendMessage
public class MyUnityPlayerActivity extends UnityPlayerActivity{
public void SendUnityMessage()
{
UnityPlayer.UnitySendMessage(UnityGameObjectName, UNITY_FUNCTION_NAME,
params);
}
}

Related

Implementing platform functionality in a static class - Xamarin.Forms

I am not sure of the terminology for what I'm looking to do, so sorry in advance!
I've found a FilePicker plugin for Xamarin.Forms (https://github.com/Studyxnet/FilePicker-Plugin-for-Xamarin-and-Windows) that implements device-specific functionality for selecting files via the CrossFilePicker class.
The way to use leverage this functionality would be something like
CrossFilePicker.Current.OpenFile("Filename.txt");
The most important part of this for me is that CrossFilePicker.Current is static and can be accessible from anywhere in the shared layer of my Xamarin.Forms app.
I need to implement a class with the same characteristics. I want to leverage device Accessibility functionality (i.e. determining if a screen reader is enabled) and I need to be able to do so with a static class.
My eventual plan is to then wrap this static class so that I can use it for unit tests too.
I don't want to import device libraries into my shared project.
TLDR: I need a static class that implements device-specific functionality.
Any help would be greatly appreciated! Thank you :)
EDIT:
Here are the files I have currently implemented in my project
IAccessibilityService Located in the shared .NET project
namespace Bitspace.Services
{
public interface IAccessibilityService
{
public bool IsScreenReaderEnabled();
public void Announcement(string message);
public void NavigationAnnouncement(string message);
}
}
DeviceAccessibility.cs Located in the shared .NET project
using System;
namespace Bitspace.Services
{
public class DeviceAccessibility
{
private static Lazy<IAccessibilityService> Implementation = new Lazy<IAccessibilityService>(() => CreateAccessibilityService(), System.Threading.LazyThreadSafetyMode.PublicationOnly);
public static IAccessibilityService Current
{
get
{
var curr = Implementation.Value;
if (curr == null)
{
throw new Exception();
}
return curr;
}
}
private static IAccessibilityService CreateAccessibilityService()
{
return new DeviceAccessibilityImplementation();
}
}
}
DeviceAccessibilityImplementation.cs Located in the Android project
using Android.Runtime;
namespace Bitspace.Services
{
[Preserve (AllMembers = true)]
public class DeviceAccessibilityImplementation : IAccessibilityService
{
public bool IsScreenReaderEnabled()
{
return true;
}
public void Announcement(string message)
{
}
public void NavigationAnnouncement(string message)
{
}
}
}
If I try to build the project, I get an error on the return new DeviceAccessibilityImplementation(); line in DeviceAccessibility.cs that says DeviceAccessibility.cs(25, 24): [CS0246] The type or namespace name 'DeviceAccessibilityImplementation' could not be found (are you missing a using directive or an assembly reference?)
However, CTRL Clicking on that line takes me to the DeviceAccessibilityImplementation.cs

How to implement native android unity plugin?

I am fresh in unity. What I have is - native android static lib that I use in my Android app by JNI. Now, I need to take this native android static lib and make it work in Unity as android plugin.
I would like to make it this way, in Android Studio build this lib with JNI interface (as I do now for this android project) and then use this lib buy JNI interface this way
using UnityEngine;
public class PluginTest : MonoBehaviour
{
const string pluginName = "com.myapp.unity.MyPlugin";
static AndroidJavaClass _pluginClass;
static AndroidJavaObject _pluginObject;
public static AndroidJavaClass PluginClass
{
get
{
if(_pluginClass == null)
{
_pluginClass = new AndroidJavaClass(pluginName);
}
return _pluginClass;
}
}
public static AndroidJavaObject PluginInstance
{
get
{
if(_pluginObject == null)
{
_pluginObject = PluginClass.CallStatic<AndroidJavaObject>("getInstance");
}
return _pluginObject;
}
}
// Start is called before the first frame update
void Start()
{
Debug.Log($"Elapsed time : {getElapsedTime()}");
}
float elapsedTime = 0f;
// Update is called once per frame
void Update()
{
elapsedTime += Time.deltaTime;
if(elapsedTime >= 5)
{
elapsedTime -= 5;
Debug.Log($"Tick : {getElapsedTime()}");
}
}
double getElapsedTime()
{
if(Application.platform == RuntimePlatform.Android)
{
return PluginInstance.Call<double>("getElapsedTime");
}
Debug.LogWarning("Wrong platform");
return 0;
}
}
Like describes in this video https://www.youtube.com/watch?v=bmNMugkOQBI
But as for me downside here is JNI implementation itself, for example if I need to call from Unity some method from native, so firstly I call method in c# , secondly this c# method call method in JNI and finally JNI call method in native.
I looks like that there is should be a way to build this native android lib in Android Studio without to use JNI and include this lib in Unity and use it directly from C# script.
Is it correct?
If I understand correctly, you are currently calling a native C/C++ function from Unity in this order:
C# -> Java -> C/C++
But you want to do this:
C# -> C/C++
If so, you can do the following:
If you have a function declared in C/C++ like so:
float FooPluginFunction () { return 5.0F; }
In your C# script you can then define the following:
[DllImport ("PluginName")]
private static extern float FooPluginFunction ();
Where 'PluginName' matches the library name, and 'FooPluginFunction' matches the function name in the library.
Calling the C# function, FooPluginFunction(), in your C# script will then attempt to call the C/C++ function. Ensure your library file is within the Unity Project.
For more information view the unity docs here

How to correctly implement and test Custom Lint Rules in Android Studio?

I'm following this tutorial and this Custom Detector Example in order to implement Custom Lint Rules. Basically what I've done is:
Create a new Android Project in Android Studio;
Create a java module for project created in step 1;
On module's build.gradle, import Lint API dependencies;
Create an Issue & IssueRegistry & CustomDetector;
Reference the IssueRegistry on module's build.gradle;
Create Unit tests;
My problem is, during the execution of my JUnits, I always receive "No Warning". When I debug the test, I can see that my Custom Detector isn't called, what am I doing wrong?
Strings.java
public class Strings {
public static final String STR_ISSUE_001_ID = "VarsMustHaveMoreThanOneCharacter";
public static final String STR_ISSUE_001_DESCRIPTION = "Avoid naming variables with only one character";
public static final String STR_ISSUE_001_EXPLANATION = "Variables named with only one character do not pass any meaning to the reader. " +
"Variables name should clear indicate the meaning of the value it is holding";
}
Issues.java
public class Issues {
public static final
Issue ISSUE_001 = Issue.create(
STR_ISSUE_001_ID,
STR_ISSUE_001_DESCRIPTION,
STR_ISSUE_001_EXPLANATION,
SECURITY,
// Priority ranging from 0 to 10 in severeness
6,
WARNING,
new Implementation(VariableNameDetector.class, ALL_RESOURCES_SCOPE)
);
}
IssuesRegistry.java
public class IssueRegistry extends com.android.tools.lint.client.api.IssueRegistry {
#Override
public List<Issue> getIssues() {
List<Issue> issues = new ArrayList<>();
issues.add(ISSUE_001);
return issues;
}
}
VariableNameDetector.java
public class VariableNameDetector extends Detector implements Detector.JavaScanner {
public VariableNameDetector() {
}
#Override
public boolean appliesToResourceRefs() {
return false;
}
#Override
public boolean appliesTo(Context context, File file) {
return true;
}
#Override
#Nullable
public AstVisitor createJavaVisitor(JavaContext context) {
return new NamingConventionVisitor(context);
}
#Override
public List<String> getApplicableMethodNames() {
return null;
}
#Override
public List<Class<? extends Node>> getApplicableNodeTypes() {
List<Class<? extends Node>> types = new ArrayList<>(1);
types.add(lombok.ast.VariableDeclaration.class);
return types;
}
#Override
public void visitMethod(
JavaContext context,
AstVisitor visitor,
MethodInvocation methodInvocation
) {
}
#Override
public void visitResourceReference(
JavaContext context,
AstVisitor visitor,
Node node,
String type,
String name,
boolean isFramework
) {
}
private class NamingConventionVisitor extends ForwardingAstVisitor {
private final JavaContext context;
NamingConventionVisitor(JavaContext context) {
this.context = context;
}
#Override
public boolean visitVariableDeclaration(VariableDeclaration node) {
StrictListAccessor<VariableDefinitionEntry, VariableDeclaration> varDefinitions =
node.getVariableDefinitionEntries();
for (VariableDefinitionEntry varDefinition : varDefinitions) {
String name = varDefinition.astName().astValue();
if (name.length() == 1) {
context.report(
ISSUE_001,
context.getLocation(node),
STR_ISSUE_001_DESCRIPTION
);
return true;
}
}
return false;
}
}
}
build.gradle
apply plugin: 'java'
configurations {
lintChecks
}
ext {
VERSION_LINT_API = '24.3.1'
VERSION_LINT_API_TESTS = '24.3.1'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.tools.lint:lint-api:$VERSION_LINT_API"
implementation "com.android.tools.lint:lint-checks:$VERSION_LINT_API"
testImplementation "com.android.tools.lint:lint-tests:$VERSION_LINT_API_TESTS"
}
jar {
manifest {
attributes('Lint-Registry': 'br.com.edsilfer.lint_rules.resources.IssueRegistry')
}
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
TestVariableNameDetector.java
private static final String ARG_DEFAULT_LINT_SUCCESS_LOG = "No warnings.";
#Override
protected Detector getDetector() {
return new VariableNameDetector();
}
#Override
protected List<Issue> getIssues() {
return Collections.singletonList(Issues.ISSUE_001);
}
public void test_file_with_no_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test.java", "public class Test {public String sampleVariable;}"))
);
}
public void test_file_with_variables_with_length_equals_01() throws Exception {
assertEquals(
ARG_DEFAULT_LINT_SUCCESS_LOG,
lintProject(java("assets/Test3.java", "public class Test {public String a;bnvhgvhj}"))
);
}
}
P.S.: on Java's module I do not have access to assetsor res folder, that is the reason why I've created a String.java and I'm using java(to, source) in my Unit test - I assumed that this java method does the same as the xml from the tutorial link I referenced at the top of this question.
It turned out that in my case the problem was with the JUnit itself. I think that the way I was attempting to simulate the file was wrong. The text below is part of the README.md of a sample project that I've created in order to document what I've learned from this API and answers the question in the title:
Create
Create a new Android Project;
Create a new Java Library Module - Custom Lint Rules are packaged into .jar libraries once they are ready, therefore the easiest way to implement them using them is inside a Java Module Library;
On module's build.gradle:
add target and source compatibility to Java 1.7;
add dependencies for lint-api, lint-checks and test dependencies;
add jar packing task containing two attributes: Manifest-Version and Lint-Registry, set the first to 1.0 and the second as the full path to a class that will later on contain the issue's catalog;
add a default tasl assemble;
[OPTIONAL]: add a task that will copy the generated .jar into ~/.android/lint;
Check REF001 and choose a Detector that best suits your needs, based on it create and implement a class to fulfill the Detector's role;
Still based on REF0001 chosen file, create and implement a Checker class, later referring to it inside Detector's createJavaVisitor() method;
for the sake of SRP, do not place Checker in the same file of Detector's class;
Copy the generated .jar file from build/lib to ~/.android/lint - if you added a task on build.gradle that does this you can skip this step;
Restart the computer - once created and moved into ~/.android/lint, the Custom Rules should be read by Lint next time the program starts. In order to have the alert boxes inside Android Studio, it is enough to invalidate caches and restart the IDE, however, to have your custom rules caught on Lint Report when ./gradlew check, it might be necessary to restart your computer;
Testing Detectors and Checkers
Testing Custom Rules is not an easy task to do - mainly due the lack of documentation for official APIs. This section will present two approaches for dealing with this. The main goal of this project is to create custom rules that will be run against real files, therefore, test files will be necessary for testing them. They can be places in src/test/resources folder from your Lint Java Library Module;
Approach 01: LintDetectorTest
Make sure you've added all test dependencies - checkout sample project's build.gradle;
Copy EnhancedLintDetectorTest.java and FileUtils.java into your project's test directory;
There is a known bug with Android Studio that prevents it from seeing files from src/test/resources folder, these files are a workaround for that;
EnhancedLintDetectorTest.java should return all issues that will be subject of tests. A nice way to do so is getting them from Issue Registry;
Create a test class that extends from EnhancedLintDetectorTest.java;
Implement getDetector() method returning an instance of the Detector to be tested;
Use lintFiles("test file path taking resources dir as root") to perform the check of the Custom Rules and use its result object to assert the tests;
Note that LintDetectorTest.java derives from TestCase.java, therefore, you're limited to JUnit 3.
Approach 02: Lint JUnit Rule
You might have noticed that Approach 01 might be a little overcomplicated, despite the fact that you're limited to JUnit 3 features. Because of that GitHub user a11n created a Lint JUnit Rule that allows the test of Custom Lint Rules in a easier way that counts with JUnit 4 and up features. Please, refer to his project README.md for details about how to create tests using this apprach.
Currently, Lint JUnit Rule do not correct the root dir for test files and you might no be able to see the tests passing from the IDE - however it works when test are run from command line. An issue and PR were created in order to fix this bug.
I'm not sure how to use the AST Api, however I'm personally using Psi and this is one of my lint checks that are working for me.
public final class RxJava2MethodCheckReturnValueDetector extends Detector implements Detector.JavaPsiScanner {
static final Issue ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE =
Issue.create("MethodMissingCheckReturnValue", "Method is missing the #CheckReturnValue annotation",
"Methods returning RxJava Reactive Types should be annotated with the #CheckReturnValue annotation.",
MESSAGES, 8, WARNING,
new Implementation(RxJava2MethodCheckReturnValueDetector.class, EnumSet.of(JAVA_FILE, TEST_SOURCES)));
#Override public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
return Collections.<Class<? extends PsiElement>>singletonList(PsiMethod.class);
}
#Override public JavaElementVisitor createPsiVisitor(#NonNull final JavaContext context) {
return new CheckReturnValueVisitor(context);
}
static class CheckReturnValueVisitor extends JavaElementVisitor {
private final JavaContext context;
CheckReturnValueVisitor(final JavaContext context) {
this.context = context;
}
#Override public void visitMethod(final PsiMethod method) {
final PsiType returnType = method.getReturnType();
if (returnType != null && Utils.isRxJava2TypeThatRequiresCheckReturnValueAnnotation(returnType)) {
final PsiAnnotation[] annotations = method.getModifierList().getAnnotations();
for (final PsiAnnotation annotation : annotations) {
if ("io.reactivex.annotations.CheckReturnValue".equals(annotation.getQualifiedName())) {
return;
}
}
final boolean isMethodMissingCheckReturnValueSuppressed = context.getDriver().isSuppressed(context, ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, method);
if (!isMethodMissingCheckReturnValueSuppressed) {
context.report(ISSUE_METHOD_MISSING_CHECK_RETURN_VALUE, context.getLocation(method.getNameIdentifier()), "Method should have #CheckReturnValue annotation");
}
}
}
}
}
Checkout the many more I wrote here.

Can't pass back event from Unity to android library jar

I am quite new at Unity and I am trying to create a Unity plugin for an Android library jar and I am facing the following issues:
I can't find a way to pass the back button event to Android library. It seems that Unity prevents this event to be passed to the library layer. What I have managed to do is to pass touch events to the activity on the Android side by setting the following line
<meta-data android:name="unityplayer.ForwardNativeEventsToDal vik" android:value="true" />
at <activity android:name="com.unity3d.player.UnityPlayerProxyActivity" ... > in the AndroidManifest
However I cannot pass the back button event in a way that I do not have to change the code in the library
What I do for now in my script is
public void Update() {
if(Input.GetKeyDown(KeyCode.Escape))
Application.Quit();
}
However I need the option to pass that back event that I capture, to the library and not handle it at the Unity layer.
Maybe somehow like this.
myActivity.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK));
or this
myActivity.onBackPressed()
but through a C# script. How can I accomplish that within a script?
C# script:
void Update ()
{
if (Input.GetKeyUp (KeyCode.Escape)) {
AndroidJavaClass jc = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject> ("currentActivity");
jo.Call ("yourBackEventFunction");
}
}
And your Android lib
public class YourActivity extends
com.unity3d.player.UnityPlayerNativeActivity {
…
public void yourBackEventFunction() {
// Do something here
}
…
}
========================
Edit:
If you want to call onBackPressed, you can do so:
In YourActivity.java
#Override
public void onBackPressed() {
runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(UnityPlayerNativeActivity.this,
"on Back Pressed", Toast.LENGTH_SHORT)
.show();
}
});
super.onBackPressed();
}
And in C# script:
void Update ()
{
if (Input.GetKeyUp (KeyCode.Escape)) {
Debug.Log ("onResume Received");
AndroidJavaClass jc = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject> ("currentActivity");
jo.Call ("onBackPressed");
}
}
Or inside YourActivity you can:
Create your YourActivity Extends UnityPlayerNativeActivty
Include your Jar lib and call the function you want.
Use C# script to call your Activity.
Note that in Unity, there is only 1 Activity, and it must be UnityPlayerNativeActivity OR it must be an Activity EXTENDS FROM UnityPlayerNativeActivity. You can not use any others activity from your Jar without extend from UnityPlayerNativeActivity.
If YourActivity class inside JAR extends UnityPlayerNativeActivity. and you don't want to change your JAR, then you create a new Activity class extends YourActivity. Create a new Jar + old Jar and make a new plugins.
========================
If you want to call a function directly from C# to Java class, you still can do it by using Android NDK to build an JNI lib
This sample demonstrates how Java code can be used to interact with
the Android OS and how C++ creates a bridge between C# and Java. The
scene in the package displays a button which when clicked fetches the
application cache directory, as defined by the Android OS. Please note
that you will need both the JDK and the Android NDK to compile the
plugins.
Reference here: http://docs.unity3d.com/Documentation/Manual/PluginsForAndroid.html
Example here: http://docs.unity3d.com/Documentation/Images/manual/AndroidJavaPlugin.zip
But I'm not sure if you can use your own Activity or not. And I recommend creating a new Activity Extends from UnityPlayerNativeActivty, because that way is more simple and unstandable than this way.
Br,
Frank

Using cordova plugin with a native Android interface

I wrote a Cordova plugin for Android to build a phonegap app with an HTML5 GUI.
I now want to have a native interface too and was wondering what is the neatest option to reuse my plugin for the native UI. Basically I would like to have two apps, one with a phonegap (HTML5) interface and one with a native Android interface, both of them using the Cordova plugin.
The plugin extends the CordovaPlugin class, so for this reason I don't know how to use it without calling the following method from the javascript in the WebView, as described here http://docs.phonegap.com/en/2.3.0/guide_plugin-development_android_index.md.html
exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);
I just want to call the native side of the plugin without going through a WebView:
#Override
public boolean execute(String action, JSONArray args,
CallbackContext callbackContext) throws JSONException { ... }
Provided that I could just adapt the code from the plugin fairly easily, I would like to find a method by which the plugin remains exactly the same for better decoupling of frontend/backend (I could change the code in one app without the need to replicate it in the other app).
Is this possible at all? I understand this is not the point of having a Cordova plugin, but I would like to find a way around it.
Thanks.
In my opinion you need to apply Facade pattern:
http://en.wikipedia.org/wiki/Facade_pattern
Simply extract your business logic from Cordova plugin to dedicated class called MyFacade and hide all your business logic behind.
The other way is to do something like this:
MyCordovaPlugin myPlugin = new MyCordovaPlugin();
myPlugin.execute("foo", new JSONArray(), new MyCallbackContext() {
#override
public void handlePluginResult(PluginResult pluginResult) {
//your code for handling plugin result for Android UI
}
}
Where MyCallbackContext implementation is:
public abstract class MyCallbackContext extends CallbackContext {
public MyCallbackContext() {
super(null, null);
}
public void sendPluginResult(PluginResult pluginResult) {
synchronized (this) {
if (finished) {
Log.w(LOG_TAG, "Attempted to send a second callback for ID: " + callbackId + "\nResult was: " + pluginResult.getMessage());
return;
} else {
finished = !pluginResult.getKeepCallback();
}
}
handlePluginResult(pluginResult);
}
public abstract void handlePluginResult(PluginResult pluginResult);
}
This second way works only with current version of Cordova and is based on this source codes:
https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/api/CordovaPlugin.java
https://github.com/apache/cordova-android/blob/master/framework/src/org/apache/cordova/api/CallbackContext.java

Categories

Resources