Bladeren bron

Adding in the spaceship sample complete with scoreloop integration. There are still a couple of bugs in the challenge code that need to be resolved with the scoreloop team. It is currently set with the default UI.

Also adding in the Google Games code.  Need to complete the JNI calls and get it integrating with the library.
Dale Ducharme 12 jaren geleden
bovenliggende
commit
b88c67eb31
33 gewijzigde bestanden met toevoegingen van 4820 en 289 verwijderingen
  1. 33 0
      gameplay/android/java/.project
  2. 9 0
      gameplay/android/java/AndroidManifest.xml
  3. 83 0
      gameplay/android/java/build.xml
  4. 190 0
      gameplay/android/java/src/org/gameplay3d/lib/BaseGameActivity.java
  5. 1149 0
      gameplay/android/java/src/org/gameplay3d/lib/GameHelper.java
  6. 118 0
      gameplay/android/java/src/org/gameplay3d/lib/GoogleGamesSocial.java
  7. 12 0
      gameplay/android/java/src/org/gameplay3d/lib/TestClass.java
  8. 8 1
      gameplay/android/jni/Android.mk
  9. 3 0
      gameplay/src/Base.h
  10. 13 1
      gameplay/src/Game.cpp
  11. 10 0
      gameplay/src/Game.h
  12. 5 0
      gameplay/src/Game.inl
  13. 13 0
      gameplay/src/PlatformBlackBerry.cpp
  14. 6 1
      gameplay/src/SocialAchievement.h
  15. 51 5
      gameplay/src/SocialController.cpp
  16. 18 3
      gameplay/src/SocialController.h
  17. 24 3
      gameplay/src/SocialSession.h
  18. 40 0
      gameplay/src/SocialSessionListener.cpp
  19. 23 0
      gameplay/src/SocialSessionListener.h
  20. 909 0
      gameplay/src/social/GoogleGamesSocialSession.cpp
  21. 201 0
      gameplay/src/social/GoogleGamesSocialSession.h
  22. 832 236
      gameplay/src/social/ScoreloopSocialSession.cpp
  23. 70 9
      gameplay/src/social/ScoreloopSocialSession.h
  24. 7 3
      samples/spaceship/.cproject
  25. 4 0
      samples/spaceship/android/AndroidManifest.xml
  26. 16 9
      samples/spaceship/bar-descriptor.xml
  27. 15 0
      samples/spaceship/game.config
  28. 96 0
      samples/spaceship/res/challenge.form
  29. 65 0
      samples/spaceship/res/menu.form
  30. 237 0
      samples/spaceship/res/menu.theme
  31. BIN
      samples/spaceship/res/menuAtlas.png
  32. 442 15
      samples/spaceship/src/SpaceshipGame.cpp
  33. 118 3
      samples/spaceship/src/SpaceshipGame.h

+ 33 - 0
gameplay/android/java/.project

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>libgameplay3d</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

+ 9 - 0
gameplay/android/java/AndroidManifest.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.gameplay3d.lib"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="8"/>
+
+</manifest> 

+ 83 - 0
gameplay/android/java/build.xml

@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="gameplay3dandroid" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
+            unless="sdk.dir"
+    />
+
+    <!--
+        Import per project custom build rules if present at the root of the project.
+        This is the place to put custom intermediary targets such as:
+            -pre-build
+            -pre-compile
+            -post-compile (This is typically used for code obfuscation.
+                           Compiled code location: ${out.classes.absolute.dir}
+                           If this is not done in place, override ${out.dex.input.absolute.dir})
+            -post-package
+            -post-build
+            -pre-clean
+    -->
+    <import file="custom_rules.xml" optional="true" />
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: 1 -->
+    <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>

+ 190 - 0
gameplay/android/java/src/org/gameplay3d/lib/BaseGameActivity.java

@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gameplay3d.sample_spaceship.basegameutils;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+import com.google.android.gms.appstate.AppStateClient;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.games.GamesClient;
+import com.google.android.gms.plus.PlusClient;
+
+/**
+ * Example base class for games. This implementation takes care of setting up
+ * the GamesClient object and managing its lifecycle. Subclasses only need to
+ * override the @link{#onSignInSucceeded} and @link{#onSignInFailed} abstract
+ * methods. To initiate the sign-in flow when the user clicks the sign-in
+ * button, subclasses should call @link{#beginUserInitiatedSignIn}. By default,
+ * this class only instantiates the GamesClient object. If the PlusClient or
+ * AppStateClient objects are also wanted, call the BaseGameActivity(int)
+ * constructor and specify the requested clients. For example, to request
+ * PlusClient and GamesClient, use BaseGameActivity(CLIENT_GAMES | CLIENT_PLUS).
+ * To request all available clients, use BaseGameActivity(CLIENT_ALL).
+ * Alternatively, you can also specify the requested clients via
+ * @link{#setRequestedClients}, but you must do so before @link{#onCreate}
+ * gets called, otherwise the call will have no effect.
+ *
+ * @author Bruno Oliveira (Google)
+ */
+public abstract class BaseGameActivity extends FragmentActivity implements
+        GameHelper.GameHelperListener {
+
+    // The game helper object. This class is mainly a wrapper around this object.
+    protected GameHelper mHelper;
+
+    // We expose these constants here because we don't want users of this class
+    // to have to know about GameHelper at all.
+    public static final int CLIENT_GAMES = GameHelper.CLIENT_GAMES;
+    public static final int CLIENT_APPSTATE = GameHelper.CLIENT_APPSTATE;
+    public static final int CLIENT_PLUS = GameHelper.CLIENT_PLUS;
+    public static final int CLIENT_ALL = GameHelper.CLIENT_ALL;
+
+    // Requested clients. By default, that's just the games client.
+    protected int mRequestedClients = CLIENT_GAMES;
+
+    // stores any additional scopes.
+    private String[] mAdditionalScopes;
+
+    protected String mDebugTag = "BaseGameActivity";
+    protected boolean mDebugLog = false;
+
+    /** Constructs a BaseGameActivity with default client (GamesClient). */
+    protected BaseGameActivity() {
+        super();
+        mHelper = new GameHelper(this);
+    }
+
+    /**
+     * Constructs a BaseGameActivity with the requested clients.
+     * @param requestedClients The requested clients (a combination of CLIENT_GAMES,
+     *         CLIENT_PLUS and CLIENT_APPSTATE).
+     */
+    protected BaseGameActivity(int requestedClients) {
+        super();
+        setRequestedClients(requestedClients);
+    }
+
+    /**
+     * Sets the requested clients. The preferred way to set the requested clients is
+     * via the constructor, but this method is available if for some reason your code
+     * cannot do this in the constructor. This must be called before onCreate in order to
+     * have any effect. If called after onCreate, this method is a no-op.
+     *
+     * @param requestedClients A combination of the flags CLIENT_GAMES, CLIENT_PLUS
+     *         and CLIENT_APPSTATE, or CLIENT_ALL to request all available clients.
+     * @param additionalScopes.  Scopes that should also be requested when the auth
+     *         request is made.
+     */
+    protected void setRequestedClients(int requestedClients, String ... additionalScopes) {
+        mRequestedClients = requestedClients;
+        mAdditionalScopes = additionalScopes;
+    }
+
+    @Override
+    protected void onCreate(Bundle b) {
+        super.onCreate(b);
+        mHelper = new GameHelper(this);
+        if (mDebugLog) {
+            mHelper.enableDebugLog(mDebugLog, mDebugTag);
+        }
+        mHelper.setup(this, mRequestedClients, mAdditionalScopes);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mHelper.onStart(this);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mHelper.onStop();
+    }
+
+    @Override
+    protected void onActivityResult(int request, int response, Intent data) {
+        super.onActivityResult(request, response, data);
+        mHelper.onActivityResult(request, response, data);
+    }
+
+    protected GamesClient getGamesClient() {
+        return mHelper.getGamesClient();
+    }
+
+    protected AppStateClient getAppStateClient() {
+        return mHelper.getAppStateClient();
+    }
+
+    protected PlusClient getPlusClient() {
+        return mHelper.getPlusClient();
+    }
+
+    protected boolean isSignedIn() {
+        return mHelper.isSignedIn();
+    }
+
+    protected void beginUserInitiatedSignIn() {
+        mHelper.beginUserInitiatedSignIn();
+    }
+
+    protected void signOut() {
+        mHelper.signOut();
+    }
+
+    protected void showAlert(String title, String message) {
+        mHelper.showAlert(title, message);
+    }
+
+    protected void showAlert(String message) {
+        mHelper.showAlert(message);
+    }
+
+    protected void enableDebugLog(boolean enabled, String tag) {
+        mDebugLog = true;
+        mDebugTag = tag;
+        if (mHelper != null) {
+            mHelper.enableDebugLog(enabled, tag);
+        }
+    }
+
+    protected String getInvitationId() {
+        return mHelper.getInvitationId();
+    }
+
+    protected void reconnectClients(int whichClients) {
+        mHelper.reconnectClients(whichClients);
+    }
+
+    protected String getScopes() {
+        return mHelper.getScopes();
+    }
+
+    protected String[] getScopesArray() {
+        return mHelper.getScopesArray();
+    }
+
+    protected boolean hasSignInError() {
+        return mHelper.hasSignInError();
+    }
+
+    protected GameHelper.SignInFailureReason getSignInError() {
+        return mHelper.getSignInError();
+    }
+}

+ 1149 - 0
gameplay/android/java/src/org/gameplay3d/lib/GameHelper.java

@@ -0,0 +1,1149 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gameplay3d.sample_spaceship.basegameutils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Vector;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+
+import com.google.android.gms.appstate.AppStateClient;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesClient;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+import com.google.android.gms.common.Scopes;
+import com.google.android.gms.games.GamesActivityResultCodes;
+import com.google.android.gms.games.GamesClient;
+import com.google.android.gms.games.multiplayer.Invitation;
+import com.google.android.gms.plus.PlusClient;
+
+public class GameHelper implements GooglePlayServicesClient.ConnectionCallbacks,
+        GooglePlayServicesClient.OnConnectionFailedListener {
+
+    /** Listener for sign-in success or failure events. */
+    public interface GameHelperListener {
+        /**
+         * Called when sign-in fails. As a result, a "Sign-In" button can be
+         * shown to the user; when that button is clicked, call
+         * @link{GamesHelper#beginUserInitiatedSignIn}. Note that not all calls to this
+         * method mean an error; it may be a result of the fact that automatic
+         * sign-in could not proceed because user interaction was required
+         * (consent dialogs). So implementations of this method should NOT
+         * display an error message unless a call to @link{GamesHelper#hasSignInError}
+         * indicates that an error indeed occurred.
+         */
+        void onSignInFailed();
+
+        /** Called when sign-in succeeds. */
+        void onSignInSucceeded();
+    }
+
+    // States we can be in
+    public static final int STATE_UNCONFIGURED = 0;
+    public static final int STATE_DISCONNECTED = 1;
+    public static final int STATE_CONNECTING = 2;
+    public static final int STATE_CONNECTED = 3;
+
+    // State names (for debug logging, etc)
+    public static final String[] STATE_NAMES = {
+            "UNCONFIGURED", "DISCONNECTED", "CONNECTING", "CONNECTED"
+    };
+
+    // State we are in right now
+    int mState = STATE_UNCONFIGURED;
+
+    // Are we expecting the result of a resolution flow?
+    boolean mExpectingResolution = false;
+
+    /**
+     * The Activity we are bound to. We need to keep a reference to the Activity
+     * because some games methods require an Activity (a Context won't do). We
+     * are careful not to leak these references: we release them on onStop().
+     */
+    Activity mActivity = null;
+
+    // OAuth scopes required for the clients. Initialized in setup().
+    String mScopes[];
+
+    // Request code we use when invoking other Activities to complete the
+    // sign-in flow.
+    final static int RC_RESOLVE = 9001;
+
+    // Request code when invoking Activities whose result we don't care about.
+    final static int RC_UNUSED = 9002;
+
+    // Client objects we manage. If a given client is not enabled, it is null.
+    GamesClient mGamesClient = null;
+    PlusClient mPlusClient = null;
+    AppStateClient mAppStateClient = null;
+
+    // What clients we manage (OR-able values, can be combined as flags)
+    public final static int CLIENT_NONE = 0x00;
+    public final static int CLIENT_GAMES = 0x01;
+    public final static int CLIENT_PLUS = 0x02;
+    public final static int CLIENT_APPSTATE = 0x04;
+    public final static int CLIENT_ALL = CLIENT_GAMES | CLIENT_PLUS | CLIENT_APPSTATE;
+
+    // What clients were requested? (bit flags)
+    int mRequestedClients = CLIENT_NONE;
+
+    // What clients are currently connected? (bit flags)
+    int mConnectedClients = CLIENT_NONE;
+
+    // What client are we currently connecting?
+    int mClientCurrentlyConnecting = CLIENT_NONE;
+
+    // Whether to automatically try to sign in on onStart().
+    boolean mAutoSignIn = true;
+
+    /*
+     * Whether user has specifically requested that the sign-in process begin.
+     * If mUserInitiatedSignIn is false, we're in the automatic sign-in attempt
+     * that we try once the Activity is started -- if true, then the user has
+     * already clicked a "Sign-In" button or something similar
+     */
+    boolean mUserInitiatedSignIn = false;
+
+    // The connection result we got from our last attempt to sign-in.
+    ConnectionResult mConnectionResult = null;
+
+    // The error that happened during sign-in.
+    SignInFailureReason mSignInFailureReason = null;
+
+    // Print debug logs?
+    boolean mDebugLog = false;
+    String mDebugTag = "GameHelper";
+
+    /*
+     * If we got an invitation id when we connected to the games client, it's
+     * here. Otherwise, it's null.
+     */
+    String mInvitationId;
+
+    // Listener
+    GameHelperListener mListener = null;
+
+    /**
+     * Construct a GameHelper object, initially tied to the given Activity.
+     * After constructing this object, call @link{setup} from the onCreate()
+     * method of your Activity.
+     */
+    public GameHelper(Activity activity) {
+        mActivity = activity;
+    }
+
+    static private final int TYPE_DEVELOPER_ERROR = 1001;
+    static private final int TYPE_GAMEHELPER_BUG = 1002;
+    boolean checkState(int type, String operation, String warning, int... expectedStates) {
+        for (int expectedState : expectedStates) {
+            if (mState == expectedState) {
+                return true;
+            }
+        }
+        StringBuilder sb = new StringBuilder();
+        if (type == TYPE_DEVELOPER_ERROR) {
+            sb.append("GameHelper: you attempted an operation at an invalid. ");
+        } else {
+            sb.append("GameHelper: bug detected. Please report it at our bug tracker ");
+            sb.append("https://github.com/playgameservices/android-samples/issues. ");
+            sb.append("Please include the last couple hundred lines of logcat output ");
+            sb.append("and describe the operation that caused this. ");
+        }
+        sb.append("Explanation: ").append(warning);
+        sb.append("Operation: ").append(operation).append(". ");
+        sb.append("State: ").append(STATE_NAMES[mState]).append(". ");
+        if (expectedStates.length == 1) {
+            sb.append("Expected state: ").append(STATE_NAMES[expectedStates[0]]).append(".");
+        } else {
+            sb.append("Expected states:");
+            for (int expectedState : expectedStates) {
+                sb.append(" " ).append(STATE_NAMES[expectedState]);
+            }
+            sb.append(".");
+        }
+
+        logWarn(sb.toString());
+        return false;
+    }
+
+    void assertConfigured(String operation) {
+        if (mState == STATE_UNCONFIGURED) {
+            String error = "GameHelper error: Operation attempted without setup: " + operation +
+                    ". The setup() method must be called before attempting any other operation.";
+            logError(error);
+            throw new IllegalStateException(error);
+        }
+    }
+
+    /**
+     * Same as calling @link{setup(GameHelperListener, int)} requesting only the
+     * CLIENT_GAMES client.
+     */
+    public void setup(GameHelperListener listener) {
+        setup(listener, CLIENT_GAMES);
+    }
+
+    /**
+     * Performs setup on this GameHelper object. Call this from the onCreate()
+     * method of your Activity. This will create the clients and do a few other
+     * initialization tasks. Next, call @link{#onStart} from the onStart()
+     * method of your Activity.
+     *
+     * @param listener The listener to be notified of sign-in events.
+     * @param clientsToUse The clients to use. Use a combination of
+     *            CLIENT_GAMES, CLIENT_PLUS and CLIENT_APPSTATE, or CLIENT_ALL
+     *            to request all clients.
+     * @param additionalScopes Any scopes to be used that are outside of the ones defined
+     *            in the Scopes class.
+     *            I.E. for YouTube uploads one would add
+     *            "https://www.googleapis.com/auth/youtube.upload"
+     */
+    public void setup(GameHelperListener listener, int clientsToUse, String ... additionalScopes) {
+        if (mState != STATE_UNCONFIGURED) {
+            String error = "GameHelper: you called GameHelper.setup() twice. You can only call " +
+                    "it once.";
+            logError(error);
+            throw new IllegalStateException(error);
+        }
+        mListener = listener;
+        mRequestedClients = clientsToUse;
+
+        debugLog("Setup: requested clients: " + mRequestedClients);
+
+        Vector<String> scopesVector = new Vector<String>();
+        if (0 != (clientsToUse & CLIENT_GAMES)) {
+            scopesVector.add(Scopes.GAMES);
+        }
+        if (0 != (clientsToUse & CLIENT_PLUS)) {
+            scopesVector.add(Scopes.PLUS_LOGIN);
+        }
+        if (0 != (clientsToUse & CLIENT_APPSTATE)) {
+            scopesVector.add(Scopes.APP_STATE);
+        }
+
+        if (null != additionalScopes) {
+            for (String scope : additionalScopes) {
+                scopesVector.add(scope);
+            }
+        }
+
+        mScopes = new String[scopesVector.size()];
+        scopesVector.copyInto(mScopes);
+
+        debugLog("setup: scopes:");
+        for (String scope : mScopes) {
+            debugLog("  - " + scope);
+        }
+
+        if (0 != (clientsToUse & CLIENT_GAMES)) {
+            debugLog("setup: creating GamesClient");
+            mGamesClient = new GamesClient.Builder(getContext(), this, this)
+                    .setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL)
+                    .setScopes(mScopes)
+                    .create();
+        }
+
+        if (0 != (clientsToUse & CLIENT_PLUS)) {
+            debugLog("setup: creating GamesPlusClient");
+            mPlusClient = new PlusClient.Builder(getContext(), this, this)
+                    .setScopes(mScopes)
+                    .build();
+        }
+
+        if (0 != (clientsToUse & CLIENT_APPSTATE)) {
+            debugLog("setup: creating AppStateClient");
+            mAppStateClient = new AppStateClient.Builder(getContext(), this, this)
+                    .setScopes(mScopes)
+                    .create();
+        }
+        setState(STATE_DISCONNECTED);
+    }
+
+    void setState(int newState) {
+        String oldStateName = STATE_NAMES[mState];
+        String newStateName = STATE_NAMES[newState];
+        mState = newState;
+        debugLog("State change " + oldStateName + " -> " + newStateName);
+    }
+
+    /**
+     * Returns the GamesClient object. In order to call this method, you must have
+     * called @link{setup} with a set of clients that includes CLIENT_GAMES.
+     */
+    public GamesClient getGamesClient() {
+        if (mGamesClient == null) {
+            throw new IllegalStateException("No GamesClient. Did you request it at setup?");
+        }
+        return mGamesClient;
+    }
+
+    /**
+     * Returns the AppStateClient object. In order to call this method, you must have
+     * called @link{#setup} with a set of clients that includes CLIENT_APPSTATE.
+     */
+    public AppStateClient getAppStateClient() {
+        if (mAppStateClient == null) {
+            throw new IllegalStateException("No AppStateClient. Did you request it at setup?");
+        }
+        return mAppStateClient;
+    }
+
+    /**
+     * Returns the PlusClient object. In order to call this method, you must have
+     * called @link{#setup} with a set of clients that includes CLIENT_PLUS.
+     */
+    public PlusClient getPlusClient() {
+        if (mPlusClient == null) {
+            throw new IllegalStateException("No PlusClient. Did you request it at setup?");
+        }
+        return mPlusClient;
+    }
+
+    /** Returns whether or not the user is signed in. */
+    public boolean isSignedIn() {
+        return mState == STATE_CONNECTED;
+    }
+
+    /**
+     * Returns whether or not there was a (non-recoverable) error during the
+     * sign-in process.
+     */
+    public boolean hasSignInError() {
+        return mSignInFailureReason != null;
+    }
+
+    /**
+     * Returns the error that happened during the sign-in process, null if no
+     * error occurred.
+     */
+    public SignInFailureReason getSignInError() {
+        return mSignInFailureReason;
+    }
+
+    /** Call this method from your Activity's onStart(). */
+    public void onStart(Activity act) {
+        mActivity = act;
+
+        debugLog("onStart, state = " + STATE_NAMES[mState]);
+        assertConfigured("onStart");
+
+        switch (mState) {
+            case STATE_DISCONNECTED:
+                // we are not connected, so attempt to connect
+                if (mAutoSignIn) {
+                    debugLog("onStart: Now connecting clients.");
+                    startConnections();
+                } else {
+                    debugLog("onStart: Not connecting (user specifically signed out).");
+                }
+                break;
+            case STATE_CONNECTING:
+                // connection process is in progress; no action required
+                debugLog("onStart: connection process in progress, no action taken.");
+                break;
+            case STATE_CONNECTED:
+                // already connected (for some strange reason). No complaints :-)
+                debugLog("onStart: already connected (unusual, but ok).");
+                break;
+            default:
+                String msg =  "onStart: BUG: unexpected state " + STATE_NAMES[mState];
+                logError(msg);
+                throw new IllegalStateException(msg);
+        }
+    }
+
+    /** Call this method from your Activity's onStop(). */
+    public void onStop() {
+        debugLog("onStop, state = " + STATE_NAMES[mState]);
+        assertConfigured("onStop");
+        switch (mState) {
+            case STATE_CONNECTED:
+            case STATE_CONNECTING:
+                // kill connections
+                debugLog("onStop: Killing connections");
+                killConnections();
+                break;
+            case STATE_DISCONNECTED:
+                debugLog("onStop: not connected, so no action taken.");
+                break;
+            default:
+                String msg =  "onStop: BUG: unexpected state " + STATE_NAMES[mState];
+                logError(msg);
+                throw new IllegalStateException(msg);
+        }
+
+        // let go of the Activity reference
+        mActivity = null;
+    }
+
+    /** Convenience method to show an alert dialog. */
+    public void showAlert(String title, String message) {
+        (new AlertDialog.Builder(getContext())).setTitle(title).setMessage(message)
+                .setNeutralButton(android.R.string.ok, null).create().show();
+    }
+
+    /** Convenience method to show an alert dialog. */
+    public void showAlert(String message) {
+        (new AlertDialog.Builder(getContext())).setMessage(message)
+                .setNeutralButton(android.R.string.ok, null).create().show();
+    }
+
+    /**
+     * Returns the invitation ID received through an invitation notification.
+     * This should be called from your GameHelperListener's
+     *
+     * @link{GameHelperListener#onSignInSucceeded} method, to check if there's an
+     * invitation available. In that case, accept the invitation.
+     * @return The id of the invitation, or null if none was received.
+     */
+    public String getInvitationId() {
+        if (!checkState(TYPE_DEVELOPER_ERROR, "getInvitationId",
+                "Invitation ID is only available when connected " +
+                "(after getting the onSignInSucceeded callback).", STATE_CONNECTED)) {
+            return null;
+        }
+        return mInvitationId;
+    }
+
+    /** Enables debug logging */
+    public void enableDebugLog(boolean enabled, String tag) {
+        mDebugLog = enabled;
+        mDebugTag = tag;
+        if (enabled) {
+            debugLog("Debug log enabled, tag: " + tag);
+        }
+    }
+
+    /**
+     * Returns the current requested scopes. This is not valid until setup() has
+     * been called.
+     *
+     * @return the requested scopes, including the oauth2: prefix
+     */
+    public String getScopes() {
+        StringBuilder scopeStringBuilder = new StringBuilder();
+        if (null != mScopes) {
+            for (String scope: mScopes) {
+                addToScope(scopeStringBuilder, scope);
+            }
+        }
+        return scopeStringBuilder.toString();
+    }
+
+    /**
+     * Returns an array of the current requested scopes. This is not valid until
+     * setup() has been called
+     *
+     * @return the requested scopes, including the oauth2: prefix
+     */
+    public String[] getScopesArray() {
+        return mScopes;
+    }
+
+    /** Sign out and disconnect from the APIs. */
+    public void signOut() {
+        if (mState == STATE_DISCONNECTED) {
+            // nothing to do
+            debugLog("signOut: state was already DISCONNECTED, ignoring.");
+            return;
+        }
+
+        // for the PlusClient, "signing out" means clearing the default account and
+        // then disconnecting
+        if (mPlusClient != null && mPlusClient.isConnected()) {
+            debugLog("Clearing default account on PlusClient.");
+            mPlusClient.clearDefaultAccount();
+        }
+
+        // For the games client, signing out means calling signOut and disconnecting
+        if (mGamesClient != null && mGamesClient.isConnected()) {
+            debugLog("Signing out from GamesClient.");
+            mGamesClient.signOut();
+        }
+
+        // Ready to disconnect
+        debugLog("Proceeding with disconnection.");
+        killConnections();
+    }
+
+    void killConnections() {
+        if (!checkState(TYPE_GAMEHELPER_BUG, "killConnections", "killConnections() should only " +
+                "get called while connected or connecting.", STATE_CONNECTED, STATE_CONNECTING)) {
+            return;
+        }
+        debugLog("killConnections: killing connections.");
+
+        mConnectionResult = null;
+        mSignInFailureReason = null;
+
+        if (mGamesClient != null && mGamesClient.isConnected()) {
+            debugLog("Disconnecting GamesClient.");
+            mGamesClient.disconnect();
+        }
+        if (mPlusClient != null && mPlusClient.isConnected()) {
+            debugLog("Disconnecting PlusClient.");
+            mPlusClient.disconnect();
+        }
+        if (mAppStateClient != null && mAppStateClient.isConnected()) {
+            debugLog("Disconnecting AppStateClient.");
+            mAppStateClient.disconnect();
+        }
+        mConnectedClients = CLIENT_NONE;
+        debugLog("killConnections: all clients disconnected.");
+        setState(STATE_DISCONNECTED);
+    }
+
+    static String activityResponseCodeToString(int respCode) {
+        switch (respCode) {
+            case Activity.RESULT_OK:
+                return "RESULT_OK";
+            case Activity.RESULT_CANCELED:
+                return "RESULT_CANCELED";
+            case GamesActivityResultCodes.RESULT_APP_MISCONFIGURED:
+                return "RESULT_APP_MISCONFIGURED";
+            case GamesActivityResultCodes.RESULT_LEFT_ROOM:
+                return "RESULT_LEFT_ROOM";
+            case GamesActivityResultCodes.RESULT_LICENSE_FAILED:
+                return "RESULT_LICENSE_FAILED";
+            case GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED:
+                return "RESULT_RECONNECT_REQUIRED";
+            case GamesActivityResultCodes.RESULT_SIGN_IN_FAILED:
+                return "SIGN_IN_FAILED";
+            default:
+                return String.valueOf(respCode);
+        }
+    }
+
+    /**
+     * Handle activity result. Call this method from your Activity's
+     * onActivityResult callback. If the activity result pertains to the sign-in
+     * process, processes it appropriately.
+     */
+    public void onActivityResult(int requestCode, int responseCode, Intent intent) {
+        debugLog("onActivityResult: req=" + (requestCode == RC_RESOLVE ? "RC_RESOLVE" :
+                String.valueOf(requestCode)) + ", resp=" +
+                activityResponseCodeToString(responseCode));
+        if (requestCode != RC_RESOLVE) {
+            debugLog("onActivityResult: request code not meant for us. Ignoring.");
+            return;
+        }
+
+        // no longer expecting a resolution
+        mExpectingResolution = false;
+
+        if (mState != STATE_CONNECTING) {
+            debugLog("onActivityResult: ignoring because state isn't STATE_CONNECTING (" +
+                    "it's " + STATE_NAMES[mState] + ")");
+            return;
+        }
+
+        // We're coming back from an activity that was launched to resolve a
+        // connection problem. For example, the sign-in UI.
+        if (responseCode == Activity.RESULT_OK) {
+            // Ready to try to connect again.
+            debugLog("onAR: Resolution was RESULT_OK, so connecting current client again.");
+            connectCurrentClient();
+        } else if (responseCode == GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED) {
+            debugLog("onAR: Resolution was RECONNECT_REQUIRED, so reconnecting.");
+            connectCurrentClient();
+        } else if (responseCode == Activity.RESULT_CANCELED) {
+            // User cancelled.
+            debugLog("onAR: Got a cancellation result, so disconnecting.");
+            mAutoSignIn = false;
+            mUserInitiatedSignIn = false;
+            mSignInFailureReason = null; // cancelling is not a failure!
+            killConnections();
+            notifyListener(false);
+        } else {
+            // Whatever the problem we were trying to solve, it was not
+            // solved. So give up and show an error message.
+            debugLog("onAR: responseCode="  + activityResponseCodeToString(responseCode) +
+                        ", so giving up.");
+            giveUp(new SignInFailureReason(mConnectionResult.getErrorCode(), responseCode));
+        }
+    }
+
+    void notifyListener(boolean success) {
+        debugLog("Notifying LISTENER of sign-in " + (success ? "SUCCESS" :
+                mSignInFailureReason != null ? "FAILURE (error)" : "FAILURE (no error)"));
+        if (mListener != null) {
+            if (success) {
+                mListener.onSignInSucceeded();
+            } else {
+                mListener.onSignInFailed();
+            }
+        }
+    }
+
+    /**
+     * Starts a user-initiated sign-in flow. This should be called when the user
+     * clicks on a "Sign In" button. As a result, authentication/consent dialogs
+     * may show up. At the end of the process, the GameHelperListener's
+     * onSignInSucceeded() or onSignInFailed() methods will be called.
+     */
+    public void beginUserInitiatedSignIn() {
+        if (mState == STATE_CONNECTED) {
+            // nothing to do
+            logWarn("beginUserInitiatedSignIn() called when already connected. " +
+                    "Calling listener directly to notify of success.");
+            notifyListener(true);
+            return;
+        } else if (mState == STATE_CONNECTING) {
+            logWarn("beginUserInitiatedSignIn() called when already connecting. " +
+                    "Be patient! You can only call this method after you get an " +
+                    "onSignInSucceeded() or onSignInFailed() callback. Suggestion: disable " +
+                    "the sign-in button on startup and also when it's clicked, and re-enable " +
+                    "when you get the callback.");
+            // ignore call (listener will get a callback when the connection process finishes)
+            return;
+        }
+
+        debugLog("Starting USER-INITIATED sign-in flow.");
+
+        // sign in automatically on onStart()
+        mAutoSignIn = true;
+
+        // Is Google Play services available?
+        int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getContext());
+        debugLog("isGooglePlayServicesAvailable returned " + result);
+        if (result != ConnectionResult.SUCCESS) {
+            // Google Play services is not available.
+            debugLog("Google Play services not available. Show error dialog.");
+            mSignInFailureReason = new SignInFailureReason(result, 0);
+            showFailureDialog();
+            notifyListener(false);
+            return;
+        }
+
+        // indicate that user is actively trying to sign in (so we know to resolve
+        // connection problems by showing dialogs)
+        mUserInitiatedSignIn = true;
+
+        if (mConnectionResult != null) {
+            // We have a pending connection result from a previous failure, so
+            // start with that.
+            debugLog("beginUserInitiatedSignIn: continuing pending sign-in flow.");
+            setState(STATE_CONNECTING);
+            resolveConnectionResult();
+        } else {
+            // We don't have a pending connection result, so start anew.
+            debugLog("beginUserInitiatedSignIn: starting new sign-in flow.");
+            startConnections();
+        }
+    }
+
+    Context getContext() {
+        return mActivity;
+    }
+
+    void addToScope(StringBuilder scopeStringBuilder, String scope) {
+        if (scopeStringBuilder.length() == 0) {
+            scopeStringBuilder.append("oauth2:");
+        } else {
+            scopeStringBuilder.append(" ");
+        }
+        scopeStringBuilder.append(scope);
+    }
+
+    void startConnections() {
+        if (!checkState(TYPE_GAMEHELPER_BUG, "startConnections", "startConnections should " +
+                "only get called when disconnected.", STATE_DISCONNECTED)) {
+            return;
+        }
+        debugLog("Starting connections.");
+        setState(STATE_CONNECTING);
+        mInvitationId = null;
+        connectNextClient();
+    }
+
+    void connectNextClient() {
+        // do we already have all the clients we need?
+        debugLog("connectNextClient: requested clients: " + mRequestedClients +
+                    ", connected clients: " + mConnectedClients);
+
+        // failsafe, in case we somehow lost track of what clients are connected or not.
+        if (mGamesClient != null && mGamesClient.isConnected() &&
+                (0 == (mConnectedClients & CLIENT_GAMES))) {
+            logWarn("GamesClient was already connected. Fixing.");
+            mConnectedClients |= CLIENT_GAMES;
+        }
+        if (mPlusClient != null && mPlusClient.isConnected() &&
+                (0 == (mConnectedClients & CLIENT_PLUS))) {
+            logWarn("PlusClient was already connected. Fixing.");
+            mConnectedClients |= CLIENT_PLUS;
+        }
+        if (mAppStateClient != null && mAppStateClient.isConnected() &&
+                (0 == (mConnectedClients & CLIENT_APPSTATE))) {
+            logWarn("AppStateClient was already connected. Fixing");
+            mConnectedClients |= CLIENT_APPSTATE;
+        }
+
+        int pendingClients = mRequestedClients & ~mConnectedClients;
+        debugLog("Pending clients: " + pendingClients);
+
+        if (pendingClients == 0) {
+            debugLog("All clients now connected. Sign-in successful!");
+            succeedSignIn();
+            return;
+        }
+
+        // which client should be the next one to connect?
+        if (mGamesClient != null && (0 != (pendingClients & CLIENT_GAMES))) {
+            debugLog("Connecting GamesClient.");
+            mClientCurrentlyConnecting = CLIENT_GAMES;
+        } else if (mPlusClient != null && (0 != (pendingClients & CLIENT_PLUS))) {
+            debugLog("Connecting PlusClient.");
+            mClientCurrentlyConnecting = CLIENT_PLUS;
+        } else if (mAppStateClient != null && (0 != (pendingClients & CLIENT_APPSTATE))) {
+            debugLog("Connecting AppStateClient.");
+            mClientCurrentlyConnecting = CLIENT_APPSTATE;
+        } else {
+            // hmmm, getting here would be a bug.
+            throw new AssertionError("Not all clients connected, yet no one is next. R="
+                    + mRequestedClients + ", C=" + mConnectedClients);
+        }
+
+        connectCurrentClient();
+    }
+
+    void connectCurrentClient() {
+        if (mState == STATE_DISCONNECTED) {
+            // we got disconnected during the connection process, so abort
+            logWarn("GameHelper got disconnected during connection process. Aborting.");
+            return;
+        }
+        if (!checkState(TYPE_GAMEHELPER_BUG, "connectCurrentClient", "connectCurrentClient " +
+                "should only get called when connecting.", STATE_CONNECTING)) {
+            return;
+        }
+
+        switch (mClientCurrentlyConnecting) {
+            case CLIENT_GAMES:
+                mGamesClient.connect();
+                break;
+            case CLIENT_APPSTATE:
+                mAppStateClient.connect();
+                break;
+            case CLIENT_PLUS:
+                mPlusClient.connect();
+                break;
+        }
+    }
+
+    /**
+     * Disconnects the indicated clients, then connects them again.
+     * @param whatClients Indicates which clients to reconnect.
+     */
+    public void reconnectClients(int whatClients) {
+        checkState(TYPE_DEVELOPER_ERROR, "reconnectClients", "reconnectClients should " +
+                "only be called when connected. Proceeding anyway.", STATE_CONNECTED);
+        boolean actuallyReconnecting = false;
+
+        if ((whatClients & CLIENT_GAMES) != 0 && mGamesClient != null
+                && mGamesClient.isConnected()) {
+            debugLog("Reconnecting GamesClient.");
+            actuallyReconnecting = true;
+            mConnectedClients &= ~CLIENT_GAMES;
+            mGamesClient.reconnect();
+        }
+        if ((whatClients & CLIENT_APPSTATE) != 0 && mAppStateClient != null
+                && mAppStateClient.isConnected()) {
+            debugLog("Reconnecting AppStateClient.");
+            actuallyReconnecting = true;
+            mConnectedClients &= ~CLIENT_APPSTATE;
+            mAppStateClient.reconnect();
+        }
+        if ((whatClients & CLIENT_PLUS) != 0 && mPlusClient != null
+                && mPlusClient.isConnected()) {
+            // PlusClient doesn't need reconnections.
+            logWarn("GameHelper is ignoring your request to reconnect " +
+                    "PlusClient because this is unnecessary.");
+        }
+
+        if (actuallyReconnecting) {
+            setState(STATE_CONNECTING);
+        } else {
+            // No reconnections are to take place, so for consistency we call the listener
+            // as if sign in had just succeeded.
+            debugLog("No reconnections needed, so behaving as if sign in just succeeded");
+            notifyListener(true);
+        }
+    }
+
+    /** Called when we successfully obtain a connection to a client. */
+    @Override
+    public void onConnected(Bundle connectionHint) {
+        debugLog("onConnected: connected! client=" + mClientCurrentlyConnecting);
+
+        // Mark the current client as connected
+        mConnectedClients |= mClientCurrentlyConnecting;
+        debugLog("Connected clients updated to: " + mConnectedClients);
+
+        // If this was the games client and it came with an invite, store it for
+        // later retrieval.
+        if (mClientCurrentlyConnecting == CLIENT_GAMES && connectionHint != null) {
+            debugLog("onConnected: connection hint provided. Checking for invite.");
+            Invitation inv = connectionHint.getParcelable(GamesClient.EXTRA_INVITATION);
+            if (inv != null && inv.getInvitationId() != null) {
+                // accept invitation
+                debugLog("onConnected: connection hint has a room invite!");
+                mInvitationId = inv.getInvitationId();
+                debugLog("Invitation ID: " + mInvitationId);
+            }
+        }
+
+        // connect the next client in line, if any.
+        connectNextClient();
+    }
+
+    void succeedSignIn() {
+        checkState(TYPE_GAMEHELPER_BUG, "succeedSignIn", "succeedSignIn should only " +
+                "get called in the connecting or connected state. Proceeding anyway.",
+                STATE_CONNECTING, STATE_CONNECTED);
+        debugLog("All requested clients connected. Sign-in succeeded!");
+        setState(STATE_CONNECTED);
+        mSignInFailureReason = null;
+        mAutoSignIn = true;
+        mUserInitiatedSignIn = false;
+        notifyListener(true);
+    }
+
+    /** Handles a connection failure reported by a client. */
+    @Override
+    public void onConnectionFailed(ConnectionResult result) {
+        // save connection result for later reference
+        debugLog("onConnectionFailed");
+
+        mConnectionResult = result;
+        debugLog("Connection failure:");
+        debugLog("   - code: " +  errorCodeToString(mConnectionResult.getErrorCode()));
+        debugLog("   - resolvable: " + mConnectionResult.hasResolution());
+        debugLog("   - details: " + mConnectionResult.toString());
+
+        if (!mUserInitiatedSignIn) {
+            // If the user didn't initiate the sign-in, we don't try to resolve
+            // the connection problem automatically -- instead, we fail and wait
+            // for the user to want to sign in. That way, they won't get an
+            // authentication (or other) popup unless they are actively trying
+            // to
+            // sign in.
+            debugLog("onConnectionFailed: since user didn't initiate sign-in, failing now.");
+            mConnectionResult = result;
+            setState(STATE_DISCONNECTED);
+            notifyListener(false);
+            return;
+        }
+
+        debugLog("onConnectionFailed: since user initiated sign-in, resolving problem.");
+
+        // Resolve the connection result. This usually means showing a dialog or
+        // starting an Activity that will allow the user to give the appropriate
+        // consents so that sign-in can be successful.
+        resolveConnectionResult();
+    }
+
+    /**
+     * Attempts to resolve a connection failure. This will usually involve
+     * starting a UI flow that lets the user give the appropriate consents
+     * necessary for sign-in to work.
+     */
+    void resolveConnectionResult() {
+        // Try to resolve the problem
+        checkState(TYPE_GAMEHELPER_BUG, "resolveConnectionResult",
+                "resolveConnectionResult should only be called when connecting. Proceeding anyway.",
+                STATE_CONNECTING);
+
+        if (mExpectingResolution) {
+            debugLog("We're already expecting the result of a previous resolution.");
+            return;
+        }
+
+        debugLog("resolveConnectionResult: trying to resolve result: " + mConnectionResult);
+        if (mConnectionResult.hasResolution()) {
+            // This problem can be fixed. So let's try to fix it.
+            debugLog("Result has resolution. Starting it.");
+            try {
+                // launch appropriate UI flow (which might, for example, be the
+                // sign-in flow)
+                mExpectingResolution = true;
+                mConnectionResult.startResolutionForResult(mActivity, RC_RESOLVE);
+            } catch (SendIntentException e) {
+                // Try connecting again
+                debugLog("SendIntentException, so connecting again.");
+                connectCurrentClient();
+            }
+        } else {
+            // It's not a problem what we can solve, so give up and show an
+            // error.
+            debugLog("resolveConnectionResult: result has no resolution. Giving up.");
+            giveUp(new SignInFailureReason(mConnectionResult.getErrorCode()));
+        }
+    }
+
+    /**
+     * Give up on signing in due to an error. Shows the appropriate error
+     * message to the user, using a standard error dialog as appropriate to the
+     * cause of the error. That dialog will indicate to the user how the problem
+     * can be solved (for example, re-enable Google Play Services, upgrade to a
+     * new version, etc).
+     */
+    void giveUp(SignInFailureReason reason) {
+        checkState(TYPE_GAMEHELPER_BUG, "giveUp", "giveUp should only be called when " +
+                "connecting. Proceeding anyway.", STATE_CONNECTING);
+        mAutoSignIn = false;
+        killConnections();
+        mSignInFailureReason = reason;
+        showFailureDialog();
+        notifyListener(false);
+    }
+
+    /** Called when we are disconnected from a client. */
+    @Override
+    public void onDisconnected() {
+        debugLog("onDisconnected.");
+        if (mState == STATE_DISCONNECTED) {
+            // This is expected.
+            debugLog("onDisconnected is expected, so no action taken.");
+            return;
+        }
+
+        // Unexpected disconnect (rare!)
+        logWarn("Unexpectedly disconnected. Severing remaining connections.");
+
+        // kill the other connections too, and revert to DISCONNECTED state.
+        killConnections();
+        mSignInFailureReason = null;
+
+        // call the sign in failure callback
+        debugLog("Making extraordinary call to onSignInFailed callback");
+        notifyListener(false);
+    }
+
+    /** Shows an error dialog that's appropriate for the failure reason. */
+    void showFailureDialog() {
+        Context ctx = getContext();
+        if (ctx == null) {
+            debugLog("*** No context. Can't show failure dialog.");
+            return;
+        }
+        debugLog("Making error dialog for failure: " + mSignInFailureReason);
+        Dialog errorDialog = null;
+        int errorCode = mSignInFailureReason.getServiceErrorCode();
+        int actResp = mSignInFailureReason.getActivityResultCode();
+
+        switch (actResp) {
+            case GamesActivityResultCodes.RESULT_APP_MISCONFIGURED:
+                errorDialog = makeSimpleDialog(ctx.getString(
+                        R.string.gamehelper_app_misconfigured));
+                printMisconfiguredDebugInfo();
+                break;
+            case GamesActivityResultCodes.RESULT_SIGN_IN_FAILED:
+                errorDialog = makeSimpleDialog(ctx.getString(
+                        R.string.gamehelper_sign_in_failed));
+                break;
+            case GamesActivityResultCodes.RESULT_LICENSE_FAILED:
+                errorDialog = makeSimpleDialog(ctx.getString(
+                        R.string.gamehelper_license_failed));
+                break;
+            default:
+                // No meaningful Activity response code, so generate default Google
+                // Play services dialog
+                errorDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, mActivity,
+                        RC_UNUSED, null);
+                if (errorDialog == null) {
+                    // get fallback dialog
+                    debugLog("No standard error dialog available. Making fallback dialog.");
+                    errorDialog = makeSimpleDialog(ctx.getString(R.string.gamehelper_unknown_error)
+                            + " " + errorCodeToString(errorCode));
+                }
+        }
+
+        debugLog("Showing error dialog.");
+        errorDialog.show();
+    }
+
+    Dialog makeSimpleDialog(String text) {
+        return (new AlertDialog.Builder(getContext())).setMessage(text)
+                .setNeutralButton(android.R.string.ok, null).create();
+    }
+
+    void debugLog(String message) {
+        if (mDebugLog) {
+            Log.d(mDebugTag, "GameHelper: " + message);
+        }
+    }
+
+    void logWarn(String message) {
+        Log.w(mDebugTag, "!!! GameHelper WARNING: " + message);
+    }
+
+    void logError(String message) {
+        Log.e(mDebugTag, "*** GameHelper ERROR: " + message);
+    }
+
+    static String errorCodeToString(int errorCode) {
+        switch (errorCode) {
+            case ConnectionResult.DEVELOPER_ERROR:
+                return "DEVELOPER_ERROR(" + errorCode + ")";
+            case ConnectionResult.INTERNAL_ERROR:
+                return "INTERNAL_ERROR(" + errorCode + ")";
+            case ConnectionResult.INVALID_ACCOUNT:
+                return "INVALID_ACCOUNT(" + errorCode + ")";
+            case ConnectionResult.LICENSE_CHECK_FAILED:
+                return "LICENSE_CHECK_FAILED(" + errorCode + ")";
+            case ConnectionResult.NETWORK_ERROR:
+                return "NETWORK_ERROR(" + errorCode + ")";
+            case ConnectionResult.RESOLUTION_REQUIRED:
+                return "RESOLUTION_REQUIRED(" + errorCode + ")";
+            case ConnectionResult.SERVICE_DISABLED:
+                return "SERVICE_DISABLED(" + errorCode + ")";
+            case ConnectionResult.SERVICE_INVALID:
+                return "SERVICE_INVALID(" + errorCode + ")";
+            case ConnectionResult.SERVICE_MISSING:
+                return "SERVICE_MISSING(" + errorCode + ")";
+            case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED:
+                return "SERVICE_VERSION_UPDATE_REQUIRED(" + errorCode + ")";
+            case ConnectionResult.SIGN_IN_REQUIRED:
+                return "SIGN_IN_REQUIRED(" + errorCode + ")";
+            case ConnectionResult.SUCCESS:
+                return "SUCCESS(" + errorCode + ")";
+            default:
+                return "Unknown error code " + errorCode;
+        }
+    }
+
+    // Represents the reason for a sign-in failure
+    public static class SignInFailureReason {
+        public static final int NO_ACTIVITY_RESULT_CODE = -100;
+        int mServiceErrorCode = 0;
+        int mActivityResultCode = NO_ACTIVITY_RESULT_CODE;
+        public int getServiceErrorCode() {
+            return mServiceErrorCode;
+        }
+        public int getActivityResultCode() {
+            return mActivityResultCode;
+        }
+        public SignInFailureReason(int serviceErrorCode, int activityResultCode) {
+            mServiceErrorCode = serviceErrorCode;
+            mActivityResultCode = activityResultCode;
+        }
+        public SignInFailureReason(int serviceErrorCode) {
+            this(serviceErrorCode, NO_ACTIVITY_RESULT_CODE);
+        }
+        @Override
+        public String toString() {
+            return "SignInFailureReason(serviceErrorCode:" +
+                    errorCodeToString(mServiceErrorCode) +
+                    ((mActivityResultCode == NO_ACTIVITY_RESULT_CODE) ? ")" :
+                    (",activityResultCode:" +
+                    activityResponseCodeToString(mActivityResultCode) + ")"));
+        }
+    }
+
+    void printMisconfiguredDebugInfo() {
+        debugLog("****");
+        debugLog("****");
+        debugLog("**** APP NOT CORRECTLY CONFIGURED TO USE GOOGLE PLAY GAME SERVICES");
+        debugLog("**** This is usually caused by one of these reasons:");
+        debugLog("**** (1) Your package name and certificate fingerprint do not match");
+        debugLog("****     the client ID you registered in Developer Console.");
+        debugLog("**** (2) Your App ID was incorrectly entered.");
+        debugLog("**** (3) Your game settings have not been published and you are ");
+        debugLog("****     trying to log in with an account that is not listed as");
+        debugLog("****     a test account.");
+        debugLog("****");
+        Context ctx = getContext();
+        if (ctx == null) {
+            debugLog("*** (no Context, so can't print more debug info)");
+            return;
+        }
+
+        debugLog("**** To help you debug, here is the information about this app");
+        debugLog("**** Package name         : " + getContext().getPackageName());
+        debugLog("**** Cert SHA1 fingerprint: " + getSHA1CertFingerprint());
+        debugLog("**** App ID from          : " + getAppIdFromResource());
+        debugLog("****");
+        debugLog("**** Check that the above information matches your setup in ");
+        debugLog("**** Developer Console. Also, check that you're logging in with the");
+        debugLog("**** right account (it should be listed in the Testers section if");
+        debugLog("**** your project is not yet published).");
+        debugLog("****");
+        debugLog("**** For more information, refer to the troubleshooting guide:");
+        debugLog("****   http://developers.google.com/games/services/android/troubleshooting");
+    }
+
+    String getAppIdFromResource() {
+        try {
+            Resources res = getContext().getResources();
+            String pkgName = getContext().getPackageName();
+            int res_id = res.getIdentifier("app_id", "string", pkgName);
+            return res.getString(res_id);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            return "??? (failed to retrieve APP ID)";
+        }
+    }
+
+    String getSHA1CertFingerprint() {
+        try {
+            Signature[] sigs = getContext().getPackageManager().getPackageInfo(
+                    getContext().getPackageName(), PackageManager.GET_SIGNATURES).signatures;
+            if (sigs.length == 0) {
+                return "ERROR: NO SIGNATURE.";
+            } else if (sigs.length > 1) {
+                return "ERROR: MULTIPLE SIGNATURES";
+            }
+            byte[] digest = MessageDigest.getInstance("SHA1").digest(sigs[0].toByteArray());
+            StringBuilder hexString = new StringBuilder();
+            for (int i = 0; i < digest.length; ++i) {
+                if (i > 0) {
+                    hexString.append(":");
+                }
+                byteToString(hexString, digest[i]);
+            }
+            return hexString.toString();
+
+        } catch (PackageManager.NameNotFoundException ex) {
+            ex.printStackTrace();
+            return "(ERROR: package not found)";
+        } catch (NoSuchAlgorithmException ex) {
+            ex.printStackTrace();
+            return "(ERROR: SHA1 algorithm not found)";
+        }
+    }
+
+    void byteToString(StringBuilder sb, byte b) {
+        int unsigned_byte = b < 0 ? b + 256 : b;
+        int hi = unsigned_byte / 16;
+        int lo = unsigned_byte % 16;
+        sb.append("0123456789ABCDEF".substring(hi, hi + 1));
+        sb.append("0123456789ABCDEF".substring(lo, lo + 1));
+    }
+
+}

+ 118 - 0
gameplay/android/java/src/org/gameplay3d/lib/GoogleGamesSocial.java

@@ -0,0 +1,118 @@
+    
+package org.gameplay3d.lib;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.android.gms.games.Player;
+import com.google.example.games.basegameutils.BaseGameActivity;
+
+public class GoogleGamesSocial extends BaseGameActivity {
+	
+	private static List<Player> mFriends = new ArrayList<Player>();
+    private static final int FRIENDS_PER_PAGE = 10;
+	
+	public static void gameServicesSignIn() 
+	{
+        ((appname)mContext).runOnUiThread(new Runnable() 
+		{
+            public void run() {
+                ((appname)mContext).beginUserInitiatedSignIn();
+            }
+        });
+    }
+
+    public static void updateTopScoreLeaderboard(int score) 
+	{
+		if (mContext.isSignedIn())
+			mContext.getGamesClient().submitScore("leaderboardid", score);
+    }
+
+	public static void submitAchievement(String achievementId)
+	{
+        if (mContext.isSignedIn())
+		{
+            mContext.getGamesClient().unlockAchievement(achievementId);
+        }
+    }
+	
+	public static void incrementAchievement(String achievementId, int percentage)
+	{
+        if (mContext.isSignedIn())
+		{
+            mContext.getGamesClient().incrementAchievement(achievementId, percentage);
+        }
+    }
+
+    public static void displayLeaderboards() 
+	{
+		if (mContext.isSignedIn())
+		{
+			((appname)mContext).runOnUiThread(new Runnable() 
+			{
+				public void run() {
+					((appname)mContext).startActivityForResult(((appname)mContext).getGamesClient().getLeaderboardIntent("leaderboardidfromgoogleplay"), 5001);
+				}
+			});
+		}
+		else
+		{
+			gameServicesSignIn();
+		}
+    }
+
+    public static void displayAchievements() 
+	{
+		if (mContext.isSignedIn())
+		{
+			((appname)mContext).runOnUiThread(new Runnable() 
+			{
+				public void run() {
+					((appname)mContext).startActivityForResult(((appname)mContext).getGamesClient().getAchievementsIntent(), 5001);
+				}
+			});
+		}
+		else
+		{
+			gameServicesSignIn();
+		}
+    }
+
+    public static void loadFriends() 
+	{
+        if (mFriends.size() > 0) {
+            mFriends.clear();
+        }
+
+        ((appname)mContext).runOnUiThread(new Runnable() 
+		{
+                public void run() 
+				{        
+            ((appname)mContext).getGamesClient().loadInvitablePlayers(new OnPlayersLoadedListener() {
+
+                @Override
+                public void onPlayersLoaded(int statusCode, PlayerBuffer playerBuffer) {
+
+                    if (statusCode == GamesClient.STATUS_OK) {
+                        for (Player player : playerBuffer) {
+                            mFriends.add(player);
+                        }
+
+                        if (playerBuffer.getCount() == FRIENDS_PER_PAGE) {
+                            ((appname)mContext).getGamesClient().loadMoreInvitablePlayers(this, FRIENDS_PER_PAGE);
+                        } else {
+                            // call out and return all the friends <more code to call into C++>
+
+                            for (Player friend : mFriends) {
+                                Log.i(TAG, String.format("Found player with id [%s] and display name [%s]", friend.getPlayerId(), friend.getDisplayName()));
+                            }
+                        }
+                    }
+                }
+            }, FRIENDS_PER_PAGE, false);
+        });
+    }
+}

+ 12 - 0
gameplay/android/java/src/org/gameplay3d/lib/TestClass.java

@@ -0,0 +1,12 @@
+
+package org.gameplay3d.lib;
+
+public class TestClass
+{
+   public TestClass() { }
+ 
+   public int ComputeMult(float a, float b)
+   {
+      return (int)(a * b);
+   }
+}

+ 8 - 1
gameplay/android/jni/Android.mk

@@ -91,6 +91,12 @@ LOCAL_SRC_FILES := \
     ScriptController.cpp \
     ScriptTarget.cpp \
     Slider.cpp \
+	SocialAchievement.cpp \
+	SocialChallenge.cpp \
+	SocialController.cpp \
+	SocialPlayer.cpp \
+	SocialScore.cpp \
+	SocialSessionListener.cpp \
     SpriteBatch.cpp \
     Technique.cpp \
     Terrain.cpp \
@@ -106,6 +112,7 @@ LOCAL_SRC_FILES := \
     VertexAttributeBinding.cpp \
     VertexFormat.cpp \
     VerticalLayout.cpp \
+	social/GoogleGamesSocialSession.cpp \
     lua/lua_AbsoluteLayout.cpp \
     lua/lua_AIAgent.cpp \
     lua/lua_AIAgentListener.cpp \
@@ -281,7 +288,7 @@ LOCAL_SRC_FILES := \
     lua/lua_VerticalLayout.cpp
 
     
-LOCAL_CFLAGS := -D__ANDROID__ -I"../../external-deps/lua/include" -I"../../external-deps/bullet/include" -I"../../external-deps/libpng/include" -I"../../external-deps/oggvorbis/include" -I"../../external-deps/openal/include"
+LOCAL_CFLAGS := -D__ANDROID__ -DGP_USE_SOCIAL -I"../../external-deps/lua/include" -I"../../external-deps/bullet/include" -I"../../external-deps/libpng/include" -I"../../external-deps/oggvorbis/include" -I"../../external-deps/openal/include"
 LOCAL_STATIC_LIBRARIES := android_native_app_glue
 
 include $(BUILD_STATIC_LIBRARY)

+ 3 - 0
gameplay/src/Base.h

@@ -293,14 +293,17 @@ typedef GLuint RenderBufferHandle;
     typedef void* SocialPlayerHandle;
     typedef void* SocialAchievementHandle;
     typedef void* SocialScoreHandle;
+    typedef void* SocialChallengeHandle;
 #elif defined(WIN32)
     typedef unsigned long SocialPlayerHandle;
     typedef unsigned long SocialAchievementHandle;
     typedef unsigned long SocialScoreHandle;
+    typedef unsigned long SocialChallengeHandle;
 #else
     typedef unsigned int SocialPlayerHandle;
     typedef unsigned int SocialAchievementHandle;
     typedef unsigned int SocialScoreHandle;
+    typedef unsigned int SocialChallengeHandle;
 #endif
 
 }

+ 13 - 1
gameplay/src/Game.cpp

@@ -24,7 +24,7 @@ Game::Game()
       _clearDepth(1.0f), _clearStencil(0), _properties(NULL),
       _animationController(NULL), _audioController(NULL),
       _physicsController(NULL), _aiController(NULL), _audioListener(NULL),
-      _timeEvents(NULL), _scriptController(NULL), _scriptListeners(NULL)
+      _timeEvents(NULL), _scriptController(NULL), _socialController(NULL), _scriptListeners(NULL)
 {
     GP_ASSERT(__gameInstance == NULL);
     __gameInstance = this;
@@ -114,6 +114,9 @@ bool Game::startup()
     _scriptController = new ScriptController();
     _scriptController->initialize();
 
+    _socialController = new SocialController();
+    _socialController->initialize();
+
     // Load any gamepads, ui or physical.
     loadGamepads();
 
@@ -194,6 +197,9 @@ void Game::shutdown()
         _aiController->finalize();
         SAFE_DELETE(_aiController);
 
+        _socialController->initialize();
+        SAFE_DELETE(_socialController);
+
         // Note: we do not clean up the script controller here
         // because users can call Game::exit() from a script.
 
@@ -222,6 +228,7 @@ void Game::pause()
         _audioController->pause();
         _physicsController->pause();
         _aiController->pause();
+        _socialController->pause();
     }
 
     ++_pausedCount;
@@ -245,6 +252,7 @@ void Game::resume()
             _audioController->resume();
             _physicsController->resume();
             _aiController->resume();
+            _socialController->resume();
         }
     }
 }
@@ -329,6 +337,9 @@ void Game::frame()
         // Audio Rendering.
         _audioController->update(elapsedTime);
 
+        // Social Update.
+        _socialController->update(elapsedTime);
+
         // Graphics Rendering.
         render(elapsedTime);
 
@@ -391,6 +402,7 @@ void Game::updateOnce()
     _aiController->update(elapsedTime);
     _audioController->update(elapsedTime);
     _scriptController->update(elapsedTime);
+    _socialController->update(elapsedTime);
 }
 
 void Game::setViewport(const Rectangle& viewport)

+ 10 - 0
gameplay/src/Game.h

@@ -9,6 +9,7 @@
 #include "AudioController.h"
 #include "AnimationController.h"
 #include "PhysicsController.h"
+#include "SocialController.h"
 #include "AIController.h"
 #include "AudioListener.h"
 #include "Rectangle.h"
@@ -258,6 +259,14 @@ public:
      */
     inline ScriptController* getScriptController() const;
 
+    /**
+     * Gets the social controller for managing control of social apis
+     * associated with the game.
+     *
+     * @return The script controller for this game.
+     */
+    inline SocialController* getSocialController() const;
+
     /**
      * Gets the audio listener for 3D audio.
      * 
@@ -735,6 +744,7 @@ private:
     AudioListener* _audioListener;              // The audio listener in 3D space.
     std::priority_queue<TimeEvent, std::vector<TimeEvent>, std::less<TimeEvent> >* _timeEvents;     // Contains the scheduled time events.
     ScriptController* _scriptController;            // Controls the scripting engine.
+    SocialController* _socialController;		// Controls social aspect of the game.
     std::vector<ScriptListener*>* _scriptListeners; // Lua script listeners.
 
     // Note: Do not add STL object member variables on the stack; this will cause false memory leaks to be reported.

+ 5 - 0
gameplay/src/Game.inl

@@ -63,6 +63,11 @@ inline AIController* Game::getAIController() const
     return _aiController;
 }
 
+inline SocialController* Game::getSocialController() const
+{
+    return _socialController;
+}
+
 template <class T>
 void Game::renderOnce(T* instance, void (T::*method)(void*), void* cookie)
 {

+ 13 - 0
gameplay/src/PlatformBlackBerry.cpp

@@ -3,6 +3,7 @@
 #include "Base.h"
 #include "Platform.h"
 #include "FileSystem.h"
+#include "SocialController.h"
 #include "Game.h"
 #include "Form.h"
 #include "ScriptController.h"
@@ -792,6 +793,14 @@ Platform* Platform::create(Game* game, void* attachToWindow)
         goto error;
     }
 
+    // Window group
+	rc = screen_create_window_group(__screenWindow, "windowgroup");
+	if (rc)
+	{
+		perror("screen_create_window_group failed");
+		goto error;
+	}
+
     // Set/get any window properties.
     rc = screen_set_window_property_iv(__screenWindow, SCREEN_PROPERTY_FORMAT, &screenFormat);
     if (rc)
@@ -1086,6 +1095,10 @@ int Platform::enterMessagePump()
             if (event == NULL)
                 break;
 
+            // if the social controller needs to deal with the event do that here
+            if (Game::getInstance()->getSocialController()->handleEvent(event))
+            	break;
+
             domain = bps_event_get_domain(event);
 
             if (domain == screen_get_domain())

+ 6 - 1
gameplay/src/SocialAchievement.h

@@ -13,10 +13,15 @@ class SocialAchievement
 public:
 
     /**
-     * The name/title of the achievement.
+     * The name of the achievement.
      */
     std::string name;
 
+    /**
+     * The title of the achievement.
+     */
+    std::string title;
+
     /**
      * The value of progress (0 to getTotal) representing the current unit value of completion.
      */

+ 51 - 5
gameplay/src/SocialController.cpp

@@ -2,6 +2,7 @@
 #include "SocialController.h"
 #include "Game.h"
 #include "social/ScoreloopSocialSession.h"
+#include "social/GoogleGamesSocialSession.h"
 
 namespace gameplay
 {
@@ -20,6 +21,18 @@ void SocialController::initialize()
 }
 
 void SocialController::finalize()
+{
+	if (_session)
+		_session->synchronizeAchievements();
+}
+
+void SocialController::pause()
+{
+	if (_session)
+		_session->synchronizeAchievements();
+}
+
+void SocialController::resume()
 {
 }
 
@@ -27,20 +40,53 @@ void SocialController::update(float elapsedTime)
 {
 }
 
+bool SocialController::handleEvent(void *event)
+{
+	if (_session)
+		return _session->handleEvent(event);
+
+	return false;
+}
+
 void SocialController::authenticate(SocialSessionListener* listener)
 {
-#if defined(__QNX__) && defined(GP_USE_SOCIAL)
-    Properties* socialProperties = Game::getInstance()->getConfig()->getNamespace("social");
-    const char* providerStr = socialProperties->getString("provider");
-    if(strcmp(providerStr, "Scoreloop") == 0)
+#ifdef GP_USE_SOCIAL
+#if defined(__QNX__)
+    Properties* socialProperties = Game::getInstance()->getConfig()->getNamespace("social", true);
+    const char* providerStr = "";
+
+    if (socialProperties)
     {
-        ScoreloopSocialSession::authenticate(listener, socialProperties);
+    	providerStr = socialProperties->getString("provider");
+    }
+
+    if (strcmp(providerStr, "Scoreloop") == 0)
+    {
+        _session = ScoreloopSocialSession::authenticate(listener, socialProperties);
+    }
+    else
+    {
+        listener->authenticateEvent(SocialSessionListener::ERROR_INITIALIZATION, NULL);
+    }
+#elif defined(__ANDROID__)
+    Properties* socialProperties = Game::getInstance()->getConfig()->getNamespace("social", true);
+    const char* providerStr = "";
+
+    if (socialProperties)
+    {
+    	providerStr = socialProperties->getString("provider");
+    }
+
+    if (strcmp(providerStr, "GoogleGames") == 0)
+    {
+        _session = GoogleGamesSocialSession::authenticate(listener, socialProperties);
     }
     else
     {
         listener->authenticateEvent(SocialSessionListener::ERROR_INITIALIZATION, NULL);
     }
 #endif
+#endif
 }
 
 }

+ 18 - 3
gameplay/src/SocialController.h

@@ -21,9 +21,9 @@ namespace gameplay
          provider = Scoreloop
          id  = d346c484-12aa-49a2-a0a0-de2f87492d72
          secret = aAa+DehBfyGO/CYaE3nWomgu7SIbWFczUih+Qwf3/n7u0y3nyq5Hag==
-         version = "1.0"
-         language = "en"
-         currency = "ASC"
+         version = 1.0
+         language = en
+         currency = ASC
          leaderboard_mappings
          {
              // Format: leaderboardId = provider value
@@ -47,6 +47,11 @@ public:
      */
     void authenticate(SocialSessionListener* listener);
 
+    /**
+     * Handle the event from the event loop if needed.
+     */
+    bool handleEvent(void *event);
+
 private:
 
     /**
@@ -69,6 +74,16 @@ private:
      */
     void finalize();
 
+    /**
+     * Pauses the controller.
+     */
+    void pause();
+
+    /**
+     * Resumes the controller.
+     */
+    void resume();
+
     /**
      * Callback for when the controller receives a frame update event.
      */

+ 24 - 3
gameplay/src/SocialSession.h

@@ -5,6 +5,7 @@
 #include "SocialPlayer.h"
 #include "SocialAchievement.h"
 #include "SocialScore.h"
+#include "SocialChallenge.h"
 #include "Properties.h"
 
 namespace gameplay
@@ -22,7 +23,7 @@ public:
     enum CommunityScope
     {
         COMMUNITY_SCOPE_FRIENDS,
-        COMMUNITY_SCOPE_ALL,
+        COMMUNITY_SCOPE_ALL
     };
 
     enum TimeScope
@@ -39,13 +40,17 @@ public:
      */
     virtual SocialSessionListener* getListener() = 0;
 
-    virtual SocialPlayer* getUser() = 0;
+    virtual const SocialPlayer& getUser() const = 0;
 
     virtual void loadFriends() = 0;
 
     virtual void loadAchievements() = 0;
 
-    virtual void submitAchievement(const char* achievementId, unsigned int value) = 0;
+    virtual void submitAchievement(const char* achievementId, unsigned int value, bool achieved=false) = 0;
+
+    virtual void incrementAchievement(const char* achievementId, unsigned int increment=1) = 0;
+
+    virtual void synchronizeAchievements() = 0;
 
     /**
      * Asynchronously request the scores for the count where the player is in the middle.
@@ -67,10 +72,26 @@ public:
 
     virtual void submitScore(const char* leaderboardId, float value) = 0;
 
+    virtual void submitChallenge(const SocialPlayer *player, unsigned int wager, float score, const char* leaderboardId=0) = 0;
+
+    virtual void loadChallenges(bool showOpenChallengesOnly=true) = 0;
+
+    virtual void replyToChallenge(const SocialChallenge *challenge, bool accept) = 0;
+
     virtual void loadSavedData(const char* key) = 0;
 
     virtual void submitSavedData(const char* key, std::string data) = 0;
 
+    virtual void displayLeaderboard(const char* leaderboardId) = 0;
+
+    virtual void displayAchievements() = 0;
+
+    virtual void displayChallenges() = 0;
+
+    virtual void displayChallengeSubmit(const SocialChallenge *challenge, float score) = 0;
+
+    virtual bool handleEvent(void *event) { return true; }
+
 protected:
 
     /**

+ 40 - 0
gameplay/src/SocialSessionListener.cpp

@@ -27,10 +27,18 @@ void SocialSessionListener::loadAchievementsEvent(ResponseCode code, std::vector
 {
 }
 
+void SocialSessionListener::synchronizeAchievementEvent(ResponseCode code)
+{
+}
+
 void SocialSessionListener::submitAchievementEvent(ResponseCode code)
 {
 }
 
+void SocialSessionListener::awardAchievedEvent(ResponseCode code, const SocialAchievement &achievement)
+{
+}
+
 void SocialSessionListener::loadScoresEvent(ResponseCode code, std::vector<SocialScore> scores)
 {
 }
@@ -39,6 +47,22 @@ void SocialSessionListener::submitScoreEvent(ResponseCode code)
 {
 }
 
+void SocialSessionListener::submitChallengeEvent(ResponseCode code, const SocialChallenge &challenge)
+{
+}
+
+void SocialSessionListener::startChallengeEvent(ResponseCode code, const SocialChallenge &challenge)
+{
+}
+
+void SocialSessionListener::replyToChallengeEvent(ResponseCode code)
+{
+}
+
+void SocialSessionListener::loadChallengesEvent(ResponseCode code, std::vector<SocialChallenge> challenges)
+{
+}
+
 void SocialSessionListener::loadSavedDataEvent(ResponseCode code, std::string data)
 {
 }
@@ -47,6 +71,22 @@ void SocialSessionListener::submitSavedDataEvent(ResponseCode code)
 {
 }
 
+void SocialSessionListener::uiEvent(ResponseCode code, std::string errorMessage)
+{
+}
+
+void SocialSessionListener::displayedLeaderboardEvent(ResponseCode code, std::string errorMessage)
+{
+}
+
+void SocialSessionListener::displayedAchievementsEvent(ResponseCode code, std::string errorMessage)
+{
+}
+
+void SocialSessionListener::displayedChallengesEvent(ResponseCode code, std::string errorMessage)
+{
+}
+
 }
 
 

+ 23 - 0
gameplay/src/SocialSessionListener.h

@@ -4,6 +4,7 @@
 #include "SocialPlayer.h"
 #include "SocialAchievement.h"
 #include "SocialScore.h"
+#include "SocialChallenge.h"
 
 namespace gameplay
 {
@@ -43,6 +44,8 @@ public:
         ERROR_INVALID_USER,
         ERROR_INVALID_ARG,
         ERROR_SERVER,
+        ERROR_CANCELLED,
+        ERROR_NOT_SUPPORTED,
         ERROR_UNKNOWN
     };
 
@@ -52,16 +55,36 @@ public:
 
     virtual void loadAchievementsEvent(ResponseCode code, std::vector<SocialAchievement> achievements);
 
+    virtual void synchronizeAchievementEvent(ResponseCode code);
+
     virtual void submitAchievementEvent(ResponseCode code);
 
+    virtual void awardAchievedEvent(ResponseCode code, const SocialAchievement &achievement);
+
     virtual void loadScoresEvent(ResponseCode code, std::vector<SocialScore> scores);
 
     virtual void submitScoreEvent(ResponseCode code);
 
+    virtual void submitChallengeEvent(ResponseCode code, const SocialChallenge &challenge);
+
+    virtual void startChallengeEvent(ResponseCode code, const SocialChallenge &challenge);
+
+    virtual void replyToChallengeEvent(ResponseCode code);
+
+    virtual void loadChallengesEvent(ResponseCode code, std::vector<SocialChallenge> challenges);
+
     virtual void loadSavedDataEvent(ResponseCode code, std::string data);
 
     virtual void submitSavedDataEvent(ResponseCode code);
 
+    virtual void uiEvent(ResponseCode code, std::string errorMessage="");
+
+    virtual void displayedLeaderboardEvent(ResponseCode code, std::string errorMessage="");
+
+    virtual void displayedAchievementsEvent(ResponseCode code, std::string errorMessage="");
+
+    virtual void displayedChallengesEvent(ResponseCode code, std::string errorMessage="");
+
 };
 
 }

+ 909 - 0
gameplay/src/social/GoogleGamesSocialSession.cpp

@@ -0,0 +1,909 @@
+#if defined(__ANDROID__) && defined(GP_USE_SOCIAL)
+
+#include "Base.h"
+#include "GoogleGamesSocialSession.h"
+#include <android_native_app_glue.h>
+#include <android/log.h>
+
+namespace gameplay
+{
+
+extern struct android_app* __state;
+
+GoogleGamesSocialSession* GoogleGamesSocialSession::_session = NULL;
+
+GoogleGamesSocialSession::GoogleGamesSocialSession()
+    : SocialSession(),
+      _listener(NULL), _properties(NULL), _pendingUserResponse(false), _pendingFriendsResponse(false),
+      _pendingScoresResponse(false), _pendingSubmitScoreResponse(false), _pendingAchievementResponse(false),
+      _pendingDataResponse(false),
+      _key(NULL)
+{
+    _userOp = USEROP_GET_LOCALUSER;
+}
+
+GoogleGamesSocialSession::~GoogleGamesSocialSession()
+{
+}
+
+SocialSessionListener* GoogleGamesSocialSession::getListener()
+{
+    return _listener;
+}
+
+SocialSession *GoogleGamesSocialSession::authenticate(SocialSessionListener* listener, Properties* properties)
+{
+    if (!_session)
+    {
+		_session = new GoogleGamesSocialSession();
+		_session->_listener = listener;
+		_session->_properties = properties;
+
+		const char* gameId = properties->getString("id");
+		const char* gameSecret = properties->getString("secret");
+		const char* gameVersion = properties->getString("version");
+		const char* gameCurrency = properties->getString("currency");
+		const char* gameLanguage = properties->getString("language");
+#ifdef __ANDROID__
+	    android_app* state = __state;
+	    GP_ASSERT(state && state->activity && state->activity->vm);
+	    JavaVM* jvm = state->activity->vm;
+	    JNIEnv* env = NULL;
+	    jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
+	    jint r = jvm->AttachCurrentThread(&env, NULL);
+	    if (r == JNI_ERR)
+	    {
+	        GP_ERROR("Failed to retrieve JVM environment to authenticate.");
+	        return 0;
+	    }
+	    GP_ASSERT(env);
+
+		jclass testClassHandle = env->FindClass("org/gameplay3d/lib/TestClass");
+		jmethodID computeMultHandle = env->GetMethodID(testClassHandle, "ComputeMult", "(FF)I");
+		jmethodID testClassCtor = env->GetMethodID(testClassHandle, "<init>", "()V");
+		jobject testClassInstance = env->NewObject(testClassHandle, testClassCtor);
+
+		jfloat a = 4.0f;
+		jfloat b = 0.5f;
+		jint result = env->CallIntMethod(testClassInstance, computeMultHandle, a, b);
+		fprintf(stderr, "the results is %d\n", result);
+
+		jvm->DetachCurrentThread();
+#endif
+    }
+
+    return _session;
+}
+
+const SocialPlayer& GoogleGamesSocialSession::getUser() const
+{
+	return _user;
+}
+
+/**
+ * @see SocialSession::loadFriends
+ */
+void loadFriends()
+{
+
+}
+
+/**
+ * @see SocialSession::loadAchievements
+ */
+void loadAchievements()
+{
+
+}
+
+/**
+ * @see SocialSession::submitAchievement
+ */
+void submitAchievement(const char* achievementId, unsigned int value, bool achieved=false)
+{
+
+}
+
+/**
+ * @see SocialSession::incrementAchievement
+ */
+void incrementAchievement(const char* achievementId, unsigned int increment=1)
+{
+
+}
+
+/**
+  * @see SocialSession::syncAchievements
+  */
+void synchronizeAchievements()
+{
+
+}
+
+/**
+ * @see SocialSession::loadScores
+ */
+void loadScores(const char* leaderboardId, SocialSession::CommunityScope community, SocialSession::TimeScope time, const SocialPlayer& player, unsigned int count)
+{
+
+}
+
+/**
+ * @see SocialSession::loadScores
+ */
+void loadScores(const char* leaderboardId, SocialSession::CommunityScope community, SocialSession::TimeScope time, unsigned int start, unsigned int count)
+{
+
+}
+
+/**
+ * @see SocialSession::submitScore
+ */
+void submitScore(const char* leaderboardId, float score)
+{
+
+}
+
+/**
+  * @see SocialSession::submitChallenge
+  */
+void submitChallenge(const SocialPlayer *player, unsigned int wager, float score, const char* leaderboardId=0)
+{
+
+}
+
+/**
+  * @see SocialSession::loadChallenges
+  */
+void loadChallenges(bool showOpenChallengesOnly=true)
+{
+
+}
+
+/**
+  * @see SocialSession::acceptChallenge
+  */
+void replyToChallenge(const SocialChallenge *challenge, bool accept)
+{
+
+}
+
+/**
+ * @see SocialSession::requestSavedData
+ */
+void loadSavedData(const char* key) {}
+
+/**
+ * @see SocialSession::submitSavedData
+ */
+void submitSavedData(const char* key, std::string data) {}
+
+void displayLeaderboard(const char* leaderboardId) {}
+
+void displayAchievements() {}
+
+void displayChallenges() {}
+
+void displayChallengeSubmit(const SocialChallenge *challenge, float score) {}
+
+#if 0
+
+void GoogleGamesSocialSession::loadFriends()
+{
+    if (_pendingFriendsResponse)
+    {
+        _listener->loadFriendsEvent(SocialSessionListener::ERROR_PENDING_RESPONSE, _friends);
+        return;
+    }
+    _pendingFriendsResponse = true;
+
+    // Create a scoreloop users controller (for finding friends)
+    SC_Error_t rc;
+    if (_usersController == NULL)
+    {
+        rc = SC_Client_CreateUsersController(_client, &_usersController, GoogleGamesSocialSession::loadFriendsCallback, this);
+        if (rc != SC_OK)
+        {
+            _listener->loadFriendsEvent(SocialSessionListener::ERROR_INITIALIZATION, _friends);
+            return;
+        }
+    }
+    rc = SC_UsersController_LoadBuddies(_usersController, (SC_User_h)_user.handle);
+    if (rc != SC_OK)
+    {
+    	SC_UsersController_Release(_usersController);
+    	_usersController = NULL;
+    	_listener->loadFriendsEvent(SocialSessionListener::ERROR_INITIALIZATION, _friends);
+    }
+}
+
+void GoogleGamesSocialSession::loadFriendsCallback(void* cookie, SC_Error_t result)
+{
+    GoogleGamesSocialSession* session = (GoogleGamesSocialSession*)cookie;
+
+    switch (result)
+    {
+		case SC_OK:
+			{
+				SC_UserList_h usersList = SC_UsersController_GetUsers(session->_usersController);
+				unsigned int usersCount = SC_UserList_GetCount(usersList);
+				session->_friends.clear();
+				for (unsigned int i = 0; i < usersCount; i++)
+				{
+					SocialPlayer player;
+					player.handle = SC_UserList_GetAt(usersList, i);
+					SC_UserController_GetUser(session->_userController);
+					SC_String_h name = SC_User_GetLogin((SC_User_h)player.handle);
+					player.name = SC_String_GetData(name);
+					session->_friends.push_back(player);
+				}
+				session->getListener()->loadFriendsEvent(SocialSessionListener::SUCCESS, session->_friends);
+			}
+			break;
+		case SC_INVALID_USER_DATA:
+			session->getListener()->loadFriendsEvent(SocialSessionListener::ERROR_INVALID_USER, session->_friends);
+			break;
+		case SC_HTTP_SERVER_ERROR:
+			session->getListener()->loadFriendsEvent(SocialSessionListener::ERROR_SERVER, session->_friends);
+			break;
+		default:
+			session->getListener()->loadFriendsEvent(SocialSessionListener::ERROR_UNKNOWN, session->_friends);
+			break;
+    }
+
+    session->_pendingFriendsResponse = false;
+}
+
+void GoogleGamesSocialSession::loadAchievements()
+{
+    if (_pendingAchievementResponse)
+    {
+        _listener->loadAchievementsEvent(SocialSessionListener::ERROR_PENDING_RESPONSE, _achievements);
+        return;
+    }
+    _pendingAchievementResponse = true;
+
+    SC_Error_t rc;
+
+    if (_localAchievementsController == NULL)
+    {
+		rc = SC_Client_CreateLocalAchievementsController(_client, &_localAchievementsController, GoogleGamesSocialSession::loadAchievementsCallback, this);
+		if (rc != SC_OK)
+		{
+			_listener->loadAchievementsEvent(SocialSessionListener::ERROR_INITIALIZATION, _achievements);
+			_pendingAchievementResponse = false;
+			return;
+		}
+    }
+
+    if (SC_LocalAchievementsController_ShouldSynchronize(_localAchievementsController))
+    {
+        rc = SC_LocalAchievementsController_Synchronize(_localAchievementsController);
+        if (rc != SC_OK)
+        {
+            _listener->loadAchievementsEvent(SocialSessionListener::ERROR_INITIALIZATION, _achievements);
+            _pendingAchievementResponse = false;
+            return;
+        }
+    }
+    else
+    	GoogleGamesSocialSession::loadAchievementsCallback(this, SC_OK);
+}
+
+void GoogleGamesSocialSession::loadAchievementsCallback(void* cookie, SC_Error_t result)
+{
+    GoogleGamesSocialSession* session = (GoogleGamesSocialSession*)cookie;
+
+    switch (result)
+    {
+    	case SC_OK:
+			{
+				SC_AchievementList_h achievementsList = SC_LocalAchievementsController_GetAchievements(session->_localAchievementsController);
+				unsigned int achievementsCount = SC_AchievementList_GetCount(achievementsList);
+				session->_achievements.clear();
+				for (unsigned int i = 0; i < achievementsCount; i++)
+				{
+					SocialAchievement achievement;
+					achievement.handle = SC_AchievementList_GetAt(achievementsList, i);
+					SC_Award_h award = SC_Achievement_GetAward((SC_Achievement_h)achievement.handle);
+
+					SC_String_h id = SC_Award_GetIdentifier(award);
+					achievement.name = (id) ? SC_String_GetData(id) : "";
+					SC_String_h title = SC_Award_GetLocalizedTitle(award);
+					achievement.title = (title) ? SC_String_GetData(title) : "";
+					achievement.value = (unsigned int)SC_Achievement_GetValue((SC_Achievement_h)achievement.handle);
+					SC_String_h dateTime = SC_Achievement_GetDate((SC_Achievement_h)achievement.handle);
+					achievement.dateTimeAchieved = (dateTime) ? SC_String_GetData(dateTime) : "";
+					achievement.total = SC_Award_GetAchievingValue(award);
+					achievement.percentCompleted = achievement.value == 0 ? 0.0f : ((float)achievement.value / (float)achievement.total) * 100.f;
+#if 0
+					fprintf(stderr, "achievement %s\n", achievement.name.data());
+					fprintf(stderr, "isachieved %d\n", SC_Achievement_IsAchieved((SC_Achievement_h)achievement.handle));
+						fprintf(stderr, "value %d\n", SC_Achievement_GetValue((SC_Achievement_h)achievement.handle));
+						fprintf(stderr, "title %s\n", SC_String_GetData(SC_Award_GetLocalizedTitle(award)));
+						fprintf(stderr, "desc %s\n", SC_String_GetData(SC_Award_GetLocalizedDescription(award)));
+						fprintf(stderr, "money %d\n", SC_Money_GetAmount(SC_Award_GetRewardedMoney(award)));
+						fprintf(stderr, "image name %s\n", SC_String_GetData(SC_Achievement_GetImageName((SC_Achievement_h)achievement.handle)));
+#endif
+					session->_achievements.push_back(achievement);
+				}
+				session->getListener()->loadAchievementsEvent(SocialSessionListener::SUCCESS, _session->_achievements);
+			}
+
+			break;
+		case SC_INVALID_USER_DATA:
+			session->getListener()->loadAchievementsEvent(SocialSessionListener::ERROR_INVALID_USER, _session->_achievements);
+			break;
+		case SC_HTTP_SERVER_ERROR:
+			session->getListener()->loadAchievementsEvent(SocialSessionListener::ERROR_SERVER, _session->_achievements);
+			break;
+		default:
+			session->getListener()->loadAchievementsEvent(SocialSessionListener::ERROR_UNKNOWN, _session->_achievements);
+			break;
+    }
+
+    session->_pendingAchievementResponse = false;
+}
+
+const SocialAchievement* GoogleGamesSocialSession::getAchievement(const char* achievementId) const
+{
+	uint size = _achievements.size();
+
+	for (uint i = 0; i < size; i++)
+	{
+		if (strcmp(_achievements[i].name.data(), achievementId) == 0)
+			return &_achievements[i];
+	}
+	return 0;
+}
+
+void GoogleGamesSocialSession::submitAchievement(const char* achievementId, unsigned int value, bool isAchieved)
+{
+    SC_Error_t rc;
+    if (_localAchievementsController == NULL)
+    {
+		rc = SC_Client_CreateLocalAchievementsController(_client, &_localAchievementsController, GoogleGamesSocialSession::submitAchievementCallback, this);
+		if (rc != SC_OK)
+		{
+			_listener->submitAchievementEvent(SocialSessionListener::ERROR_INITIALIZATION);
+			return;
+		}
+    }
+
+    SC_Bool_t achieved = SC_FALSE;
+    if (isAchieved)
+    {
+    	rc = SC_LocalAchievementsController_SetAchievedValueForAwardIdentifier(_localAchievementsController, achievementId, &achieved);
+    }
+    else
+    {
+		rc = SC_LocalAchievementsController_SetValueForAwardIdentifier(_localAchievementsController, achievementId, value, &achieved);
+    }
+
+    if (rc != SC_OK)
+    {
+        _listener->submitAchievementEvent(SocialSessionListener::ERROR_INITIALIZATION);
+        return;
+    }
+
+    if (achieved == SC_TRUE)
+    {
+    	const SocialAchievement *achievement = getAchievement(achievementId);
+    	if (achievement)
+    		_listener->awardAchievedEvent(SocialSessionListener::SUCCESS, *achievement);
+
+    	fprintf(stderr, "AWARD ACHIEVED %s!!!\n", achievementId);
+    }
+}
+
+void GoogleGamesSocialSession::incrementAchievement(const char* achievementId, unsigned int increment)
+{
+    SC_Error_t rc;
+    if (_localAchievementsController == NULL)
+    {
+		rc = SC_Client_CreateLocalAchievementsController(_client, &_localAchievementsController, GoogleGamesSocialSession::submitAchievementCallback, this);
+		if (rc != SC_OK)
+		{
+			_listener->submitAchievementEvent(SocialSessionListener::ERROR_INITIALIZATION);
+			return;
+		}
+    }
+
+    SC_Bool_t achieved;
+	rc = SC_LocalAchievementsController_IncrementValueForAwardIdentifier(_localAchievementsController, achievementId, &achieved);
+
+    if (achieved == SC_TRUE)
+    {
+    	const SocialAchievement *achievement = getAchievement(achievementId);
+    	if (achievement)
+    		_listener->awardAchievedEvent(SocialSessionListener::SUCCESS, *achievement);
+    }
+
+    if (rc != SC_OK)
+    {
+        _listener->submitAchievementEvent(SocialSessionListener::ERROR_INITIALIZATION);
+        return;
+    }
+}
+
+void GoogleGamesSocialSession::synchronizeAchievements()
+{
+    SC_Error_t rc;
+    if (_localAchievementsController == NULL)
+    {
+		rc = SC_Client_CreateLocalAchievementsController(_client, &_localAchievementsController, GoogleGamesSocialSession::submitAchievementCallback, this);
+		if (rc != SC_OK)
+		{
+			_listener->submitAchievementEvent(SocialSessionListener::ERROR_INITIALIZATION);
+			_pendingAchievementResponse = false;
+			return;
+		}
+    }
+
+	if (SC_LocalAchievementsController_ShouldSynchronize(_localAchievementsController) == SC_TRUE)
+	{
+		_pendingAchievementResponse = true;
+
+fprintf(stderr, "we are attempting to synchronize our achievements\n");
+		rc = SC_LocalAchievementsController_Synchronize(_localAchievementsController);
+		if (rc != SC_OK)
+		{
+			_listener->submitAchievementEvent(SocialSessionListener::ERROR_INITIALIZATION);
+			_pendingAchievementResponse = false;
+			return;
+		}
+	}
+}
+
+void GoogleGamesSocialSession::submitAchievementCallback(void* cookie, SC_Error_t result)
+{
+    GoogleGamesSocialSession* session = (GoogleGamesSocialSession*)cookie;
+
+    fprintf(stderr, "submitAchievementCallback is called here\n");
+
+    switch (result)
+    {
+		case SC_OK:
+			session->getListener()->submitAchievementEvent(SocialSessionListener::SUCCESS);
+			break;
+		case SC_INVALID_USER_DATA:
+			session->getListener()->submitAchievementEvent(SocialSessionListener::ERROR_INVALID_USER);
+			break;
+		case SC_HTTP_SERVER_ERROR:
+			session->getListener()->submitAchievementEvent(SocialSessionListener::ERROR_SERVER);
+			break;
+		default:
+			session->getListener()->submitAchievementEvent(SocialSessionListener::ERROR_UNKNOWN);
+			break;
+    }
+
+    session->_pendingAchievementResponse = false;
+}
+
+void GoogleGamesSocialSession::loadScores(const char* leaderboardId, CommunityScope community, TimeScope time, const SocialPlayer& player, unsigned int count)
+{
+    if (_pendingScoresResponse)
+     {
+         _listener->loadScoresEvent(SocialSessionListener::ERROR_PENDING_RESPONSE, _scores);
+         return;
+     }
+    _pendingScoresResponse = true;
+
+    SC_Error_t rc;
+    if (_scoresController == NULL)
+    {
+        rc = SC_Client_CreateScoresController(_client, &_scoresController, GoogleGamesSocialSession::loadScoresCallback, this);
+        if (rc != SC_OK)
+        {
+            _listener->loadScoresEvent(SocialSessionListener::ERROR_INITIALIZATION, _scores);
+            _pendingScoresResponse = false;
+            return;
+        }
+    }
+
+    // Lookup the mode to be mapped for the specified leaderboard id
+    if (leaderboardId != NULL)
+    {
+        Properties* leaderboardMappings = _properties->getNamespace("leaderboard_mappings", false);
+        if (leaderboardMappings)
+        {
+            int mode = leaderboardMappings->getInt(leaderboardId);
+            if (mode >=  0)
+            {
+                SC_ScoresController_SetMode(_scoresController, (unsigned int)mode);
+            }
+        }
+    }
+    // Set the search list filter
+    SC_ScoresSearchList_t searchList;
+    searchList.countrySelector = SC_COUNTRY_SELECTOR_ALL;
+    searchList.buddyhoodUser = (SC_User_h)_user.handle;
+
+    switch (community)
+    {
+		case COMMUNITY_SCOPE_FRIENDS:
+			searchList.usersSelector = SC_USERS_SELECTOR_BUDDYHOOD;
+			break;
+		case COMMUNITY_SCOPE_ALL:
+		default:
+			searchList.usersSelector = SC_USERS_SELECTOR_ALL;
+			break;
+    }
+
+    switch(time)
+    {
+		case TIME_SCOPE_TODAY:
+			searchList.timeInterval = SC_TIME_INTERVAL_24H;
+			break;
+		case TIME_SCOPE_WEEK:
+            searchList.timeInterval = SC_TIME_INTERVAL_7DAYS;
+            break;
+		case TIME_SCOPE_ALL:
+		default:
+            searchList.timeInterval = SC_TIME_INTERVAL_ALL;
+            break;
+    }
+    SC_ScoresController_SetSearchList(_scoresController, searchList);
+
+    // Specify the player and range count to get the scores around
+    SC_User_h user = (SC_User_h)player.handle;
+    rc = SC_ScoresController_LoadScoresAroundUser(_scoresController, (SC_User_h)_user.handle, count);
+    if (rc != SC_OK)
+    {
+        SC_ScoresController_Release(_scoresController);
+        _scoresController = NULL;
+        _listener->loadScoresEvent(SocialSessionListener::ERROR_INITIALIZATION, _scores);
+    }
+}
+
+void GoogleGamesSocialSession::loadScores(const char* leaderboardId, CommunityScope community, TimeScope time, unsigned int start, unsigned int count)
+{
+    if (_pendingScoresResponse)
+    {
+        _listener->loadScoresEvent(SocialSessionListener::ERROR_PENDING_RESPONSE, _scores);
+        return;
+    }
+    _pendingScoresResponse = true;
+
+    SC_Error_t rc;
+    if (_scoresController == NULL)
+    {
+        rc = SC_Client_CreateScoresController(_client, &_scoresController, GoogleGamesSocialSession::loadScoresCallback, this);
+        if (rc != SC_OK)
+        {
+            _listener->loadScoresEvent(SocialSessionListener::ERROR_INITIALIZATION, _scores);
+            _pendingScoresResponse = false;
+            return;
+        }
+    }
+
+    // Lookup the mode to be mapped for the specified leaderboard id
+    if (leaderboardId != NULL)
+    {
+        Properties* leaderboardMappings = _properties->getNamespace("leaderboard_mappings", false);
+        if (leaderboardMappings)
+        {
+            int mode = leaderboardMappings->getInt(leaderboardId);
+            if (mode >=  0)
+            {
+                SC_ScoresController_SetMode(_scoresController, (unsigned int)mode);
+            }
+        }
+    }
+
+    // Set the search list filter
+    SC_ScoresSearchList_t searchList;
+    searchList.countrySelector = SC_COUNTRY_SELECTOR_ALL;
+    searchList.buddyhoodUser = (SC_User_h)_user.handle;
+
+    switch (community)
+    {
+		case COMMUNITY_SCOPE_FRIENDS:
+			searchList.usersSelector = SC_USERS_SELECTOR_BUDDYHOOD;
+			break;
+		case COMMUNITY_SCOPE_ALL:
+		default:
+			searchList.usersSelector = SC_USERS_SELECTOR_ALL;
+			break;
+    }
+
+    switch (time)
+    {
+		case TIME_SCOPE_TODAY:
+			searchList.timeInterval = SC_TIME_INTERVAL_24H;
+			break;
+		case TIME_SCOPE_WEEK:
+			searchList.timeInterval = SC_TIME_INTERVAL_7DAYS;
+			break;
+		case TIME_SCOPE_ALL:
+		default:
+			searchList.timeInterval = SC_TIME_INTERVAL_ALL;
+			break;
+    }
+
+    SC_ScoresController_SetSearchList(_scoresController, searchList);
+
+    // Set the search range
+   SC_Range_t range;
+   range.offset = start;
+   range.length = count;
+   rc = SC_ScoresController_LoadScores(_scoresController, range);
+   if (rc != SC_OK)
+   {
+       SC_ScoresController_Release(_scoresController);
+       _scoresController = NULL;
+       _pendingScoresResponse = false;
+       _listener->loadScoresEvent(SocialSessionListener::ERROR_INITIALIZATION, _scores);
+   }
+}
+
+void GoogleGamesSocialSession::loadScoresCallback(void* cookie, SC_Error_t result)
+{
+    GoogleGamesSocialSession* session = (GoogleGamesSocialSession*)cookie;
+
+    switch (result)
+    {
+    	case SC_OK:
+        {
+            SC_ScoreFormatter_h scoreFormatter = SC_Client_GetScoreFormatter(session->_client);
+
+            SC_ScoreList_h scoresList = SC_ScoresController_GetScores(session->_scoresController);
+            unsigned int scoresCount = SC_ScoreList_GetCount(scoresList);
+            session->_scores.clear();
+            for (unsigned int i = 0; i < scoresCount; i++)
+            {
+                SocialScore score;
+                score.handle = SC_ScoreList_GetAt(scoresList, i);
+                score.rank = SC_Score_GetRank((SC_Score_h)score.handle);
+                score.value = (float)SC_Score_GetResult((SC_Score_h)score.handle);
+                SC_String_h formatted;
+                SC_ScoreFormatter_FormatScore(scoreFormatter, (SC_Score_h)score.handle, SC_SCORE_FORMAT_DEFAULT, &formatted);
+                score.valueFormatted = SC_String_GetData(formatted);
+                SC_User_h player = SC_Score_GetUser((SC_Score_h)score.handle);
+                SC_String_h playerStr = SC_User_GetLogin(player);
+                score.playerName = SC_String_GetData(playerStr);
+                session->_scores.push_back(score);
+            }
+            session->getListener()->loadScoresEvent(SocialSessionListener::SUCCESS, session->_scores);
+        }
+        break;
+
+		case SC_INVALID_USER_DATA:
+			session->getListener()->loadScoresEvent(SocialSessionListener::ERROR_INVALID_USER, session->_scores);
+			break;
+		case SC_HTTP_SERVER_ERROR:
+			session->getListener()->loadScoresEvent(SocialSessionListener::ERROR_SERVER, session->_scores);
+			break;
+		default:
+			session->getListener()->loadScoresEvent(SocialSessionListener::ERROR_UNKNOWN, session->_scores);
+			break;
+    }
+
+    session->_pendingScoresResponse = false;
+}
+
+void GoogleGamesSocialSession::submitScore(const char* leaderboardId, float value)
+{
+    if (_pendingSubmitScoreResponse)
+    {
+        _listener->submitScoreEvent(SocialSessionListener::ERROR_PENDING_RESPONSE);
+        return;
+    }
+    _pendingSubmitScoreResponse = true;
+
+    SC_Error_t rc;
+    rc = SC_Client_CreateScoreController(_client, &_scoreController, GoogleGamesSocialSession::submitScoreCallback, this);
+    if (rc != SC_OK)
+    {
+        _listener->submitScoreEvent(SocialSessionListener::ERROR_INITIALIZATION);
+        _pendingSubmitScoreResponse = false;
+        return;
+    }
+
+    // Create a score to submit
+    SC_Score_h score;
+    rc = SC_Client_CreateScore(_client, &score);
+    if (rc != SC_OK)
+    {
+        _listener->submitScoreEvent(SocialSessionListener::ERROR_INITIALIZATION);
+        _pendingSubmitScoreResponse = false;
+        return;
+    }
+
+    // Set the leaderboard to associate this score with
+    if (leaderboardId != NULL)
+    {
+        Properties* leaderboardMappings = _properties->getNamespace("leaderboard_mappings", true);
+        if (leaderboardMappings)
+        {
+            int mode = leaderboardMappings->getInt(leaderboardId);
+            if (mode >=  0)
+            {
+                SC_Score_SetMode(score, (unsigned int)mode);
+            }
+        }
+    }
+
+    // Set the score value and submit the score
+    SC_Score_SetResult(score, value);
+    rc = SC_ScoreController_SubmitScore(_scoreController, score);
+    if (rc != SC_OK)
+    {
+         SC_ScoreController_Release(_scoreController);
+         _scoreController = NULL;
+         _pendingSubmitScoreResponse = false;
+         _listener->submitScoreEvent(SocialSessionListener::ERROR_INITIALIZATION);
+    }
+
+    if (_acceptedChallenge)
+    {
+        if (_pendingChallengeResponse)
+        {
+            _listener->submitChallengeEvent(SocialSessionListener::ERROR_PENDING_RESPONSE, *_acceptedChallenge);
+            return;
+        }
+        _pendingChallengeResponse = true;
+
+    	rc = SC_ChallengeController_SubmitChallengeScore(_challengeController, score);
+    	if (rc != SC_OK)
+    	{
+    		_listener->submitChallengeEvent(SocialSessionListener::ERROR_INITIALIZATION, *_acceptedChallenge);
+    		return;
+    	}
+    	_acceptedChallenge = 0;
+    }
+
+    SC_Score_Release(score);
+}
+
+void GoogleGamesSocialSession::submitScoreCallback(void* cookie, SC_Error_t result)
+{
+    GoogleGamesSocialSession* session = (GoogleGamesSocialSession*)cookie;
+
+    switch (result)
+    {
+		case SC_OK:
+			session->getListener()->submitScoreEvent(SocialSessionListener::SUCCESS);
+			break;
+		case SC_INVALID_USER_DATA:
+			session->getListener()->submitScoreEvent(SocialSessionListener::ERROR_INVALID_USER);
+			break;
+		case SC_HTTP_SERVER_ERROR:
+			session->getListener()->submitScoreEvent(SocialSessionListener::ERROR_SERVER);
+			break;
+		default:
+			session->getListener()->submitScoreEvent(SocialSessionListener::ERROR_UNKNOWN);
+			break;
+    }
+
+    session->_pendingSubmitScoreResponse = false;
+}
+
+void GoogleGamesSocialSession::submitChallenge(const SocialPlayer *player, unsigned int wager, float score, const char* leaderboardId)
+{
+	_listener->submitChallengeEvent(SocialSessionListener::ERROR_NOT_SUPPORTED, 0);
+}
+
+void GoogleGamesSocialSession::replyToChallenge(const SocialChallenge *challenge, bool accept)
+{
+	_listener->replyToChallengeEvent(SocialSessionListener::ERROR_NOT_SUPPORTED);
+}
+
+void GoogleGamesSocialSession::loadChallenges(bool showOpenChallengesOnly)
+{
+	_listener->loadChallengesEvent(SocialSessionListener::ERROR_NOT_SUPPORTED, _challenges);
+}
+
+void GoogleGamesSocialSession::loadSavedData(const char* key)
+{
+    if (_pendingDataResponse)
+    {
+        _listener->loadSavedDataEvent(SocialSessionListener::ERROR_PENDING_RESPONSE, "");
+        return;
+    }
+
+    SC_Error_t rc = SC_UserController_LoadUserContext(_userController);
+    if (rc != SC_OK)
+    {
+        _listener->loadSavedDataEvent(SocialSessionListener::ERROR_INITIALIZATION, "");
+        return;
+    }
+    _pendingDataResponse = true;
+    _key = key;
+}
+
+void GoogleGamesSocialSession::submitSavedData(const char* key, std::string data)
+{
+    if (_pendingDataResponse)
+    {
+        _listener->submitSavedDataEvent(SocialSessionListener::ERROR_PENDING_RESPONSE);
+        return;
+    }
+    _pendingDataResponse = true;
+
+    SC_Context_h context;
+    SC_Error_t rc = SC_Context_New(&context);
+    if (rc != SC_OK)
+    {
+        _listener->submitSavedDataEvent(SocialSessionListener::ERROR_INITIALIZATION);
+        return;
+    }
+    SC_String_h str;
+    rc = SC_String_New(&str, data.c_str());
+    if (rc != SC_OK)
+    {
+        _listener->submitSavedDataEvent(SocialSessionListener::ERROR_INITIALIZATION);
+        return;
+    }
+    SC_Context_Put(context, key, str);
+    SC_User_SetContext((SC_User_h)_user.handle, context);
+    SC_UserController_SetUser(_userController, (SC_User_h)_user.handle);
+    rc = SC_UserController_UpdateUserContext(_userController);
+    if (rc != SC_OK)
+    {
+        _listener->submitSavedDataEvent(SocialSessionListener::ERROR_INITIALIZATION);
+        return;
+    }
+}
+
+void GoogleGamesSocialSession::displayLeaderboard(const char* leaderboardId)
+{
+	SC_Error_t rc;
+	int mode = 0;
+
+    // Set the leaderboard to associate this leaderboard with
+    if (leaderboardId != NULL)
+    {
+        Properties* leaderboardMappings = _properties->getNamespace("leaderboard_mappings", true);
+        if (leaderboardMappings)
+        {
+            mode = leaderboardMappings->getInt(leaderboardId);
+        }
+    }
+
+	rc = SCUI_Client_SetLeadearboardFlags(_uiClient, SCUI_LEADERBOARD_FLAGS_SHOW_LIST_AROUND_USER);
+	if (rc != SC_OK)
+	{
+		_listener->uiEvent(SocialSessionListener::ERROR_INITIALIZATION, SC_MapErrorToStr(rc));
+		return;
+	}
+
+	rc = SCUI_Client_ShowLeaderboardView(_uiClient, mode, SCUI_LEADERBOARD_TYPE_GLOBAL, NULL);
+	if (rc != SC_OK)
+	{
+		_listener->uiEvent(SocialSessionListener::ERROR_INITIALIZATION, SC_MapErrorToStr(rc));
+	}
+}
+
+void GoogleGamesSocialSession::displayAchievements()
+{
+	SC_Error_t rc;
+
+	rc = SCUI_Client_SetAchievementFlags(_uiClient, SCUI_ACHIEVEMENT_FLAGS_DEFAULT);
+	if (rc != SC_OK)
+	{
+		_listener->uiEvent(SocialSessionListener::ERROR_INITIALIZATION, SC_MapErrorToStr(rc));
+		return;
+	}
+
+	rc = SCUI_Client_ShowAchievementsView(_uiClient);
+	if (rc != SC_OK)
+	{
+		_listener->uiEvent(SocialSessionListener::ERROR_INITIALIZATION, SC_MapErrorToStr(rc));
+	}
+}
+
+void GoogleGamesSocialSession::displayChallenges()
+{
+	_listener->uiEvent(SocialSessionListener::ERROR_NOT_SUPPORTED, "displayChallenges is not supported by Google Games Services");
+}
+
+void GoogleGamesSocialSession::displayChallengeSubmit(const SocialChallenge *challenge, float score)
+{
+	_listener->uiEvent(SocialSessionListener::ERROR_NOT_SUPPORTED, "displayChallengeSubmit is not supported by Google Games Services");
+}
+#endif
+
+}
+
+#endif

+ 201 - 0
gameplay/src/social/GoogleGamesSocialSession.h

@@ -0,0 +1,201 @@
+#if defined(__ANDROID__) && defined(GP_USE_SOCIAL)
+
+#ifndef GOOGLEGAMESSOCIALSESSION_H_
+#define GOOGLEGAMESSOCIALSESSION_H_
+
+#include "SocialSession.h"
+
+namespace gameplay
+{
+
+/**
+ * Google Games implementation of SocialSession
+ *
+ * Note: ensure game.config has the following properties
+ *
+  @verbatim
+    social
+    {
+         provider = GoogleGames
+         id = d346c484-12aa-49a2-a0a0-de2f87492d72
+         secret = aAa+DehBfyGO/CYaE3nWomgu7SIbWFczUih+Qwf3/n7u0y3nyq5Hag==
+         version = 1.0
+         language = en
+         currency = ASC
+         leaderboard_mappings
+         {
+             // Format: leaderboardId =  mode <unsigned int>
+             easy = 0
+             medium = 1
+             hard = 2
+         }
+    }
+ *
+ * Note: Ensure your bar-descriptor.xml has:
+ *
+ * <action>read_device_identifying_information</action>
+ *
+ * @script{ignore}
+ */
+class GoogleGamesSocialSession : public SocialSession
+{
+    friend class SocialController;
+
+public:
+
+    /**
+     * @see SocialSession::getListener
+     */
+    SocialSessionListener* getListener();
+
+    /**
+     * Initializes the session with the local client definition for this game.
+     *
+     * @param listener The listener for responses for this session
+     * @param properties The properties to initialize this session with for this game.
+     */
+    static SocialSession *authenticate(SocialSessionListener* listener, Properties* properties);
+
+    /**
+     * @see SocialSession::getUser
+     */
+    const SocialPlayer& getUser() const;
+
+    /**
+     * @see SocialSession::loadFriends
+     */
+    void loadFriends();
+
+    /**
+     * @see SocialSession::loadAchievements
+     */
+    void loadAchievements();
+
+    /**
+     * @see SocialSession::submitAchievement
+     */
+    void submitAchievement(const char* achievementId, unsigned int value, bool achieved=false);
+
+    /**
+     * @see SocialSession::incrementAchievement
+     */
+    void incrementAchievement(const char* achievementId, unsigned int increment=1);
+
+    /**
+      * @see SocialSession::syncAchievements
+      */
+    void synchronizeAchievements();
+
+    /**
+     * @see SocialSession::loadScores
+     */
+    void loadScores(const char* leaderboardId, CommunityScope community, TimeScope time, const SocialPlayer& player, unsigned int count);
+
+    /**
+     * @see SocialSession::loadScores
+     */
+    void loadScores(const char* leaderboardId, CommunityScope community, TimeScope time, unsigned int start, unsigned int count);
+
+    /**
+     * @see SocialSession::submitScore
+     */
+    void submitScore(const char* leaderboardId, float score);
+
+    /**
+      * @see SocialSession::submitChallenge
+      */
+    void submitChallenge(const SocialPlayer *player, unsigned int wager, float score, const char* leaderboardId=0);
+
+    /**
+      * @see SocialSession::loadChallenges
+      */
+    void loadChallenges(bool showOpenChallengesOnly=true);
+
+    /**
+      * @see SocialSession::acceptChallenge
+      */
+    void replyToChallenge(const SocialChallenge *challenge, bool accept);
+
+    /**
+     * @see SocialSession::requestSavedData
+     */
+    void loadSavedData(const char* key);
+
+    /**
+     * @see SocialSession::submitSavedData
+     */
+    void submitSavedData(const char* key, std::string data);
+
+    void displayLeaderboard(const char* leaderboardId);
+
+    void displayAchievements();
+
+    void displayChallenges();
+
+    void displayChallengeSubmit(const SocialChallenge *challenge, float score);
+
+
+protected:
+
+private:
+
+    /**
+     * Contructor
+     */
+    GoogleGamesSocialSession();
+
+    /**
+     * Destructor
+     */
+    virtual ~GoogleGamesSocialSession();
+#if 0
+
+    static void userCallback(void* cookie, unsigned int result);
+
+    static void uiCallback(void *cookie, SCUI_Result_t result, const void *data);
+
+    static void loadFriendsCallback(void* cookie, SC_Error_t result);
+
+    static void loadAchievementsCallback(void* cookie, SC_Error_t result);
+
+    static void submitAchievementCallback(void* cookie, SC_Error_t result);
+
+    static void loadScoresCallback(void* cookie, SC_Error_t result);
+
+    static void submitScoreCallback(void* cookie, SC_Error_t result);
+
+    const SocialAchievement* getAchievement(const char* achievementId) const;
+
+    SocialChallenge &addChallenge(SC_Challenge_h challenge);
+#endif
+    static GoogleGamesSocialSession* _session;
+
+    enum UserOp
+    {
+        USEROP_GET_LOCALUSER,
+        USEROP_WRITE_CONTEXT,
+        USEROP_READ_CONTEXT
+    };
+
+    SocialSessionListener* _listener;
+    Properties* _properties;
+    bool _pendingUserResponse;
+    bool _pendingFriendsResponse;
+    bool _pendingScoresResponse;
+    bool _pendingSubmitScoreResponse;
+    bool _pendingAchievementResponse;
+    bool _pendingDataResponse;
+    const char* _key;
+    std::string _data;
+    SocialPlayer _user;
+    UserOp _userOp;
+    std::vector<SocialPlayer> _friends;
+    std::vector<SocialAchievement> _achievements;
+    std::vector<SocialScore> _scores;
+};
+
+}
+
+#endif
+
+#endif

File diff suppressed because it is too large
+ 832 - 236
gameplay/src/social/ScoreloopSocialSession.cpp


+ 70 - 9
gameplay/src/social/ScoreloopSocialSession.h

@@ -5,6 +5,7 @@
 
 #include "SocialSession.h"
 #include <scoreloop/sc_client.h>
+#include <scoreloop/scui_client.h>
 #include <scoreloop/sc_init.h>
 #include <pthread.h>
 
@@ -22,9 +23,9 @@ namespace gameplay
          provider = Scoreloop
          id  = d346c484-12aa-49a2-a0a0-de2f87492d72
          secret = aAa+DehBfyGO/CYaE3nWomgu7SIbWFczUih+Qwf3/n7u0y3nyq5Hag==
-         version = "1.0"
-         language = "en"
-         currency = "ASC"
+         version = 1.0
+         language = en
+         currency = ASC
          leaderboard_mappings
          {
              // Format: leaderboardId =  mode <unsigned int>
@@ -57,12 +58,12 @@ public:
      * @param listener The listener for responses for this session
      * @param properties The properties to initialize this session with for this game.
      */
-    static void authenticate(SocialSessionListener* listener, Properties* properties);
+    static SocialSession *authenticate(SocialSessionListener* listener, Properties* properties);
 
     /**
      * @see SocialSession::getUser
      */
-    SocialPlayer* getUser();
+    const SocialPlayer& getUser() const;
 
     /**
      * @see SocialSession::loadFriends
@@ -77,7 +78,17 @@ public:
     /**
      * @see SocialSession::submitAchievement
      */
-    void submitAchievement(const char* achievementId, unsigned int value);
+    void submitAchievement(const char* achievementId, unsigned int value, bool achieved=false);
+
+    /**
+     * @see SocialSession::incrementAchievement
+     */
+    void incrementAchievement(const char* achievementId, unsigned int increment=1);
+
+    /**
+      * @see SocialSession::syncAchievements
+      */
+    void synchronizeAchievements();
 
     /**
      * @see SocialSession::loadScores
@@ -94,6 +105,21 @@ public:
      */
     void submitScore(const char* leaderboardId, float score);
 
+    /**
+      * @see SocialSession::submitChallenge
+      */
+    void submitChallenge(const SocialPlayer *player, unsigned int wager, float score, const char* leaderboardId=0);
+
+    /**
+      * @see SocialSession::loadChallenges
+      */
+    void loadChallenges(bool showOpenChallengesOnly=true);
+
+    /**
+      * @see SocialSession::acceptChallenge
+      */
+    void replyToChallenge(const SocialChallenge *challenge, bool accept);
+
     /**
      * @see SocialSession::requestSavedData
      */
@@ -104,6 +130,17 @@ public:
      */
     void submitSavedData(const char* key, std::string data);
 
+    void displayLeaderboard(const char* leaderboardId);
+
+    void displayAchievements();
+
+    void displayChallenges();
+
+    void displayChallengeSubmit(const SocialChallenge *challenge, float score);
+
+protected:
+
+    bool handleEvent(void *event);
 
 private:
 
@@ -117,22 +154,34 @@ private:
      */
     virtual ~ScoreloopSocialSession();
 
-    static void authenticate(SocialSessionListener* listener, Properties* properties);
-
     static void* platformEventCallback(void* data);
 
     static void userCallback(void* cookie, unsigned int result);
 
+    static void uiCallback(void *cookie, SCUI_Result_t result, const void *data);
+
     static void loadFriendsCallback(void* cookie, SC_Error_t result);
 
     static void loadAchievementsCallback(void* cookie, SC_Error_t result);
 
     static void submitAchievementCallback(void* cookie, SC_Error_t result);
 
+    static void submitChallengeCallback(void *cookie, SC_Error_t result);
+
+    static void replyToChallengeCallback(void *cookie, SC_Error_t result);
+
+    static void submittedChallengeScoreCallback(void *cookie, SC_Error_t result);
+
+    static void loadChallengesCallback(void *cookie, SC_Error_t result);
+
     static void loadScoresCallback(void* cookie, SC_Error_t result);
 
     static void submitScoreCallback(void* cookie, SC_Error_t result);
 
+    const SocialAchievement* getAchievement(const char* achievementId) const;
+
+    SocialChallenge &addChallenge(SC_Challenge_h challenge);
+
     static ScoreloopSocialSession* _session;
 
     enum UserOp
@@ -144,23 +193,35 @@ private:
 
     SocialSessionListener* _listener;
     Properties* _properties;
-    bool _pendingResponse;
+    bool _pendingUserResponse;
+    bool _pendingFriendsResponse;
+    bool _pendingScoresResponse;
+    bool _pendingSubmitScoreResponse;
+    bool _pendingAchievementResponse;
+    bool _pendingChallengeResponse;
+    bool _pendingChallengesResponse;
+    bool _pendingDataResponse;
     SC_InitData_t _initData;
     SC_Client_h _client;
+    SCUI_Client_h _uiClient;
     SC_UserController_h _userController;
     SC_UsersController_h _usersController;
     SC_LocalAchievementsController_h _localAchievementsController;
     SC_ScoresController_h _scoresController;
     SC_ScoreController_h _scoreController;
+    SC_ChallengeController_h _challengeController;
+    SC_ChallengesController_h _challengesController;
     pthread_cond_t _channelCond;
     pthread_mutex_t _channelMutex;
     const char* _key;
     std::string _data;
     SocialPlayer _user;
+    const SocialChallenge* _acceptedChallenge;
     UserOp _userOp;
     std::vector<SocialPlayer> _friends;
     std::vector<SocialAchievement> _achievements;
     std::vector<SocialScore> _scores;
+    std::vector<SocialChallenge> _challenges;
 };
 
 }

+ 7 - 3
samples/spaceship/.cproject

@@ -25,6 +25,7 @@
 								<option id="com.qnx.qcc.option.compiler.security.2012394098" name="Enhanced Security (-fstack-protector-strong)" superClass="com.qnx.qcc.option.compiler.security" value="false" valueType="boolean"/>
 								<option id="com.qnx.qcc.option.compiler.defines.1031425461" name="Defines (-D)" superClass="com.qnx.qcc.option.compiler.defines" valueType="definedSymbols">
 									<listOptionValue builtIn="false" value="_FORTIFY_SOURCE=2"/>
+									<listOptionValue builtIn="false" value="GP_USE_SOCIAL"/>
 								</option>
 								<option id="com.qnx.qcc.option.compiler.includePath.1786077513" name="Include Directories (-I)" superClass="com.qnx.qcc.option.compiler.includePath" valueType="includePath">
 									<listOptionValue builtIn="false" value="${QNX_TARGET}/../target-override/usr/include"/>
@@ -62,7 +63,7 @@
 									<listOptionValue builtIn="false" value="EGL"/>
 									<listOptionValue builtIn="false" value="screen"/>
 									<listOptionValue builtIn="false" value="m"/>
-									<listOptionValue builtIn="false" value="png14"/>
+									<listOptionValue builtIn="false" value="png16"/>
 									<listOptionValue builtIn="false" value="pps"/>
 									<listOptionValue builtIn="false" value="bps"/>
 									<listOptionValue builtIn="false" value="gestures"/>
@@ -72,6 +73,7 @@
 									<listOptionValue builtIn="false" value="lua"/>
 									<listOptionValue builtIn="false" value="bullet"/>
 									<listOptionValue builtIn="false" value="vorbis"/>
+									<listOptionValue builtIn="false" value="scoreloopcore"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.linker.1645280653" superClass="com.qnx.qcc.inputType.linker">
 									<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
@@ -148,7 +150,7 @@
 									<listOptionValue builtIn="false" value="EGL"/>
 									<listOptionValue builtIn="false" value="screen"/>
 									<listOptionValue builtIn="false" value="m"/>
-									<listOptionValue builtIn="false" value="png14"/>
+									<listOptionValue builtIn="false" value="png16"/>
 									<listOptionValue builtIn="false" value="pps"/>
 									<listOptionValue builtIn="false" value="bps"/>
 									<listOptionValue builtIn="false" value="gestures"/>
@@ -158,6 +160,7 @@
 									<listOptionValue builtIn="false" value="lua"/>
 									<listOptionValue builtIn="false" value="bullet"/>
 									<listOptionValue builtIn="false" value="vorbis"/>
+									<listOptionValue builtIn="false" value="scoreloopcore"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.linker.534133606" superClass="com.qnx.qcc.inputType.linker">
 									<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
@@ -235,7 +238,7 @@
 									<listOptionValue builtIn="false" value="EGL"/>
 									<listOptionValue builtIn="false" value="screen"/>
 									<listOptionValue builtIn="false" value="m"/>
-									<listOptionValue builtIn="false" value="png14"/>
+									<listOptionValue builtIn="false" value="png16"/>
 									<listOptionValue builtIn="false" value="pps"/>
 									<listOptionValue builtIn="false" value="bps"/>
 									<listOptionValue builtIn="false" value="gestures"/>
@@ -245,6 +248,7 @@
 									<listOptionValue builtIn="false" value="lua"/>
 									<listOptionValue builtIn="false" value="bullet"/>
 									<listOptionValue builtIn="false" value="vorbis"/>
+									<listOptionValue builtIn="false" value="scoreloopcore"/>
 								</option>
 								<inputType id="com.qnx.qcc.inputType.linker.1805822171" superClass="com.qnx.qcc.inputType.linker">
 									<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>

+ 4 - 0
samples/spaceship/android/AndroidManifest.xml

@@ -12,6 +12,10 @@
 
     <application android:icon="@drawable/icon" android:label="@string/app_name" android:hasCode="true">
 
+
+        <meta-data android:name="com.google.android.gms.games.APP_ID"
+            	   android:value="@string/app_id" />
+            
         <!-- Our activity is the built-in NativeActivity framework class.
              This will take care of integrating with our NDK code. -->
         <activity android:name="android.app.NativeActivity"

+ 16 - 9
samples/spaceship/bar-descriptor.xml

@@ -52,19 +52,25 @@
     <asset path="res/propulsion_glow.png">res/propulsion_glow.png</asset>
     <asset path="../../gameplay/res/logo_powered_white.png">res/logo_powered_white.png</asset>
     <asset path="../../gameplay/res/shaders">res/shaders</asset>
+    <asset path="game.config">game.config</asset>
+    <asset path="scoreloop">scoreloop</asset>
+    <asset path="res/menu.form">res/menu.form</asset>
+    <asset path="res/menu.theme">res/menu.theme</asset>
+    <asset path="res/menuAtlas.png">res/menuAtlas.png</asset>
+    <asset path="res/challenge.form">res/challenge.form</asset>
     
     <configuration id="com.qnx.qcc.configuration.exe.debug.1898582830" name="Device-Debug">
-       <platformArchitecture>armle-v7</platformArchitecture>
-       <asset path="Device-Debug/sample-spaceship" entry="true" type="Qnx/Elf">sample-spaceship</asset>
-    </configuration>
+       <platformArchitecture>armle-v7</platformArchitecture>
+       <asset path="Device-Debug/sample-spaceship" entry="true" type="Qnx/Elf">sample-spaceship</asset>
+    </configuration>
     <configuration id="com.qnx.qcc.configuration.exe.release.791928190" name="Device-Release">
-        <platformArchitecture>armle-v7</platformArchitecture>
-       <asset path="Device-Release/sample-spaceship" entry="true" type="Qnx/Elf">sample-spaceship</asset>
-    </configuration>
+        <platformArchitecture>armle-v7</platformArchitecture>
+       <asset path="Device-Release/sample-spaceship" entry="true" type="Qnx/Elf">sample-spaceship</asset>
+    </configuration>
     <configuration id="com.qnx.qcc.configuration.exe.debug.1233004640" name="Simulator">
-       <platformArchitecture>x86</platformArchitecture>
-       <asset path="Simulator/sample-spaceship" entry="true" type="Qnx/Elf">sample-spaceship</asset>
-    </configuration>
+       <platformArchitecture>x86</platformArchitecture>
+       <asset path="Simulator/sample-spaceship" entry="true" type="Qnx/Elf">sample-spaceship</asset>
+    </configuration>
     
     <!-- The icon for the application, which should be 114x114. -->
     <icon>
@@ -76,6 +82,7 @@
 
     <!-- Request permission to execute native code.  Required for native applications. -->
     <action system="true">run_native</action>
+    <permission>read_device_identifying_information</permission>
     
     <!-- The permissions requested by your application. -->
     <!--  <action>access_shared</action> -->

+ 15 - 0
samples/spaceship/game.config

@@ -4,4 +4,19 @@ window
     width = 1280
     height = 720
     fullscreen = false
+}
+
+social
+{
+     provider = Scoreloop
+     id  = ca02880e-9698-4a39-99d8-0117bee2b0e4
+     secret = H/6hp8lK8Aff9w6dVsikxVoqPxbyEqy653Dfj7akjNloHZd+ykAxOw==
+     version = 1.0
+     language = en
+     currency = DOJ
+     leaderboard_mappings
+     {
+         // Format: leaderboardId =  mode <unsigned int>
+         leaderboard = 0
+     }
 }

+ 96 - 0
samples/spaceship/res/challenge.form

@@ -0,0 +1,96 @@
+form challenge
+{
+    theme = res/menu.theme
+    style = background
+	layout = LAYOUT_FLOW
+    alignment = ALIGN_VCENTER_HCENTER
+
+    size = 1200, 600
+
+    container openchallenges
+	{
+	   	style = background
+        alignment = ALIGN_LEFT
+        layout = LAYOUT_VERTICAL
+        size = 500, 500
+		position = 0, 0
+        
+	    label historylabel
+	    {
+	    	position = 15, 0
+	        style = title
+	        textAlignment = ALIGN_TOP_LEFT
+	        autoWidth = true
+	        height = 50
+	        text = Open Challenges
+	    }
+	    
+	    button refreshChallenges
+	    {
+	        style = buttonStyle
+	        height = 100
+	        width = 400
+	        text = Refresh Challanges...
+	    }
+	    
+	    container challengeList
+	    {
+	    	consumeInputEvents = false
+			scroll = SCROLL_VERTICAL
+	    	style = noBorder
+	
+	        width = 410
+	        height = 400
+	    }
+    }
+    
+	container challengefriends
+	{
+	   	style = background
+        alignment = ALIGN_RIGHT
+        layout = LAYOUT_VERTICAL
+        size = 500, 700
+		position = 600, 0
+        
+	    label currentlabel
+	    {
+	    	position = 15, 0
+	        style = title
+	        textAlignment = ALIGN_TOP_LEFT
+	        autoWidth = true
+	        height = 50
+	        text = Create Challenge
+	    }
+	 
+	    container friendsList
+	    {
+	        style = noBorder
+	
+	        position = 0, 121
+	        width = 410
+	        height = 600
+	        consumeInputEvents = false
+			scroll = SCROLL_VERTICAL
+		
+	  	    button challengeanyone
+		    {
+		        style = buttonStyle
+		        
+		        position = 0, 0
+		        height = 100
+		        width = 400
+		        text = Anyone
+		    }
+	    }
+    }
+    
+    button back
+    {
+        style = buttonStyle
+        
+        position = 1200, 500
+        height = 100
+        width = 100
+        text = <<
+    }
+}

+ 65 - 0
samples/spaceship/res/menu.form

@@ -0,0 +1,65 @@
+form menu
+{
+    theme = res/menu.theme
+    style = background
+
+    alignment = ALIGN_VCENTER_HCENTER
+
+    size = 1000, 150
+
+    label congrats
+    {
+        style = title
+        textAlignment = ALIGN_TOP_HCENTER
+        autoWidth = true
+        height = 50
+        text = Please choose an option...
+    }
+    
+    container buttons
+    {
+        style = background
+        alignment = ALIGN_BOTTOM_HCENTER
+        size = 950, 101
+
+	    button reset
+	    {
+	        style = buttonStyle
+	        layout = LAYOUT_ABSOLUTE
+	        height = 100
+	        width = 200
+	        position = 000, 0
+	        text = Play
+	    }
+	
+	    button leaderboard
+	    {
+	        style = buttonStyle
+		layout = LAYOUT_ABSOLUTE
+	        height = 100
+	        width = 200
+	        position = 250, 0
+	        text = Leaderboard
+	    }
+		
+	    button achievements
+	    {
+	        style = buttonStyle
+	        layout = LAYOUT_ABSOLUTE
+	        height = 100
+	        width = 200
+	        position = 500, 0
+	        text = Achievements
+	    }
+	
+	    button challenges
+	    {
+	        style = buttonStyle
+	        layout = LAYOUT_ABSOLUTE
+	        height = 100
+	        width = 200
+	        position = 750, 0
+	        text = Challenges
+	    }
+    }
+}

+ 237 - 0
samples/spaceship/res/menu.theme

@@ -0,0 +1,237 @@
+theme menuTheme
+{
+    texture = res/menuAtlas.png
+
+    imageList normalImages
+    {
+        color = #4A8799ff
+
+        image unchecked
+        {
+            region = 78, 1, 46, 46
+        }
+
+        image checked
+        {
+            region = 78, 46, 46, 46
+        }
+
+        image unselected
+        {
+            region = 127, 1, 46, 46
+        }
+
+        image selected
+        {
+            region = 127, 46, 46, 46
+        }
+
+        image minCap
+        {
+            region = 3, 99, 8, 24
+        }
+
+        image maxCap
+        {
+            region = 3, 99, 8, 24
+        }
+
+        image marker
+        {
+            region = 16, 113, 18, 18
+        }
+
+        image track
+        {
+            region = 39, 119, 32, 6
+        }
+
+        image textCaret
+        {
+            region = 5, 149, 11, 25
+            color = #C3D9BFff
+        }
+        
+        image scrollBarTopCap
+        {
+            region = 0, 99, 12, 5
+        }
+
+        image verticalScrollBar
+        {
+            region = 0, 104, 12, 19
+        }
+
+        image scrollBarBottomCap
+        {
+            region = 0, 138, 12, 5
+        }
+
+        image scrollBarLeftCap
+        {
+            region = 35, 115, 5, 12
+        }
+
+        image horizontalScrollBar
+        {
+            region = 43, 115, 19, 12
+        }
+
+        image scrollBarRightCap
+        {
+            region = 65, 115, 5, 12
+        }
+        
+    }
+
+    imageList activeImages : normalImages
+    {
+        color = #C3D9BFff
+
+        image unchecked
+        {
+            region = 78, 91, 46, 46
+        }
+
+        image checked
+        {
+            region = 78, 91, 46, 46
+        }
+
+        image unselected
+        {
+            region = 127, 91, 46, 46
+        }
+
+        image selected
+        {
+            region = 127, 91, 46, 46
+        }
+    }
+
+    skin mainNormal
+    {
+        border
+        {
+            left = 10
+            right = 10
+            top = 10
+            bottom = 10
+        }
+        
+        region = 1, 1, 74, 74
+        color = #4A8799ff
+    }
+
+    skin mainActive : mainNormal
+    {
+        color = #C3D9BFff
+    }
+
+    skin empty
+    {
+        region = 0, 0, 1, 1
+    }
+
+    style background
+    {
+        stateNormal
+        {
+        }
+    }
+
+    style basic
+    {
+        stateNormal
+        {
+            skin = mainNormal
+            imageList = normalImages
+
+            font = res/airstrip28.gpb
+            textColor = #ffffffff
+            fontSize = 15
+            textAlignment = ALIGN_VCENTER_HCENTER
+        }
+
+        stateActive
+        {
+            imageList = activeImages
+        }
+    }
+
+    style buttonStyle : basic
+    {
+        padding
+        {
+            top = -10
+            bottom = -10
+        }
+
+        stateNormal
+        {
+            font = res/airstrip28.gpb
+            fontSize = 30
+        }
+
+        stateActive
+        {
+            skin = mainActive
+        }
+    }
+
+    style noBorder
+    {
+        stateNormal
+        {
+            imageList = normalImages
+            font = res/airstrip28.gpb
+            textColor = #ffffffff
+            fontSize = 15
+            textAlignment = ALIGN_VCENTER_HCENTER
+        }
+
+        stateActive
+        {
+            imageList = activeImages
+            textAlignment = ALIGN_VCENTER_HCENTER
+        }
+    }
+
+    style iconNoBorder : noBorder
+    {
+        stateNormal
+        {
+            fontSize = 20
+            textAlignment = ALIGN_VCENTER_LEFT
+        }
+
+        stateActive
+        {
+            fontSize = 20
+            textAlignment = ALIGN_VCENTER_LEFT
+        }
+    }
+
+    style title
+    {
+        padding
+        {
+            left = -5
+            right = -5
+        }
+
+        stateNormal
+        {
+            skin = empty
+            textColor = #ffffffff
+            font = res/airstrip28.gpb
+            fontSize = 25
+            textAlignment = ALIGN_TOP_HCENTER
+        }
+
+        stateActive
+        {
+            textColor = #C3D9BFff
+        }
+    }
+}

BIN
samples/spaceship/res/menuAtlas.png


+ 442 - 15
samples/spaceship/src/SpaceshipGame.cpp

@@ -1,5 +1,9 @@
 #include "SpaceshipGame.h"
 
+#ifdef __QNX__
+#include <bps/dialog.h>
+#endif
+
 // Declare our game instance
 SpaceshipGame game;
 
@@ -59,10 +63,14 @@ SpaceshipGame game;
 // Clamp function
 #define CLAMP(x, min, max) (x < min ? min : (x > max ? max : x))
 
-SpaceshipGame::SpaceshipGame() 
+static const char *leaderboardName = "leaderboard";
+
+SpaceshipGame::SpaceshipGame()
     : _scene(NULL), _cameraNode(NULL), _shipGroupNode(NULL), _shipNode(NULL), _propulsionNode(NULL), _glowNode(NULL),
-      _stateBlock(NULL), _font(NULL), _throttle(0), _shipTilt(0), _finished(false), _finishedTime(0), _pushing(false), _time(0), 
-       _glowDiffuseParameter(NULL), _shipSpecularParameter(NULL), _spaceshipSound(NULL)
+      _stateBlock(NULL), _font(NULL), _throttle(0), _shipTilt(0), _finished(true), _finishedTime(0), _pushing(false), _time(0),
+       _glowDiffuseParameter(NULL), _shipSpecularParameter(NULL), _spaceshipSound(NULL), _socialSession(NULL), _currentChallenge(NULL),
+       _challengedPlayer(NULL), _hitSomething(false), _creatingChallenge(false), _hasAcceptedChallenge(false),
+       _menu(NULL), _challengeForm(NULL), _friendsContainer(NULL), _challengeContainer(NULL)
 {
 }
 
@@ -98,10 +106,30 @@ void SpaceshipGame::initialize()
     _backgroundSound = AudioSource::create("res/background.ogg");
     if (_backgroundSound)
         _backgroundSound->setLooped(true);
-    
+
     // Create font
     _font = Font::create("res/airstrip28.gpb");
 
+    // Create menu.
+    _menu = Form::create("res/menu.form");
+    _menu->setEnabled(true);
+
+    _challengeForm = Form::create("res/challenge.form");
+    _challengeForm->setEnabled(false);
+
+    // Listen for menu-button click events.
+    _menu->getControl("reset")->addListener(this, Control::Listener::CLICK);
+    _menu->getControl("leaderboard")->addListener(this, Control::Listener::CLICK);
+    _menu->getControl("achievements")->addListener(this, Control::Listener::CLICK);
+    _menu->getControl("challenges")->addListener(this, Control::Listener::CLICK);
+
+    _challengeForm->getControl("challengeanyone")->addListener(this, Control::Listener::CLICK);
+    _challengeForm->getControl("refreshChallenges")->addListener(this, Control::Listener::CLICK);
+    _challengeForm->getControl("back")->addListener(this, Control::Listener::CLICK);
+
+    _friendsContainer = static_cast<Container*>(_challengeForm->getControl("friendsList"));
+    _challengeContainer = static_cast<Container*>(_challengeForm->getControl("challengeList"));
+
     // Store camera node
     _cameraNode = _scene->findNode("camera1");
 
@@ -109,6 +137,8 @@ void SpaceshipGame::initialize()
     _initialShipPos = _shipGroupNode->getTranslation();
     _initialShipRot = _shipGroupNode->getRotation();
     _initialCameraPos = _cameraNode->getTranslation();
+
+    getSocialController()->authenticate(this);
 }
 
 void SpaceshipGame::initializeSpaceship()
@@ -208,7 +238,7 @@ void SpaceshipGame::initializeMaterial(Material* material, bool lighting, bool s
         material->getParameter("u_lightDirection")->setValue(lightDirection);
         material->getParameter("u_lightColor")->setValue(lightNode->getLight()->getColor());
         material->getParameter("u_ambientColor")->setValue(AMBIENT_LIGHT_COLOR);
-       
+
 
         if (specular)
         {
@@ -227,13 +257,15 @@ void SpaceshipGame::finalize()
     SAFE_RELEASE(_font);
     SAFE_RELEASE(_stateBlock);
     SAFE_RELEASE(_scene);
+    SAFE_RELEASE(_menu);
+    SAFE_RELEASE(_challengeForm);
 }
 
 void SpaceshipGame::update(float elapsedTime)
 {
     // Calculate elapsed time in seconds
     float t = (float)elapsedTime / 1000.0;
-    
+
     if (!_finished)
     {
         _time += t;
@@ -245,10 +277,14 @@ void SpaceshipGame::update(float elapsedTime)
     else
     {
         // Stop the background track
-        if (_backgroundSound->getState() != AudioSource::STOPPED)
+        if (_backgroundSound->getState() == AudioSource::PLAYING || _backgroundSound->getState() == AudioSource::PAUSED)
+		{
             _backgroundSound->stop();
+        	_throttle = 0.0f;
 
-        _throttle = 0.0f;
+        	postScore(_time);
+        	updateAchievements(_time);
+		}
     }
 
     // Set initial force due to gravity
@@ -266,7 +302,7 @@ void SpaceshipGame::update(float elapsedTime)
         // We will use this vector to apply a "pushing" force to the space ship, similar to what
         // happens when you hold a magnet close to an object with opposite polarity.
         Vector2 pushForce((shipCenterScreen.x - _pushPoint.x), -(shipCenterScreen.y - _pushPoint.y));
-        
+
         // Transform the vector so that a smaller magnitude emits a larger force and applying the
         // maximum touch distance.
         float distance = (std::max)(TOUCH_DISTANCE_MAX - pushForce.length(), 0.0f);
@@ -341,7 +377,7 @@ void SpaceshipGame::update(float elapsedTime)
         // Play sound effect
         if (_spaceshipSound->getState() != AudioSource::PLAYING)
             _spaceshipSound->play();
-        
+
         // Set the pitch based on the throttle
         _spaceshipSound->setPitch(_throttle * SOUND_PITCH_SCALE);
     }
@@ -354,6 +390,12 @@ void SpaceshipGame::update(float elapsedTime)
     // Modify ship glow effect based on the throttle
     _glowDiffuseParameter->setValue(Vector4(1, 1, 1, _throttle * ENGINE_POWER));
     _shipSpecularParameter->setValue(SPECULAR - ((SPECULAR-2.0f) * _throttle));
+
+    if (_menu->isEnabled())
+        _menu->update(elapsedTime);
+
+    if (_challengeForm->isEnabled())
+    	_challengeForm->update(elapsedTime);
 }
 
 void SpaceshipGame::handleCollisions(float t)
@@ -401,6 +443,7 @@ void SpaceshipGame::handleCollisions(float t)
         {
             _velocity.x = (std::min)(_velocity.x - friction  * t, 0.0f);
         }
+		_hitSomething = true;
     }
 
     // Keep the ship within the playable area of the map
@@ -418,6 +461,7 @@ void SpaceshipGame::handleCollisions(float t)
             _finished = true;
             _finishedTime = getAbsoluteTime();
             _pushing = false;
+            _menu->setEnabled(true);
         }
     }
 }
@@ -448,6 +492,7 @@ void SpaceshipGame::resetGame()
     _velocity.set(0, 0);
     _shipGroupNode->setTranslation(_initialShipPos);
     _cameraNode->setTranslation(_initialCameraPos);
+    _hitSomething = false;
 }
 
 void SpaceshipGame::render(float elapsedTime)
@@ -462,6 +507,14 @@ void SpaceshipGame::render(float elapsedTime)
 
     // Draw game text (yellow)
     drawText();
+
+    // Draw menu
+    if (_menu->isEnabled())
+        _menu->draw();
+
+    // Draw menu
+    if (_challengeForm->isEnabled())
+    	_challengeForm->draw();
 }
 
 void SpaceshipGame::drawSplash(void* param)
@@ -496,7 +549,7 @@ void SpaceshipGame::drawText()
     char text[1024];
     sprintf(text, "%dsec.", (int)_time);
     _font->drawText(text, getWidth() - 120, 10, Vector4(1, 1, 0, 1), _font->getSize());
-    if (_finished)
+    if (0 && _finished)
     {
         _font->drawText("Click to Play Again", getWidth()/2 - 175, getHeight()/2 - 40, Vector4::one(), _font->getSize());
     }
@@ -521,10 +574,10 @@ void SpaceshipGame::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int
     switch (evt)
     {
     case Touch::TOUCH_PRESS:
-        if (_finished && (getAbsoluteTime() - _finishedTime) > 1000L)
-        {
-            resetGame();
-        }
+     //   if (_finished && (getAbsoluteTime() - _finishedTime) > 1000L)
+     //   {
+     //       resetGame();
+     //   }
     case Touch::TOUCH_MOVE:
         if (!_finished)
         {
@@ -538,3 +591,377 @@ void SpaceshipGame::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int
         break;
     }
 }
+
+void SpaceshipGame::controlEvent(Control* control, EventType evt)
+{
+    // Handle UI events.
+    switch (evt)
+    {
+		case Listener::CLICK:
+			if (strcmp(control->getId(), "reset") == 0)
+			{
+				// Play again.
+				_menu->setEnabled(false);
+				if (_creatingChallenge)
+				{
+					_creatingChallenge = false;
+					_challengedPlayer = 0;
+				}
+				resetGame();
+			}
+			else if (strcmp(control->getId(), "back") == 0)
+			{
+				_challengeForm->setEnabled(false);
+				_menu->setEnabled(true);
+			}
+			else if (_socialSession && strcmp(control->getId(), "leaderboard") == 0)
+			{
+				// Display the leaderboard.
+				_socialSession->displayLeaderboard("leaderboard");
+			}
+			else if (_socialSession && strcmp(control->getId(), "achievements") == 0)
+			{
+				// Display the achievements.
+				_socialSession->displayAchievements();
+			}
+			else if (_socialSession && strcmp(control->getId(), "challenges") == 0)
+			{
+				// Display the challenges.
+				_socialSession->displayChallenges();
+			//	_menu->setEnabled(false);
+			//	_challengeForm->setEnabled(true);
+			}
+			else if (strcmp(control->getId(), "challengeanyone") == 0)
+			{
+				_challengedPlayer = 0;
+				_challengeForm->setEnabled(false);
+				_creatingChallenge = true;
+				resetGame();
+			}
+			else if (strncmp(control->getId(), "friend_", 7) == 0)
+			{
+				char *player = strchr((char *)control->getId(), '_');
+				player++;
+				fprintf(stderr, "searching for player %s\n", player);
+				_challengedPlayer = getPlayer(player);
+				_challengeForm->setEnabled(false);
+				_creatingChallenge = true;
+				resetGame();
+			}
+			else if (_socialSession && strcmp(control->getId(), "refreshChallenges") == 0)
+			{
+				_socialSession->loadChallenges(true);
+			}
+			else if (_socialSession && strncmp(control->getId(), "openchallenge_", 14) == 0)
+			{
+				char *challengeName = strchr((char *)control->getId(), '_');
+				challengeName++;
+				const SocialChallenge *challenge = getChallenge(challengeName);
+			//	_challengeForm->setEnabled(false);
+				if (challenge)
+				{
+					_hasAcceptedChallenge = true;
+					_socialSession->replyToChallenge(challenge, true);
+					_challengeForm->setEnabled(false);
+					resetGame();
+				}
+			}
+
+			break;
+    }
+}
+
+void SpaceshipGame::postScore(double result)
+{
+fprintf(stderr, "postScore with new result %lf\n", result);
+
+	if (_socialSession)
+	{
+		_socialSession->submitScore(leaderboardName, result);
+
+		if (_creatingChallenge)
+		{
+			_socialSession->submitChallenge(_challengedPlayer, 0, result);
+			_creatingChallenge = false;
+			_challengedPlayer = 0;
+		}
+		else if (_hasAcceptedChallenge)
+		{
+			if (_currentChallenge)
+				_socialSession->displayChallengeSubmit(_currentChallenge, result);
+
+			_currentChallenge = 0;
+			_hasAcceptedChallenge = false;
+		}
+	}
+}
+
+void SpaceshipGame::updateAchievements(double time)
+{
+	// go through our achievements and update them accordingly
+	if (_socialSession)
+	{
+		// increase the game count awards
+		_socialSession->incrementAchievement("rim.spaceship.firsttime");
+		_socialSession->incrementAchievement("rim.spaceship.tentimes");
+		_socialSession->incrementAchievement("rim.spaceship.fiftytimes");
+		_socialSession->incrementAchievement("rim.spaceship.hundredtimes");
+
+		// clean run award
+		if (!_hitSomething)
+			_socialSession->incrementAchievement("rim.spaceship.cleanrun");
+
+		if (time < 16)
+			_socialSession->submitAchievement("rim.spaceship.under16", 1, true);
+
+		if (time < 17)
+			_socialSession->submitAchievement("rim.spaceship.under17", 1, true);
+
+		if (time < 20)
+			_socialSession->submitAchievement("rim.spaceship.under20", 1, true);
+
+		_socialSession->synchronizeAchievements();
+	}
+}
+
+const SocialPlayer *SpaceshipGame::getPlayer(const char *name) const
+{
+	for (uint i = 0; i < _friends.size(); i++)
+	{
+		if (strcmp(_friends[i].name.data(), name) == 0)
+			return &_friends[i];
+	}
+
+	return 0;
+}
+
+const SocialChallenge *SpaceshipGame::getChallenge(const char *date) const
+{
+	for (uint i = 0; i < _challenges.size(); i++)
+	{
+		if (strcmp(_challenges[i].dateTimeIssued.data(), date) == 0)
+			return &_challenges[i];
+	}
+
+	return 0;
+}
+
+void SpaceshipGame::authenticateEvent(ResponseCode code, SocialSession* session)
+{
+	fprintf(stderr, "authentication FINISHED\n");
+
+	if (code == SUCCESS)
+	{
+		_socialSession = session;
+		_socialSession->loadFriends();
+	}
+	else
+		fprintf(stderr, "Error authenticating the social session %d\n", code);
+}
+
+void SpaceshipGame::loadFriendsEvent(ResponseCode code, std::vector<SocialPlayer> friends)
+{
+	if (code == SUCCESS)
+	{
+		_friends.clear();
+		_friends = friends;
+
+		buildFriendsChooser();
+
+		for (uint i = 0 ; i < _friends.size(); i++)
+		{
+			fprintf(stderr, "Friend %d is %s\n", i, _friends[i].name.data());
+		}
+
+		_socialSession->loadAchievements();
+	}
+	else
+	{
+		fprintf(stderr, "Error loading friends\n");
+	}
+}
+
+void SpaceshipGame::loadAchievementsEvent(ResponseCode code, std::vector<SocialAchievement> achievements)
+{
+	if (code == SUCCESS)
+	{
+		for (uint i = 0 ; i < achievements.size(); i++)
+		{
+			fprintf(stderr, "Achievement %d is %s\n", i, achievements[i].name.data());
+		}
+	}
+	else
+		fprintf(stderr, "Error loading achievements %d\n", code);
+}
+
+void SpaceshipGame::submitAchievementEvent(ResponseCode code)
+{
+	fprintf(stderr, "submitAchievementEvent code is %d!!!\n", code);
+}
+
+void SpaceshipGame::synchronizeAchievementEvent(ResponseCode code)
+{
+	fprintf(stderr, "syncAchievementEvent code is %d!!!\n", code);
+}
+
+void SpaceshipGame::awardAchievedEvent(ResponseCode code, const SocialAchievement &achievement)
+{
+	if (code == SUCCESS)
+	{
+#ifdef __QNX__
+		char message[256];
+		sprintf(message, "You've earned the %s award.", achievement.title.data());
+
+		dialog_instance_t dialog = 0;
+
+		fprintf(stderr, "launching a dialog %s\n", message);
+
+		dialog_create_toast(&dialog);
+
+		dialog_set_group_id(dialog, "dialogId");
+
+		dialog_set_toast_position(dialog, DIALOG_POSITION_TOP_CENTER);
+		dialog_set_toast_message_text(dialog, message);
+
+		dialog_show(dialog);
+#endif
+	}
+}
+
+void SpaceshipGame::loadScoresEvent(ResponseCode code, std::vector<SocialScore> scores)
+{
+	fprintf(stderr, "loaded Scores\n");
+
+	if (code == SUCCESS)
+	{
+	//	for (uint i = 0 ; i < scores.size(); i++)
+	//	{
+	//		fprintf(stderr, "Score %d for %s is %lf\n", i, scores[i].playerName.data(), scores[i].value);
+	//	}
+	}
+	else
+		fprintf(stderr, "error loading scores %d\n", code);
+}
+
+void SpaceshipGame::submitScoreEvent(ResponseCode code)
+{
+	fprintf(stderr, "finished submitting score\n");
+
+	_socialSession->loadScores("leaderboard", SocialSession::COMMUNITY_SCOPE_ALL, SocialSession::TIME_SCOPE_ALL, 1, 20);
+}
+
+void SpaceshipGame::submitChallengeEvent(ResponseCode code, const SocialChallenge &challenge)
+{
+	fprintf(stderr, "SPACESHIP submittedChallengeEvent %d\n", code);
+}
+
+void SpaceshipGame::startChallengeEvent(ResponseCode code, const SocialChallenge &challenge)
+{
+	if (code == SUCCESS)
+	{
+		_hasAcceptedChallenge = true;
+		_currentChallenge = &challenge;
+		_menu->setEnabled(false);
+		resetGame();
+	}
+}
+
+void SpaceshipGame::replyToChallengeEvent(ResponseCode code)
+{
+	fprintf(stderr, "SPACESHIP replyToChallengeEvent %d\n", code);
+}
+
+void SpaceshipGame::loadChallengesEvent(ResponseCode code, std::vector<SocialChallenge> challenges)
+{
+	fprintf(stderr, "loaded challenges\n");
+
+	if (code == SUCCESS)
+	{
+		_challenges.clear();
+		_challenges = challenges;
+
+		buildChallengeChooser();
+
+		for (uint i = 0 ; i < challenges.size(); i++)
+		{
+			fprintf(stderr, "Challenge score %lf issued on %s by %s for %s\n", challenges[i].score, challenges[i].dateTimeIssued.data(), challenges[i].issuedPlayerName.data(), challenges[i].challengedPlayerName.data());
+		}
+	}
+	else
+		fprintf(stderr, "error loading challenges %d\n", code);
+}
+
+void SpaceshipGame::loadSavedDataEvent(ResponseCode code, std::string data)
+{
+}
+
+void SpaceshipGame::submitSavedDataEvent(ResponseCode code)
+{
+}
+
+void SpaceshipGame::buildFriendsChooser()
+{
+	fprintf(stderr, "build the friend chooser\n");
+
+	Theme* theme = _challengeForm->getTheme();
+	Theme::Style* buttonStyle = theme->getStyle("buttonStyle");
+
+	if (!_friendsContainer) return;
+
+	const size_t size = _friends.size();
+	for (size_t i = 0; i < size; ++i)
+	{
+		char buf[128];
+		sprintf(buf, "friend_%s", _friends[i].name.data());
+		Button* button = Button::create(buf, buttonStyle);
+		button->setText(_friends[i].name.data());
+		button->setPosition(0, 110*(i+1));
+		button->setWidth(400);
+		button->setHeight(100);
+		button->setConsumeInputEvents(false);   // This lets the user scroll the container if they swipe starting from a button.
+		button->addListener(this, Control::Listener::CLICK);
+		_friendsContainer->addControl(button);
+		button->release();
+	}
+
+//	_friendsContainer->setScroll(Container::SCROLL_VERTICAL);
+//	_sampleSelectForm->setState(Control::FOCUS);
+}
+
+void SpaceshipGame::buildChallengeChooser()
+{
+	fprintf(stderr, "build the challenge chooser\n");
+
+	if (!_challengeContainer) return;
+#if 0
+	const std::vector<Control*> controls = _challengeContainer->getControls();
+	if (controls.size() > 1)
+	{
+		for (int i = controls.size() - 1; i >= 0; i--)
+			_challengeContainer->removeControl(i);
+	}
+#endif
+
+	Theme* theme = _challengeForm->getTheme();
+	Theme::Style* buttonStyle = theme->getStyle("buttonStyle");
+
+	const size_t size = _challenges.size();
+
+	for (size_t i = 0; i < size; ++i)
+	{
+		char buf[128];
+		sprintf(buf, "openchallenge_%s", _challenges[i].dateTimeIssued.data());
+		Button* button = Button::create(buf, buttonStyle);
+		button->setText(_challenges[i].issuedPlayerName.data());
+		button->setPosition(0, 110*i);
+		button->setWidth(400);
+		button->setHeight(100);
+		button->setConsumeInputEvents(false);   // This lets the user scroll the container if they swipe starting from a button.
+		button->addListener(this, Control::Listener::CLICK);
+		_challengeContainer->addControl(button);
+		button->release();
+	}
+
+//	_challengeContainer->setScroll(Container::SCROLL_VERTICAL);
+//	_sampleSelectForm->setState(Control::FOCUS);
+}

+ 118 - 3
samples/spaceship/src/SpaceshipGame.h

@@ -8,7 +8,7 @@ using namespace gameplay;
 /**
  * Spaceship game.
  */
-class SpaceshipGame : public Game
+class SpaceshipGame : public Game, public SocialSessionListener, public Control::Listener
 {
 public:
 
@@ -43,7 +43,7 @@ protected:
      * @see Game::finalize
      */
     void finalize();
-    
+
     /**
      * @see Game::update
      */
@@ -54,6 +54,82 @@ protected:
      */
     void render(float elapsedTime);
 
+
+    /**
+     * @see SocialSessionListener::authenticateEvent
+     */
+    void authenticateEvent(ResponseCode code, SocialSession* session);
+
+    /**
+     * @see SocialSessionListener::loadFriendsEvent
+     */
+    void loadFriendsEvent(ResponseCode code, std::vector<SocialPlayer> friends);
+
+    /**
+     * @see SocialSessionListener::loadAchievementsEvent
+     */
+    void loadAchievementsEvent(ResponseCode code, std::vector<SocialAchievement> achievements);
+
+    /**
+     * @see SocialSessionListener::submitAchievementEvent
+     */
+    void synchronizeAchievementEvent(ResponseCode code);
+
+    /**
+     * @see SocialSessionListener::sumbitAchievementEvent
+     */
+    void submitAchievementEvent(ResponseCode code);
+
+    /**
+     * @see SocialSessionListener::awardAchievedEvent
+     */
+    void awardAchievedEvent(ResponseCode code, const SocialAchievement &achievement);
+
+    /**
+     * @see SocialSessionListener::loadScoresEvent
+     */
+    void loadScoresEvent(ResponseCode code, std::vector<SocialScore> scores);
+
+    /**
+     * @see SocialSessionListener::submitScoreEvent
+     */
+    void submitScoreEvent(ResponseCode code);
+
+    /**
+     * @see SocialSessionListener::submitChallengeEvent
+     */
+    void submitChallengeEvent(ResponseCode code, const SocialChallenge &challenge);
+
+    /**
+     * @see SocialSessionListener::startChallengeEvent
+     */
+    void startChallengeEvent(ResponseCode code, const SocialChallenge &challenge);
+
+    /**
+      * @see SocialSessionListener::replyToChallengeEvent
+      */
+    void replyToChallengeEvent(ResponseCode code);
+
+    /**
+     * @see SocialSessionListener::loadChallengesEvent
+     */
+    void loadChallengesEvent(ResponseCode code);
+
+    /**
+     * @see SocialSessionListener::loadChallengesEvent
+     */
+    void loadChallengesEvent(ResponseCode code, std::vector<SocialChallenge> challenges);
+
+    /**
+     * @see SocialSessionListener::loadSavedDataEvent
+     */
+    void loadSavedDataEvent(ResponseCode code, std::string data);
+
+    /**
+     * @see SocialSessionListener::submitSavedDataEvent
+     */
+    void submitSavedDataEvent(ResponseCode code);
+
 private:
 
     /**
@@ -104,6 +180,29 @@ private:
      */
     void drawText();
 
+    /**
+     * Posts score.
+     */
+    void postScore(double result);
+
+    /**
+     * Update the achievements.
+     */
+    void updateAchievements(double time);
+
+    /**
+     * @see ControlListener::controlEvent
+     */
+    void controlEvent(Control* control, EventType evt);
+
+    const SocialPlayer *getPlayer(const char *name) const;
+
+    const SocialChallenge *getChallenge(const char *date) const;
+
+    void buildFriendsChooser();
+
+    void buildChallengeChooser();
+
     // Scene variables
     Scene* _scene;
     Node* _cameraNode;
@@ -131,7 +230,7 @@ private:
     double _finishedTime;
     bool _pushing;
     Vector2 _pushPoint;
-    
+
     // Game time in seconds
     float _time;
 
@@ -143,6 +242,22 @@ private:
     AudioSource* _backgroundSound;
     AudioSource* _spaceshipSound;
 
+    // Social
+    SocialSession* _socialSession;
+    const SocialChallenge* _currentChallenge;
+    const SocialPlayer* _challengedPlayer;
+    std::vector<SocialPlayer> _friends;
+    std::vector<SocialChallenge> _challenges;
+    bool _hitSomething;
+    bool _creatingChallenge;
+    bool _hasAcceptedChallenge;
+
+    // End-game Menu
+    Form* _menu;
+    Form* _challengeForm;
+    Container* _friendsContainer;
+    Container* _challengeContainer;
+
 };
 
 #endif

Some files were not shown because too many files changed in this diff