I want to use the light sensor on Android with Unity. Unity can't do this and there is no asset/plugin on the store who could do that. And google isn't so frendly with this question...
Simple version
This part explain how to use my code.
Put the .jar file into Assets/Plugins/Android folder
Then simply add the script LightSensorPluginScript.cs on the GameObject you wanted.
Then if you want to get the sensor value:
TextMesh tm;
LightSensorPluginScript test;
void Start() {
tm = transform.GetComponent<TextMesh>();
test = GetComponent<LightSensorPluginScript> ();
}
void Update() {
tm.text = test.getLux().ToString();
}
All the files can be found in this zip archive
Detailed version
This part explain how to create the plugin.
First of all you have to create an Android library. If you are using Android Studio, they are converted into .aar files. Extract them like a zip file and you will find a classes.jar file which is the correct .jar you want. You can rename it as you want, Unity didn't care.
Android Java code
public class LightSensorLib{
private SensorManager mSensorManager;
private Sensor mSensorRot;
private float lux = -1000;
public void init(Context context) {
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mSensorRot = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
SensorEventListener mySensorEventListener = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent event) {
float sensorData[];
if(event.sensor.getType()== Sensor.TYPE_LIGHT) {
sensorData = event.values.clone();
lux = sensorData[0];
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
};
mSensorManager.registerListener(mySensorEventListener, mSensorRot, 500);
}
public float getLux () {
return lux;
}
}
Then I have a C# script who does the bridge between Android Java and Unity
C# Bridge code
public class LightSensorPluginScript : MonoBehaviour {
private AndroidJavaObject activityContext = null;
private AndroidJavaObject jo = null;
AndroidJavaClass activityClass = null;
void Start () {
#if UNITY_ANDROID
activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
jo = new AndroidJavaObject("com.etiennefrank.lightsensorlib.LightSensorLib");
jo.Call("init", activityContext);
#endif
}
public float getLux() {
#if UNITY_ANDROID
return jo.Call<float>("getLux");
#endif
}
}
Now you can do what is explained in the Simple version part of this post to use the sensor.
If you want any precision I would be glad to answer and update the post.
For the IOs version I'm sorry but I have no mac to develop this... So if you want to lend me one I would be glad to do it.
I could add it as a plugin on the asset store but it feels a bit cumbersome. So I prefer to post it on StackOverflow where the post editor is really neat.
At last I hope it helps some developers.
using UnityEngine;
using System;
using System.Collections;
public class LightSensorPluginScript : MonoBehaviour {
private AndroidJavaObject activityContext = null;
private AndroidJavaObject jo = null;
AndroidJavaClass activityClass = null;
public TextMesh tm;
LightSensorPluginScript test;
void Start () {
#if UNITY_ANDROID
activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
tm = transform.GetComponent<TextMesh>();
test = GetComponent<LightSensorPluginScript>();
jo = new AndroidJavaObject("com.etiennefrank.lightsensorlib.LightSensorLib");
jo.Call("init", activityContext);
#endif
}
public float getLux() {
#if UNITY_ANDROID
return jo.Call<float>("getLux");
#endif
}
void Update()
{
tm.text = test.getLux().ToString();
}
}
Related
In a Xamarin Forms (3.0) application, what method(s) would I use to tell if a drawable resource exists in my Android project from shared project code?
In iOS, I can use NSFileManager to see if the file exists in my iOS project's "Resources" folder:
#if __IOS__
private bool DoesImageExist(string image)
{
//WORKS
return Foundation.NSFileManager.DefaultManager.FileExists(image);
}
#endif
In Android, I thought it should be part of the Assembly Resources, but that just returns my App.xaml file.
#if __ANDROID__
private bool DoesImageExist(string image)
{
//DOES NOT WORK
if(MyApp.Current?.GetType() is Type type)
foreach (var res in Assembly.GetAssembly(type).GetManifestResourceNames())
{
if (res.Equals(image, StringComparison.CurrentCultureIgnoreCase))
return true;
}
return false;
}
#endif
How to specifically check if a drawable exists by name in android would work like something this:
#if __ANDROID__
public bool DoesImageExist(string image)
{
var context = Android.App.Application.Context;
var resources = context.Resources;
var name = Path.GetFileNameWithoutExtension(image);
int resourceId = resources.GetIdentifier(name, "drawable", context.PackageName);
return resourceId != 0;
}
#endif
If your code is in a pcl or .net standard assembly, you will have to create an abstraction. Some kind of Ioc library works well for this. You can also have Android or iOS implement the abstract interface and make that available as a singleton somewhere. It's not as elegant, but it would work.
Basically you would implement something like this:
public interface IDrawableManager
{
bool DoesImageExist(string path);
}
Then have two implementations:
public class DroidDrawableManager : IDrawableManager
{
var context = Android.App.Application.Context;
var resources = context.Resources;
var name = Path.GetFileNameWithoutExtension(image);
int resourceId = resources.GetIdentifier(name, "drawable", context.PackageName);
return resourceId != 0;
}
public class IOSDrawableManager : IDrawableManager
{
public bool DoesImageExist(string image)
{
return Foundation.NSFileManager.DefaultManager.FileExists(image);
}
}
I've uploaded a working sample to github:
https://github.com/curtisshipley/ResourceExists
I am developing a Cordova plugin for Android and I am having difficulty overcoming accessing project resources from within an activity - the plugin should be project independent, but accessing the resources (e.g. R.java) is proving tricky.
My plugin, for now, is made up of two very simple classes: RedLaser.java and RedLaserScanner.java.
RedLaser.java
Inherits from CordovaPlugin and so contains the execute method and looks similar to the following.
public class RedLaser extends CordovaPlugin {
private static final string SCAN_ACTION = "scan";
public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
if (action.equals(SCAN_ACTION)) {
this.cordova.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
scan(args, callbackContext);
}
});
return true;
}
return false;
}
private void scan(JSONArray args, CallbackContext callbackContext) {
Intent intent = new Intent(this.cordova.getActivity().getApplicationContext(), RedLaserScanner.class);
this.cordova.startActivityForResult((CordovaPlugin) this, intent, 1);
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Do something with the result
}
}
RedLaserScanner.java
The RedLaserScanner contains the Android Activity logic and inherits from BarcodeScanActivity (which is a RedLaser SDK class, presumably itself inherits from Activity);
A very simple structure is as follows:
public class RedLaserScanner extends BarcodeScanActivity {
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.preview_overlay_new_portrait);
}
}
I am having trouble because I need to access the project's resources to access R.layout.preview_overlay_new_portrait (which are scatted in the Eclipse project) - but I cannot do this unless I import com.myProject.myApp.R - which makes my plugin have a dependency on the project itself.
I did some investigation and found cordova.getActivity().getResources() which seems useful, but this is not accessible from within my RedLaserScanner - because it does not inherit from CordovaPlugin.
Can somebody please help me with some pointers?
Thanks
I just ran into the same issue and it turns out to be pretty easy to solve. RedLaserScanner extends an activity, so you can just call getResources() like this:
setContentView(getResources("preview_overlay_new_portrait", "layout", getPackageName()));
Hooks can be used to replace source file contents to remove wrong imports and/or add the right imports of resources.
I created a script that do it without needing to specify the files. It tries to find source files (with .java extension), removes any resource import already in it and then put the right resources import (if needed), using the Cordova application package name.
This is the script:
#!/usr/bin/env node
/*
* A hook to add resources class (R.java) import to Android classes which uses it.
*/
function getRegexGroupMatches(string, regex, index) {
index || (index = 1)
var matches = [];
var match;
if (regex.global) {
while (match = regex.exec(string)) {
matches.push(match[index]);
console.log('Match:', match);
}
}
else {
if (match = regex.exec(string)) {
matches.push(match[index]);
}
}
return matches;
}
module.exports = function (ctx) {
// If Android platform is not installed, don't even execute
if (ctx.opts.cordova.platforms.indexOf('android') < 0)
return;
var fs = ctx.requireCordovaModule('fs'),
path = ctx.requireCordovaModule('path'),
Q = ctx.requireCordovaModule('q');
var deferral = Q.defer();
var platformSourcesRoot = path.join(ctx.opts.projectRoot, 'platforms/android/src');
var pluginSourcesRoot = path.join(ctx.opts.plugin.dir, 'src/android');
var androidPluginsData = JSON.parse(fs.readFileSync(path.join(ctx.opts.projectRoot, 'plugins', 'android.json'), 'utf8'));
var appPackage = androidPluginsData.installed_plugins[ctx.opts.plugin.id]['PACKAGE_NAME'];
fs.readdir(pluginSourcesRoot, function (err, files) {
if (err) {
console.error('Error when reading file:', err)
deferral.reject();
return
}
var deferrals = [];
files.filter(function (file) { return path.extname(file) === '.java'; })
.forEach(function (file) {
var deferral = Q.defer();
var filename = path.basename(file);
var file = path.join(pluginSourcesRoot, filename);
fs.readFile(file, 'utf-8', function (err, contents) {
if (err) {
console.error('Error when reading file:', err)
deferral.reject();
return
}
if (contents.match(/[^\.\w]R\./)) {
console.log('Trying to get packages from file:', filename);
var packages = getRegexGroupMatches(contents, /package ([^;]+);/);
for (var p = 0; p < packages.length; p++) {
try {
var package = packages[p];
var sourceFile = path.join(platformSourcesRoot, package.replace(/\./g, '/'), filename)
if (!fs.existsSync(sourceFile))
throw 'Can\'t find file in installed platform directory: "' + sourceFile + '".';
var sourceFileContents = fs.readFileSync(sourceFile, 'utf8');
if (!sourceFileContents)
throw 'Can\'t read file contents.';
var newContents = sourceFileContents
.replace(/(import ([^;]+).R;)/g, '')
.replace(/(package ([^;]+);)/g, '$1 import ' + appPackage + '.R;');
fs.writeFileSync(sourceFile, newContents, 'utf8');
break;
}
catch (ex) {
console.log('Could not add import to "' + filename + '" using package "' + package + '". ' + ex);
}
}
}
});
deferrals.push(deferral.promise);
});
Q.all(deferrals)
.then(function() {
console.log('Done with the hook!');
deferral.resolve();
})
});
return deferral.promise;
}
Just add as an after_plugin_install hook (for Android platform) in your plugin.xml:
<hook type="after_plugin_install" src="scripts/android/addResourcesClassImport.js" />
Hope it helps someone!
I implemented a helper for this to keep things clean. It also helps when you create a plugin which takes config.xml arguments which you store in a string resource file in the plugin.
private int getAppResource(String name, String type) {
return cordova.getActivity().getResources().getIdentifier(name, type, cordova.getActivity().getPackageName());
}
You can use it as follows:
getAppResource("app_name", "string");
That would return the string resource ID for app_name, the actually value still needs to be retrieved by calling:
this.activity.getString(getAppResource("app_name", "string"))
Or for the situation in the original question:
setContentView(getAppResource("preview_overlay_new_portrait", "layout"));
These days I just create a helper which returns the value immediately from the the helper:
private String getStringResource(String name) {
return this.activity.getString(
this.activity.getResources().getIdentifier(
name, "string", this.activity.getPackageName()));
}
which in turn you'd call like this:
this.getStringResource("app_name");
I think it's important to point out that when you have the resource ID you're not always there yet.
try using android.R.layout.preview_overlay_new_portrait
I am developing an Android plugin for Unity and I am wondering if there is a way in the Android code to call a Unity C# method that returns a value and get this value.
Of course this will NOT work but is there a way, tips, tricks to achieve something like this:
String myReturnedString = UnityPlayer.UnitySendMessage("MyGameObject",
"ReturnThisString","hello");
Many thanks for your help.
In Android:
int Times_Called;
public int Get_Times_Called()
{
Times_Called += 1;
return Times_Called;
}
In Unity
AndroidJavaClass Java = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject AndroidActivity = Java.GetStatic<AndroidJavaObject>("currentActivity");
int Times_Called = AndroidActivity.Call<int>("Get_Times_Called");
Debug.Log("Get_Times_Called: " + Times_Called);
One way is to have a method in your Unity program that communicates with your Android application through a method in your Android app, by using AndroidJavaObject.Call in your Unity class.
e.g.
You have this method in your Android application.
AndroidActivity.java
String stringFromUnity = "";
public void setStringFromUnity(String input){
stringFromUnity = input;
}
Then in your Unity script (I am using C#).
UnityScript.cs
// Get the UnityPlayer class
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
// Get the current activity
AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
// Call the setStringFromUnity method from Android Activity
activity.Call("setStringFromUnity", "Here is a string for you, Android");
OR we can set the String directly using the AndroidJavaObject.Set method.
// Set the String directly provided the String is public
activity.Set<string>("stringFromUnity", "I set your String directly, hah!");
Hope this helps.
For reference, you can search for "Unity Plugins for Android" or take a look at Unity documentation at this link.
First, we create a method 'reverse' in UnityPlayerActivity
UnityPlayerActivity.java:
public class UnityPlayerActivity extends Activity {
...
public String reverse(String str) {
return new StringBuilder(str).reverse().toString();
}
...
}
Then we call next block somewhere in Unity (e.g. in Monobehaviour):
private static void CallAndroidMethod()
{
var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
var args = new object[1];
args[0] = "Hello World!";
var reversed = currentActivity.Call<string>("reverse", args);
Debug.Log($"Reversed: {reversed}");
}
Finally, checking the Log (e.g. in AndroidStudio):
I/Unity: Reversed: !dlroW olleH
Can anyone point me to an example of take a Photo and store it using MVVMCross?
I have been searching but only have found this:
Monodroid Take a picture with Camera (Doesn't Implement MVVMCross)
Video Recording (It's Video and i can't make it work :S)
The Oficialy Recipe Example (It Works but does not implement MVVMCross)
Thanks!!!
Resolved! Thanks!
To Future References: (Using Master Branch)
Credits to Stuart, I just changed the code to work with my reality
using Cirrious.MvvmCross.ExtensionMethods;
using Cirrious.MvvmCross.Interfaces.Platform.Tasks;
using Cirrious.MvvmCross.Interfaces.ServiceProvider;
using SIGEP.DummyService;
using SIGEP.Mobile.Core.Interfaces;
namespace SIGEP.Mobile.Core.Models
{
public class PhotoService : IMvxServiceConsumer<IMvxPictureChooserTask>
{
private const int MaxPixelDimension = 1024;
private const int DefaultJpegQuality = 92;
public void GetNewPhoto()
{
this.GetService<IMvxPictureChooserTask>().TakePicture(
MaxPixelDimension,
DefaultJpegQuality,
HandlePhotoAvailable,
() => { /* cancel is ignored */ });
}
public event EventHandler<PhotoStreamEventArgs> PhotoStreamAvailable;
private void HandlePhotoAvailable(Stream pictureStream)
{
var handler = PhotoStreamAvailable;
if (handler != null)
{
handler(this, new PhotoStreamEventArgs() { PictureStream = pictureStream, OnSucessGettingPhotoFileName = OnSucessGettingPhotoFileName });
}
}
public static void TakePhoto(Action<string> successFileName, Action<Exception> error)
{
var service = new PhotoService();
service.OnSucessGettingPhotoFileName = successFileName;
service.OnError = error;
service.GetNewPhoto();
service.PhotoStreamAvailable += new EventHandler<PhotoStreamEventArgs>(service_PhotoStreamAvailable);
}
static void service_PhotoStreamAvailable(object sender, PhotoStreamEventArgs e)
{
//grava pra ficheiro!!!
var directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var filename = Path.Combine(directory, "photo.jpeg");
string saveTo = filename;
FileStream writeStream = new FileStream(saveTo, FileMode.Create, FileAccess.Write);
ReadWriteStream(e.PictureStream, writeStream);
e.OnSucessGettingPhotoFileName(filename);
}
private static void ReadWriteStream(Stream readStream, Stream writeStream)
{
int Length = 256;
Byte[] buffer = new Byte[Length];
int bytesRead = readStream.Read(buffer, 0, Length);
// write the required bytes
while (bytesRead > 0)
{
writeStream.Write(buffer, 0, bytesRead);
bytesRead = readStream.Read(buffer, 0, Length);
}
readStream.Close();
writeStream.Close();
}
public Action<string> OnSucessGettingPhotoFileName { get; set; }
public Action<Exception> OnError { get; set; }
}
[Serializable]
[ComVisible(true)]
public class PhotoStreamEventArgs : EventArgs
{
public Stream PictureStream { get; set; }
public Action<string> OnSucessGettingPhotoFileName { get; set; }
}
}
I generally implement a service using the built-in IMvxPictureChooserTask (this is in a Plugin if using vNext):
using Cirrious.MvvmCross.ExtensionMethods;
using Cirrious.MvvmCross.Interfaces.Platform.Tasks;
using Cirrious.MvvmCross.Interfaces.ServiceProvider;
public class PhotoService
: IMvxServiceConsumer<IMvxPictureChooserTask>
, IPhotoService
{
private const int MaxPixelDimension = 1024;
private const int DefaultJpegQuality = 92;
public void GetNewPhoto()
{
Trace.Info("Get a new photo started.");
this.GetService<IMvxPictureChooserTask>().TakePicture(
MaxPixelDimension,
DefaultJpegQuality,
HandlePhotoAvailable,
() => { /* cancel is ignored */ });
}
public event EventHandler<PhotoStreamEventArgs> PhotoStreamAvailable;
private void HandlePhotoAvailable(Stream pictureStream)
{
Trace.Info("Picture available");
var handler = PhotoStreamAvailable;
if (handler != null)
{
handler(this, new PhotoStreamEventArgs() { PictureStream = pictureStream });
}
}
}
I generally register this service as a singleton during startup, and then call it from a ViewModel ICommand handler.
One app which uses this service is the Blooor sample - see BaseEditProductViewModel.cs - this isn't a sample I had anything to do with, but I believe it brings in both Picture taking and ZXing - both using external services.
One warning: On MonoDroid, you can see some strange/unexpected Activity/ViewModel lifecycle behaviour - basically you can see that the Activity you take the photo from is unloaded/wiped from memory during the photo taking. If this happens to your app then you'll probably need to start looking at questions like: Saving Android Activity state using Save Instance State - this isn't automatically handled in MvvmCross (yet).
I believe the Blooor sample might suffer from this issue - but whether a user would ever see it in normal app use is debatable.
As an alternative to the IMvxPictureChooserTask service, you can also look at using some of the cross-platform APIs from Xamarin.Mobile - see MvvmCross vnext : monodroid use a VideoView inside a plugin for a possible starting place - or for Android only you can easily implement your own.
I use opencv stitching in android project.
public class MainActivity extends Activity implements OnClickListener {
private String mWarpType;
private String mMatchConf;
private String mConfThresh;
private SharedPreferences mSettings;
public static final String SETTINGS = "Pano_Settings";
private final String SETTINGS_WARP_TYPE = "warp";
private final String SETTINGS_MATCH_CONF = "match_conf";
private final String SETTINGS_CONF_THRESH = "conf_thresh";
private String mDefaultWarpType = "spherical";
private String mDefaultMatchConf = "0.5";
private String mDefaultConfThresh = "0.8";
...
public native int Stitch(Object[] args);
public void onClick(View v) {
switch (v.getId()) {
case R.id.GoButton:
List<String> s = new ArrayList<String>();
s.add("Stitch");
s.add("/sdcard/tesseract/images1.jpeg");
s.add("/sdcard/tesseract/images2.jpeg");
s.add("--warp");
s.add(mWarpType);
s.add("--conf_thresh");
s.add(mConfThresh);
s.add("--match_conf");
s.add(mMatchConf);
s.add("--work_megapix");
s.add("0.2");
s.add("--seam_megapix");
s.add("0.2");
s.add("--expos_comp");
s.add("gain");
s.add("--output");
s.add("/sdcard/tesseract/");
Integer i = Stitch(s.toArray());
Log.d("1",i.toString());
break;
default:
break;
}
}
}
application is started but when Stitch(s.toArray()) is called I get the error:
W/dalvikvm(15392): No implementation found for native Lcom/prototype/MainActivity;.Stitch ([Ljava/lang/Object;)I
OpenCV successfully added in the workplace and my project -> Properties -> Android -> Library add -> OpenCV lib project
version OpenCV 2.4.2.
sample was taken from the project android-opencv-panorama.
You probably copied the native code "as is" from the sample, but your Java class has a different package and name. Look for the function named Java_<some more>_Stitch() in your cpp file, and rename it to become:
Java_com_prototype_MainActivity_Stitch()