callback) {
+ mClient.ManuallyAuthenticate(callback);
+ }
+
+ ///
+ /// Determines whether the user is authenticated.
+ ///
+ ///
+ /// true if the user is authenticated; otherwise, false.
+ ///
+ public bool IsAuthenticated()
+ {
+ return mClient != null && mClient.IsAuthenticated();
+ }
+
+ ///
+ /// Requests server-side access to Player Games Services for the currently signed in player.
+ ///
+ /// When requested an authorization code is returned that can be used by your game-server to
+ /// exchange for an access token and conditionally a refresh token (when
+ /// forceRefreshToken is true). The access token may then be used by your game-server to
+ /// access the Play Games Services web APIs. This is commonly used to complete a sign-in flow
+ /// by verifying the Play Games Services player id.
+ ///
+ /// If forceRefreshToken is true, when exchanging the authorization code a refresh
+ /// token will be returned in addition to the access token. The refresh token allows the
+ /// game-server to request additional access tokens, allowing your game-server to continue
+ /// accesses Play Games Services while the user is not actively playing your app.
+ /// If set to true, a refresh token will be returned along with the access token.
+ /// The callback to invoke with the server authorization code.
+ public void RequestServerSideAccess(bool forceRefreshToken, Action callback)
+ {
+ Misc.CheckNotNull(callback);
+
+ if (!IsAuthenticated())
+ {
+ OurUtils.Logger.e("RequestServerSideAccess() can only be called after authentication.");
+ InvokeCallbackOnGameThread(callback, null);
+ return;
+ }
+
+ mClient.RequestServerSideAccess(forceRefreshToken, callback);
+ }
+
+ ///
+ /// Requests server-side access to Player Games Services for the currently signed in player.
+ ///
+ /// When requested an authorization code is returned that can be used by your game-server to
+ /// exchange for an access token and conditionally a refresh token (when
+ /// forceRefreshToken is true). The access token may then be used by your game-server to
+ /// access the Play Games Services web APIs. This is commonly used to complete a sign-in flow
+ /// by verifying the Play Games Services player id.
+ ///
+ /// If forceRefreshToken is true, when exchanging the authorization code a refresh
+ /// token will be returned in addition to the access token. The refresh token allows the
+ /// game-server to request additional access tokens, allowing your game-server to continue
+ /// accesses Play Games Services while the user is not actively playing your app.
+ /// If set to true, a refresh token will be returned along with the access token.
+ /// The OAuth 2.0 scopes to request access to.
+ /// The callback to invoke with the AuthResponse.
+ public void RequestServerSideAccess(bool forceRefreshToken, List scopes, Action callback)
+ {
+ Misc.CheckNotNull(callback);
+
+ if (!IsAuthenticated())
+ {
+ OurUtils.Logger.e("RequestServerSideAccess() can only be called after authentication.");
+ InvokeCallbackOnGameThread(callback, null);
+ return;
+ }
+
+ mClient.RequestServerSideAccess(forceRefreshToken, scopes, callback);
+ }
+
+ ///
+ /// Requests access to the recall API.
+ ///
+ /// The callback to invoke with the recall access.
+ public void RequestRecallAccess(Action callback)
+ {
+ Misc.CheckNotNull(callback);
+
+ mClient.RequestRecallAccessToken(callback);
+ }
+
+ ///
+ /// Loads the users.
+ ///
+ /// User identifiers.
+ /// Callback invoked when complete.
+ public void LoadUsers(string[] userIds, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "GetUserId() can only be called after authentication.");
+ callback(new IUserProfile[0]);
+
+ return;
+ }
+
+ mClient.LoadUsers(userIds, callback);
+ }
+
+ ///
+ /// Returns the user's Google ID.
+ ///
+ ///
+ /// The user's Google ID. No guarantees are made as to the meaning or format of
+ /// this identifier except that it is unique to the user who is signed in.
+ ///
+ public string GetUserId()
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "GetUserId() can only be called after authentication.");
+ return "0";
+ }
+
+ return mClient.GetUserId();
+ }
+
+ ///
+ /// Gets the player stats.
+ ///
+ /// Callback invoked when completed.
+ public void GetPlayerStats(Action callback)
+ {
+ if (mClient != null && mClient.IsAuthenticated())
+ {
+ mClient.GetPlayerStats(callback);
+ }
+ else
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "GetPlayerStats can only be called after authentication.");
+
+ callback(CommonStatusCodes.SignInRequired, new PlayerStats());
+ }
+ }
+
+ ///
+ /// Returns the user's display name.
+ ///
+ ///
+ /// The user display name. For example, "Bruno Oliveira"
+ ///
+ public string GetUserDisplayName()
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "GetUserDisplayName can only be called after authentication.");
+ return string.Empty;
+ }
+
+ return mClient.GetUserDisplayName();
+ }
+
+ ///
+ /// Returns the user's avatar URL if they have one.
+ ///
+ ///
+ /// The URL, or null if the user is not authenticated or does not have
+ /// an avatar.
+ ///
+ public string GetUserImageUrl()
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "GetUserImageUrl can only be called after authentication.");
+ return null;
+ }
+
+ return mClient.GetUserImageUrl();
+ }
+
+ ///
+ /// Reports the progress of an achievement (reveal, unlock or increment). This method attempts
+ /// to implement the expected behavior of ISocialPlatform.ReportProgress as closely as possible,
+ /// as described below. Although this method works with incremental achievements for compatibility
+ /// purposes, calling this method for incremental achievements is not recommended,
+ /// since the Play Games API exposes incremental achievements in a very different way
+ /// than the interface presented by ISocialPlatform.ReportProgress. The implementation of this
+ /// method for incremental achievements attempts to produce the correct result, but may be
+ /// imprecise. If possible, call instead.
+ ///
+ ///
+ /// The ID of the achievement to unlock, reveal or increment. This can be a raw Google Play
+ /// Games achievement ID (alphanumeric string), or an alias that was previously configured
+ /// by a call to .
+ ///
+ ///
+ /// Progress of the achievement. If the achievement is standard (not incremental), then
+ /// a progress of 0.0 will reveal the achievement and 100.0 will unlock it. Behavior of other
+ /// values is undefined. If the achievement is incremental, then this value is interpreted
+ /// as the total percentage of the achievement's progress that the player should have
+ /// as a result of this call (regardless of the progress they had before). So if the
+ /// player's previous progress was 30% and this call specifies 50.0, the new progress will
+ /// be 50% (not 80%).
+ ///
+ ///
+ /// Callback that will be called to report the result of the operation: true on
+ /// success, false otherwise.
+ ///
+ public void ReportProgress(string achievementID, double progress, Action callback)
+ {
+ callback = ToOnGameThread(callback);
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "ReportProgress can only be called after authentication.");
+ callback.Invoke(false);
+
+ return;
+ }
+
+ // map ID, if it's in the dictionary
+ GooglePlayGames.OurUtils.Logger.d("ReportProgress, " + achievementID + ", " + progress);
+ achievementID = MapId(achievementID);
+
+ // if progress is 0.0, we just want to reveal it
+ if (progress < 0.000001)
+ {
+ GooglePlayGames.OurUtils.Logger.d(
+ "Progress 0.00 interpreted as request to reveal.");
+ mClient.RevealAchievement(achievementID, callback);
+ return;
+ }
+
+ mClient.LoadAchievements(ach =>
+ {
+ for (int i = 0; i < ach.Length; i++)
+ {
+ if (ach[i].Id == achievementID)
+ {
+ if (ach[i].IsIncremental)
+ {
+ GooglePlayGames.OurUtils.Logger.d("Progress " + progress +
+ " interpreted as incremental target (approximate).");
+
+ if (progress >= 0.0 && progress <= 1.0)
+ {
+ // in a previous version, incremental progress was reported by using the range [0-1]
+ GooglePlayGames.OurUtils.Logger.w(
+ "Progress " + progress +
+ " is less than or equal to 1. You might be trying to use values in the range of [0,1], while values are expected to be within the range [0,100]. If you are using the latter, you can safely ignore this message.");
+ }
+
+ mClient.SetStepsAtLeast(achievementID, progressToSteps(progress, ach[i].TotalSteps), callback);
+ }
+ else
+ {
+ if (progress >= 100)
+ {
+ // unlock it!
+ GooglePlayGames.OurUtils.Logger.d("Progress " + progress + " interpreted as UNLOCK.");
+ mClient.UnlockAchievement(achievementID, callback);
+ }
+ else
+ {
+ // not enough to unlock
+ GooglePlayGames.OurUtils.Logger.d(
+ "Progress " + progress + " not enough to unlock non-incremental achievement.");
+ callback.Invoke(false);
+ }
+ }
+
+ return;
+ }
+ }
+
+ // Achievement not found
+ GooglePlayGames.OurUtils.Logger.e("Unable to locate achievement " + achievementID);
+ callback.Invoke(false);
+ });
+ }
+
+ ///
+ /// Converts a progress value to a number of steps.
+ ///
+ ///
+ /// The progress value.
+ ///
+ ///
+ /// The total number of steps.
+ ///
+ ///
+ /// The number of steps.
+ ///
+ internal static int progressToSteps(double progress, int totalSteps) {
+ return (progress >= 100.0) ? totalSteps : (int) (progress * totalSteps / 100.0);
+ }
+
+ ///
+ /// Reveals the achievement with the passed identifier. This is a Play Games extension of the ISocialPlatform API.
+ ///
+ /// If the operation succeeds, the callback
+ /// will be invoked on the game thread with true. If the operation fails, the
+ /// callback will be invoked with false. This operation will immediately fail if
+ /// the user is not authenticated (the callback will immediately be invoked with
+ /// false). If the achievement is already in a revealed state, this call will
+ /// succeed immediately.
+ ///
+ ///
+ /// The ID of the achievement to increment. This can be a raw Google Play
+ /// Games achievement ID (alphanumeric string), or an alias that was previously configured
+ /// by a call to .
+ ///
+ ///
+ /// The callback to call to report the success or failure of the operation. The callback
+ /// will be called with true to indicate success or false for failure.
+ ///
+ public void RevealAchievement(string achievementID, Action callback = null)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "RevealAchievement can only be called after authentication.");
+ if (callback != null)
+ {
+ callback.Invoke(false);
+ }
+
+ return;
+ }
+
+ // map ID, if it's in the dictionary
+ GooglePlayGames.OurUtils.Logger.d(
+ "RevealAchievement: " + achievementID);
+ achievementID = MapId(achievementID);
+ mClient.RevealAchievement(achievementID, callback);
+ }
+
+ ///
+ /// Unlocks the achievement with the passed identifier. This is a Play Games extension of the ISocialPlatform API.
+ ///
+ /// If the operation succeeds, the callback
+ /// will be invoked on the game thread with true. If the operation fails, the
+ /// callback will be invoked with false. This operation will immediately fail if
+ /// the user is not authenticated (the callback will immediately be invoked with
+ /// false). If the achievement is already unlocked, this call will
+ /// succeed immediately.
+ ///
+ ///
+ /// The ID of the achievement to increment. This can be a raw Google Play
+ /// Games achievement ID (alphanumeric string), or an alias that was previously configured
+ /// by a call to .
+ ///
+ ///
+ /// The callback to call to report the success or failure of the operation. The callback
+ /// will be called with true to indicate success or false for failure.
+ ///
+ public void UnlockAchievement(string achievementID, Action callback = null)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "UnlockAchievement can only be called after authentication.");
+ if (callback != null)
+ {
+ callback.Invoke(false);
+ }
+
+ return;
+ }
+
+ // map ID, if it's in the dictionary
+ GooglePlayGames.OurUtils.Logger.d(
+ "UnlockAchievement: " + achievementID);
+ achievementID = MapId(achievementID);
+ mClient.UnlockAchievement(achievementID, callback);
+ }
+
+ ///
+ /// Increments an achievement. This is a Play Games extension of the ISocialPlatform API.
+ ///
+ ///
+ /// The ID of the achievement to increment. This can be a raw Google Play
+ /// Games achievement ID (alphanumeric string), or an alias that was previously configured
+ /// by a call to .
+ ///
+ ///
+ /// The number of steps to increment the achievement by.
+ ///
+ ///
+ /// The callback to call to report the success or failure of the operation. The callback
+ /// will be called with true to indicate success or false for failure.
+ ///
+ public void IncrementAchievement(string achievementID, int steps, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "IncrementAchievement can only be called after authentication.");
+ if (callback != null)
+ {
+ callback.Invoke(false);
+ }
+
+ return;
+ }
+
+ // map ID, if it's in the dictionary
+ GooglePlayGames.OurUtils.Logger.d(
+ "IncrementAchievement: " + achievementID + ", steps " + steps);
+ achievementID = MapId(achievementID);
+ mClient.IncrementAchievement(achievementID, steps, callback);
+ }
+
+ ///
+ /// Set an achievement to have at least the given number of steps completed.
+ /// Calling this method while the achievement already has more steps than
+ /// the provided value is a no-op. Once the achievement reaches the
+ /// maximum number of steps, the achievement is automatically unlocked,
+ /// and any further mutation operations are ignored.
+ ///
+ ///
+ /// The ID of the achievement to increment. This can be a raw Google Play
+ /// Games achievement ID (alphanumeric string), or an alias that was previously configured
+ /// by a call to .
+ ///
+ ///
+ /// The number of steps to increment the achievement by.
+ ///
+ ///
+ /// The callback to call to report the success or failure of the operation. The callback
+ /// will be called with true to indicate success or false for failure.
+ ///
+ public void SetStepsAtLeast(string achievementID, int steps, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "SetStepsAtLeast can only be called after authentication.");
+ if (callback != null)
+ {
+ callback.Invoke(false);
+ }
+
+ return;
+ }
+
+ // map ID, if it's in the dictionary
+ GooglePlayGames.OurUtils.Logger.d(
+ "SetStepsAtLeast: " + achievementID + ", steps " + steps);
+ achievementID = MapId(achievementID);
+ mClient.SetStepsAtLeast(achievementID, steps, callback);
+ }
+
+ ///
+ /// Loads the Achievement descriptions.
+ ///
+ /// The callback to receive the descriptions
+ public void LoadAchievementDescriptions(Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "LoadAchievementDescriptions can only be called after authentication.");
+ if (callback != null)
+ {
+ callback.Invoke(null);
+ }
+
+ return;
+ }
+
+ mClient.LoadAchievements(ach =>
+ {
+ IAchievementDescription[] data = new IAchievementDescription[ach.Length];
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = new PlayGamesAchievement(ach[i]);
+ }
+
+ callback.Invoke(data);
+ });
+ }
+
+ ///
+ /// Loads the achievement state for the current user.
+ ///
+ /// The callback to receive the achievements
+ public void LoadAchievements(Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("LoadAchievements can only be called after authentication.");
+ callback.Invoke(null);
+
+ return;
+ }
+
+ mClient.LoadAchievements(ach =>
+ {
+ IAchievement[] data = new IAchievement[ach.Length];
+ for (int i = 0; i < data.Length; i++)
+ {
+ data[i] = new PlayGamesAchievement(ach[i]);
+ }
+
+ callback.Invoke(data);
+ });
+ }
+
+ ///
+ /// Creates an achievement object which may be subsequently used to report an
+ /// achievement.
+ ///
+ ///
+ /// The achievement object.
+ ///
+ public IAchievement CreateAchievement()
+ {
+ return new PlayGamesAchievement();
+ }
+
+ ///
+ /// Reports a score to a leaderboard.
+ ///
+ ///
+ /// The score to report.
+ ///
+ ///
+ /// The ID of the leaderboard on which the score is to be posted. This may be a raw
+ /// Google Play Games leaderboard ID or an alias configured through a call to
+ /// .
+ ///
+ ///
+ /// The callback to call to report the success or failure of the operation. The callback
+ /// will be called with true to indicate success or false for failure.
+ ///
+ public void ReportScore(long score, string board, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("ReportScore can only be called after authentication.");
+ if (callback != null)
+ {
+ callback.Invoke(false);
+ }
+
+ return;
+ }
+
+ GooglePlayGames.OurUtils.Logger.d("ReportScore: score=" + score + ", board=" + board);
+ string leaderboardId = MapId(board);
+ mClient.SubmitScore(leaderboardId, score, callback);
+ }
+
+ ///
+ /// Submits the score for the currently signed-in player
+ /// to the leaderboard associated with a specific id
+ /// and metadata (such as something the player did to earn the score).
+ ///
+ /// Score to report.
+ /// leaderboard id.
+ /// metadata about the score.
+ /// Callback invoked upon completion.
+ public void ReportScore(long score, string board, string metadata, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("ReportScore can only be called after authentication.");
+ if (callback != null)
+ {
+ callback.Invoke(false);
+ }
+
+ return;
+ }
+
+ GooglePlayGames.OurUtils.Logger.d("ReportScore: score=" + score +
+ ", board=" + board +
+ " metadata=" + metadata);
+ string leaderboardId = MapId(board);
+ mClient.SubmitScore(leaderboardId, score, metadata, callback);
+ }
+
+ ///
+ /// Loads the scores relative the player.
+ ///
+ /// This returns the 25
+ /// (which is the max results returned by the SDK per call) scores
+ /// that are around the player's score on the Public, all time leaderboard.
+ /// Use the overloaded methods which are specific to GPGS to modify these
+ /// parameters.
+ ///
+ /// Leaderboard Id
+ /// Callback to invoke when completed.
+ public void LoadScores(string leaderboardId, Action callback)
+ {
+ LoadScores(
+ leaderboardId,
+ LeaderboardStart.PlayerCentered,
+ mClient.LeaderboardMaxResults(),
+ LeaderboardCollection.Public,
+ LeaderboardTimeSpan.AllTime,
+ (scoreData) => callback(scoreData.Scores));
+ }
+
+ ///
+ /// Loads the scores using the provided parameters. This call may fail when trying to load friends with
+ /// ResponseCode.ResolutionRequired if the user has not share the friends list with the game. In this case, use
+ /// AskForLoadFriendsResolution to request access.
+ ///
+ /// Leaderboard identifier.
+ /// Start either top scores, or player centered.
+ /// Row count. the number of rows to return.
+ /// Collection. social or public
+ /// Time span. daily, weekly, all-time
+ /// Callback to invoke when completed.
+ public void LoadScores(
+ string leaderboardId,
+ LeaderboardStart start,
+ int rowCount,
+ LeaderboardCollection collection,
+ LeaderboardTimeSpan timeSpan,
+ Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("LoadScores can only be called after authentication.");
+ callback(new LeaderboardScoreData(
+ leaderboardId,
+ ResponseStatus.NotAuthorized));
+ return;
+ }
+
+ mClient.LoadScores(
+ leaderboardId,
+ start,
+ rowCount,
+ collection,
+ timeSpan,
+ callback);
+ }
+
+ ///
+ /// Loads more scores. This call may fail when trying to load friends with
+ /// ResponseCode.ResolutionRequired if the user has not share the friends list with the game. In this case, use
+ /// AskForLoadFriendsResolution to request access.
+ ///
+ /// This is used to load the next "page" of scores.
+ /// Token used to recording the loading.
+ /// Row count.
+ /// Callback invoked when complete.
+ public void LoadMoreScores(
+ ScorePageToken token,
+ int rowCount,
+ Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("LoadMoreScores can only be called after authentication.");
+ callback(
+ new LeaderboardScoreData(
+ token.LeaderboardId,
+ ResponseStatus.NotAuthorized));
+ return;
+ }
+
+ mClient.LoadMoreScores(token, rowCount, callback);
+ }
+
+ ///
+ /// Returns a leaderboard object that can be configured to
+ /// load scores.
+ ///
+ /// The leaderboard object.
+ public ILeaderboard CreateLeaderboard()
+ {
+ return new PlayGamesLeaderboard(mDefaultLbUi);
+ }
+
+ ///
+ /// Shows the standard Google Play Games achievements user interface,
+ /// which allows the player to browse their achievements.
+ ///
+ public void ShowAchievementsUI()
+ {
+ ShowAchievementsUI(null);
+ }
+
+ ///
+ /// Shows the standard Google Play Games achievements user interface,
+ /// which allows the player to browse their achievements.
+ ///
+ /// If non-null, the callback is invoked when
+ /// the achievement UI is dismissed
+ public void ShowAchievementsUI(Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("ShowAchievementsUI can only be called after authentication.");
+ return;
+ }
+
+ GooglePlayGames.OurUtils.Logger.d("ShowAchievementsUI callback is " + callback);
+ mClient.ShowAchievementsUI(callback);
+ }
+
+ ///
+ /// Shows the standard Google Play Games leaderboards user interface,
+ /// which allows the player to browse their leaderboards. If you have
+ /// configured a specific leaderboard as the default through a call to
+ /// , the UI will show that
+ /// specific leaderboard only. Otherwise, a list of all the leaderboards
+ /// will be shown.
+ ///
+ public void ShowLeaderboardUI()
+ {
+ GooglePlayGames.OurUtils.Logger.d("ShowLeaderboardUI with default ID");
+ ShowLeaderboardUI(MapId(mDefaultLbUi), null);
+ }
+
+ ///
+ /// Shows the standard Google Play Games leaderboard UI for the given
+ /// leaderboard.
+ ///
+ ///
+ /// The ID of the leaderboard to display. This may be a raw
+ /// Google Play Games leaderboard ID or an alias configured through a call to
+ /// .
+ ///
+ public void ShowLeaderboardUI(string leaderboardId)
+ {
+ if (leaderboardId != null)
+ {
+ leaderboardId = MapId(leaderboardId);
+ }
+
+ ShowLeaderboardUI(leaderboardId, LeaderboardTimeSpan.AllTime, null);
+ }
+
+ ///
+ /// Shows the leaderboard UI and calls the specified callback upon
+ /// completion.
+ ///
+ /// leaderboard ID, can be null meaning all leaderboards.
+ /// Callback to call. If null, nothing is called.
+ public void ShowLeaderboardUI(string leaderboardId, Action callback)
+ {
+ ShowLeaderboardUI(leaderboardId, LeaderboardTimeSpan.AllTime, callback);
+ }
+
+ ///
+ /// Shows the leaderboard UI and calls the specified callback upon
+ /// completion.
+ ///
+ /// leaderboard ID, can be null meaning all leaderboards.
+ /// Timespan to display scores in the leaderboard.
+ /// Callback to call. If null, nothing is called.
+ public void ShowLeaderboardUI(
+ string leaderboardId,
+ LeaderboardTimeSpan span,
+ Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("ShowLeaderboardUI can only be called after authentication.");
+ if (callback != null)
+ {
+ callback(UIStatus.NotAuthorized);
+ }
+
+ return;
+ }
+
+ GooglePlayGames.OurUtils.Logger.d("ShowLeaderboardUI, lbId=" +
+ leaderboardId + " callback is " + callback);
+ mClient.ShowLeaderboardUI(leaderboardId, span, callback);
+ }
+
+ ///
+ /// Sets the default leaderboard for the leaderboard UI. After calling this
+ /// method, a call to will show only the specified
+ /// leaderboard instead of showing the list of all leaderboards.
+ ///
+ ///
+ /// The ID of the leaderboard to display on the default UI. This may be a raw
+ /// Google Play Games leaderboard ID or an alias configured through a call to
+ /// .
+ ///
+ public void SetDefaultLeaderboardForUI(string lbid)
+ {
+ GooglePlayGames.OurUtils.Logger.d("SetDefaultLeaderboardForUI: " + lbid);
+ if (lbid != null)
+ {
+ lbid = MapId(lbid);
+ }
+
+ mDefaultLbUi = lbid;
+ }
+
+ ///
+ /// Loads the friends that also play this game. See loadConnectedPlayers.
+ ///
+ /// This is a callback variant of LoadFriends. When completed,
+ /// the friends list set in the user object, so they can accessed via the
+ /// friends property as needed.
+ ///
+ /// The current local user
+ /// Callback invoked when complete.
+ public void LoadFriends(ILocalUser user, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "LoadScores can only be called after authentication.");
+ if (callback != null)
+ {
+ callback(false);
+ }
+
+ return;
+ }
+
+ mClient.LoadFriends(callback);
+ }
+
+ ///
+ /// Loads the leaderboard based on the constraints in the leaderboard
+ /// object.
+ ///
+ /// The leaderboard object. This is created by
+ /// calling CreateLeaderboard(), and then initialized appropriately.
+ /// Callback invoked when complete.
+ public void LoadScores(ILeaderboard board, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e("LoadScores can only be called after authentication.");
+ if (callback != null)
+ {
+ callback(false);
+ }
+
+ return;
+ }
+
+ LeaderboardTimeSpan timeSpan;
+ switch (board.timeScope)
+ {
+ case TimeScope.AllTime:
+ timeSpan = LeaderboardTimeSpan.AllTime;
+ break;
+ case TimeScope.Week:
+ timeSpan = LeaderboardTimeSpan.Weekly;
+ break;
+ case TimeScope.Today:
+ timeSpan = LeaderboardTimeSpan.Daily;
+ break;
+ default:
+ timeSpan = LeaderboardTimeSpan.AllTime;
+ break;
+ }
+
+ ((PlayGamesLeaderboard) board).loading = true;
+ GooglePlayGames.OurUtils.Logger.d("LoadScores, board=" + board +
+ " callback is " + callback);
+ mClient.LoadScores(
+ board.id,
+ LeaderboardStart.PlayerCentered,
+ board.range.count > 0 ? board.range.count : mClient.LeaderboardMaxResults(),
+ board.userScope == UserScope.FriendsOnly ? LeaderboardCollection.Social : LeaderboardCollection.Public,
+ timeSpan,
+ (scoreData) => HandleLoadingScores(
+ (PlayGamesLeaderboard) board, scoreData, callback));
+ }
+
+ ///
+ /// Check if the leaderboard is currently loading.
+ ///
+ /// true, if loading was gotten, false otherwise.
+ /// The leaderboard to check for loading in progress
+ public bool GetLoading(ILeaderboard board)
+ {
+ return board != null && board.loading;
+ }
+
+ ///
+ /// Shows the Player Profile UI for the given user identifier.
+ ///
+ /// User Identifier.
+ ///
+ /// The game's own display name of the player referred to by userId.
+ ///
+ ///
+ /// The game's own display name of the current player.
+ ///
+ /// Callback invoked upon completion.
+ public void ShowCompareProfileWithAlternativeNameHintsUI(string userId,
+ string otherPlayerInGameName,
+ string currentPlayerInGameName,
+ Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "ShowCompareProfileWithAlternativeNameHintsUI can only be called after authentication.");
+ InvokeCallbackOnGameThread(callback, UIStatus.NotAuthorized);
+
+ return;
+ }
+
+ GooglePlayGames.OurUtils.Logger.d(
+ "ShowCompareProfileWithAlternativeNameHintsUI, userId=" + userId + " callback is " +
+ callback);
+ mClient.ShowCompareProfileWithAlternativeNameHintsUI(userId, otherPlayerInGameName,
+ currentPlayerInGameName, callback);
+ }
+
+ ///
+ /// Returns if the user has allowed permission for the game to access the friends list.
+ ///
+ /// If true, this call will clear any locally cached data and
+ /// attempt to fetch the latest data from the server. Normally, this should be set to
+ /// false to gain advantages of data caching.
+ /// Callback invoked upon completion.
+ public void GetFriendsListVisibility(bool forceReload,
+ Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "GetFriendsListVisibility can only be called after authentication.");
+ InvokeCallbackOnGameThread(callback, FriendsListVisibilityStatus.NotAuthorized);
+ return;
+ }
+
+ GooglePlayGames.OurUtils.Logger.d("GetFriendsListVisibility, callback is " + callback);
+ mClient.GetFriendsListVisibility(forceReload, callback);
+ }
+
+ ///
+ /// Shows the appropriate platform-specific friends sharing UI.
+ /// The callback to invoke when complete. If null,
+ /// no callback is called.
+ ///
+ public void AskForLoadFriendsResolution(Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "AskForLoadFriendsResolution can only be called after authentication.");
+ InvokeCallbackOnGameThread(callback, UIStatus.NotAuthorized);
+ return;
+ }
+
+ GooglePlayGames.OurUtils.Logger.d("AskForLoadFriendsResolution callback is " + callback);
+ mClient.AskForLoadFriendsResolution(callback);
+ }
+
+ ///
+ /// Gets status of the last call to load friends.
+ ///
+ public LoadFriendsStatus GetLastLoadFriendsStatus()
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "GetLastLoadFriendsStatus can only be called after authentication.");
+ return LoadFriendsStatus.NotAuthorized;
+ }
+
+ return mClient.GetLastLoadFriendsStatus();
+ }
+
+ ///
+ /// Loads the first page of the user's friends.
+ ///
+ ///
+ /// The number of entries to request for this initial page. Note that if cached
+ /// data already exists, the returned buffer may contain more than this size, but it is
+ /// guaranteed to contain at least this many if the collection contains enough records.
+ ///
+ ///
+ /// If true, this call will clear any locally cached data and attempt to
+ /// fetch the latest data from the server. This would commonly be used for something like a
+ /// user-initiated refresh. Normally, this should be set to false to gain advantages of data caching.
+ ///
+ /// Callback invoked upon completion with the status.
+ public void LoadFriends(int pageSize, bool forceReload,
+ Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "LoadFriends can only be called after authentication.");
+ InvokeCallbackOnGameThread(callback, LoadFriendsStatus.NotAuthorized);
+ return;
+ }
+
+ mClient.LoadFriends(pageSize, forceReload, callback);
+ }
+
+ ///
+ /// Loads the friends list page
+ ///
+ ///
+ /// The number of entries to request for this initial page. Note that if cached
+ /// data already exists, the returned buffer may contain more than this size, but it is
+ /// guaranteed to contain at least this many if the collection contains enough records.
+ ///
+ ///
+ public void LoadMoreFriends(int pageSize, Action callback)
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.e(
+ "LoadMoreFriends can only be called after authentication.");
+ InvokeCallbackOnGameThread(callback, LoadFriendsStatus.NotAuthorized);
+ return;
+ }
+
+ mClient.LoadMoreFriends(pageSize, callback);
+ }
+
+ ///
+ /// Handles the processing of scores during loading.
+ ///
+ /// leaderboard being loaded
+ /// Score data.
+ /// Callback invoked when complete.
+ internal void HandleLoadingScores(
+ PlayGamesLeaderboard board,
+ LeaderboardScoreData scoreData,
+ Action callback)
+ {
+ bool ok = board.SetFromData(scoreData);
+ if (ok && !board.HasAllScores() && scoreData.NextPageToken != null)
+ {
+ int rowCount = board.range.count - board.ScoreCount;
+
+ // need to load more scores
+ mClient.LoadMoreScores(
+ scoreData.NextPageToken,
+ rowCount,
+ (nextScoreData) =>
+ HandleLoadingScores(board, nextScoreData, callback));
+ }
+ else
+ {
+ callback(ok);
+ }
+ }
+
+ ///
+ /// Internal implmentation of getFriends.Gets the friends.
+ ///
+ /// The friends.
+ internal IUserProfile[] GetFriends()
+ {
+ if (!IsAuthenticated())
+ {
+ GooglePlayGames.OurUtils.Logger.d("Cannot get friends when not authenticated!");
+ return new IUserProfile[0];
+ }
+
+ return mClient.GetFriends();
+ }
+
+ ///
+ /// Maps the alias to the identifier.
+ ///
+ /// Alias to map
+ /// This maps an aliased ID to the actual id. The intent of
+ /// this method is to allow easy to read constants to be used instead of
+ /// the generated ids.
+ ///
+ /// The identifier, or null if not found.
+ private string MapId(string id)
+ {
+ if (id == null)
+ {
+ return null;
+ }
+
+ if (mIdMap.ContainsKey(id))
+ {
+ string result = mIdMap[id];
+ GooglePlayGames.OurUtils.Logger.d("Mapping alias " + id + " to ID " + result);
+ return result;
+ }
+
+ return id;
+ }
+
+ private static void InvokeCallbackOnGameThread(Action callback, T data)
+ {
+ if (callback == null)
+ {
+ return;
+ }
+
+ PlayGamesHelperObject.RunOnGameThread(() => { callback(data); });
+ }
+
+ private static Action ToOnGameThread(Action toConvert)
+ {
+ if (toConvert == null)
+ {
+ return delegate { };
+ }
+
+ return (val) => PlayGamesHelperObject.RunOnGameThread(() => toConvert(val));
+ }
+ }
+}
+#endif
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesPlatform.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesPlatform.cs.meta
new file mode 100644
index 0000000..994be87
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesPlatform.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: c1de7754a6e7f4fb08b76780a184b3ca
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesScore.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesScore.cs
new file mode 100644
index 0000000..132a74e
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesScore.cs
@@ -0,0 +1,149 @@
+//
+// Copyright (C) 2014 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.
+//
+
+#if UNITY_ANDROID
+
+namespace GooglePlayGames
+{
+ using System;
+ using UnityEngine.SocialPlatforms;
+
+ ///
+ /// Represents a score on a Google Play Games leaderboard. Implements the Unity
+ /// IScore interface.
+ ///
+ public class PlayGamesScore : IScore
+ {
+ ///
+ /// The ID of the leaderboard this score belongs to.
+ ///
+ private string mLbId = null;
+
+ ///
+ /// The numerical value of the score.
+ ///
+ private long mValue = 0;
+
+ ///
+ /// The rank of this score on the leaderboard.
+ ///
+ private ulong mRank = 0;
+
+ ///
+ /// The ID of the player who achieved this score.
+ ///
+ private string mPlayerId = string.Empty;
+
+ ///
+ /// The metadata associated with this score (also known as a score tag).
+ ///
+ private string mMetadata = string.Empty;
+
+ ///
+ /// The date and time when the score was achieved.
+ ///
+ private DateTime mDate = new DateTime(1970, 1, 1, 0, 0, 0);
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The date the score was achieved.
+ /// The leaderboard ID.
+ /// The rank of the score.
+ /// The player's ID.
+ /// The numerical score value.
+ /// The metadata (score tag) associated with the score.
+ internal PlayGamesScore(DateTime date, string leaderboardId,
+ ulong rank, string playerId, ulong value, string metadata)
+ {
+ this.mDate = date;
+ mLbId = leaderboardID;
+ this.mRank = rank;
+ this.mPlayerId = playerId;
+ this.mValue = (long) value;
+ this.mMetadata = metadata;
+ }
+
+ ///
+ /// Reports this score to the Google Play Games services. This is equivalent
+ /// to calling .
+ ///
+ /// A callback to be invoked with a boolean indicating the success of the operation.
+ public void ReportScore(Action callback)
+ {
+ PlayGamesPlatform.Instance.ReportScore(mValue, mLbId, mMetadata, callback);
+ }
+
+ ///
+ /// Gets or sets the ID of the leaderboard this score is for.
+ ///
+ public string leaderboardID
+ {
+ get { return mLbId; }
+ set { mLbId = value; }
+ }
+
+ ///
+ /// Gets or sets the score value.
+ ///
+ public long value
+ {
+ get { return mValue; }
+ set { mValue = value; }
+ }
+
+ ///
+ /// Gets the date and time this score was achieved.
+ ///
+ public DateTime date
+ {
+ get { return mDate; }
+ }
+
+ ///
+ /// Gets the score value as a formatted string.
+ ///
+ public string formattedValue
+ {
+ get { return mValue.ToString(); }
+ }
+
+ ///
+ /// Gets the ID of the user who achieved this score.
+ ///
+ public string userID
+ {
+ get { return mPlayerId; }
+ }
+
+ ///
+ /// Gets the rank of this score in the leaderboard.
+ ///
+ public int rank
+ {
+ get { return (int) mRank; }
+ }
+
+ ///
+ /// Gets the metadata associated with this score (also known as a score tag).
+ ///
+ public string metaData
+ {
+ get { return mMetadata; }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesScore.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesScore.cs.meta
new file mode 100644
index 0000000..e45e321
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesScore.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 2a6e2425305ab455a91061b1eb955b38
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesUserProfile.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesUserProfile.cs
new file mode 100644
index 0000000..0f9440d
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesUserProfile.cs
@@ -0,0 +1,299 @@
+//
+// Copyright (C) 2014 Google Inc. All Rights Reserved.
+//
+// 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.
+//
+
+#if UNITY_ANDROID
+
+namespace GooglePlayGames
+{
+ using System;
+ using System.Collections;
+ using GooglePlayGames.OurUtils;
+ using UnityEngine;
+#if UNITY_2017_2_OR_NEWER
+ using UnityEngine.Networking;
+#endif
+ using UnityEngine.SocialPlatforms;
+
+ ///
+ /// Represents a Google Play Games user profile. Implements the Unity's IUserProfile
+ /// interface and is used as a base class for .
+ ///
+ public class PlayGamesUserProfile : IUserProfile
+ {
+ ///
+ /// The user's display name.
+ ///
+ private string mDisplayName;
+
+ ///
+ /// The user's unique player ID.
+ ///
+ private string mPlayerId;
+
+ ///
+ /// The URL for the user's avatar image.
+ ///
+ private string mAvatarUrl;
+
+ ///
+ /// A flag indicating if this user is a friend of the local user.
+ ///
+ private bool mIsFriend;
+
+ ///
+ /// A flag to prevent multiple concurrent image loading coroutines.
+ ///
+ private volatile bool mImageLoading = false;
+
+ ///
+ /// The cached user avatar image.
+ ///
+ private Texture2D mImage;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The user's display name.
+ /// The user's player ID.
+ /// The URL of the user's avatar.
+ internal PlayGamesUserProfile(string displayName, string playerId,
+ string avatarUrl)
+ {
+ mDisplayName = displayName;
+ mPlayerId = playerId;
+ setAvatarUrl(avatarUrl);
+ mImageLoading = false;
+ mIsFriend = false;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The user's display name.
+ /// The user's player ID.
+ /// The URL of the user's avatar.
+ /// A flag indicating if the user is a friend.
+ internal PlayGamesUserProfile(string displayName, string playerId, string avatarUrl,
+ bool isFriend)
+ {
+ mDisplayName = displayName;
+ mPlayerId = playerId;
+ mAvatarUrl = avatarUrl;
+ mImageLoading = false;
+ mIsFriend = isFriend;
+ }
+
+ ///
+ /// Resets the user's identity with new information. If the avatar URL has changed,
+ /// the old image is discarded.
+ ///
+ /// The new display name.
+ /// The new player ID.
+ /// The new avatar URL.
+ protected void ResetIdentity(string displayName, string playerId,
+ string avatarUrl)
+ {
+ mDisplayName = displayName;
+ mPlayerId = playerId;
+ mIsFriend = false;
+ if (mAvatarUrl != avatarUrl)
+ {
+ mImage = null;
+ setAvatarUrl(avatarUrl);
+ }
+
+ mImageLoading = false;
+ }
+
+ #region IUserProfile implementation
+
+ ///
+ /// Gets the user's display name.
+ ///
+ /// The name of the user.
+ public string userName
+ {
+ get { return mDisplayName; }
+ }
+
+ ///
+ /// Gets the user's unique player ID.
+ ///
+ /// The player ID.
+ public string id
+ {
+ get { return mPlayerId; }
+ }
+
+ ///
+ /// Gets the user's game-specific identifier. In this implementation, it is the same as the player ID.
+ ///
+ public string gameId
+ {
+ get { return mPlayerId; }
+ }
+
+ ///
+ /// Gets a value indicating whether this user is a friend of the local user.
+ ///
+ /// true if this user is a friend; otherwise, false.
+ public bool isFriend
+ {
+ get { return mIsFriend; }
+ }
+
+ ///
+ /// Gets the user's current state. In this implementation, it always returns 'Online'.
+ ///
+ public UserState state
+ {
+ get { return UserState.Online; }
+ }
+
+ ///
+ /// Gets the user's avatar image as a .
+ /// The image is loaded asynchronously. Returns null until the image has been loaded.
+ ///
+ /// The user's avatar image.
+ public Texture2D image
+ {
+ get
+ {
+ if (!mImageLoading && mImage == null && !string.IsNullOrEmpty(AvatarURL))
+ {
+ OurUtils.Logger.d("Starting to load image: " + AvatarURL);
+ mImageLoading = true;
+ PlayGamesHelperObject.RunCoroutine(LoadImage());
+ }
+
+ return mImage;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Gets the URL of the user's avatar.
+ ///
+ public string AvatarURL
+ {
+ get { return mAvatarUrl; }
+ }
+
+ ///
+ /// An enumerator that asynchronously loads the user's avatar image from the .
+ ///
+ /// An to be used with a coroutine.
+ internal IEnumerator LoadImage()
+ {
+ // The URL can be null if the user does not have an avatar configured.
+ if (!string.IsNullOrEmpty(AvatarURL))
+ {
+#if UNITY_2017_2_OR_NEWER
+ UnityWebRequest www = UnityWebRequestTexture.GetTexture(AvatarURL);
+ www.SendWebRequest();
+#else
+ WWW www = new WWW(AvatarURL);
+#endif
+ while (!www.isDone)
+ {
+ yield return null;
+ }
+
+ if (www.error == null)
+ {
+#if UNITY_2017_2_OR_NEWER
+ this.mImage = DownloadHandlerTexture.GetContent(www);
+#else
+ this.mImage = www.texture;
+#endif
+ }
+ else
+ {
+ mImage = Texture2D.blackTexture;
+ OurUtils.Logger.e("Error downloading image: " + www.error);
+ }
+
+ mImageLoading = false;
+ }
+ else
+ {
+ OurUtils.Logger.e("No URL found.");
+ mImage = Texture2D.blackTexture;
+ mImageLoading = false;
+ }
+ }
+
+ ///
+ /// Determines whether the specified is equal to the current .
+ /// Equality is based on the player ID.
+ ///
+ /// The to compare with the current .
+ /// true if the specified object is equal to the current object; otherwise, false.
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ PlayGamesUserProfile other = obj as PlayGamesUserProfile;
+ if (other == null)
+ {
+ return false;
+ }
+
+ return StringComparer.Ordinal.Equals(mPlayerId, other.mPlayerId);
+ }
+
+ ///
+ /// Serves as a hash function for a object.
+ ///
+ /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table.
+ public override int GetHashCode()
+ {
+ return typeof(PlayGamesUserProfile).GetHashCode() ^ mPlayerId.GetHashCode();
+ }
+
+ ///
+ /// Returns a that represents the current .
+ ///
+ /// A string representation of the object.
+ public override string ToString()
+ {
+ return string.Format("[Player: '{0}' (id {1})]", mDisplayName, mPlayerId);
+ }
+
+ ///
+ /// Sets the avatar URL, ensuring it uses HTTPS.
+ ///
+ /// The avatar URL to set.
+ private void setAvatarUrl(string avatarUrl)
+ {
+ mAvatarUrl = avatarUrl;
+ if (!string.IsNullOrEmpty(mAvatarUrl) && !mAvatarUrl.StartsWith("https") && mAvatarUrl.StartsWith("http"))
+ {
+ mAvatarUrl = mAvatarUrl.Insert(4, "s");
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesUserProfile.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesUserProfile.cs.meta
new file mode 100644
index 0000000..70b194e
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/ISocialPlatform/PlayGamesUserProfile.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: ab1b90315f37e498a849765260dd436c
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils.meta
new file mode 100644
index 0000000..01304aa
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils.meta
@@ -0,0 +1,5 @@
+fileFormatVersion: 2
+guid: dc34e4ac2f7e6420da72898e7b511098
+folderAsset: yes
+DefaultImporter:
+ userData:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Logger.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Logger.cs
new file mode 100644
index 0000000..af8b8c3
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Logger.cs
@@ -0,0 +1,92 @@
+//
+// Copyright (C) 2014 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.
+//
+
+namespace GooglePlayGames.OurUtils
+{
+ using System;
+ using UnityEngine;
+
+ public class Logger
+ {
+ private static bool debugLogEnabled = false;
+
+ public static bool DebugLogEnabled
+ {
+ get { return debugLogEnabled; }
+
+ set { debugLogEnabled = value; }
+ }
+
+ private static bool warningLogEnabled = true;
+
+ public static bool WarningLogEnabled
+ {
+ get { return warningLogEnabled; }
+
+ set { warningLogEnabled = value; }
+ }
+
+ public static void d(string msg)
+ {
+ if (debugLogEnabled)
+ {
+ PlayGamesHelperObject.RunOnGameThread(() =>
+ Debug.Log(ToLogMessage(string.Empty, "DEBUG", msg)));
+ }
+ }
+
+ public static void w(string msg)
+ {
+ if (warningLogEnabled)
+ {
+ PlayGamesHelperObject.RunOnGameThread(() =>
+ Debug.LogWarning(ToLogMessage("!!!", "WARNING", msg)));
+ }
+ }
+
+ public static void e(string msg)
+ {
+ if (warningLogEnabled)
+ {
+ PlayGamesHelperObject.RunOnGameThread(() =>
+ Debug.LogWarning(ToLogMessage("***", "ERROR", msg)));
+ }
+ }
+
+ public static string describe(byte[] b)
+ {
+ return b == null ? "(null)" : "byte[" + b.Length + "]";
+ }
+
+ private static string ToLogMessage(string prefix, string logType, string msg)
+ {
+ string timeString = null;
+ try
+ {
+ timeString = DateTime.Now.ToString("MM/dd/yy H:mm:ss zzz");
+ }
+ catch (Exception)
+ {
+ PlayGamesHelperObject.RunOnGameThread(() =>
+ Debug.LogWarning("*** [Play Games Plugin " + PluginVersion.VersionString + "] ERROR: Failed to format DateTime.Now"));
+ timeString = string.Empty;
+ }
+
+ return string.Format("{0} [Play Games Plugin " + PluginVersion.VersionString+ "] {1} {2}: {3}",
+ prefix, timeString, logType, msg);
+ }
+ }
+}
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Logger.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Logger.cs.meta
new file mode 100644
index 0000000..05bbca7
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Logger.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: cde7cfd197b4a47edac2efe305e22e78
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Misc.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Misc.cs
new file mode 100644
index 0000000..8af6ea8
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Misc.cs
@@ -0,0 +1,100 @@
+//
+// Copyright (C) 2014 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.
+//
+
+namespace GooglePlayGames.OurUtils
+{
+ using System;
+
+ public static class Misc
+ {
+ public static bool BuffersAreIdentical(byte[] a, byte[] b)
+ {
+ if (a == b)
+ {
+ // not only identical but the very same!
+ return true;
+ }
+
+ if (a == null || b == null)
+ {
+ // one of them is null, the other one isn't
+ return false;
+ }
+
+ if (a.Length != b.Length)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < a.Length; i++)
+ {
+ if (a[i] != b[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static byte[] GetSubsetBytes(byte[] array, int offset, int length)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException("array");
+ }
+
+ if (offset < 0 || offset >= array.Length)
+ {
+ throw new ArgumentOutOfRangeException("offset");
+ }
+
+ if (length < 0 || (array.Length - offset) < length)
+ {
+ throw new ArgumentOutOfRangeException("length");
+ }
+
+ if (offset == 0 && length == array.Length)
+ {
+ return array;
+ }
+
+ byte[] piece = new byte[length];
+ Array.Copy(array, offset, piece, 0, length);
+ return piece;
+ }
+
+ public static T CheckNotNull(T value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ return value;
+ }
+
+ public static T CheckNotNull(T value, string paramName)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(paramName);
+ }
+
+ return value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Misc.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Misc.cs.meta
new file mode 100644
index 0000000..b4bca20
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/Misc.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: ee52269f55933442fa5ea52e688ebec2
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/NearbyHelperObject.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/NearbyHelperObject.cs
new file mode 100644
index 0000000..3b09327
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/NearbyHelperObject.cs
@@ -0,0 +1,104 @@
+#if UNITY_ANDROID
+
+namespace GooglePlayGames.OurUtils
+{
+ using BasicApi.Nearby;
+ using System;
+ using UnityEngine;
+
+ public class NearbyHelperObject : MonoBehaviour
+ {
+ // our (singleton) instance
+ private static NearbyHelperObject instance = null;
+
+ // timers to keep track of discovery and advertising
+ private static double mAdvertisingRemaining = 0;
+ private static double mDiscoveryRemaining = 0;
+
+ // nearby client to stop discovery and to stop advertising
+ private static INearbyConnectionClient mClient = null;
+
+ public static void CreateObject(INearbyConnectionClient client)
+ {
+ if (instance != null)
+ {
+ return;
+ }
+
+ mClient = client;
+ if (Application.isPlaying)
+ {
+ // add an invisible game object to the scene
+ GameObject obj = new GameObject("PlayGames_NearbyHelper");
+ DontDestroyOnLoad(obj);
+ instance = obj.AddComponent();
+ }
+ else
+ {
+ instance = new NearbyHelperObject();
+ }
+ }
+
+ private static double ToSeconds(TimeSpan? span)
+ {
+ if (!span.HasValue)
+ {
+ return 0;
+ }
+
+ if (span.Value.TotalSeconds < 0)
+ {
+ return 0;
+ }
+
+ return span.Value.TotalSeconds;
+ }
+
+ public static void StartAdvertisingTimer(TimeSpan? span)
+ {
+ mAdvertisingRemaining = ToSeconds(span);
+ }
+
+ public static void StartDiscoveryTimer(TimeSpan? span)
+ {
+ mDiscoveryRemaining = ToSeconds(span);
+ }
+
+ public void Awake()
+ {
+ DontDestroyOnLoad(gameObject);
+ }
+
+ public void OnDisable()
+ {
+ if (instance == this)
+ {
+ instance = null;
+ }
+ }
+
+ public void Update()
+ {
+ // check if currently advertising
+ if (mAdvertisingRemaining > 0)
+ {
+ mAdvertisingRemaining -= Time.deltaTime;
+ if (mAdvertisingRemaining < 0)
+ {
+ mClient.StopAdvertising();
+ }
+ }
+
+ // check if currently discovering
+ if (mDiscoveryRemaining > 0)
+ {
+ mDiscoveryRemaining -= Time.deltaTime;
+ if (mDiscoveryRemaining < 0)
+ {
+ mClient.StopDiscovery(mClient.GetServiceId());
+ }
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/NearbyHelperObject.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/NearbyHelperObject.cs.meta
new file mode 100644
index 0000000..2573b0d
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/NearbyHelperObject.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: b66cca4a5a1f4a5092a280c452185308
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlatformUtils.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlatformUtils.cs
new file mode 100644
index 0000000..43ce29e
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlatformUtils.cs
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2014 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.
+//
+
+#if UNITY_ANDROID
+namespace GooglePlayGames.OurUtils
+{
+ using UnityEngine;
+ using System;
+
+ public static class PlatformUtils
+ {
+ ///
+ /// Check if the Google Play Games platform is supported at runtime.
+ ///
+ /// If the platform is supported.
+ public static bool Supported
+ {
+ get
+ {
+#if UNITY_EDITOR
+ return false;
+#else
+ return true;
+#endif
+ }
+ }
+ }
+}
+#endif //UNITY_ANDROID
\ No newline at end of file
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlatformUtils.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlatformUtils.cs.meta
new file mode 100644
index 0000000..3b2ccfd
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlatformUtils.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 053811e778f3d4e3e98065f5db5bd005
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlayGamesHelperObject.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlayGamesHelperObject.cs
new file mode 100644
index 0000000..6c8551c
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlayGamesHelperObject.cs
@@ -0,0 +1,222 @@
+//
+// Copyright (C) 2014 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.
+//
+
+namespace GooglePlayGames.OurUtils
+{
+ using System;
+ using System.Collections;
+ using UnityEngine;
+ using System.Collections.Generic;
+
+ public class PlayGamesHelperObject : MonoBehaviour
+ {
+ // our (singleton) instance
+ private static PlayGamesHelperObject instance = null;
+
+ // are we a dummy instance (used in the editor?)
+ private static bool sIsDummy = false;
+
+ // queue of actions to run on the game thread
+ private static List sQueue = new List();
+
+ // member variable used to copy actions from the sQueue and
+ // execute them on the game thread. It is a member variable
+ // to help minimize memory allocations.
+ List localQueue = new List();
+
+ // flag that alerts us that we should check the queue
+ // (we do this just so we don't have to lock() the queue every
+ // frame to check if it's empty or not).
+ private volatile static bool sQueueEmpty = true;
+
+ // callback for application pause and focus events
+ private static List> sPauseCallbackList =
+ new List>();
+
+ private static List> sFocusCallbackList =
+ new List>();
+
+ // Call this once from the game thread
+ public static void CreateObject()
+ {
+ if (instance != null)
+ {
+ return;
+ }
+
+ if (Application.isPlaying)
+ {
+ // add an invisible game object to the scene
+ GameObject obj = new GameObject("PlayGames_QueueRunner");
+ DontDestroyOnLoad(obj);
+ instance = obj.AddComponent();
+ }
+ else
+ {
+ instance = new PlayGamesHelperObject();
+ sIsDummy = true;
+ }
+ }
+
+ public void Awake()
+ {
+ DontDestroyOnLoad(gameObject);
+ }
+
+ public void OnDisable()
+ {
+ if (instance == this)
+ {
+ instance = null;
+ }
+ }
+
+ public static void RunCoroutine(IEnumerator action)
+ {
+ if (instance != null)
+ {
+ RunOnGameThread(() => instance.StartCoroutine(action));
+ }
+ }
+
+ public static void RunOnGameThread(System.Action action)
+ {
+ if (action == null)
+ {
+ throw new ArgumentNullException("action");
+ }
+
+ if (sIsDummy)
+ {
+ return;
+ }
+
+ lock (sQueue)
+ {
+ sQueue.Add(action);
+ sQueueEmpty = false;
+ }
+ }
+
+ public void Update()
+ {
+ if (sIsDummy || sQueueEmpty)
+ {
+ return;
+ }
+
+ // first copy the shared queue into a local queue
+ localQueue.Clear();
+ lock (sQueue)
+ {
+ // transfer the whole queue to our local queue
+ localQueue.AddRange(sQueue);
+ sQueue.Clear();
+ sQueueEmpty = true;
+ }
+
+ // execute queued actions (from local queue)
+ // use a loop to avoid extra memory allocations using the
+ // forEach
+ for (int i = 0; i < localQueue.Count; i++)
+ {
+ localQueue[i].Invoke();
+ }
+ }
+
+ public void OnApplicationFocus(bool focused)
+ {
+ foreach (Action cb in sFocusCallbackList)
+ {
+ try
+ {
+ cb(focused);
+ }
+ catch (Exception e)
+ {
+ Logger.e("Exception in OnApplicationFocus:" +
+ e.Message + "\n" + e.StackTrace);
+ }
+ }
+ }
+
+ public void OnApplicationPause(bool paused)
+ {
+ foreach (Action cb in sPauseCallbackList)
+ {
+ try
+ {
+ cb(paused);
+ }
+ catch (Exception e)
+ {
+ Logger.e("Exception in OnApplicationPause:" +
+ e.Message + "\n" + e.StackTrace);
+ }
+ }
+ }
+
+ ///
+ /// Adds a callback that is called when the Unity method OnApplicationFocus
+ /// is called.
+ ///
+ ///
+ /// Callback.
+ public static void AddFocusCallback(Action callback)
+ {
+ if (!sFocusCallbackList.Contains(callback))
+ {
+ sFocusCallbackList.Add(callback);
+ }
+ }
+
+ ///
+ /// Removes the callback from the list to call when handling OnApplicationFocus
+ /// is called.
+ ///
+ /// true, if focus callback was removed, false otherwise.
+ /// Callback.
+ public static bool RemoveFocusCallback(Action callback)
+ {
+ return sFocusCallbackList.Remove(callback);
+ }
+
+ ///
+ /// Adds a callback that is called when the Unity method OnApplicationPause
+ /// is called.
+ ///
+ ///
+ /// Callback.
+ public static void AddPauseCallback(Action callback)
+ {
+ if (!sPauseCallbackList.Contains(callback))
+ {
+ sPauseCallbackList.Add(callback);
+ }
+ }
+
+ ///
+ /// Removes the callback from the list to call when handling OnApplicationPause
+ /// is called.
+ ///
+ /// true, if focus callback was removed, false otherwise.
+ /// Callback.
+ public static bool RemovePauseCallback(Action callback)
+ {
+ return sPauseCallbackList.Remove(callback);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlayGamesHelperObject.cs.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlayGamesHelperObject.cs.meta
new file mode 100644
index 0000000..b4be3e6
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/OurUtils/PlayGamesHelperObject.cs.meta
@@ -0,0 +1,14 @@
+fileFormatVersion: 2
+guid: 7dd6f93ee6cb54945aea72a87542f720
+labels:
+- gvh
+- gvh_version-2.1.0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms.meta
new file mode 100644
index 0000000..dd9d70b
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms.meta
@@ -0,0 +1,5 @@
+fileFormatVersion: 2
+guid: 58fac82a81a11415b99606841f6040a6
+folderAsset: yes
+DefaultImporter:
+ userData:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms/Android.meta b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms/Android.meta
new file mode 100644
index 0000000..a8981c4
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms/Android.meta
@@ -0,0 +1,9 @@
+fileFormatVersion: 2
+guid: 5c9032ae026414e1bbe872da53708edd
+folderAsset: yes
+timeCreated: 1441206393
+licenseType: Pro
+DefaultImporter:
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms/Android/AndroidClient.cs b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms/Android/AndroidClient.cs
new file mode 100644
index 0000000..c25d19a
--- /dev/null
+++ b/Assets/GooglePlayGames/com.google.play.games/Runtime/Scripts/Platforms/Android/AndroidClient.cs
@@ -0,0 +1,1128 @@
+//
+// Copyright (C) 2014 Google Inc. All Rights Reserved.
+//
+// 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.
+//
+
+#if UNITY_ANDROID
+#pragma warning disable 0642 // Possible mistaken empty statement
+
+namespace GooglePlayGames.Android
+{
+ using GooglePlayGames.BasicApi;
+ using GooglePlayGames.BasicApi.Events;
+ using GooglePlayGames.BasicApi.SavedGame;
+ using GooglePlayGames.OurUtils;
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using UnityEngine;
+ using UnityEngine.SocialPlatforms;
+
+ public class AndroidClient : IPlayGamesClient
+ {
+ private enum AuthState
+ {
+ Unauthenticated,
+ Authenticated
+ }
+
+ private readonly object GameServicesLock = new object();
+ private readonly object AuthStateLock = new object();
+ private readonly static String PlayGamesSdkClassName =
+ "com.google.android.gms.games.PlayGamesSdk";
+
+ private volatile ISavedGameClient mSavedGameClient;
+ private volatile IEventsClient mEventsClient;
+ private volatile Player mUser = null;
+ private volatile AuthState mAuthState = AuthState.Unauthenticated;
+ private IUserProfile[] mFriends = new IUserProfile[0];
+ private LoadFriendsStatus mLastLoadFriendsStatus = LoadFriendsStatus.Unknown;
+
+ AndroidJavaClass mGamesClass = new AndroidJavaClass("com.google.android.gms.games.PlayGames");
+ private static string TasksClassName = "com.google.android.gms.tasks.Tasks";
+
+ private AndroidJavaObject mFriendsResolutionException = null;
+
+ private readonly int mLeaderboardMaxResults = 25; // can be from 1 to 25
+
+ private readonly int mFriendsMaxResults = 200; // the maximum load friends page size
+
+ internal AndroidClient()
+ {
+ PlayGamesHelperObject.CreateObject();
+ InitializeSdk();
+ }
+
+ private static void InitializeSdk() {
+ using (var playGamesSdkClass = new AndroidJavaClass(PlayGamesSdkClassName)) {
+ playGamesSdkClass.CallStatic("initialize", AndroidHelperFragment.GetActivity());
+ }
+ }
+
+ public void Authenticate(Action callback)
+ {
+ Authenticate( /* isAutoSignIn= */ true, callback);
+ }
+
+ public void ManuallyAuthenticate(Action callback)
+ {
+ Authenticate( /* isAutoSignIn= */ false, callback);
+ }
+
+ private void Authenticate(bool isAutoSignIn, Action callback)
+ {
+ callback = AsOnGameThreadCallback(callback);
+ lock (AuthStateLock)
+ {
+ // If the user is already authenticated, just fire the callback, we don't need
+ // any additional work.
+ if (mAuthState == AuthState.Authenticated)
+ {
+ OurUtils.Logger.d("Already authenticated.");
+ InvokeCallbackOnGameThread(callback, SignInStatus.Success);
+ return;
+ }
+ }
+
+ string methodName = isAutoSignIn ? "isAuthenticated" : "signIn";
+
+ OurUtils.Logger.d("Starting Auth using the method " + methodName);
+ using (var client = getGamesSignInClient())
+ using (
+ var task = client.Call(methodName))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(task, authenticationResult =>
+ {
+ bool isAuthenticated = authenticationResult.Call("isAuthenticated");
+ SignInOnResult(isAuthenticated, callback);
+ });
+
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ OurUtils.Logger.e("Authentication failed - " + exception.Call("toString"));
+ callback(SignInStatus.InternalError);
+ });
+ }
+ }
+
+ private void SignInOnResult(bool isAuthenticated, Action callback)
+ {
+ if (isAuthenticated)
+ {
+ using (var signInTasks = new AndroidJavaObject("java.util.ArrayList"))
+ {
+ AndroidJavaObject taskGetPlayer =
+ getPlayersClient().Call("getCurrentPlayer");
+ signInTasks.Call("add", taskGetPlayer);
+
+ using (var tasks = new AndroidJavaClass(TasksClassName))
+ using (var allTask = tasks.CallStatic("whenAll", signInTasks))
+ {
+ AndroidTaskUtils.AddOnCompleteListener(
+ allTask,
+ completeTask =>
+ {
+ if (completeTask.Call("isSuccessful"))
+ {
+ using (var resultObject = taskGetPlayer.Call("getResult"))
+ {
+ mUser = AndroidJavaConverter.ToPlayer(resultObject);
+ }
+
+ lock (GameServicesLock)
+ {
+ mSavedGameClient = new AndroidSavedGameClient(this);
+ mEventsClient = new AndroidEventsClient();
+ }
+
+ mAuthState = AuthState.Authenticated;
+ InvokeCallbackOnGameThread(callback, SignInStatus.Success);
+ OurUtils.Logger.d("Authentication succeeded");
+ LoadAchievements(ignore => { });
+ }
+ else
+ {
+ if (completeTask.Call("isCanceled"))
+ {
+ InvokeCallbackOnGameThread(callback, SignInStatus.Canceled);
+ return;
+ }
+
+ using (var exception = completeTask.Call("getException"))
+ {
+ OurUtils.Logger.e(
+ "Authentication failed - " + exception.Call("toString"));
+ InvokeCallbackOnGameThread(callback, SignInStatus.InternalError);
+ }
+ }
+ }
+ );
+ }
+ }
+ }
+ else
+ {
+ lock (AuthStateLock)
+ {
+ OurUtils.Logger.e("Returning an error code.");
+ InvokeCallbackOnGameThread(callback, SignInStatus.Canceled);
+ }
+ }
+ }
+
+ public void RequestServerSideAccess(bool forceRefreshToken, Action callback)
+ {
+ callback = AsOnGameThreadCallback(callback);
+
+ if (!GameInfo.WebClientIdInitialized())
+ {
+ throw new InvalidOperationException("Requesting server side access requires web " +
+ "client id to be configured.");
+ }
+
+ using (var client = getGamesSignInClient())
+ using (var task = client.Call("requestServerSideAccess",
+ GameInfo.WebClientId, forceRefreshToken))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(
+ task,
+ authCode => callback(authCode)
+ );
+
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ OurUtils.Logger.e("Requesting server side access task failed - " +
+ exception.Call("toString"));
+ callback(null);
+ });
+ }
+ }
+
+ public void RequestServerSideAccess(bool forceRefreshToken, List scopes, Action callback)
+ {
+ callback = AsOnGameThreadCallback(callback);
+
+ if (!GameInfo.WebClientIdInitialized())
+ {
+ throw new InvalidOperationException("Requesting server-side access requires a web client ID to be configured.");
+ }
+
+ if (scopes == null)
+ {
+ throw new ArgumentException("At least one scope must be provided.");
+ }
+
+ var javaScopesList = new AndroidJavaObject("java.util.ArrayList");
+
+ foreach (var scope in scopes)
+ {
+ javaScopesList.Call("add", getJavaScopeEnum(scope));
+ }
+
+ using (var client = getGamesSignInClient())
+ using (var task = client.Call(
+ "requestServerSideAccess",
+ GameInfo.WebClientId,
+ forceRefreshToken,
+ javaScopesList))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(task, result => callback(ToAuthResponse(result)));
+
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ OurUtils.Logger.e("Requesting server-side access with scopes task failed - " + exception.Call("toString"));
+ callback(new AuthResponse(null, new List())); // Return empty response on failure
+ });
+ }
+ }
+
+ private AuthResponse ToAuthResponse(AndroidJavaObject result)
+ {
+ string authCode = result.Call("getAuthCode");
+ var grantedScopesObject = result.Call("getGrantedScopes");
+
+ var grantedScopesList = new List();
+ if (grantedScopesObject != null)
+ {
+ int size = grantedScopesObject.Call("size");
+ for (int i = 0; i < size; i++)
+ {
+ var javaScopeEnum = grantedScopesObject.Call("get", i);
+ string javaScopeName = javaScopeEnum.Call("name");
+ if (Enum.TryParse(javaScopeName, out AuthScope grantedScope))
+ {
+ grantedScopesList.Add(grantedScope);
+ }
+ else
+ {
+ OurUtils.Logger.w($"Unrecognized scope {javaScopeName} returned from java side.");
+ }
+ }
+ }
+ AuthResponse authResponse = new AuthResponse(authCode, grantedScopesList);
+ return authResponse;
+ }
+
+ private AndroidJavaObject getJavaScopeEnum(AuthScope scope)
+ {
+ String AuthScopeClassName = "com.google.android.gms.games.gamessignin.AuthScope";
+ var javaAuthScopeClass = new AndroidJavaClass(AuthScopeClassName);
+ return javaAuthScopeClass.CallStatic("valueOf", scope.GetValue());
+ }
+
+
+ public void RequestRecallAccessToken(Action callback)
+ {
+ callback = AsOnGameThreadCallback(callback);
+ using (var client = getRecallClient())
+ using (var task = client.Call("requestRecallAccess"))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(
+ task,
+ recallAccess => {
+ var sessionId = recallAccess.Call("getSessionId");
+ callback(new RecallAccess(sessionId));
+ }
+ );
+
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ OurUtils.Logger.e("Requesting Recall access task failed - " +
+ exception.Call("toString"));
+ callback(null);
+ });
+ }
+ }
+
+ private static Action AsOnGameThreadCallback(Action callback)
+ {
+ if (callback == null)
+ {
+ return delegate { };
+ }
+
+ return result => InvokeCallbackOnGameThread(callback, result);
+ }
+
+ private static void InvokeCallbackOnGameThread(Action callback)
+ {
+ if (callback == null)
+ {
+ return;
+ }
+
+ PlayGamesHelperObject.RunOnGameThread(() => { callback(); });
+ }
+
+ private static void InvokeCallbackOnGameThread(Action callback, T data)
+ {
+ if (callback == null)
+ {
+ return;
+ }
+
+ PlayGamesHelperObject.RunOnGameThread(() => { callback(data); });
+ }
+
+
+ private static Action AsOnGameThreadCallback(
+ Action toInvokeOnGameThread)
+ {
+ return (result1, result2) =>
+ {
+ if (toInvokeOnGameThread == null)
+ {
+ return;
+ }
+
+ PlayGamesHelperObject.RunOnGameThread(() => toInvokeOnGameThread(result1, result2));
+ };
+ }
+
+ private static void InvokeCallbackOnGameThread(Action callback, T1 t1, T2 t2)
+ {
+ if (callback == null)
+ {
+ return;
+ }
+
+ PlayGamesHelperObject.RunOnGameThread(() => { callback(t1, t2); });
+ }
+
+ public bool IsAuthenticated()
+ {
+ lock (AuthStateLock)
+ {
+ return mAuthState == AuthState.Authenticated;
+ }
+ }
+
+ public void LoadFriends(Action callback)
+ {
+ LoadAllFriends(mFriendsMaxResults, /* forceReload= */ false, /* loadMore= */ false, callback);
+ }
+
+ private void LoadAllFriends(int pageSize, bool forceReload, bool loadMore,
+ Action callback)
+ {
+ LoadFriendsPaginated(pageSize, loadMore, forceReload, result =>
+ {
+ mLastLoadFriendsStatus = result;
+ switch (result)
+ {
+ case LoadFriendsStatus.Completed:
+ InvokeCallbackOnGameThread(callback, true);
+ break;
+ case LoadFriendsStatus.LoadMore:
+ // There are more friends to load.
+ LoadAllFriends(pageSize, /* forceReload= */ false, /* loadMore= */ true, callback);
+ break;
+ case LoadFriendsStatus.ResolutionRequired:
+ case LoadFriendsStatus.InternalError:
+ case LoadFriendsStatus.NotAuthorized:
+ InvokeCallbackOnGameThread(callback, false);
+ break;
+ default:
+ GooglePlayGames.OurUtils.Logger.d("There was an error when loading friends." + result);
+ InvokeCallbackOnGameThread(callback, false);
+ break;
+ }
+ });
+ }
+
+ public void LoadFriends(int pageSize, bool forceReload,
+ Action callback)
+ {
+ LoadFriendsPaginated(pageSize, /* isLoadMore= */ false, /* forceReload= */ forceReload,
+ callback);
+ }
+
+ public void LoadMoreFriends(int pageSize, Action callback)
+ {
+ LoadFriendsPaginated(pageSize, /* isLoadMore= */ true, /* forceReload= */ false,
+ callback);
+ }
+
+ private void LoadFriendsPaginated(int pageSize, bool isLoadMore, bool forceReload,
+ Action callback)
+ {
+ mFriendsResolutionException = null;
+ using (var playersClient = getPlayersClient())
+ using (var task = isLoadMore
+ ? playersClient.Call("loadMoreFriends", pageSize)
+ : playersClient.Call("loadFriends", pageSize,
+ forceReload))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(
+ task, annotatedData =>
+ {
+ using (var playersBuffer = annotatedData.Call("get"))
+ {
+ AndroidJavaObject metadata = playersBuffer.Call("getMetadata");
+ var areMoreFriendsToLoad = metadata != null &&
+ metadata.Call("getString",
+ "next_page_token") != null;
+ mFriends = AndroidJavaConverter.playersBufferToArray(playersBuffer);
+ mLastLoadFriendsStatus = areMoreFriendsToLoad
+ ? LoadFriendsStatus.LoadMore
+ : LoadFriendsStatus.Completed;
+ InvokeCallbackOnGameThread(callback, mLastLoadFriendsStatus);
+ }
+ });
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ AndroidHelperFragment.IsResolutionRequired(exception, resolutionRequired =>
+ {
+ if (resolutionRequired)
+ {
+ mFriendsResolutionException =
+ exception.Call("getResolution");
+ mLastLoadFriendsStatus = LoadFriendsStatus.ResolutionRequired;
+ mFriends = new IUserProfile[0];
+ InvokeCallbackOnGameThread(callback, LoadFriendsStatus.ResolutionRequired);
+ }
+ else
+ {
+ mFriendsResolutionException = null;
+
+ if (IsApiException(exception))
+ {
+ var statusCode = exception.Call("getStatusCode");
+ if (statusCode == /* GamesClientStatusCodes.NETWORK_ERROR_NO_DATA */ 26504)
+ {
+ mLastLoadFriendsStatus = LoadFriendsStatus.NetworkError;
+ InvokeCallbackOnGameThread(callback, LoadFriendsStatus.NetworkError);
+ return;
+ }
+ }
+
+ mLastLoadFriendsStatus = LoadFriendsStatus.InternalError;
+ OurUtils.Logger.e("LoadFriends failed: " +
+ exception.Call("toString"));
+ InvokeCallbackOnGameThread(callback, LoadFriendsStatus.InternalError);
+ }
+ });
+ return;
+ });
+ }
+ }
+
+ private static bool IsApiException(AndroidJavaObject exception) {
+ var exceptionClassName = exception.Call("getClass")
+ .Call("getName");
+ return exceptionClassName == "com.google.android.gms.common.api.ApiException";
+ }
+
+ public LoadFriendsStatus GetLastLoadFriendsStatus()
+ {
+ return mLastLoadFriendsStatus;
+ }
+
+ public void AskForLoadFriendsResolution(Action callback)
+ {
+ if (mFriendsResolutionException == null)
+ {
+ GooglePlayGames.OurUtils.Logger.d("The developer asked for access to the friends " +
+ "list but there is no intent to trigger the UI. This may be because the user " +
+ "has granted access already or the game has not called loadFriends() before.");
+ using (var playersClient = getPlayersClient())
+ using (
+ var task = playersClient.Call("loadFriends", /* pageSize= */ 1,
+ /* forceReload= */ false))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(
+ task, annotatedData => { InvokeCallbackOnGameThread(callback, UIStatus.Valid); });
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ AndroidHelperFragment.IsResolutionRequired(exception, resolutionRequired =>
+ {
+ if (resolutionRequired)
+ {
+ mFriendsResolutionException =
+ exception.Call("getResolution");
+ AndroidHelperFragment.AskForLoadFriendsResolution(
+ mFriendsResolutionException, AsOnGameThreadCallback(callback));
+ return;
+ }
+
+ if (IsApiException(exception))
+ {
+ var statusCode = exception.Call("getStatusCode");
+ if (statusCode ==
+ /* GamesClientStatusCodes.NETWORK_ERROR_NO_DATA */ 26504)
+ {
+ InvokeCallbackOnGameThread(callback, UIStatus.NetworkError);
+ return;
+ }
+ }
+
+ OurUtils.Logger.e("LoadFriends failed: " +
+ exception.Call("toString"));
+ InvokeCallbackOnGameThread(callback, UIStatus.InternalError);
+ });
+ });
+ }
+ }
+ else
+ {
+ AndroidHelperFragment.AskForLoadFriendsResolution(mFriendsResolutionException,
+ AsOnGameThreadCallback(callback));
+ }
+ }
+
+ public void ShowCompareProfileWithAlternativeNameHintsUI(string playerId,
+ string otherPlayerInGameName,
+ string currentPlayerInGameName,
+ Action callback)
+ {
+ AndroidHelperFragment.ShowCompareProfileWithAlternativeNameHintsUI(
+ playerId, otherPlayerInGameName, currentPlayerInGameName,
+ AsOnGameThreadCallback(callback));
+ }
+
+ public void GetFriendsListVisibility(bool forceReload,
+ Action callback)
+ {
+ using (var playersClient = getPlayersClient())
+ using (
+ var task = playersClient.Call("getCurrentPlayer", forceReload))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(task, annotatedData =>
+ {
+ AndroidJavaObject currentPlayerInfo =
+ annotatedData.Call("get").Call(
+ "getCurrentPlayerInfo");
+ int playerListVisibility =
+ currentPlayerInfo.Call("getFriendsListVisibilityStatus");
+ InvokeCallbackOnGameThread(callback,
+ AndroidJavaConverter.ToFriendsListVisibilityStatus(playerListVisibility));
+ });
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ InvokeCallbackOnGameThread(callback, FriendsListVisibilityStatus.NetworkError);
+ return;
+ });
+ }
+ }
+
+ public IUserProfile[] GetFriends()
+ {
+ return mFriends;
+ }
+
+ public string GetUserId()
+ {
+ if (mUser == null)
+ {
+ return null;
+ }
+
+ return mUser.id;
+ }
+
+ public string GetUserDisplayName()
+ {
+ if (mUser == null)
+ {
+ return null;
+ }
+
+ return mUser.userName;
+ }
+
+ public string GetUserImageUrl()
+ {
+ if (mUser == null)
+ {
+ return null;
+ }
+
+ return mUser.AvatarURL;
+ }
+
+ public void GetPlayerStats(Action callback)
+ {
+ using (var playerStatsClient = getPlayerStatsClient())
+ using (var task = playerStatsClient.Call("loadPlayerStats", /* forceReload= */ false))
+ {
+ AndroidTaskUtils.AddOnSuccessListener(
+ task,
+ annotatedData =>
+ {
+ using (var playerStatsJava = annotatedData.Call("get"))
+ {
+ int numberOfPurchases = playerStatsJava.Call("getNumberOfPurchases");
+ float avgSessionLength = playerStatsJava.Call("getAverageSessionLength");
+ int daysSinceLastPlayed = playerStatsJava.Call("getDaysSinceLastPlayed");
+ int numberOfSessions = playerStatsJava.Call("getNumberOfSessions");
+ float sessionPercentile = playerStatsJava.Call("getSessionPercentile");
+ float spendPercentile = playerStatsJava.Call("getSpendPercentile");
+ float spendProbability = playerStatsJava.Call("getSpendProbability");
+ float churnProbability = playerStatsJava.Call("getChurnProbability");
+ float highSpenderProbability = playerStatsJava.Call("getHighSpenderProbability");
+ float totalSpendNext28Days = playerStatsJava.Call("getTotalSpendNext28Days");
+
+ PlayerStats result = new PlayerStats(
+ numberOfPurchases,
+ avgSessionLength,
+ daysSinceLastPlayed,
+ numberOfSessions,
+ sessionPercentile,
+ spendPercentile,
+ spendProbability,
+ churnProbability,
+ highSpenderProbability,
+ totalSpendNext28Days);
+
+ InvokeCallbackOnGameThread(callback, CommonStatusCodes.Success, result);
+ }
+ });
+
+ AndroidTaskUtils.AddOnFailureListener(task, exception =>
+ {
+ OurUtils.Logger.e("GetPlayerStats failed: " + exception.Call