Jelajahi Sumber

Merge pull request #1161 from dducharme/next

Fixing bug where the value's first character was an = sign.
Sean Taylor 12 tahun lalu
induk
melakukan
ceec06fcb3
34 mengubah file dengan 4821 tambahan dan 290 penghapusan
  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. 1 1
      gameplay/src/Properties.cpp
  15. 6 1
      gameplay/src/SocialAchievement.h
  16. 51 5
      gameplay/src/SocialController.cpp
  17. 18 3
      gameplay/src/SocialController.h
  18. 24 3
      gameplay/src/SocialSession.h
  19. 40 0
      gameplay/src/SocialSessionListener.cpp
  20. 23 0
      gameplay/src/SocialSessionListener.h
  21. 909 0
      gameplay/src/social/GoogleGamesSocialSession.cpp
  22. 201 0
      gameplay/src/social/GoogleGamesSocialSession.h
  23. 832 236
      gameplay/src/social/ScoreloopSocialSession.cpp
  24. 70 9
      gameplay/src/social/ScoreloopSocialSession.h
  25. 7 3
      samples/spaceship/.cproject
  26. 4 0
      samples/spaceship/android/AndroidManifest.xml
  27. 16 9
      samples/spaceship/bar-descriptor.xml
  28. 15 0
      samples/spaceship/game.config
  29. 96 0
      samples/spaceship/res/challenge.form
  30. 65 0
      samples/spaceship/res/menu.form
  31. 237 0
      samples/spaceship/res/menu.theme
  32. TEMPAT SAMPAH
      samples/spaceship/res/menuAtlas.png
  33. 442 15
      samples/spaceship/src/SpaceshipGame.cpp
  34. 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())

+ 1 - 1
gameplay/src/Properties.cpp

@@ -184,7 +184,7 @@ void Properties::readProperties(Stream* stream)
                 name = trimWhiteSpace(name);
 
                 // Scan for next token, the property's value.
-                value = strtok(NULL, "=");
+                value = strtok(NULL, "");
                 if (value == NULL)
                 {
                     GP_ERROR("Error parsing properties file: attribute with name ('%s') but no value.", name);

+ 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 ditekan karena terlalu besar
+ 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
+        }
+    }
+}

TEMPAT SAMPAH
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

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini