Receiving SMS with MonoDroid - c#

Edit: This is now resolved - posting solution here in case someone needs it in future or anyone can suggest a better way of doing this. I removed the intent stuff from my manifest and just setup the BroadcastReceiver in my SmsReceiver class. This now works.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Util;
using Android.Telephony;
namespace dummyAndroid
{
[BroadcastReceiver(Enabled = true, Label = "SMS Receiver")]
[IntentFilter(new string[] { "android.provider.Telephony.SMS_RECEIVED" })]
public class SmsReceiver : Android.Content.BroadcastReceiver
{
public static readonly string INTENT_ACTION = "android.provider.Telephony.SMS_RECEIVED";
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action == INTENT_ACTION)
{
StringBuilder buffer = new StringBuilder();
Bundle bundle = intent.Extras;
if (bundle != null)
{
Java.Lang.Object[] pdus = (Java.Lang.Object[])bundle.Get("pdus");
SmsMessage[] msgs;
msgs = new SmsMessage[pdus.Length];
for (int i = 0; i < msgs.Length; i++)
{
msgs[i] = SmsMessage.CreateFromPdu((byte[])pdus[i]);
Log.Info("SmsReceiver", "SMS Received from: " + msgs[i].OriginatingAddress);
Log.Info("SmsReceiver", "SMS Data: " + msgs[i].MessageBody.ToString());
}
Log.Info("SmsReceiver", "SMS Received");
}
}
}
}
}
I'm writing an app that sends/receives SMS messages and have got sending working via the SMS Mananger.
I'm now trying to receive SMS in Mono for Android and am new to Android development so am probably doing something wrong!
I have added the following to my manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" package="com.me.dummyAndroid" android:versionCode="1" android:versionName="1">
<uses-sdk android:targetSdkVersion="8" />
<application
android:label="meAndroidSMS"
android:theme="#android:style/Theme.NoTitleBar.Fullscreen">
<receiver android:name=".SmsReceiver">
<intent-filter>
<action android:name=
"android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
</manifest>
I then created a new class called SmsReceiver.cs which I've added the function onReceive into but there doesn't appear to be the getExtras function within intent which according to the online tutorial I read I would need (http://www.techques.com/question/1-3542320/IPhone-Android-SMS-intercept-and-redirection-to-an-application.).
namespace dummyAndroid
{
class SmsReceiver
{
public void onReceive(Context context, Intent intent)
{
Bundle bundle = intent.getExtras();
}
}
}
I appreciate I'm in a bit over my head on Android and MonoDroid for sure but maybe someone can point me in the right direction!

I have some working code here (I can't remember the source, but I think it was someone associated with Xamarin who wrote this sample), which displays a Toast when an SMS is recieved.
using System.Text;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Util;
using Android.Widget;
using Android.Telephony;
using Environment = System.Environment;
namespace MonoDroid.SMSFun
{
[BroadcastReceiver(Enabled = true, Label = "SMS Receiver")]
[IntentFilter(new[] { "android.provider.Telephony.SMS_RECEIVED" })]
public class SMSBroadcastReceiver : BroadcastReceiver
{
private const string Tag = "SMSBroadcastReceiver";
private const string IntentAction = "android.provider.Telephony.SMS_RECEIVED";
public override void OnReceive(Context context, Intent intent)
{
Log.Info(Tag, "Intent received: " + intent.Action);
if (intent.Action != IntentAction) return;
var bundle = intent.Extras;
if (bundle == null) return;
var pdus = bundle.Get("pdus");
var castedPdus = JNIEnv.GetArray<Java.Lang.Object>(pdus.Handle);
var msgs = new SmsMessage[castedPdus.Length];
var sb = new StringBuilder();
for (var i = 0; i < msgs.Length; i++)
{
var bytes = new byte[JNIEnv.GetArrayLength(castedPdus[i].Handle)];
JNIEnv.CopyArray(castedPdus[i].Handle, bytes);
msgs[i] = SmsMessage.CreateFromPdu(bytes);
sb.Append(string.Format("SMS From: {0}{1}Body: {2}{1}", msgs[i].OriginatingAddress,
Environment.NewLine, msgs[i].MessageBody));
}
Toast.MakeText(context, sb.ToString(), ToastLength.Long).Show();
}
}
}
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="monodroid.smsfun" android:versionCode="1" android:versionName="1.0">
<uses-sdk android:targetSdkVersion="8" />
<application android:label="SMSFun">
</application>
<uses-permission android:name="android.permission.RECEIVE_SMS" />
</manifest>

Related

C# SerialPort with Java Android phone

This bounty has ended. Answers to this question are eligible for a +50 reputation bounty. Bounty grace period ends in 1 hour.
obeid_s is looking for an answer from a reputable source.
what i'm trying to do:
Create an App for windows to connect android phone.
Send and receive data.
Windows (C#) is the host device --> android UsbAccessory.
C#:
now i can select the COM5 (Android phone) and connect to it, using this code:
serialPort1.PortName = this.devicesList.Text; // COM5
serialPort1.Open();
addToList("SerialPort is opened", Color.Green);
Java (Android Phone):
UsbManager manager = (UsbManager) getSystemService(Context.USB_SERVICE);
UsbAccessory[] accessories = manager.getAccessoryList();
accessories is Always Null, i tried every code i find, but i could not do it.
what i'm doing wrong?
Thanks.
::: EDIT :::
Android Studio Code (Java)
package com.example.camerausb;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.widget.Toast;
import androidx.annotation.Nullable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class UsbConnector extends Activity {
private static final String ACTION_USB_PERMISSION = "com.example.camerausb.UsbConnector.USB_PERMISSION";
public UsbManager usbManager;
public UsbAccessory usbAccessory;
public PendingIntent mPermissionIntent;
public Context global_context;
private IntentFilter filter;
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
synchronized (this) {
UsbAccessory accessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
Toast.makeText(global_context, "Allow Usb Permission", Toast.LENGTH_LONG).show();
// TODO:: open Accessory method
} else {
Toast.makeText(global_context, "Deny Usb Permission", Toast.LENGTH_LONG).show();
}
}
}
else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
Toast.makeText(global_context, "Usb detached", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(global_context, "something else", Toast.LENGTH_LONG).show();
}
}
};
public UsbConnector(Context context) {
super();
global_context = context;
Toast.makeText(global_context, "started", Toast.LENGTH_LONG).show();
usbManager = (UsbManager) global_context.getSystemService(Context.USB_SERVICE);
mPermissionIntent = PendingIntent.getBroadcast(global_context, 0, new Intent(ACTION_USB_PERMISSION), 0);
filter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
usbManager.requestPermission(usbAccessory, mPermissionIntent);
global_context.registerReceiver(mUsbReceiver, filter);
}
public BroadcastReceiver getReceiver() {
return this.mUsbReceiver;
}
public IntentFilter getFilter() {
return this.filter;
}
public void resumeAccessory(Intent intent) {
// Intent intent = getIntent();
String action = intent.getAction();
Toast.makeText(global_context, "resume: " + action, Toast.LENGTH_LONG).show();
UsbAccessory[] accessories = usbManager.getAccessoryList();
if (accessories != null) {
Toast.makeText(global_context, "Accessory not NULL", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(global_context, "Accessory is NULL", Toast.LENGTH_LONG).show();
}
}
}
Manifest :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.camerausb">
<uses-sdk android:minSdkVersion="12" />
<uses-feature android:name="android.hardware.usb.accessory" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.CameraUsb">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="#xml/accessory_filter" />
</activity>
</application>
</manifest>
xml/accessory_filter :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-accessory model="DemoKit" manufacturer="Google" version="1.0" />
</resources>
someone told me to add usbManager.requestPermission(usbAccessory, mPermissionIntent);
the phone screen goes to black and luck it self!!

FCM Notifications in Xamarin.Forms only working in android emulator

I've been trying to implement FCM into my xamarin.forms app in VS2019 and I've been stuck on a problem for a week. Could someone help me out please? It may be something glaringly obvious I've missed...
Problem: When I port the microsoft xamarin code (see link below) into my xamarin.forms app, the FCM notifications will only display in the android emulator, not on an android physical device.
I've tried stripping this back into a bare-bones xamarin.forms application, and I'm experiencing exactly the same problem. The code is similar to the microsoft sample solution found at:
https://learn.microsoft.com/en-us/samples/xamarin/monodroid-samples/firebase-fcmnotifications/
I'll note that compiling the microsoft example does actually work on both the emulated device and also the physical device, both receive and display notifications when the app is minimized.
I've tried this on multiple physical devices, all with the same results - I never get an FCM message on a physical device, compiling exactly the same code and running in the emulated environment - no probs! I get the FCM messages every time.
On the physical devices (4 of them) are a selection running Samsung, MIUI, Huawei and stock android. I have ensured the usual things like enabling Autostart, disabling battery optimisation, ensuring that my app allowed notifications, and ensuring that in app into - that it is registered for FCM notifications and allowing them. (all to no avail).
I've added the appropriate NuGet packages
(Xamarin.Essentials, Xamarin.Firebase.Common, Xamarin.FirebaseIid, Xamarin.Firebase.Messaging, Xamarin.GooglePlayServices.Base+Basement+Tasks).
I'm not getting any compiler errors or other warnings.
I noticed there was a couple of differences in the OnCreate methods, I'm passing the data for a savedInstanceState and it looks as though in the MS example they're passing a new bundle, but I'm not clear that this would make any difference, and would not explain this behaviour.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.corp.my-fcm-test" android:installLocation="auto">
<uses-sdk android:minSdkVersion="23" android:targetSdkVersion="28" />
<application android:label="my-fcm-test">
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver" android:exported="false" />
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver" android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
</application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
MainActivity.cs:
using System;
using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using myappname.Droid;
using Android.Gms.Common;
using Firebase.Iid;
using Firebase.Messaging;
using Xamarin.Forms;
using System.Runtime.CompilerServices;
namespace my_fcm_test.Droid
{
[Activity(Label = "my_fcm_test", Icon = "#mipmap/icon", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
const string TAG = "*** *** *** MainActivity";
internal static readonly string CHANNEL_ID = "my_notification_channel";
internal static readonly int NOTIFICATION_ID = 100;
TextView msgText;
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(savedInstanceState);
// go through each of the keys in inten.extras and log what they are.
if (Intent.Extras != null)
{
foreach (var key in Intent.Extras.KeySet())
{
var value = Intent.Extras.GetString(key);
Console.WriteLine(TAG, "Key: {0} Value: {1}", key, value);
}
}
CreateNotificationChannel();
IsPlayServicesAvailable();
Console.WriteLine(TAG, "InstanceID token: " + FirebaseInstanceId.Instance.Token);
FirebaseMessaging.Instance.SubscribeToTopic("news");
Console.WriteLine(TAG, "Subscribed to remote notifications");
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
public bool IsPlayServicesAvailable()
{
var resultCode = GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(this);
if (resultCode != ConnectionResult.Success)
{
if (GoogleApiAvailability.Instance.IsUserResolvableError(resultCode))
{
msgText.Text = GoogleApiAvailability.Instance.GetErrorString(resultCode);
}
else
{
Console.WriteLine("*** *** *** This device is not supported");
Finish();
}
return false;
}
Console.WriteLine("*** *** *** Google Play Services is available.");
return true;
}
void CreateNotificationChannel()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var channel = new NotificationChannel(CHANNEL_ID, "FCM Notifications", NotificationImportance.Default)
{
Description = "Firebase Cloud Messages appear in this channel"
};
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.CreateNotificationChannel(channel);
}
}
}
MyFirebaseMessagingServices.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Support.V4.App;
using Android.Views;
using Android.Widget;
using Firebase.Messaging;
using my_fcm_test.Droid;
namespace my-fcm-test.Droid
{
[Service]
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
public class MyFirebaseMessagingService : FirebaseMessagingService
{
const string TAG = "MyFirebaseMsgService";
public override void OnMessageReceived(RemoteMessage message)
{
Console.WriteLine(TAG, "From: " + message.From);
var body = message.GetNotification().Body;
Console.WriteLine(TAG, "Notification Message Body: " + body);
SendNotification(body, message.Data);
}
void SendNotification(string messageBody, IDictionary<string, string> data)
{
var intent = new Intent(this, typeof(MainActivity));
intent.AddFlags(ActivityFlags.ClearTop);
foreach (var key in data.Keys)
{
intent.PutExtra(key, data[key]);
}
var pendingIntent = PendingIntent.GetActivity(this, MainActivity.NOTIFICATION_ID, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new NotificationCompat.Builder(this, MainActivity.CHANNEL_ID)
.SetContentTitle("FCM Message")
.SetContentText(messageBody)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
var notificationManager = NotificationManagerCompat.From(this);
notificationManager.Notify(MainActivity.NOTIFICATION_ID, notificationBuilder.Build());
}
}
}
MyFirebaseIIDService.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Firebase.Iid;
namespace my-fcm-test.Droid
{
[Service]
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
public class MyFirebaseIIDService : FirebaseInstanceIdService
{
const string TAG = "MyFirebaseIIDService";
public override void OnTokenRefresh()
{
var refreshedToken = FirebaseInstanceId.Instance.Token;
Console.WriteLine(TAG, "Refreshed token: " + refreshedToken);
SendRegistrationToServer(refreshedToken);
}
void SendRegistrationToServer(string token)
{
// Add custom implementation, as needed.
}
}
}
And of course I've included my google-services.json, I'm going to have to assume that file is correct because:
a) I generated a google-services.json file to use with the microsoft xamarin sample, and it worked fine on all devices.
b) I don't change the google-services.json file between doing a debug build and a release build.
NB I've changed the namespaces for this post.
any help appreciated...
open Android Project properties, in Android options (Packaging Properties) code shrinker keep it blank. It will work

How can I load a JSON from an URL using unity for android?

I'm doing a Unity app for android that scans a QR code and displays a model with some buttons. The buttons displays the info of an event, said info is stored in a website. The way the app request the info is:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Android;
using UnityEngine.Networking;
using UnityEngine.UI;
public class WebTextLoader : MonoBehaviour
{
// Start is called before the first frame update
[Serializable]
public class PlaceInfo
{
public string Titulo = "";
public string Texto = "";
}
public string URL;
public Text TituloUI;
public Text TextoUI;
public PlaceInfo placeInfo;
public void Start()
{
if (Debug.isDebugBuild)
{
StartCoroutine(GetRequest(URL));
}
}
IEnumerator GetRequest(string uri)
{
using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
{
// Request and wait for the desired page.
yield return webRequest.SendWebRequest();
string jsonForm = uri;
if (webRequest.isNetworkError)
{
Debug.Log("Error loading");
}
else
{
try
{
placeInfo = JsonUtility.FromJson<PlaceInfo>(webRequest.downloadHandler.text);
TituloUI.text = placeInfo.Titulo;
TextoUI.text = placeInfo.Texto;
}
catch
{
Debug.Log("Error in connection");
}
}
}
}
}
With this, the app works perfectly on the computer, using the editor, but when I build the apk and install it on my phone...it no longer works. The text never loads, I checked the Android manifest and everything seems ok:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" android:installLocation="preferExternal" android:versionCode="5" android:versionName="5.5">
<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
<application android:icon="#drawable/app_icon" android:label="#string/app_name" android:debuggable="true">
<activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="#string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
Also in player settings, I checked "internet" as "require"
I don't know that i'm missing here, specially since the host i'm using is a paid one so I shouldn't have problems with limitations or stuff.
I'm doing something wrong?

AndroidJavaClass/AndroidJavaObject causing built application to crash

I have an Android application that builds and runs find before the introduction of the following code to an object in my scene. My goal was to vibrate at different lengths for my application instead of just using the default Handheld.Vibrate() function. Once this code is added, my application still builds to my device just fine, but crashes on startup.
I am using Unity 2019.2.9f1, and my minimum API level is set to 25, using Mono and .NET 2.0.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HapticController : Singleton<HapticController> {
#if UNITY_ANDROID && !UNITY_EDITOR
public static AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
public static AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
public static AndroidJavaObject vibrator = currentActivity.Call<AndroidJavaObject>("getSystemService", "vibrator");
#else
public static AndroidJavaClass unityPlayer;
public static AndroidJavaObject currentActivity;
public static AndroidJavaObject vibrator;
#endif
bool isVibrating;
...
// Update is called once per frame
void Update()
{
if(!isVibrating && ( *other condition* ) ){
isVibrating = true; //this will later be changed, just trying to get it to vibrate once
Vibrate(200);
}
}
public static void Vibrate(long milliseconds)
{
if(isAndroid() && vibrator != null){
print("vibrating");
vibrator.Call("vibrate", milliseconds);
} else {
Handheld.Vibrate();
}
}
private static bool isAndroid()
{
#if UNITY_ANDROID && !UNITY_EDITOR
return true;
#else
return false;
#endif
}
}
I know that it is the AndroidJavaClass/AndroidJavaObject calls at the top that are causing the crash. I have checked the logs in logcat in Android Studio, but there is no notable output that would have anything to do with the crash. Is there something I am not accounting for that would cause this?
EDIT: I should add that I am using an SDK (Mapbox) that has its own AndroidManifest.xml. It instructs you to rename it and move it to the Plugins/Android folder. You can see it here:
<!--
Android Manifest for UniAndroid Permission (2016/03/19 sanukin39)
--- if already have AndroidManifest file at Assets/Plugins/Android/ ----
Copy the activity and meta-data sentence to your AndroidManifest.xml
--- if not ---
Rename this file to AndroidManifest.xml and add permission you want to add And move the file to Assets/Plugins/Android
-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product">
<application android:icon="#drawable/app_icon" android:label="#string/app_name">
<activity android:name="net.sanukin.OverrideUnityActivity"
android:label="#string/app_name"
android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />
</application>
</manifest>
For anyone else struggling with this, I was able to solve this with a combination of Farhan's answer and also doing the following:
Instead of initializing the AndroidJavaClass and AndroidJavaObjects at the top, I created an initializer function responsible for doing that. I added the line
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
above of it in order to force it to initialize them before the scene has loaded. I am not sure why this is required, but it fixed it for me. You can see the full function below:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class HapticController : Singleton<HapticController> {
public static AndroidJavaClass unityPlayer = null;
public static AndroidJavaObject currentActivity = null;
public static AndroidJavaObject vibrator = null;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize(){
#if UNITY_ANDROID && !UNITY_EDITOR
if (Application.platform == RuntimePlatform.Android) {
unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
vibrator = currentActivity.Call<AndroidJavaObject>("getSystemService", "vibrator");
}
#endif
}
...
}
You should add following vibration permission in {Project location}\Assets\Plugins\Android\AndroidManifest.xml
<uses-permission android:name="android.permission.VIBRATE"/>
So your final AndroidManifest becomes.
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:theme="#style/UnityThemeSelector"
android:icon="#drawable/app_icon"
android:label="#string/app_name"
android:debuggable="true">
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>

Read Android intent extra data on Unity app launch

I am launching an Unity application from another Android application using a custom implicit intent. This is working fine, but I cannot figure out how to read the intent extra data in Unity?
ANDROID INTENT TO LAUNCH UNITY APP
i=new Intent();
i.setAction("com.company.unityapp.MyMethod");
i.putExtra("KEY","This is the message string");
startActivity(i);
UNITY APP AndroidManifest.xml
<intent-filter>
<action android:name="com.company.unityapp.MyMethod" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
I have a GameObject in my scene with a script attached. Inside the start method I have this code to try and read the extra data that was passed along with the intent
AndroidJavaClass UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaObject intent = currentActivity.Call<AndroidJavaObject>("getIntent");
bool hasExtra = intent.Call<bool> ("hasExtra", "arguments");
if (hasExtra) {
AndroidJavaObject extras = intent.Call<AndroidJavaObject> ("getExtras");
arguments = extras.Call<string> ("getString", "arguments");
}
This is not working and arguments is always empty. Any help would be appreciated.
It took me quite some time to figure this out. All solutions found online were only partly complete. Below is the full solution for launching a Unity application from another android application using a custom implicit Intent and also how to access the extra data sent with the Intent inside Unity.
To accomplish this you need to create a Android plugin that will be used by Unity to access the Intent extra data.
ANDROID PLUGIN:
You need to copy the classes.jar from Unity installation folder to the android plugin folder /lib/classes.jar
public class MainActivity extends UnityPlayerActivity {
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
handleNewIntent(intent);
}
private void handleNewIntent(Intent intent){
String text = intent.getStringExtra("KEY");
UnityPlayer.UnitySendMessage("AccessManager","OnAccessToken", text);
}
}
AndroidManifest.xml
Important here is the package name used: com.company.plugin
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.plugin">
<application
android:allowBackup="true" android:icon="#mipmap/ic_launcher" android:label="#string/app_name"
android:supportsRtl="true" android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Gradle build file:
Add the following to the app gradle build file to be able to create a .jar to be used with Unity
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
sourceSets {
main {
java {
srcDir 'src/main/java'
}
}
}
...
...
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:design:23.2.1'
compile files('libs/classes.jar')
}
//task to delete the old jar
task deleteOldJar(type: Delete) {
delete 'release/AndroidPlugin.jar'
}
//task to export contents as jar
task exportJar(type: Copy) {
from('build/intermediates/bundles/release/')
into('release/')
include('classes.jar')
///Rename the jar
rename('classes.jar', 'AndroidPlugin.jar')
}
exportJar.dependsOn(deleteOldJar, build)
Copy the created AndroidPlugin.jar to Unity Assets/Plugins/Android
UNITY APP:
Set the bundle identifier in PlayerSettings to be the same as set in the Android Plugin - com.company.plugin
Create custom AndroidManifest.xml file in Assets/Plugins/Android
Important here is to use the same package name as used in the plugin.
Also note the Intent name: com.company.plugin.do
AndroidManifest.XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.plugin"
android:versionCode="1" android:versionName="1.0">
<uses-sdk android:minSdkVersion="9" />
<application android:label="#string/app_name">
<activity android:name=".MainActivity" android:label="#string/app_name"
android:launchMode="singleTask" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:screenOrientation="sensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.company.plugin.do" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
</application>
</manifest>
Create a unity script named AccessManager and attach the script to a game object in the scene. OnAccessToken is the method that will receive the message sent from the android plugin and will contain the extra data sent from the intent.
public class accessManager : MonoBehaviour {
public void OnAccessToken(string accessToken)
{
Debug.Log("Message Received!!!! :" + accessToken);
}
}
ANDROID APP:
Create a standard Android application that will launch the Unity Application and send the Intent extra data
public void LaunchUnityApp(){
Intent i=new Intent();
i.setAction("com.company.plugin.do");
i.setType("text/plain");
i.putExtra("KEY","This is the text message sent from Android");
startActivity(i);
}
You don't need a plugin to achieve this. Do your intent from Android like this:
Intent launchIntent = getPackageManager().getLaunchIntentForPackage("com.package.game");
launchIntent.putExtra("my_text", "Some data params");
if(launchIntent != null){
startActivity(launchIntent);
}else{
Log.d("Unity", "Couldnt start unity game");
}
Then in your unity Monobehaviour Class, receive it like this
private void Awake () {
getIntentData ();
}
private bool getIntentData () {
#if (!UNITY_EDITOR && UNITY_ANDROID)
return CreatePushClass (new AndroidJavaClass ("com.unity3d.player.UnityPlayer"));
#endif
return false;
}
public bool CreatePushClass (AndroidJavaClass UnityPlayer) {
#if UNITY_ANDROID
AndroidJavaObject currentActivity = UnityPlayer.GetStatic<AndroidJavaObject> ("currentActivity");
AndroidJavaObject intent = currentActivity.Call<AndroidJavaObject> ("getIntent");
AndroidJavaObject extras = GetExtras (intent);
if (extras != null) {
string ex = GetProperty (extras, "my_text");
return true;
}
#endif
return false;
}
private AndroidJavaObject GetExtras (AndroidJavaObject intent) {
AndroidJavaObject extras = null;
try {
extras = intent.Call<AndroidJavaObject> ("getExtras");
} catch (Exception e) {
Debug.Log (e.Message);
}
return extras;
}
private string GetProperty (AndroidJavaObject extras, string name) {
string s = string.Empty;
try {
s = extras.Call<string> ("getString", name);
} catch (Exception e) {
Debug.Log (e.Message);
}
return s;
}
Credit: https://wenrongdev.com/get-android-intent-data-for-unity/
(updated) https://wenrongdev.com/posts/get-android-intent-data-for-unity/

Categories