Android Open Source - MorseWithPals Game Helper






From Project

Back to project page MorseWithPals.

License

The source code is released under:

MIT License

If you think the Android project MorseWithPals listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright (C) 2013 Google Inc./*from   ww  w .  j a va  2s .com*/
 *
 * 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 com.google.example.games.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.games.multiplayer.turnbased.TurnBasedMatch;
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;

    /*
     * If we got turn-based match when we connected to the games client, it's here. Otherwise, it's
     * null.
     */
    TurnBasedMatch mTurnBasedMatch;

    // 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");

            // If you want to suppress the signin interstitial, set setShowConnectingPopup to false.
            mGamesClient = new GamesClient.Builder(getContext(), this, this)
                    .setGravityForPopups(Gravity.TOP | Gravity.CENTER_HORIZONTAL)
                    .setScopes(mScopes)
                    .setShowConnectingPopup(true)
                    .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;
    }

    /**
     * Returns the tbmp match received through an invitation notification. This
     * should be called from your GameHelperListener's
     * @link{GameHelperListener#onSignInSucceeded} method, to check if there's a
     * match available.
     * @return The match, or null if none was received.
     */
    public TurnBasedMatch getTurnBasedMatch() {
        if (!checkState(TYPE_DEVELOPER_ERROR, "getTurnBasedMatch",
                "TurnBasedMatch is only available when connected "
                        + "(after getting the onSignInSucceeded callback).",
                STATE_CONNECTED)) {
            return null;
        }
        return mTurnBasedMatch;
    }

    /** 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) {

        // Don't retain references to old matches.
        mTurnBasedMatch = null;

        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);
            }

            debugLog("onConnected: connection hint provided. Checking for TBMP game.");

            TurnBasedMatch match = connectionHint
                    .getParcelable(GamesClient.EXTRA_TURN_BASED_MATCH);

            if (match != null) {
                mTurnBasedMatch = match;
            }

        }

        // 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));
    }

}




Java Source Code List

com.funintended.morsepals.AuthActivity.java
com.funintended.morsepals.BusProvider.java
com.funintended.morsepals.CountdownTimer.java
com.funintended.morsepals.GameActivity.java
com.funintended.morsepals.GameChallengeFragment.java
com.funintended.morsepals.GameState.java
com.funintended.morsepals.MainActivity.java
com.funintended.morsepals.MorseCodeConverter.java
com.funintended.morsepals.MorseGameHost.java
com.funintended.morsepals.MorseHelpDialog.java
com.funintended.morsepals.PreGameFragment.java
com.google.example.games.basegameutils.BaseGameActivity.java
com.google.example.games.basegameutils.GameHelper.java