gt:         if (mState == STATE_DISCONNECTED) {
context: /*
   * 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 eu.codlab.flappi.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 eu.codlab.flappi.R;
  
  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();
  
          void onErrorServices();
  
          /** 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() {
  