• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!
  • Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

Android Google Cloud - Cloud Event doesn't get triggered after saving

Mert

Member
Simply after cloud_string_save function, nothing returns in Cloud Event.

*I checked Log;
-data is saved.
-I can load saved data successfully.

The reason is that I put a little "Saving" screen in saving process. But to end this saving process, I need to know when the saving procedure ends.

(I also put a random message code on Cloud Event to see if anything returns, nothing happens)
 

mMcFab

Member
Ah, I know this one!

It was causing me no end of trouble, so I decided to investigate the Google Play Services Extension - here's what I found - saving does not call a cloud event. I don't know if this is a bug, but I do know how to fix it - at least on Android! (Because I needed to fix it too!).
Now, I don't know if this works with other providers, but this definitely worked for Google Cloud for me.

Open up the project folder and navigate to "extensions\GooglePlayServicesExtension\AndroidSource\Java"
and open up "GooglePlayServicesExtension.java" in some kind of text editor. It may also be worth making a backup of the file in case I've screwed something up.
Replace the contents with this code:
Code:
package ${YYAndroidPackageName};

import ${YYAndroidPackageName}.RunnerActivity;
import com.yoyogames.runner.RunnerJNILib;

import android.util.Log;
import android.os.Handler;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Activity;
import android.os.AsyncTask;
import android.support.v4.app.FragmentManager;
import android.app.DialogFragment;
import android.os.Bundle;
import android.content.Intent;
import android.content.DialogInterface;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.AdView;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.InterstitialAd;
import android.os.Build;
import ${YYAndroidPackageName}.R;
import android.app.Activity;
import android.view.ViewGroup;
import android.view.View;
import android.widget.AbsoluteLayout;
import com.yoyogames.runner.RunnerJNILib;
import com.google.android.gms.common.GooglePlayServicesUtil;

import java.io.IOException;
import java.util.Calendar;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;


import android.annotation.TargetApi;
import com.google.android.gms.*;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.games.snapshot.Snapshot;
import com.google.android.gms.games.snapshot.SnapshotMetadata;
import com.google.android.gms.games.snapshot.SnapshotMetadataChange;
import com.google.android.gms.games.snapshot.Snapshots;

import com.google.android.gms.games.Games;
import com.google.android.gms.games.Player;
import com.google.android.gms.plus.Plus;

import com.google.android.gms.drive.Drive;
import com.google.android.gms.games.*;
//import com.google.android.gms.appstate.*;
import com.google.android.gms.common.api.*;
import com.google.android.gms.common.GooglePlayServicesUtil;

import android.content.IntentSender.SendIntentException;
//Can't extend BaseGameUtils as we already extend RunnerSocial

public class GooglePlayServicesExtension extends RunnerSocial implements GoogleApiClient.ConnectionCallbacks,GoogleApiClient.OnConnectionFailedListener
{
    private GoogleApiClient mGoogleApiClient;
    public GcmPush mGcmPush;    //Push notifications
    private int LastGSId=-1;
    public static GooglePlayServicesExtension CurrentGoogleExtension;

    private String mCurrentSaveName = "DefaultSave";
    private byte [] mSaveGameData = null;
 
    private RunnerBillingInterface iap_controller=null;
 
    // Request code to use when launching the resolution activity
    private static final int REQUEST_RESOLVE_ERROR = 1001;
    // Unique tag for the error dialog fragment
    private static final String DIALOG_ERROR = "dialog_error";
    // Bool to track whether the app is already resolving an error

    private boolean mResolvingConnectionFailure = false;
    private boolean mAutoStartSignInFlow = true;
    private boolean mSignInClicked = false;
 
    public void GooglePlayServices_Init()
    {
        //This will start all the login flow etc
 
        if(mGoogleApiClient!=null)
        {
            Log.i("yoyo","Attempting to initialise Google Play Services when they have already been initialised.");
            return;
        }
 
        String appid = RunnerActivity.CurrentActivity.mYYPrefs.getString("com.google.android.gms.games.APP_ID");
 
        if(appid == null|| appid.isEmpty())
        {
            Log.i("yoyo", "Failed to find appid, not initialising GoogleApiClient");
            return;
     
        }
        else
            Log.i("yoyo", "Found google play app id = " + appid);
 
 
        GoogleApiClient.Builder builder = new GoogleApiClient.Builder(RunnerActivity.CurrentActivity);
 
        builder.addConnectionCallbacks(this);
        builder.addOnConnectionFailedListener(this);
        builder.addApi(Games.API);
        builder.addScope(Games.SCOPE_GAMES);
        builder.addApi(Plus.API);
        builder.addScope(Plus.SCOPE_PLUS_LOGIN);
 
        boolean bCloudEnable = RunnerActivity.CurrentActivity.mYYPrefs.getBoolean("YYGoogleCloudSavingEnabled");
        if( bCloudEnable )
        {
            //These are needed if you are using the cloud saving functionality, if you have them in & do not have save games enabled in your google developer console for your game, you won't be able to log
            // in to google play services...
            Log.i("yoyo","Adding Google Drive API for cloud saving");
            builder.addApi(Drive.API);
            builder.addScope(Drive.SCOPE_APPFOLDER);
        }
 
 
        mGoogleApiClient = builder.build();
 
    }
 
    @Override
    public void Init()
    {
        CurrentGoogleExtension = this;

        //If they aren't available don't trigger the prompt for install flow as this may be a fire device etc - leave that up to the dev to decide if\when to do this.
        if(GooglePlayServices_Status()==ConnectionResult.SUCCESS)
        {
            Log.i("yoyo", "Google Play Services extension initialising" );
 
            GooglePlayServices_Init();
        }
        else
        {
            Log.i("yoyo", "Google Play Services extension not initialising as AreGooglePlayServicesAvailable returns false" );
        }
 
    }
 
 
    @Override
    public void onStart()
    {
        Log.i("yoyo","googleplayservices extension onStart called");
        //super.onStart();
 
        //Uncomment these lines if you want auto sign-in
    //    if (mGoogleApiClient != null)
    //        mGoogleApiClient.connect();
    }

    @Override
    public void onStop()
    {
        Log.i("yoyo","googleplayservices extension onStop called");
        //super.onStop();
        if (mGoogleApiClient != null && mGoogleApiClient.isConnected())
            mGoogleApiClient.disconnect();
    }
 
    @Override
    public void onResume()
    {
        if(mGcmPush !=null )
        {
            mGcmPush.deliverStoredMessages();
        }
    }
 
 
    @Override
    public void onConnected(Bundle connectionHint)
    {
        // The player is signed in. Hide the sign-in button and allow the
        // player to proceed.
        Log.i("yoyo","Sign In Succeeded");
 
 
        Player p = Games.Players.getCurrentPlayer(getApiClient());
        String displayName;
        String id;
        if (p == null) {
            Log.i("yoyo", "mGamesClient.getCurrentPlayer() is NULL!");
            displayName = "???";
            id="-1";
        }
        else {
            displayName = p.getDisplayName();
            id = p.getPlayerId();
        }
 
        Log.i("yoyo","Found displayname " + displayName + " with id " + id);
 
        RunnerJNILib.OnLoginSuccess(displayName,id,"","","","","");
 
        //Call back to generate the social event
    }
 



    public double GooglePlayServices_Status()
    {
        return (double)GooglePlayServicesUtil.isGooglePlayServicesAvailable(RunnerJNILib.ms_context);
    }
 

    // The rest of this code is all about building the error dialog

    /* Creates a dialog for an error message */
    private void showErrorDialog(int errorCode,int requestCode)
    {
        Dialog dialog = GooglePlayServicesUtil.getErrorDialog(errorCode,RunnerActivity.CurrentActivity, requestCode);
        if (dialog != null)
        {
            dialog.show();
        }
        else
        {
            // no built-in dialog: show the fallback error message
        //    showAlert(RunnerActivity.CurrentActivity, );
            Log.i("yoyo","Google Play Services Error and unable to initialise GooglePlayServicesUtil error dialog");
        }
    }



@Override
    public void onConnectionFailed(ConnectionResult result) {
 
        Log.i("yoyo","onConnectionFailed called with result:"+result);
 
        if (mResolvingConnectionFailure) {
        // Already resolving
            return;
        }

        // If the sign in button was clicked or if auto sign-in is enabled,
        // launch the sign-in flow
        if (mSignInClicked || mAutoStartSignInFlow)
        {
            mAutoStartSignInFlow = false;
            mSignInClicked = false;
            mResolvingConnectionFailure = true;

            // Attempt to resolve the connection failure using BaseGameUtils.
            // The R.string.signin_other_error value should reference a generic
            // error string in your strings.xml file, such as "There was
            // an issue with sign in, please try again later."
            if (!BaseGameUtils.resolveConnectionFailure(RunnerActivity.CurrentActivity,
                    mGoogleApiClient, result,
                    REQUEST_RESOLVE_ERROR, "Error logging in to Google Play Services")) {
                mResolvingConnectionFailure = false;
            }
 
        }
 
 
   /*     if (mResolvingError) {
            // Already attempting to resolve an error.
            Log.i("yoyo","onConnectionFailed called with resolving error already set to "+ mResolvingError);
            return;
        } else
        */
    }


 
    @Override
    public void onConnectionSuspended(int i)
    {
        // Attempt to reconnect
 
        Log.i("yoyo","onConnectionSuspended call with " + i);
 
        if (mGoogleApiClient != null)
            mGoogleApiClient.connect();
 
 
    }
 
    public void SendSocialEvent(String type,double value)
    {
 
        int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
 
        RunnerJNILib.DsMapAddString( dsMapIndex, "type", type );
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "value", value);
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "id", 9817);
        RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex,EVENT_OTHER_SOCIAL);
    }
 
    @Override
    public    void onActivityResult(int requestCode, int responseCode,Intent intent)
    {
        Log.i("yoyo","gps onActivityResult called with requestCode "+requestCode  );
 
    //    if
 
        switch (requestCode)
        {
     
            case REQUEST_RESOLVE_ERROR:
            {
             Log.d("yoyo", "onActivityResult with requestCode == REQUEST_RESOLVE_ERROR, responseCode="  + responseCode + ", intent=" + intent);
             mSignInClicked = false;
             mResolvingConnectionFailure = false;
             if (responseCode == Activity.RESULT_OK)
             {
                mGoogleApiClient.connect();
             } else {
                BaseGameUtils.showActivityResultError(RunnerActivity.CurrentActivity,requestCode,responseCode, R.string.signin_other_error);
             }
            }
            break;
            case RC_GPS_ACTIVITY:
            if(responseCode == GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED)
            {
                Log.i("yoyo","Logging out due to result of GooglePlayServices activity");
    //            Logout();
                if (mGoogleApiClient != null && mGoogleApiClient.isConnected())
                {
                    mGoogleApiClient.disconnect();
                    Log.i("yoyo","Logged out of Google Play Services");
                    RunnerJNILib.OnLoginSuccess("Not logged in","-1","","","","","");
                }
            }
            break;
     
  /*          case REQUEST_RESOLVE_ERROR:
                Log.i("yoyo","onActivityResult called with REQUEST_RESOLVE_ERROR" );
                if (resultCode == Activity.RESULT_OK)
                {
                    Log.i("yoyo","onActivityResult called with REQUEST_RESOLVE_ERROR and resultCode (result_ok)" + resultCode);
     
                    if (mGoogleApiClient != null)
                    if (!mGoogleApiClient.isConnecting() || !mGoogleApiClient.isConnected())
                    {
                //        mResolvingError = false;
                        mGoogleApiClient.connect();
                   
                    }
                }
                else
                {
                    Log.i("yoyo","onActivityResult called with REQUEST_RESOLVE_ERROR and resultCode " + resultCode);
                //    mResolvingError = false;
             
                    switch(resultCode)
                    {
                        case GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED");
                            break;
                        case GamesActivityResultCodes.RESULT_NETWORK_FAILURE:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_NETWORK_FAILURE");
                            RunnerJNILib.OnLoginSuccess("Not logged in","-1","","","","","");
                            break;
                        case GamesActivityResultCodes.RESULT_SIGN_IN_FAILED:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_SIGN_IN_FAILED");
                            RunnerJNILib.OnLoginSuccess("Not logged in","-1","","","","","");
                        break;
                        case GamesActivityResultCodes.RESULT_LICENSE_FAILED:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_LICENSE_FAILED");
                            RunnerJNILib.OnLoginSuccess("Not logged in","-1","","","","","");
                        break;

                        case GamesActivityResultCodes.RESULT_APP_MISCONFIGURED:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_APP_MISCONFIGURED");
                            RunnerJNILib.OnLoginSuccess("Not logged in","-1","","","","","");
                        break;                 
                     
                        case GamesActivityResultCodes.RESULT_INVALID_ROOM:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_INVALID_ROOM");
                        break;
                        case GamesActivityResultCodes.RESULT_LEFT_ROOM:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_LEFT_ROOM");
                        break;
                        case GamesActivityResultCodes.RESULT_SEND_REQUEST_FAILED:
                            Log.i("yoyo","gps OnActivityResult with: GamesActivityResultCodes.RESULT_SEND_REQUEST_FAILED");
                        break;
                 
                    }
             
                    SendSocialEvent("GPSGamesActivityResult",(double)resultCode);
             
                    //going to stop putting up the gps error as it's not at all helpful
                    //showErrorDialog(resultCode,requestCode);
                    //We have an error and haven't been able to resolve it...
                }
                break;
                */
            default:
         
                Log.i("yoyo","onActivityResult called with " + requestCode);
     
                //mResolvingError = false;
                break;
        }
    }
 
    Object mRunnerBilling=null;
 
    public Object InitRunnerBilling()
    {
 
        if(mRunnerBilling==null)
        {
            String FullClassName = "${YYAndroidPackageName}.RunnerBilling";
     
            try{
         
        /*        Class c = Class.forName(FullClassName);
                if(c==null)
                {
                    Log.i("yoyo","Unable to find class");
                }
                else
                {
                    Method[] methods = c.getMethods();
                    for (int i = 0; i < methods.length; i++) {
                        Log.i("yoyo","public method: " + methods[i]);
                    }
             
               
                    Constructor[] allConstructors = c.getDeclaredConstructors();
                    for (Constructor ctor : allConstructors) {
                 
                        Log.i("yoyo","constuctor:" + ctor);
                 
                    }
                }
                */
         
                iap_controller = (RunnerBillingInterface)(Class.forName(FullClassName).getConstructor().newInstance());
         
                Log.i("yoyo","Created iap_controller, about to call InitRunnerBilling");
         
                mRunnerBilling = iap_controller.InitRunnerBilling();
                Log.i("yoyo","iap_controller successfully created");
            }
            catch(Exception e)
            {
                Log.i("yoyo","Failed to initialize Google Play Services IAP functionality - could not initialise RunnerBilling:"+e);
                Log.i("yoyo",e.toString());
                Log.i("yoyo",e.getMessage());
                e.printStackTrace();
            }
        }
 
        return mRunnerBilling;
    }
 
 
    public GoogleApiClient getApiClient()
    {
        return mGoogleApiClient;
    }
 
 
    public boolean isSignedIn()
    {
    //    Log.i("yoyo","isSignedIn called" );
        if (mGoogleApiClient != null && mGoogleApiClient.isConnected())
            return true;
 
        return false;
    }
 
    @Override
    public void Login()
    {
        if(isSignedIn())
        {
            Log.i("yoyo","Called achievement login when already logged in");
            return;
        }
        Log.i("yoyo","Calling connect on googleapiclient");
        if (mGoogleApiClient != null)
        {
            mSignInClicked= true;
            mGoogleApiClient.connect();
        }
    }
 
 
    @Override
    public void Logout()
    {
        if (mGoogleApiClient != null && mGoogleApiClient.isConnected())
        {
            mSignInClicked= false;
            Games.signOut(mGoogleApiClient);
            mGoogleApiClient.disconnect();
            Log.i("yoyo","Logged out of Google Play Services");
            RunnerJNILib.OnLoginSuccess("Not logged in","-1","","","","","");
        }
    }
 

 
    //Push notifications bit
    public void setupPushNotifications(String pushID)
    {
 
        mGcmPush = new GcmPush( RunnerActivity.CurrentActivity );
        mGcmPush.Init( pushID );
        //check for stored messages on startup- also done in onResume ( in case of messages received while in background),
        //but this is called after onResume on startup
        mGcmPush.deliverStoredMessages();

    }
 
    public static void QueuePushNotification( String msgData, Integer type, Boolean bSuccess )
    {
        if( CurrentGoogleExtension.mGcmPush != null )
        {
            RunnerJNILib.GCMPushResult(msgData, type, true );
        }
    }
 
    public int PushGetLocalNotification(Integer iIndex, Integer dsMap)
    {
        if( mGcmPush != null )
        {
            return mGcmPush.pushGetLocalNotification( iIndex, dsMap);
        }
 
        return -1;
    }
    public int pushCancelLocalNotification(Integer iIndex)
    {
 
        if( mGcmPush != null )
        {
            return mGcmPush.pushCancelLocalNotification( iIndex);
        }
 
        return -1;
 
    }
 
 
    public static void PushLocalNotification( Float fireTime, String title, String message, String data )
    {
 
        if( CurrentGoogleExtension.mGcmPush != null )
        {
            Log.i("yoyo", "Push notifications title:"+title + " message:" + message+ " fireTime:" + fireTime);
            CurrentGoogleExtension.mGcmPush.pushLocalNotification( fireTime, title, message, data );
        }
        else
        {
            Log.i("yoyo", "Push notifications not enabled");
        }
    }
 
 
 
    /// Cloud save stuff
/*    public void onStateConflict( AppStateManager.StateConflictResult result )
    {
        int StateKey = result.getStateKey();
        String resolvedVersion = result.getResolvedVersion();
        byte[] localData = result.getLocalData();
        byte[] serverData = result.getServerData();
 
        Log.i("yoyo","onStateConflict called " + StateKey+ " " + resolvedVersion);
 
    //    getGameHelper().getAppStateClient().resolveState(this, StateKey, resolvedVersion, serverData);
      AppStateManager.resolve(getApiClient(), StateKey,     resolvedVersion, serverData);
 
        if(serverData!=null)
        {
            byte[] mySaveGameByteArray = new byte[ serverData.length + 1];    //Need to put in a null terminator for desc string....
            mySaveGameByteArray[0] = '\0';
            System.arraycopy(serverData, 0, mySaveGameByteArray, 1, serverData.length);
 
            RunnerJNILib.CloudResultData( mySaveGameByteArray, 0, LastGSId );//STATUS_NEW_GAME_DATA
            mySaveGameByteArray = null;
        }
        else
        {
            RunnerJNILib.CloudResultData( null, 0, LastGSId );//STATUS_NEW_GAME_DATA
        }
    }
*/
    //@Override
    //public void onStateLoaded(int StatusCode, int StateKey, byte[] localData)
/*    public void onStateLoaded(AppStateManager.StateLoadedResult result )
    {
        int StatusCode = result.getStatus().getStatusCode();
        int StateKey = result.getStateKey();
        byte[] localData = result.getLocalData();
 
        Log.i("yoyo","onStateLoadedCalled with " + StatusCode + " Key " + StateKey);
        if(localData!=null)
        {
            byte[] mySaveGameByteArray = new byte[ localData.length + 1];    //Need to put in a null terminator for desc string....
            mySaveGameByteArray[0] = '\0';
            System.arraycopy(localData, 0, mySaveGameByteArray, 1, localData.length);
 
            RunnerJNILib.CloudResultData( mySaveGameByteArray, 0, LastGSId );
            mySaveGameByteArray = null;
        }
        else
            RunnerJNILib.CloudResultData( null, 0, LastGSId );
    }
    */
/*    ResultCallback<AppStateManager.StateResult> mResultCallback = new ResultCallback<AppStateManager.StateResult>()
    {
        @Override
        public void onResult(AppStateManager.StateResult result) {
     
            Log.i("yoyo","Cloud Sync result callback called with " + result + " status:" + result.getStatus());
     
     
            AppStateManager.StateConflictResult conflictResult = result.getConflictResult();
            AppStateManager.StateLoadedResult loadedResult = result.getLoadedResult();
            if (loadedResult != null) {
                Log.i("yoyo", "Calling onStateLoaded with " + loadedResult);
                onStateLoaded(loadedResult);
            } else if (conflictResult != null) {
                Log.i("yoyo","Calling onStateConflict with " + conflictResult);
                onStateConflict(conflictResult);
            }
            else
            {
                Log.i("yoyo","both conflict and loaded results are null");
            }
        }
    };
    */
 
    private static final int MAX_SNAPSHOT_RESOLVE_RETRIES = 3;
 
    /**
     * Conflict resolution for when Snapshots are opened.  Must be run in an AsyncTask or in a
     * background thread,
     */
    Snapshot processSnapshotOpenResult(Snapshots.OpenSnapshotResult result, int retryCount)
    {
        Snapshot mResolvedSnapshot = null;
        retryCount++;

        int status = result.getStatus().getStatusCode();
        Log.i("yoyo", "Save Result status: " + status);

        if (status == GamesStatusCodes.STATUS_OK) {
            return result.getSnapshot();
        } else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONTENTS_UNAVAILABLE) {
            return result.getSnapshot();
        } else if (status == GamesStatusCodes.STATUS_SNAPSHOT_CONFLICT) {
            Snapshot snapshot = result.getSnapshot();
            Snapshot conflictSnapshot = result.getConflictingSnapshot();

            // Resolve between conflicts by selecting the newest of the conflicting snapshots.
            mResolvedSnapshot = snapshot;

            if (snapshot.getMetadata().getLastModifiedTimestamp() <
                    conflictSnapshot.getMetadata().getLastModifiedTimestamp()) {
                mResolvedSnapshot = conflictSnapshot;
            }

            Snapshots.OpenSnapshotResult resolveResult = Games.Snapshots.resolveConflict(
                    mGoogleApiClient, result.getConflictId(), mResolvedSnapshot).await();

            if (retryCount < MAX_SNAPSHOT_RESOLVE_RETRIES) {
                // Recursively attempt again
                return processSnapshotOpenResult(resolveResult, retryCount);
            } else {
                // Failed, log error and show Toast to the user
                String message = "Could not resolve snapshot conflicts";
                Log.e("yoyo", message);
          //      Toast.makeText(getBaseContext(), message, Toast.LENGTH_LONG).show();
            }

        }

        // Fail, return null.
        return null;
    }
 
 
 
 
    public void onGSCloudSync(java.lang.Integer id)
    {
        final Integer fid = id;
        Log.i("yoyo","onGSCloudSync called with id=" + id);
 
        AsyncTask<Void, Void, Integer> task = new AsyncTask<Void, Void, Integer>() {
            @Override
            protected Integer doInBackground(Void... params) {
                // Open the saved game using its name.
                Snapshots.OpenSnapshotResult result = Games.Snapshots.open(mGoogleApiClient,mCurrentSaveName, true).await();
                 
                int status = result.getStatus().getStatusCode();
                 
                Log.i("yoyo","get save game data result :"+ result.getStatus() + " status=" +status);
                // Check the result of the open operation
                if ((status == GamesStatusCodes.STATUS_OK) ||(status == GamesStatusCodes.STATUS_SNAPSHOT_CONFLICT) ||( status==GamesStatusCodes.STATUS_SNAPSHOT_CONTENTS_UNAVAILABLE ))
                {
                    Snapshot snapshot = processSnapshotOpenResult(result,0);
             
                    //result.getSnapshot();
                    // Read the byte content of the saved game.
                    try
                    {
                        mSaveGameData = snapshot.getSnapshotContents().readFully();

                        String s = new String(mSaveGameData);
                        Log.i("yoyo","got save game data:"+ s);

                        RunnerJNILib.CloudResultData( s.getBytes(), 0/*STATUS_NEW_GAME_DATA*/, fid );
                        return result.getStatus().getStatusCode();
                    }
                    catch (IOException e)
                    {
                        Log.e("yoyo", "Error while reading Snapshot.", e);
                    }
                }
                else
                {
                    Log.e("yoyo", "Error while loading: " + result.getStatus().getStatusCode());
                }
                RunnerJNILib.CloudResultData( null, 0/*STATUS_NEW_GAME_DATA*/, fid );
                return result.getStatus().getStatusCode();
            }

            @Override
            protected void onPostExecute(Integer status) {
                // Dismiss progress dialog and reflect the changes in the UI.
                // ...
         
                Log.i("yoyo","New cloud save system onPostExecute called with status" + status);
         
            }
        };

        task.execute();
 
 
 

    }
 
 
    public void onGSStringSave(String data,String desc,Integer id)
    {//Cloud upload -- want this to call an Async event!!
            final String _data=data;
            final String _desc=desc;
            final Integer _id=id;
     
            Log.i("yoyo","Google onGSStringSave called");
     
            AsyncTask<Void, Void, Integer> task = new AsyncTask<Void, Void, Integer>()
            {
            @Override
            protected Integer doInBackground(Void... params) {
 
 
                boolean createIfMissing = true;
                byte[] databytes = _data.getBytes();
         
                // Open the snapshot, creating if necessary
                Snapshots.OpenSnapshotResult open = Games.Snapshots.open(
                        mGoogleApiClient, mCurrentSaveName, createIfMissing).await();

                if (!open.getStatus().isSuccess())
                {
                    Log.i("yoyo", "Could not open Snapshot for saving.");
                    return -1;
                }

                // Write the new data to the snapshot
                Snapshot snapshot = open.getSnapshot();
                snapshot.getSnapshotContents().writeBytes(databytes);
         
                String s = new String(databytes);
                Log.i("yoyo","saving snapshot:" + s);

                // Change metadata
                SnapshotMetadataChange metadataChange = new SnapshotMetadataChange.Builder()
                        .fromMetadata(snapshot.getMetadata())
                        .setDescription("Modified data at: " + Calendar.getInstance().getTime())
                //        .setCoverImage(bitmap)
                //        .setDescription(description)
                //        .setPlayedTimeMillis(playedTimeMillis)
                        .build();

                Snapshots.CommitSnapshotResult commit = Games.Snapshots.commitAndClose(
                        mGoogleApiClient, snapshot, metadataChange).await();

                if (!commit.getStatus().isSuccess()) {
                    Log.i("yoyo", "Failed to commit Snapshot.");
                    RunnerJNILib.CloudResultData( _desc.getBytes(), -1/*STATUS_NEW_GAME_DATA*/, _id );
         
                    return -1;
                }

                Log.i("yoyo", "Commit Snapshot success");
         
                //CUSTOM CODE -- ATTEMPT TO CALL AN ASYNC CLOUD EVENT
         
                Log.i("yoyo","Snapshot Submit Successful!: " + _desc);

                RunnerJNILib.CloudResultData( _desc.getBytes(), 3/*STATUS_NEW_GAME_DATA*/, _id );
         
                //END CUSTOM
                return 0;
            }

            @Override
            protected void onPostExecute(Integer status) {
                // Dismiss progress dialog and reflect the changes in the UI.
                // ...
         
                Log.i("yoyo","onGSStringSaves completed with " + status);
         
            }
        };

        task.execute();
    }
 
    //Achievement & Leaderboards stuff
 
    @Override
    public void PostScore(String lb, int score)
    {
        if (mGoogleApiClient != null && mGoogleApiClient.isConnected())
        {
            Games.Leaderboards.submitScore(getApiClient(),lb, score);
        }
    }
 
 
    public void onIncrementAchievement(String id,java.lang.Float Increment)
    {
 
        float increment = (float)Increment;
 
        if ( increment < 1.0 ) // as per: https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.html#increment(com.google.android.gms.common.api.GoogleApiClient, java.lang.String, int)
            Log.i("yoyo","achievement_increment must be called with a positive value above 1");
        else
        //    Log.i("yoyo","awaiting fix");
            if (mGoogleApiClient != null && mGoogleApiClient.isConnected())
            {
                //Games.Achievements.increment(getApiClient(),id,(int)increment);
                Games.Achievements.increment(getApiClient(),id,(int)increment);
         
                Log.i("yoyo", "--------------Incrementing (Double upload check)-----------------------");
            }
        //    getApiClient().incrementAchievement(id,(int)increment);
 
    }
 
    public void onPostAchievement(String id,java.lang.Float percentdone)
    {
 
        if(percentdone<=0)
            Log.i("yoyo","achievement_post must be called with a positive value");
        else if(percentdone >=100.0)
        {
            if (mGoogleApiClient != null && mGoogleApiClient.isConnected())
            {
                Games.Achievements.unlock(getApiClient(),id);
                Log.i("yoyo", "--------------Posting (Double upload check)-----------------------");
            }
            //getApiClient().unlockAchievement(id);
        }
        else
            Log.i("yoyo","GPS does not currently support posting partially complete achievements. Either call achievement_post() when the achievement is complete or use achievement_increment()");
 
    }
 
     final int RC_RESOLVE = 5000, RC_UNUSED = 5001,RC_GPS_ACTIVITY = 5011;
    public void onShowGSAchievements()
    {
        if (isSignedIn()) {
            RunnerActivity.CurrentActivity.startActivityForResult(Games.Achievements.getAchievementsIntent(getApiClient()), RC_GPS_ACTIVITY);
        }
        else {
            (new AlertDialog.Builder(RunnerActivity.CurrentActivity))
               .setTitle("Achievement Not Available")
               .setMessage("You can't view achievements because you are not signed in.")
                  .setNeutralButton("OK", null)
                  .show();
        }
    }
    public void onShowGSLeaderboards()
    {
        if (isSignedIn()) {
            RunnerActivity.CurrentActivity.startActivityForResult(Games.Leaderboards.getAllLeaderboardsIntent(getApiClient()), RC_GPS_ACTIVITY);
        }
        else {
            (new AlertDialog.Builder(RunnerActivity.CurrentActivity))
              .setTitle("Leaderboards Not Available")
              .setMessage("You can't view leaderboards because you are not signed in.")
                 .setNeutralButton("OK", null)
                 .show();
        }
    }
    private AdView adView=null;
    private InterstitialAd interstitialAd=null;
    //private String BannerId;
    private String InterstitialId;
    private String InterstitialStatus = "Not Ready";
    private String TestDeviceId;
    private boolean bUseTestAds=false;
    private AdSize BannerSize;
    private int BannerXPos;
    private int BannerYPos;
 
    private AdListener adlistener = new AdListener(){
        @Override
        public void onAdLoaded() {
            Log.i("yoyo","onAdLoaded called");
            sendInterstitialLoadedEvent( true );
        }

        @Override
        public void onAdFailedToLoad(int errorCode) {
            Log.i("yoyo","onAdFailedToLoad called");
            sendInterstitialLoadedEvent( false );
        }
        @Override
        public void onAdClosed()
        {
            sendInterstitialClosedEvent();
        }

    };
 
 
    private static final int EVENT_OTHER_SOCIAL = 70;
 
    public void GoogleMobileAds_Init(String _Arg1)
    {
 
        InterstitialId = _Arg1;
    }
 
    public void GoogleMobileAds_ShowInterstitial()
    {
        if(interstitialAd!=null)
        {
 
            RunnerActivity.ViewHandler.post( new Runnable() {
            public void run()
            {
                Log.i("yoyo","showinterstitial called");
                if (interstitialAd.isLoaded())
                {
                    RunnerActivity.CurrentActivity.runOnUiThread(new Runnable() {
                        public void run() {
                            interstitialAd.show();
                        }
                    });
                }
                else
                {
                    Log.i("yoyo", "Interstitial ad was not ready to be shown.");
                }
            }});
        }
 
    }
 
    private void initInterstitial()
    {
 
        interstitialAd = new InterstitialAd(RunnerActivity.CurrentActivity);
        interstitialAd.setAdUnitId(InterstitialId);
     
        interstitialAd.setAdListener(adlistener);
    }
 
    public void GoogleMobileAds_LoadInterstitial()
    {
        RunnerActivity.ViewHandler.post( new Runnable() {
        public void run()
        {
            if(interstitialAd==null)
                initInterstitial();
         
            AdRequest.Builder builder = new AdRequest.Builder();
            builder.addTestDevice(AdRequest.DEVICE_ID_EMULATOR);
            if( bUseTestAds)
                builder.addTestDevice(TestDeviceId);
            AdRequest adRequest = builder.build();

            // Load the interstitial ad.
            interstitialAd.loadAd(adRequest);
        }});
    }
 
    private void sendBannerLoadedEvent( boolean _bLoaded )
    {
        int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
 
        RunnerJNILib.DsMapAddString( dsMapIndex, "type", "banner_load" );
        double loaded = (_bLoaded) ? 1 : 0;
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "loaded", loaded);
 
        if( _bLoaded)
        {
            GoogleMobileAds_MoveBanner(BannerXPos, BannerYPos);
 
            RunnerJNILib.DsMapAddDouble( dsMapIndex, "width",  GoogleMobileAds_BannerGetWidth());
            RunnerJNILib.DsMapAddDouble( dsMapIndex, "height",  GoogleMobileAds_BannerGetHeight());
        }
 
        RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex,EVENT_OTHER_SOCIAL);
    }
 
    private void sendInterstitialLoadedEvent( boolean _bLoaded )
    {
        int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
        RunnerJNILib.DsMapAddString( dsMapIndex, "type", "interstitial_load" );
        double loaded = (_bLoaded) ? 1 : 0;
        RunnerJNILib.DsMapAddDouble( dsMapIndex, "loaded", loaded);
        RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex,EVENT_OTHER_SOCIAL);
    }
 
    private void sendInterstitialClosedEvent(  )
    {
        int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
        RunnerJNILib.DsMapAddString( dsMapIndex, "type", "interstitial_closed" );
 
        RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex,EVENT_OTHER_SOCIAL);
    }
 
    public void GoogleMobileAds_AddBanner(String _bannerId, double _sizeType)
    {
        GoogleMobileAds_AddBannerAt( _bannerId, _sizeType, 0, 0);
    }
 
 
    @TargetApi(11)
    void SetLayerType()
    {
        int sdkVersion =Build.VERSION.SDK_INT;
        if (sdkVersion > 10)
            adView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }
 
 
    public void GoogleMobileAds_AddBannerAt(String _bannerId, double _sizeType, double _x, double _y)
    {
        final String bannerId = _bannerId;
        BannerXPos = (int)_x;
        BannerYPos = (int)_y;
        int type = (int)(_sizeType+0.5);
 
        switch(type)
        {
        case 1: BannerSize = AdSize.BANNER; break;
        case 2: BannerSize = AdSize.MEDIUM_RECTANGLE;break;
        case 3: BannerSize = AdSize.FULL_BANNER;break;
        case 4: BannerSize = AdSize.LEADERBOARD;break;
        case 5: BannerSize = AdSize.WIDE_SKYSCRAPER; break;
        default: Log.i("yoyo", "AddBanner illegal banner size type:" + _sizeType); return;
        }
 
        RunnerActivity.ViewHandler.post( new Runnable() {
        public void run()
        {
            AbsoluteLayout layout = (AbsoluteLayout)RunnerActivity.CurrentActivity.findViewById(R.id.ad);
            ViewGroup vg = (ViewGroup)layout;
     
            //remove existing banner
            if( adView != null )
            {
                if(vg!=null)
                {
                    vg.removeView( adView );
                }
                adView.destroy();
                adView = null;
            }
     
            //create new banner
            adView = new AdView(RunnerActivity.CurrentActivity);
            //adView.setAdListener(GoogleMobileAdsExt.this);
            adView.setAdListener( new AdListener() {
                /** Called when an ad is loaded. */
               @Override
               public void onAdLoaded()
               {
                  Log.i("yoyo", "Banner Ad onAdLoaded");
                  sendBannerLoadedEvent(true);
               }
         
               @Override
               public void onAdFailedToLoad(int errorCode)
               {
                  Log.i("yoyo", "Banner Ad onAdFailedToLoad");
                  sendBannerLoadedEvent(false);
               }
         
            });
 
            SetLayerType();
     
 
            //adView.setAdSize(AdSize.BANNER);
            adView.setAdSize( BannerSize );
            adView.setAdUnitId(bannerId);
     
            if(vg != null)
            {
                vg.addView((View)adView);
     
                AdRequest.Builder builder = new AdRequest.Builder();
                builder.addTestDevice(AdRequest.DEVICE_ID_EMULATOR);
                if( bUseTestAds)
                    builder.addTestDevice(TestDeviceId);
                AdRequest adRequest = builder.build();

                // Start loading the ad in the background.
                adView.setVisibility(View.INVISIBLE);
                adView.loadAd(adRequest);
            }
        }});
    }
 
    public void GoogleMobileAds_RemoveBanner()
    {
        if( adView != null )
        {
            RunnerActivity.ViewHandler.post( new Runnable() {
            public void run()
           {
                AbsoluteLayout layout = (AbsoluteLayout)RunnerActivity.CurrentActivity.findViewById(R.id.ad);
                ViewGroup vg = (ViewGroup)layout;
                if(vg!=null)
                {
                    vg.removeView( adView );
                }
                adView.destroy();
                adView = null;
         
           }});
        }
    }
 
    public double GoogleMobileAds_BannerGetWidth()
    {
        if( BannerSize !=null )
        {
            int w =BannerSize.getWidthInPixels(RunnerJNILib.ms_context);
            return w;
        }
        return 0;
    }
 
    public double GoogleMobileAds_BannerGetHeight()
    {
        if( BannerSize !=null )
        {
            int h = BannerSize.getHeightInPixels(RunnerJNILib.ms_context);
            return h;
        }
        return 0;
    }
 
    public void GoogleMobileAds_MoveBanner( double _x, double _y )
    {
        Log.i("yoyo", "MoveBanner:" + _x + "," + _y);
        final int x = (int)_x;
        final int y = (int)_y;
        BannerXPos = x;
        BannerYPos = y;

        if( adView != null )
        {
            RunnerActivity.ViewHandler.post( new Runnable() {
            public void run()
            {
                if( x < 0 || y < 0) {
                    adView.setVisibility(View.INVISIBLE);
                }
                else
                {
                    AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, x,y );
                    adView.setLayoutParams( params);
                    adView.requestLayout();
                    adView.setVisibility(View.VISIBLE);
                }
            }});
        }
    }
 
    public void GoogleMobileAds_UseTestAds( double _useTest, String _deviceId )
    {
        bUseTestAds = (_useTest >=0.5);
        TestDeviceId = _deviceId;
    }
 
    public String GoogleMobileAds_InterstitialStatus()
    {
        if(interstitialAd!=null)
        {
            RunnerActivity.ViewHandler.post( new Runnable() {
            public void run()
            {
                if(interstitialAd.isLoaded())
                    InterstitialStatus="Ready";
                else
                    InterstitialStatus = "Not Ready";
            }});
        }

        return InterstitialStatus;
    }
 
 
}

The important bit of code is this:
Code:
public void onGSStringSave(String data,String desc,Integer id)
    {//Cloud upload -- want this to call an Async event!!
            final String _data=data;
            final String _desc=desc;
            final Integer _id=id;
     
            Log.i("yoyo","Google onGSStringSave called");
     
            AsyncTask<Void, Void, Integer> task = new AsyncTask<Void, Void, Integer>()
            {
            @Override
            protected Integer doInBackground(Void... params) {
 
 
                boolean createIfMissing = true;
                byte[] databytes = _data.getBytes();
         
                // Open the snapshot, creating if necessary
                Snapshots.OpenSnapshotResult open = Games.Snapshots.open(
                        mGoogleApiClient, mCurrentSaveName, createIfMissing).await();

                if (!open.getStatus().isSuccess())
                {
                    Log.i("yoyo", "Could not open Snapshot for saving.");
                    return -1;
                }

                // Write the new data to the snapshot
                Snapshot snapshot = open.getSnapshot();
                snapshot.getSnapshotContents().writeBytes(databytes);
         
                String s = new String(databytes);
                Log.i("yoyo","saving snapshot:" + s);

                // Change metadata
                SnapshotMetadataChange metadataChange = new SnapshotMetadataChange.Builder()
                        .fromMetadata(snapshot.getMetadata())
                        .setDescription("Modified data at: " + Calendar.getInstance().getTime())
                //        .setCoverImage(bitmap)
                //        .setDescription(description)
                //        .setPlayedTimeMillis(playedTimeMillis)
                        .build();

                Snapshots.CommitSnapshotResult commit = Games.Snapshots.commitAndClose(
                        mGoogleApiClient, snapshot, metadataChange).await();

                if (!commit.getStatus().isSuccess()) {
                    Log.i("yoyo", "Failed to commit Snapshot.");
                    RunnerJNILib.CloudResultData( _desc.getBytes(), -1/*STATUS_NEW_GAME_DATA*/, _id );
         
                    return -1;
                }

                Log.i("yoyo", "Commit Snapshot success");
         
                //CUSTOM CODE -- ATTEMPT TO CALL AN ASYNC CLOUD EVENT
         
                Log.i("yoyo","Snapshot Submit Successful!: " + _desc);

                RunnerJNILib.CloudResultData( _desc.getBytes(), 3/*STATUS_NEW_GAME_DATA*/, _id );
         
                //END CUSTOM
                return 0;
            }

            @Override
            protected void onPostExecute(Integer status) {
                // Dismiss progress dialog and reflect the changes in the UI.
                // ...
         
                Log.i("yoyo","onGSStringSaves completed with " + status);
         
            }
        };

        task.execute();
    }
But it's easier to replace the whole file than to find it and update I guess.

The important bit that I had to add was this:
Code:
RunnerJNILib.CloudResultData( _desc.getBytes(), 3/*STATUS_NEW_GAME_DATA*/, _id );
Which calls the cloud event (STATUS_NEW_GAME_DATA is some kind of old constant that was commented out originally. I just didn't bother removing it). Now, for me, saving calls a cloud event when complete, or failed.
In this case, async_load in the Cloud event is a map of these values:
  • "id" : the id returned by cloud_string_save.
  • "resultString" : always an empty string
  • "errorString" : also always an empty string (I don't yet know how to change either of these - I may investigate though)
  • "description" : The description passed in cloud_string_save - I use this to double check the ID matches, as I don't know if cloud_synchronise and cloud_string_save increment ID values together
  • "status" : This will be 3 if the upload was successful, and -1 if it failed

I don't know if this works with cloud_file_save, as there isn't a specific function for it in the extension that I can see. I assume it passes through string_save though. Either way, I don't use it so I haven't tested it.

I'm not really sure why this functionality isn't standard - I needed it to save before closing the game and to save on logout, before logging out of GP, which seems like pretty standard stuff. In fact, looking at how the function is setup and the documentation for cloud synchronise even claiming ' resultString = "GameUploadSuccess" ' and '"status" = 3' after an upload shows it was probably meant to call the Cloud event, but it was just forgotten about.

Either way, this should do the trick.

Hopefully this works for you!

EDIT: actually changed the returned status to be based on the docs. Made it 3 on upload success, left it as -1 on failure

Another Edit: I decided to do a little test with the async setup from the helpdesk guide and I found you can trigger any async event with RunnerJNILib.CreateAsynEventWithDSMap, so, with a little setup, I made the cloud result match the docs precisely (With "GameUploadSuccess" etc. errorString is GameUploadFailure on fail too.
Basically, you can replace
Code:
RunnerJNILib.CloudResultData( _desc.getBytes(), 3/*STATUS_NEW_GAME_DATA*/, _id );
With this:
Code:
int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
RunnerJNILib.DsMapAddDouble( dsMapIndex, "status", 3 );
RunnerJNILib.DsMapAddDouble( dsMapIndex, "id", _id );
RunnerJNILib.DsMapAddString( dsMapIndex, "description", _desc);
RunnerJNILib.DsMapAddString( dsMapIndex, "errorString", "");
RunnerJNILib.DsMapAddString( dsMapIndex, "resultString", "GameUploadSuccess");
RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex, 67);
The important number is 67, which is the ID for the cloud event.
 
Last edited:
H

hackerG7

Guest
I tried that but seems that it won't work on cloud_file_save.Maybe it's my problem only.Someone help..
 

Qual

Member
Omg I'll try MaddeMichael solution, as it's one of the problem that YYG seems to don't care at all while beeing reported over and over

EDIT : IT WORKS ! Thank you so much, this has been the LAST thing not working in my game since months !
 
Last edited:
Top