Skip to content

Commit

Permalink
Fixing using JavaProxy objects on non-main thread.
Browse files Browse the repository at this point in the history
Unity IL2CPP does not handle JNI proxied objects on the non main thread.
This fix moves the callback into the main thread on the Java side, and
also adds caching of the class object in case there are classloader issues.

Also added missing dependency of play-services-auth which is needed for
the token and email API calls.

Finally, minor tweak to the Minimal sample to log the access token better.

Change-Id: I4f35aa383ab28c2f913dd1d5befa05ae67cef876
  • Loading branch information
claywilkinson committed Oct 11, 2016
1 parent 12f73f3 commit c7f7554
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 69 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.txt
@@ -1,8 +1,17 @@
Version 0.9.35
Bug Fixes:
* #1194 - OnPostprocessAllAssets called before Resolver is registered.
* #1209 - Stuck incremental achievements due to lost precision (via PR #1258).
* #1313 - NSInvalidArgumentException in iOS didRegisterForRemoteNotificationsWithDeviceToken.
* #1360 - Fixed Object not set to instance in PlayServicesResolver.
* #1361 - Removed multiple Resolver version log messages.
* #1371 - Removed warning about unreachable code in GPGSUpgrader.
* #1396 - Logging calls on non-game thread crash in IL2CPP for Android.
Updated CubicPilot to use SceneManager in Unity 5.3+
New Features:
* #1134 - No API for setting OAuth scopes (via PR #1296)
* Added HighSpenderProbability, TotalSpendNext28Days to PlayerStats for Android.
* Improved Jar Resolver - see README.
Version 0.9.34
Bug Fixes:
* #1162 - Cannot build from source
Expand Down
9 changes: 5 additions & 4 deletions README.md
Expand Up @@ -234,11 +234,12 @@ For more information, consult the documentation for your version of Windows.

Install Cocoapods

Once Cocoapods is installed, the plugin will add a Podfile to the xcode project to manage
the framework dependencies.

Since Cocoapods uses workspaces to manage the project and the dependent pods, you need to
open Unity-iPhone.xcworkspace to build the project.
This plugin uses another plugin called the Unity Jar Resolver, which handles
resolving the dependencies needed for this plugin. When building for iOS in
Unity, a post build step is executed that runs Cocoapods and then adds the required
libraries and frameworks directly to the XCode project. This is different from
previous versions; you no longer need to open the workspace in XCode.

**Note:** If you are using a version of Unity less than 5.0, you may encounter
a linker error when building the Xcode application. If you see the error:
Expand Down
77 changes: 49 additions & 28 deletions samples/Minimal/Source/Assets/Minimal/MainGui.cs
Expand Up @@ -18,17 +18,21 @@
using System.Collections;
using UnityEngine.SocialPlatforms;

public class MainGui : MonoBehaviour {
public class MainGui : MonoBehaviour
{
private const float FontSizeMult = 0.05f;
private bool mWaitingForAuth = false;
private string mStatusText = "Ready.";
private bool dumpedToken = false;

void Start () {
void Start()
{
// Select the Google Play Games platform as our social platform implementation
GooglePlayGames.PlayGamesPlatform.Activate();
}

void OnGUI() {
void OnGUI()
{
GUI.skin.button.fontSize = (int)(FontSizeMult * Screen.height);
GUI.skin.label.fontSize = (int)(FontSizeMult * Screen.height);

Expand All @@ -37,50 +41,67 @@ public class MainGui : MonoBehaviour {

Rect buttonRect = new Rect(0.25f * Screen.width, 0.10f * Screen.height,
0.5f * Screen.width, 0.25f * Screen.height);
Rect imageRect = new Rect(buttonRect.x+buttonRect.width/4f,
Rect imageRect = new Rect(buttonRect.x + buttonRect.width / 4f,
buttonRect.y + buttonRect.height * 1.1f,
buttonRect.width/2f, buttonRect.width/2f);
buttonRect.width / 2f, buttonRect.width / 2f);

if (mWaitingForAuth) {
if (mWaitingForAuth)
{
return;
}

string buttonLabel;


if (Social.localUser.authenticated) {
buttonLabel = "Sign Out";
if (Social.localUser.image != null) {
GUI.DrawTexture(imageRect, Social.localUser.image,
ScaleMode.ScaleToFit);
} else {
GUI.Label(imageRect, "No image available");
}
if (Social.localUser.authenticated)
{
buttonLabel = "Sign Out";
if (Social.localUser.image != null)
{
GUI.DrawTexture(imageRect, Social.localUser.image,
ScaleMode.ScaleToFit);
}
else {
GUI.Label(imageRect, "No image available");
}

mStatusText = "Ready";

if (!dumpedToken)
{
string token = GooglePlayGames.PlayGamesPlatform.Instance.GetToken();

mStatusText = "Ready";
} else {
buttonLabel = "Authenticate";
Debug.Log("AccessToken = " + token);
dumpedToken = token != null && token.Length > 0;
}
}
else {
buttonLabel = "Authenticate";
}

if (GUI.Button(buttonRect, buttonLabel)) {
if (!Social.localUser.authenticated) {
if (GUI.Button(buttonRect, buttonLabel))
{
if (!Social.localUser.authenticated)
{
// Authenticate
mWaitingForAuth = true;
mStatusText = "Authenticating...";
Social.localUser.Authenticate((bool success) => {
Social.localUser.Authenticate((bool success) =>
{
mWaitingForAuth = false;
if (success) {
mStatusText = "Welcome " + Social.localUser.userName;
string token = GooglePlayGames.PlayGamesPlatform.Instance.GetToken();
Debug.Log(token);
} else {
mStatusText = "Authentication failed.";
if (success)
{
mStatusText = "Welcome " + Social.localUser.userName;
}
else {
mStatusText = "Authentication failed.";
}
});
} else {
}
else {
// Sign out!
mStatusText = "Signing out.";
((GooglePlayGames.PlayGamesPlatform) Social.Active).SignOut();
((GooglePlayGames.PlayGamesPlatform)Social.Active).SignOut();
}
}
}
Expand Down
Expand Up @@ -66,6 +66,15 @@ public class GPGSDependencies : AssetPostprocessor
{"packageIds", new string[] { "extra-google-m2repository" } }
});

// Auth is needed for getting the token and email.
Google.VersionHandler.InvokeInstanceMethod(
svcSupport, "DependOn",
new object[] { "com.google.android.gms", "play-services-auth",
PluginVersion.PlayServicesVersionConstraint },
namedArgs: new Dictionary<string, object>() {
{"packageIds", new string[] { "extra-google-m2repository" } }
});

Google.VersionHandler.InvokeInstanceMethod(
svcSupport, "DependOn",
new object[] { "com.android.support", "support-v4", "23.1+" },
Expand Down
Expand Up @@ -23,7 +23,7 @@ namespace GooglePlayGames.Android
using Com.Google.Android.Gms.Common.Api;
using UnityEngine;

internal class AndroidTokenClient: TokenClient
internal class AndroidTokenClient : TokenClient
{
private const string TokenFragmentClass = "com.google.games.bridge.TokenFragment";
private const string FetchTokenSignature =
Expand Down Expand Up @@ -301,10 +301,11 @@ public void GetIdToken(string serverClientId, Action<string> idTokenCallback)
fetchingIdToken = true;
idTokenScope = newScope;
idTokenCb = idTokenCallback;
Fetch(idTokenScope, false, false, true, (status) => {
Fetch(idTokenScope, false, false, true, (status) =>
{
fetchingIdToken = false;
if(status == CommonStatusCodes.Success)
if (status == CommonStatusCodes.Success)
{
idTokenCb(null);
}
Expand All @@ -322,7 +323,7 @@ public void GetIdToken(string serverClientId, Action<string> idTokenCallback)
}
}

class TokenResult : Google.Developers.JavaObjWrapper , Result
class TokenResult : Google.Developers.JavaObjWrapper, Result
{
#region Result implementation

Expand All @@ -339,6 +340,12 @@ public Status getStatus()

#endregion

public int getStatusCode()
{
return InvokeCall<int>("getStatusCode", "()I");

}

public String getAccessToken()
{
return InvokeCall<string>("getAccessToken", "()Ljava/lang/String;");
Expand Down Expand Up @@ -368,13 +375,17 @@ public TokenResultCallback(Action<int, string, string, string> callback)
public override void OnResult(TokenResult arg_Result_1)
{
if (callback != null) {
PlayGamesHelperObject.RunOnGameThread(() =>
callback(arg_Result_1.getStatus().getStatusCode(),
callback(arg_Result_1.getStatusCode(),
arg_Result_1.getAccessToken(),
arg_Result_1.getIdToken(),
arg_Result_1.getEmail()));
arg_Result_1.getEmail());
}
}

public string toString()
{
return ToString();
}
}
}
#endif
Expand Up @@ -33,6 +33,8 @@ public class JavaObjWrapper
/// </summary>
IntPtr raw;

IntPtr cachedRawClass = IntPtr.Zero;

/// <summary>
/// Initializes a new instance of the <see cref="Google.Developers.JavaObjWrapper"/> class.
/// Does not create an instance of the java class.
Expand All @@ -57,7 +59,7 @@ public JavaObjWrapper(string clazzName)
/// <param name="rawObject">Raw object.</param>
public JavaObjWrapper(IntPtr rawObject)
{
this.raw = rawObject;
raw = rawObject;
}

/// <summary>
Expand All @@ -72,6 +74,18 @@ public IntPtr RawObject
}
}

public virtual IntPtr RawClass
{
get
{
if (cachedRawClass == IntPtr.Zero && raw != IntPtr.Zero)
{
cachedRawClass = AndroidJNI.GetObjectClass(raw);
}
return cachedRawClass;
}
}

/// <summary>
/// Creates an instance of the java object. The arguments are for
/// the constructor.
Expand All @@ -86,15 +100,13 @@ public void CreateInstance(string clazzName, params object[] args)
throw new Exception("Java object already set");
}

IntPtr rawClass = AndroidJNI.FindClass(clazzName);

// TODO: use a specific signature. This could be problematic when
// using arguments that are subclasses of the declared parameter types.
IntPtr method = AndroidJNIHelper.GetConstructorID(rawClass, args);
IntPtr method = AndroidJNIHelper.GetConstructorID(RawClass, args);
jvalue[] jArgs = ConstructArgArray(args);

// assign the raw object.
raw = AndroidJNI.NewObject(rawClass, method, jArgs);
raw = AndroidJNI.NewObject(RawClass, method, jArgs);
}

/// <summary>
Expand Down Expand Up @@ -268,7 +280,7 @@ public static float GetStaticFloatField(string clsName, string name)
public void InvokeCallVoid(string name, string sig, params object[] args)
{
IntPtr rawClass = AndroidJNI.GetObjectClass(raw);
IntPtr method = AndroidJNI.GetMethodID(rawClass, name, sig);
IntPtr method = AndroidJNI.GetMethodID(RawClass, name, sig);

jvalue[] jArgs = ConstructArgArray(args);
AndroidJNI.CallVoidMethod(raw, method, jArgs);
Expand All @@ -277,16 +289,9 @@ public void InvokeCallVoid(string name, string sig, params object[] args)
public T InvokeCall<T>(string name, string sig, params object[] args)
{
Type t = typeof(T);
IntPtr rawClass = AndroidJNI.GetObjectClass(raw);
IntPtr method = AndroidJNI.GetMethodID(rawClass, name, sig);
IntPtr method = AndroidJNI.GetMethodID(RawClass, name, sig);
jvalue[] jArgs = ConstructArgArray(args);

if (rawClass == IntPtr.Zero)
{
Debug.LogError("Cannot get rawClass object!");
throw new Exception("Cannot get rawClass object");
}

if (method == IntPtr.Zero)
{
Debug.LogError("Cannot get method for " + name);
Expand Down Expand Up @@ -404,8 +409,7 @@ public static T StaticInvokeCall<T>(string type, string name, string sig, params
public T InvokeObjectCall<T>(string name, string sig,
params object[] theArgs)
{
IntPtr rawClass = AndroidJNI.GetObjectClass(raw);
IntPtr methodId = AndroidJNI.GetMethodID(rawClass, name, sig);
IntPtr methodId = AndroidJNI.GetMethodID(RawClass, name, sig);

jvalue[] jArgs = ConstructArgArray(theArgs);

Expand Down
Expand Up @@ -140,6 +140,7 @@ private void processRequests(int errorCode) {
synchronized (pendingTokenRequests) {
while (!pendingTokenRequests.isEmpty()) {
request = pendingTokenRequests.remove(0);
Log.d(TAG," Setting result to " + errorCode + " for " + request);
request.setResult(errorCode);
}
}
Expand Down Expand Up @@ -207,10 +208,10 @@ private void doGetToken(final TokenRequest tokenRequest, final String accountNam
"e: " + tokenRequest.doEmail + " a:" + tokenRequest.doAccessToken + " i:" +
tokenRequest.doIdToken);

AsyncTask<Object, Integer, TokenRequest> t =
new AsyncTask<Object, Integer, TokenRequest>() {
AsyncTask<Object, Integer, Integer> t =
new AsyncTask<Object, Integer, Integer>() {
@Override
protected TokenRequest doInBackground(Object[] params) {
protected Integer doInBackground(Object[] params) {
// initialize the email to null, since it used by all the token getters.
String accessToken;
String idToken;
Expand Down Expand Up @@ -253,8 +254,8 @@ protected TokenRequest doInBackground(Object[] params) {
}

Log.d(TAG, "Done with tokenRequest status: " + statusCode);
tokenRequest.setResult(statusCode);
return tokenRequest;
//
return statusCode;
}

/**
Expand All @@ -281,15 +282,15 @@ protected void onCancelled() {
* <p/>
* <p>This method won't be invoked if the task was cancelled.</p>
*
* @param tokenPendingResult The result of the operation computed by {@link #doInBackground}.
* @param statusCode The result of the operation computed by {@link #doInBackground}.
* @see #onPreExecute
* @see #doInBackground
* @see #onCancelled(Object)
*/
@Override
protected void onPostExecute(TokenRequest tokenPendingResult) {
protected void onPostExecute(Integer statusCode) {
Log.d(TAG, "onPostExecute for the token fetch");
super.onPostExecute(tokenPendingResult);
tokenRequest.setResult(statusCode);
}
};
t.execute();
Expand Down

0 comments on commit c7f7554

Please sign in to comment.