Unity

Quick Links

Starting Your Application – Where to begin

Because this tutorial is in-depth, it is based off the assumption that you understand basic android and unity principles. While most of the unity code given should work in iOS as well, I haven’t tested it so I can’t make that promise.

Setting up android: I assume that you already have the android sdk installed, and you have gone through the process of adding your physical android device to the system. This is Unity’s explanation of these steps, as well as Google’s explanation. Note that when you install the SDK, you have to install an Android Platform with an API level equal to or higher than 9 (Platform 2.3 or greater). However, this does not mean the target device has to run on that platform or higher.

Setting up Unity: Install Unity onto your computer, and, after creating a new project, you will need to modify the settings to be able to run the program on your android device. First, Unity needs to recognize the android sdk. The first time you build a project for Android (or if Unity later fails to locate the SDK) you will be asked to locate the folder where you installed the Android SDK (you should select the root folder of the SDK installation). The location of the Android SDK can also be changed in the editor by selecting Unity > Preferences from the menu and then clicking on External Tools in the preferences window. Go to Edit > Project Settings > Player, you will see in your inspector window your project settings for the web, stand-alone, iOS, android, and flash. In the android tab, under “Other Settings,” set your Bundle Identifier to be the project name and the minimum API Level of the device. Then you can Build and Run the Unity program, and it will be pushed onto the device.

(Note, Unity provides a way for you to perform testing on an app called UnityRemote available on Google Play appstore. This is a useful program for testing touch input and device tilt. I did not use Remote, because my program used native Android Plugins, which Remote doesn’t test)

Android Plugin

In order to use push notifications we had to develop an Android Plugin for Unity. In our build, we depend on classes.jar (usually found in C:\Program Files\Unity\Editor\Data (on Windows) or in /Applications/Unity (on Mac)) in a sub-folder called PlaybackEngines/AndroidPlayer/bin), as well as any android.jar found in the Android SDK folder (I recommend using the platform you’re deploying to).

The basic tools I will use are as follows:

AndroidJavaClass and AndroidJavaObject – Two classes found in the Unity framework to be able to access native Java code from within the Unity project.
UnityPlayer.UnitySendMessage – A basic method in Java to send a message from native Java to your Unity code.

These tools are critical for the interaction between native Java and the Unity portion of your code.

I have included the Jar file I used, so if you plan on using Urban Airship, hopefully you won’t have to write any Android code.

This project has two portions. The first part is written in Native Java, in order to handle the push notifications. If you have read through the Native Java portion, this will look very similar. We will include additional functionality in order to make interaction between Android and Unity a bit easier.

Push Notifications

First, we will register with the push notification service through the class C2DMRegisterAgent (A class that I built). This class uses AndroidJavaClass and AndroidJavaObject in order to send the register intent. In order to send the intent, we need the context of the running activity. In Unity’s java code, they have a class called UnityPlayer with the static field currentActivity. This field holds the current running activity in memory. We can use that activity as the current context in order to send the registration intent.

All of this code can be found in my C2DMRegisterAgent.cs class found in Assets > Plugins > FinSlap > Utils directory.

First I have a few helper classes that get me access to the classes and objects that I need. These two C# classes, AndroidJavaObject and AndroidJavaClass, are built through the use of JNI, and you can find these classes here: AndroidJavaObject, AndroidJavaClass, AndroidJNI, and AndroidJNIHelper. You can learn about JNI here: JNI (Not really a necessary read, but helpful if you want to understand the error messages). Basically, AndroidJavaObject allows you to construct any java object, and AndroidJavaClass allows you to run static methods or fields from any Java class. Just so you know, if your app crashes and in logcat you see what looks like a segfault or memory error followed by a Heap Dump (essentially about forty lines of random hexadecimal strings), I found that that usually means that the JNI was unable to find the particular class you were looking for. Unfortunately, the error message (the random hexadecimal strings) is not very informative, so I would suggest you use a lot of Debug.Log output in order to find where the last error was.

C2DMRegisterAgent

All of my interactions with my Android Plugin are found in Assets > Plugins > FinSlap > Utils > C2DMRegisterAgent. Essentially, this is the interface to interact with my plugin.

In C2DMRegisterAgent, I start off with a few helper methods.


		private static AndroidJavaClass getC2DMReceiver() {
			return new AndroidJavaClass(PACKAGE + ".C2DMReceiver");
		}

		private static AndroidJavaClass getUnityPlayer() {
			return new AndroidJavaClass("com.unity3d.player.UnityPlayer");
		}
		private static AndroidJavaObject getCurrentActivity() {
			return getUnityPlayer().GetStatic("currentActivity");
		}

As you can see, I use these methods to get access to the static fields in UnityPlayer and C2DMReceiver, and also to the currently running Android Activity.


		public static void registerForC2DM(string listener) {
			addListener(listener);
			registerForC2DM();
		}

		public static void addListener(string object_name) {
			Debug.Log("Adding listener");
			AndroidJavaClass receiver = getC2DMReceiver();
			receiver.CallStatic("addListener", object_name);
			Debug.Log("Done Adding listener");
		}

		public static void registerForC2DM() {
			Debug.Log("registerForC2DM start");
			AndroidJavaObject currentActivity = getCurrentActivity();
			AndroidJavaObject intent = new AndroidJavaObject(IntentClass, "com.google.android.c2dm.intent.REGISTER");
			intent.Call("putExtra", "sender", SENDER);
			AndroidJavaObject blankIntent = new AndroidJavaObject(IntentClass);
			AndroidJavaObject application = currentActivity.Call("getApplication");
			AndroidJavaClass PendingIntent = new AndroidJavaClass("android.app.PendingIntent");
			AndroidJavaObject pendingIntent = PendingIntent.CallStatic("getBroadcast", application, 0, blankIntent, 0);
			intent.Call("putExtra", "app", pendingIntent);
			application.Call("startService", intent);
			Debug.Log("registerForC2DM end");
		}

Here, I register any object name as a listener to the broadcastReceiver. I will go into more detail about that later. Then I register for C2DM by creating a register intent (IntentClass = “android.content.Intent”), and I put in my sender as an extra. The sender is the username you used to register for C2DM, but this can easily be changed for GCM. I then create the broadcast intent and send the intent. This is essentially the translation of this block of code found on Google’s walkthrough.

		Intent registrationIntent = new Intent("com.google.android.c2dm.intent.REGISTER");
		registrationIntent.putExtra("app", PendingIntent.getBroadcast(this, 0, new Intent(), 0)); // boilerplate
		registrationIntent.putExtra("sender", emailOfSender);
		startService(registrationIntent);

Also, I start and end every method with an output to the log, because if there are any segfault issues I’ll know which method they were in. Again, I cannot stress this enough, because it’ll save you from all kinds of headaches.

Registering with push notification service

I tried to avoid writing Android code at all, hoping that I could build it completely within the Unity framework. Unfortunately, the Android BroadcastReceiver must be written in Android, because Unity runs on top of the Mono engine, and Mono is not necessarily running at the time a push notification comes through. The BroadcastReceiver also must handle a new c2dm token coming in. We send this token from our native to our Unity project, and our Unity project can do whatever we want with it. In our case, we send the token to Urban Airship, through a complicated process which I will explain a bit later.

Most of this code can be found on Google’s walkthrough, with a few changes.

So I created a BroadcastReceiver with the following onReceive method:


		public void onReceive(Context context, Intent intent) {
			Bundle extras = intent.getExtras();

			Set keys = extras.keySet();
			JSONObject object = new JSONObject();
			for (String key : keys) {
				Log.d("Unity", "Key: " + key);
				Log.d("Unity", "Value: " + extras.getString(key));
				try {
					object.put(key, extras.getString(key));
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
			String message = object.toString();
			Log.d("Unity", "Sending to UnitySendMessage..." + message);
			notifyListeners(message);
			if(intent.getAction().equals("com.google.android.c2dm.intent.registration")){
				handleRegistration(context, intent);
			} else if(intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {

				if(getPushesEnabled(context)) {
					handleMessage(context, intent);
				}
			}
		}

No matter what the Java code decides to do, first, we notify any Unity listeners of the message (both register and message received). The Unity code can handle that however it wants. Before we get into that process, which is the truly customizable part, let’s look over what Java does with the messages.


		private void handleRegistration(Context context, Intent intent) {
			Log.d("Unity", "Registration received!");
			logPushExtras(intent);
		    String registration = intent.getStringExtra("registration_id");
		    if (intent.getStringExtra("error") != null) {
		        // Registration failed, should try again later.
		    } else if (intent.getStringExtra("unregistered") != null) {
		       setRegistration_id(context, null);
		    } else if (registration != null) {
		       setRegistration_id(context, registration);
		    }
		}

On Registration it just saves the C2DM registration_id to memory, or deletes it from memory on unregister.


		protected void handleMessage(Context context, Intent intent) {
			Log.d("Unity", "Message about to be pushed to notification!");

	    	String ns = Context.NOTIFICATION_SERVICE;
	    	NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(ns);

	    	String alert = intent.getStringExtra("alert");

	    	int icon = android.R.drawable.alert_light_frame;
	    	CharSequence tickerText = alert;
	    	long when = System.currentTimeMillis();

	    	Notification notification = new Notification(icon, tickerText, when);

	    	notification.flags = Notification.FLAG_AUTO_CANCEL;

	    	Context applicationContext = context.getApplicationContext();
	    	CharSequence contentTitle = getNotificationTitle(context);
	    	CharSequence contentText = alert;
	    	Intent notificationIntent;
	    	PendingIntent contentIntent = null;
			try {
				notificationIntent = new Intent(context, getPendingIntent(context));
		    	notificationIntent.putExtra("UnityData", craftJson(intent.getExtras()));
		    	notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
		    	contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

	    	notification.setLatestEventInfo(applicationContext, contentTitle, contentText, contentIntent);
	    	mNotificationManager.notify(0, notification);
		}

On message Received, it creats a notification according to the settings that the Unity programmer can define according to his own preferences. He need merely call the methods I’ve defined in the BroadcastReceiver:


		public static void enablePush(string notification_title, string pending_intent_name = "com.unity3d.player.UnityPlayerActivity") {
			Debug.Log("Enabling Push");
			AndroidJavaObject currentActivity = getCurrentActivity();
			AndroidJavaClass receiver = getC2DMReceiver();
			receiver.CallStatic("EnablePush", currentActivity, true);
			receiver.CallStatic("setNotificationTitle", currentActivity, notification_title);
			AndroidJavaClass klass = new AndroidJavaClass(pending_intent_name);
			receiver.CallStatic("setPendingIntent", currentActivity, klass);
			Debug.Log("Done enabling Push");
		}

		public static void disablePush() {
			Debug.Log("Disabling push");
			AndroidJavaObject currentActivity = getCurrentActivity();
			AndroidJavaClass receiver = getC2DMReceiver();
			receiver.CallStatic("EnablePush", currentActivity, false);
			Debug.Log("Done disabling Push");
		}

This is pretty standard notification stuff. But how do we notify Unity in case a message is received while the application is running? This is done in the notifyListener method.


		public static void notifyListeners(String message) {
			for(String listener: listeners) {
				Log.d("Unity", "Sending a message to " + listener);
				UnityPlayer.UnitySendMessage(listener, "MessageReceived", message);
			}
		}

UnityPlayer.UnitySendMessage sends a message from the Java code through the Mono engine to the Unity code. It works like this.
1. Looks for all game objects with the same name as the first parameter.
2. Looks through all the scripts in all of those game objects and if it finds a method with the same name as the second parameter, then it calls that method.
3. The method has to take in one string as a parameter and return void.

Because UnitySendMessage returns void, then Java only has one way access to Unity. If you need two way communication, you will have to have Unity react to UnitySendMessage, because Unity does have two way communication.

Sending the token to the Application Server

(I’m about to go into detail of how the Unity project is set up. Again, I am acting under the assumption that you already understand Unity and how it works. As such, I will not go into detail about how to create or manage Scenes and GameObjects.)

Just to review what we just went through, we sent the Registration Intent, and we’ve captured the response in our BroadcastReceiver. We then send that notification back to our Unity project.

So, in my implementation of this code, I ran this process before logging in. In my LoginManager gameobject, I attached the Login script (Assets > Scripts > Login.cs). Now let’s look at the UnitySendMessage method we called earlier.

UnityPlayer.UnitySendMessage(listener, “MessageReceived”, message);

And let’s walk through the process. The listener is the name of the game object, in this case “LoginManager”. MessageReceived is the name of the method within the Login script, and message is the String being passed down.

In Login.cs, we first run the registration method: C2DMRegisterAgent.registerForC2DM(gameObject.name); (gameObject.name == “LoginManager”), and then we define what to do when the message is received:


		public void MessageReceived(string message) {
			JsonData data = JsonMapper.ToObject(message);
			// Note that if you use JsonMapper, it does not have the method ContainsKey. I wrote that into their open source myself.
			if(data != null && data.ContainsKey("registration_id")) {
				ManualAirshipHandler.instance.init((string)data["registration_id"]);
			}
		}

As you can see by this method, I first filter the message, because this will receive any Registration and MessageReceived messages. Then it passes on the registration_id to the ManualAirshipHandler, which is a class I built to handle interaction with UrbanAirship. You can use this class if you want.

Interacting with the Provider

Now, I am getting into some heavy code, so instead, I will describe what is happening on an abstract level. First, I define a random UUID (version 4), and assign that as an apid. And apid is Urban Airship’s token that is specific for that device. I then tell Urban Airship about that apid through a POST request to https://boxoffice.urbanairship.com/firstrun. The jar file that they include in their sdk does this in the background without your knowledge. You include the data as a basic URL endoded entity apid=&package=. In response you get a random token which you can disregard (I think it’s used for Urban Airship’s Helium service).

Then we send a second PUT request with the registration_id to https://device-api.urbanairship.com/api/apids/. The first request does not need any form of credentials, but the second request must have a header “Authorization” be set to a Base64 encoding of your APPKEY + “:” + APPSECRET. I built a method as follows:


	private static string EncodeTo64(string toEncode) {
	      byte[] toEncodeAsBytes
	            = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
	      string returnValue
	            = System.Convert.ToBase64String(toEncodeAsBytes);
	      return returnValue;
	    }

So you would have the header be “Authorization: Basic ” + EncodeTo64(APPKEY + “:” + APPSECRET). Don’t forget the “Basic” keyword. The response to this is “OK”. We can then send the apid to our own provider.

You can see this process in detail by digging through the classes:

ManualAirshipHandler (Assets > Plugins > FinSlap > UrbanAirship) gets called. It calls UrbanAirshipNetworkManager (Assets > Plugins > FinSlap > UrbanAirship). UrbanAirshipNetworkManager extends NetworkManager (Assets > Plugins > FinSlap > Utils), which defines the Network Request. NetworkManager sends the Request to NetworkPool (Assets > Plugins > FinSlap > Utils) which manages the requests as asynchronous. With the response, we call the callback function handed down to us from the top (in this case ManualAirshipHandler).

Because this process isn’t that important, I didn’t include it in this report. What’s important is that somehow the phone send the C2DM token to whoever wants to sent push notifications to that device.

How we handle incoming push notifications

Earlier I showed you a bit of code that put a local status bar notification on the phone when a message is received. Let’s go into more detail of what was going on. Here’s the method again.


		public void onReceive(Context context, Intent intent) {
			Bundle extras = intent.getExtras();

			Set keys = extras.keySet();
			JSONObject object = new JSONObject();
			for (String key : keys) {
				Log.d("Unity", "Key: " + key);
				Log.d("Unity", "Value: " + extras.getString(key));
				try {
					object.put(key, extras.getString(key));
				} catch (JSONException e) {
					e.printStackTrace();
				}
			}
			String message = object.toString();
			Log.d("Unity", "Sending to UnitySendMessage..." + message);
			notifyListeners(message);
			if(intent.getAction().equals("com.google.android.c2dm.intent.registration")){
				handleRegistration(context, intent);
			} else if(intent.getAction().equals("com.google.android.c2dm.intent.RECEIVE")) {

				if(getPushesEnabled(context)) {
					handleMessage(context, intent);
				}
			}
		}

As you can see, the program puts a status bar notification up when pushes are enabled. In my code, I created a Persistent script which would persist through all of the scenes of the Unity app.


		using UnityEngine;
		using System.Collections;
		using UrbanAirship;
		using Utils;

		public class PersistentScript : MonoBehaviour {
			private static bool created = false;
			// Use this for initialization
			void Awake() {
			    if (!created) {
			    	Debug.Log("---------------------------------------------------------------------------------------------------------------------------");
			        // this is the first instance - make it persist
			        DontDestroyOnLoad(this.gameObject);
			    	OnApplicationFocus(true);
			        created = true;
			    } else {
			        // this must be a duplicate from a scene reload - DESTROY!
			        Destroy(this.gameObject);
			    }
			}

			void OnApplicationPause(bool pause) {
				Debug.Log("application is " + (pause ? "" : "not ") + "pausing");
				OnApplicationFocus(!pause);
			}
			void OnApplicationQuit() {
				OnApplicationFocus(false);
				Debug.Log("Quit! End");
			}
			void OnApplicationFocus(bool focus) {
				Debug.Log("Application is " + (focus ? "" : "not ") + "focusing");
				if(focus) {
					C2DMRegisterAgent.disablePush();
				} else {
					C2DMRegisterAgent.enablePush("Chatter");
				}
				//NotificationManager.instance.setFocus(focus);
			}

			// Update is called once per frame
			void Update () {
				if(Input.GetKeyDown(KeyCode.Escape)) {
					Debug.Log("Pressed quit!");
					Application.Quit();
				}
			}
		}

The “———–” output is useful for determining when the app opens up for the first time. Then, it declares that the GameObject that the script is applied to will never be destroyed, but if any other object has the same script on it, it will destroy it immediately. This effectively creates a singleton object. When the application is turning on, then we desable push (because we want the notifications to appear in app). When the application is turning off, we enable push.

How to handle in app pushes

If we have the application running, we don’t want a push to appear outside of the application, because Unity takes up the full screen. We want to be able to handle that within the app. There are two different instances where we would react differently – if we are in the right context as the notification, and if we are in the wrong context. For example, in a chat app, if we are in a conversation with John, and we get a message from John, we would react differently than if we were in a conversation with Marissa and we get a message from John.

The nifty thing about Unity is that we can call C2DMRegisterAgent.addListener(this.gameObject.name) in any script, and then receive any notifications that come in from then on. So, in our application, our AuthorizedScreen is a listener:


		public void MessageReceived(string json) {
			Debug.Log("Messge received: " + json);
			int account_id = translateNotification(JsonMapper.ToObject(json));
			if(account_id != -1 && !inSpot(account_id))
				messageButton_account = contactList.GetComponent().getAccount(account_id);
		}

Essentially, we just put up a button saying that there is a new message, when clicked it will take them to that conversation. However, this button only comes up if they are not looking at that conversation. If they move on their own to that conversation then the button dissappears.

However, where it really shines is that each conversation is also a listener:


		public void MessageReceived(string json) {
			Debug.Log("Messge received: " + json);
			JsonData data = JsonMapper.ToObject(json);
			int id = -2;
			if(data.ContainsKey("fromAccount") && Int32.TryParse((string) data["fromAccount"], out id) && id == this.account_id);
				refresh();
		}

This way, if the conversation is already loaded, it refreshes automatically.

Conclusion

Now you have push notifications in your Android Unity project. Just a few last suggestions:

  1. Remember that a segfault is usually because you’re implementing AndroidJavaClass or AndroidJavaObject wrong.
  2. Make sure you change the AndroidManifest file to include permissions for Register and Push Notifications. This AndroidManifest.xml file goes into Assets > Plugins > Android folder.
  3. Include the Jar for your Android plugin (and any other jars you may want) in Assets > Plugins > Android folder, and if you have custom resources, you can put them into the Assets > Plugins > Android > res folder.
  4. When looking at Unity documentation, generally what applies for iOS applies for Android. They’re not very clear with the distinction, but if you can do it for iOS, try the same method calls in Android to see if it works. (See http://docs.unity3d.com/Documentation/Manual/android-API.html, you will note that although it’s called iPhoneSettings, it’s used in Android. Weird, huh?)
  5. I tried to build the Utils folder to be reusable code. If you’re looking for an easy way to send NetworkRequests or to write to files, please check out those modules.
Useful guides:
  1. Android SDK setup
  2. Unity: Getting started in Android
  3. Writing Android plugins for Unity
  4. Saving data to a file
  5. Use Unity Android in a subview
  6. Integrating Unity Android in Eclipse
  7. Differentiating between Android and iOS
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s