Sending data from Unity

Share Text + Unity

I’m pretty new to Unity-Android integration, so I’m filled with pride after every little success. And I bet this one is pretty trivial, especially for Android veterans. But I’m posting it anyway – for the sake of Unity developers. There just aren’t enough examples out there that tie the pieces together.

I wanted my Unity app to have a share function. All it needs to do is send some text as is easily done on Android. All that is missing is a tiny Android plugin.

Android Plugin

I already covered the subject of creating an Android plugin in another post. I will expand on it to keep this post short.

MainActivity.java

Again I subclassed UnityPlayerActivity. This utilizes the main activity as context for starting a new intent. I then added a small method, shareText:

package com.companyname.appname;

import android.content.Intent;

public class MainActivity extends UnityPlayerActivity {
    public void shareText(String subject, String text, String chooserTitle) {
        Intent sendIntent = new Intent();
        sendIntent.setAction(Intent.ACTION_SEND);
        sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
        sendIntent.putExtra(Intent.EXTRA_TEXT, text);
        sendIntent.setType("text/plain");
        startActivity(Intent.createChooser(sendIntent, chooserTitle));
    }
}

shareText takes 3 string parameters:

  1. subject – The desired subject line of a message. Note that not all senders send the subject. Make sure that your text is sufficient by itself.
  2. text – The actual text message to be sent.
  3. chooserTitle – The title for the chooser dialog. It is displayed when the user is prompted to choose how to send the data. For example, it can be “Share via”.

The Unity App

Now all that is left is to call the shareText method from within Unity. This Boo method does the trick:

ifdef UNITY_ANDROID:
    static def ShareText(subject as string, text as string, chooserTitle as string):
        jc = AndroidJavaClass("com.unity3d.player.UnityPlayer")
        jo = jc.GetStatic[of AndroidJavaObject]("currentActivity")
        jo.Call("shareText", subject, text, chooserTitle)

This method simply calls the Java method as documented by Unity.

That’s the whole deal. Enjoy.

Serverless authentication with Instagram

Instagram + Unity

In this post I will explain how to implement a Unity app for Android which allows the user to authenticate using an Instagram account – without a server (i.e., directly against Instagram’s server.) In the process we’ll also learn how to write an Android plugin.

Overview

Instagram supports client-side authentication (OAuth implicit flow, a.k.a. 2-legged OAuth). This flow enables an app to obtain a user’s OAuth credentials directly: the access token is delivered to the app via the response URI. However, the flow was designed with JavaScript applications in mind, as the app is expected to have access to the response URI (“Simply grab the access_token off the URL fragment and you’re good to go.”)  Alas, this is a challenge for a mobile app, as the app needs to relinquish control to the browser when directing the user to authenticate. How then can the app regain control and obtain the response URI with the access token?

The solution is to configure a redirect URI with a custom scheme (instead of https). An intent filter will be added to the app to handle this custom scheme. This will result in the browser directing the flow back to the app when the response URI arrives. Adding an intent filter to an Android Unity app is seemingly a simple task, but it has its share of pit falls and online examples are scarce. Full instructions ahead.

Flow

The chronological order of events in runtime (once everything is already set up and running):

  1. A user opens the app manually
  2. The user chooses to sign in. The app redirects the user to a browser to sign in on Instagram’s page
  3. Upon successful authentication Instagram redirects the browser to a URI with a custom scheme
  4. The Android OS opens the app automatically to handle this URI
  5. The plugin (Java) code is called, which parses the URI and then hands over the data to a script in the Unity project

Tools

I’m using Windows 7. The tools I used for the job:

  1. Unity Android Pro v4.1.3
  2. Android SDK v21
  3. Eclipse 4.2.1 + ADT v22

For help with installation see my previous post.

Note: The Unity scripts in this example are written in Boo. There’s about a 98% chance you’re writing in C# or UnityScript, but I’m sure you’ll find no problem understanding the scripts – they’re quite minimal.

Register Your Application with Instagram

First you will need an Instagram account. Then you should register your app.

Configure the Redirect URI using a custom scheme.
For example: mycustomscheme://oauth/callback/instagram/.
The important part is the scheme: mycustomscheme. Choose something unique.

The Website URL is not important. Choose whatever you want.

When the registration is complete you will get a Client ID. It looks something like this: c46692e6a1e57748a2b3f2b553f84c67. Keep it for later. You can access and edit this information any time.

Tip: You can manage your applications to revoke access. This might come in handy later, during debugging, in case you’ve already signed in and granted your application access but want to deny it. (Suppose you want to test the error case.)

Create an Android Plugin

The plugin will handle the response URI, extract the access token and send it to the Unity app. It will be written in Java.

Time to open Eclipse. Create a new Android Application Project. Most of the parameters are not important, such as application name, project name or Android SDK versions. They are controlled by Unity’s build settings. You should, however, pay attention to 3 things:

  1. Package name. For example: com.companyname.appname. Make sure this is the same as the Bundle Identifier in Unity’s Player Settings.
  2. Check the “Mark this project as a library” to produce a JAR file. (This can also be done later from the project properties under Android.)
  3. Activity Name. I’ll use MainActivity (the default) in this example. You will need to use this later when adding the plugin to your Unity project.

Add classes.jar to the Classpath

The location of classes.jar depends on your installation of Unity. The default location is: C:\Program Files (x86)\Unity\Editor\Data\PlaybackEngines\androidplayer\bin\classes.jar. More info here.

  1. Open: Project => Properties => Java Build Path => Add External JARs…
  2. Locate and select classes.jar

MainActivity.java

We will subclass UnityPlayerActivity and implement onNewIntent:

package com.companyname.appname;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

public class MainActivity extends UnityPlayerActivity {
    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleAccessToken(intent);
    }

    private void handleAccessToken(Intent intent) {
        Uri uri = intent.getData();
        if (uri != null && uri.toString().startsWith("mycustomscheme")) {
            String accessToken = "";
            if (uri.getFragment() != null) {
                accessToken = uri.getFragment().replace("access_token=", "");
                Log.d("Unity", "Found access token: " + accessToken);
            } else {
                Log.d("Unity", "Access token not found. URI: " + uri.toString());
            }
            UnityPlayer.UnitySendMessage("AccessManager", "OnAccessToken", accessToken);
        }
    }
}

Our new activity extends the standard UnityPlayerActivity class. The onNewIntent method will be called when the activity is re-launched. If a URI is found, it is parsed and the access token is extracted. UnityPlayer.UnitySendMessage sends the token to a script inside Unity, where AccessManager is the name of the game object and OnAccessToken is the name of the method. We’ll later create them in Unity.

That’s it. You should find an output JAR file in the project’s bin folder. We’ll copy it later to the Unity project.

Tip: When writing log output (Log.d) I used “Unity” for the tag. This is the same tag that Unity uses for Debug.Log. So when I use logcat to read the log output I can filter easily:

adb logcat Unity:V *:S

Error Handling

The response URI for a successful sign-in looks like this:

mycustomscheme://oauth/callback/instagram/#access_token=316140977.f598def.cf36764c5c6f4488fa95362592f14f91

For a failure it looks something like this:

mycustomscheme://oauth/callback/instagram/?error_reason=user_denied&error=access_denied&error_description=The+user+denied+your+request.

When the code above detects there is no fragment, it passes an empty string instead of an access token.

The Unity App

The Unity app needs to open the sign-in URL when the user chooses to sign in. And it needs to receive the access token from the plugin we wrote earlier.

Open Sign-In URL in Browser

All you need to do is open this link:

https://instagram.com/oauth/authorize/?client_id=c46692e6a1e57748a2b3f2b553f84c67&redirect_uri=mycustomscheme%3A%2F%2Foauth%2Fcallback%2Finstagram%2F&response_type=token
  • Fill in the Client ID you received from Instagram.
  • Use the Redirect URI you configured earlier. Replace the colon with “%3A” and all the slashes with “%2F” as above.

Notice the last parameter: response_type=token. This instructs Instagram to use the implicit flow and return the access token in the response URI.

The following Boo code simply formats the link string and calls Application.OpenURL:

SIGN_IN_URL_FORMAT = "https://instagram.com/oauth/authorize/?client_id={0}&redirect_uri={1}&response_type=token"
CLIENT_ID = "c46692e6a1e57748a2b3f2b553f84c67"
SIGN_IN_REDIRECT_URL = "mycustomscheme%3A%2F%2Foauth%2Fcallback%2Finstagram%2F"

signInUrl = SIGN_IN_URL % (CLIENT_ID, SIGN_IN_REDIRECT_URL)
Application.OpenURL(signInUrl)

Handling the Response

Create a game object named AccessManager and attach a script to it with a method named OnAccessToken. Make sure to use the same game object and method names as in the Java plugin code (UnityPlayer.UnitySendMessage). The method should have one parameter of type string, which will receive the access token. You can then use it to access Instagram API. (You may also want to store it using PlayerPrefs.)

Example code in Boo:

def OnAccessToken(accessToken as string):
    Debug.Log("Received access token: " + accessToken)
    PlayerPrefs.SetString("access_token", accessToken)
    // do something useful here

Adding the Plugin

The last part is to add the plugin we created earlier to the Unity project:

  1. Create a subdirectory Assets/Plugins/Android in your Unity project directory
  2. Copy the JAR file we created earlier to Assets/Plugins/Android.
  3. Create an AndroidManifest.xml file in Assets/Plugins/Android.
AndroidManifest.xml

Here we define our activity, MainActivity, as the main activity and also add an intent filter:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application android:icon="@drawable/app_icon" 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>
        <data android:scheme="mycustomscheme" />
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
      </intent-filter>
    </activity>
  </application>
</manifest>

Use the same names you used in the Java plugin:

  • Activity (.MainActivity). Mind the prepending dot!
  • Custom scheme (mycustomscheme)

There is no need to specify the package name (com.companyname.appname) here. It is determined by Unity’s Player Settings (“Bundle Identifier”).

Notice the parameter android:launchMode=”singleTask”. This is the only mode that worked for me.

During a build Unity will extend this file and merge a few bits into it, such as the bundle identifier (package name), Android versions and permissions. The extended file, with the same name, can be found in the Temp/StagingArea subdirectory, which is deleted when Unity is closed.

That’s it! Should be working now.

Testing the Custom Scheme URI

If you open a browser on your mobile and try to open your URI (e.g. mycustomscheme://oauth/callback/instagram/) you will most likely find yourself in a Google search results page. That’s just how the browser works. To manually open your URI you’ll have to find a creative way to open a URI directly. For example:

  • Create a simple HTML that redirects to your URI, copy (email?) it to the mobile device and open it with some HTML viewer
  • Create a redirecting HTML and put it on your server, if you have one
  • Open the URI using your Unity app using Application.OpenURL (haven’t tried this one myself)

What’s Next?

There are two issue worth mentioning. I haven’t finished investigating them and tinkering with them yet, but I’ll say a few words.

UnityPlayerNativeActivity and UnityPlayerProxyActivity

In this solution we subclassed UnityPlayerActivity. We ignored two other (important?) classes, UnityPlayerNativeActivity and UnityPlayerProxyActivity, which were introduced in Gingerbread.

  • UnityPlayerNativeActivity – Parallel to UnityPlayerActivity. Can also be subclassed.
  • UnityPlayerProxyActivity – The top activity; it determines which of the other two activities (regular or native) should be started.

It is possible that ignoring the native player results in reduced performance in some cases.

WebView

I managed to open a WebView inside Unity using gree’s plugin. I will soon try to use a WebView for signing in instead of sending the user to the browser.

Edit: I’ve tried using WebView and it didn’t work. It would not delegate handling of a custom scheme. Instead it shows an error. I assume this behavior could be overridden if I had a way to set my own WebViewClient and override shouldOverrideUrlLoading.

iOS

See you then.

Enjoy!