├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── doc ├── 1_YOUR_GOOGLE_PLAY_GAME_APP_ID.png ├── 2_YOUR_GOOGLE_PLAY_GAME_APP_ID.png ├── 3.png ├── 4.png ├── 5.png ├── 6_if_Signing_certificate_fingerprint_(SHA1)_is_blank.png ├── 7.png ├── 8.png ├── cocoon_APP_ID1.png ├── cocoon_APP_ID2.png ├── intelxdk.config.additions.xml ├── iossandbox1.png ├── iossandbox2.png ├── iossandbox3.png └── xdk_APP_ID.png ├── example ├── basic │ └── index.html └── basic_tag │ └── index.html ├── package.json ├── plugin.xml ├── src ├── android │ ├── Game.java │ ├── GameHelper.java │ ├── GameHelperUtils.java │ └── ids.xml └── ios │ ├── Game.h │ └── Game.m └── www └── game.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must end with two \r 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Sang Ki Kwon (Cranberrygame) 4 | Email: cranberrygame@yahoo.com 5 | Homepage: http://www.github.com/cranberrygame 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cordova Game plugin 2 | ==================== 3 | 4 | # Overview # 5 | Show leaderboard and achievements (google play game and game center, SDK) 6 | 7 | [android, ios] [cordova cli] [xdk] [phonegap build service] 8 | 9 | Requires google play developer account https://play.google.com/apps/publish/
10 | Requires apple developer account https://developer.apple.com/devcenter/ios/index.action 11 | 12 | This is open source cordova plugin. 13 | 14 | You can see Cordova Plugins in one page: http://cranberrygame.github.io?referrer=github 15 | 16 | ```c 17 | cf) 18 | 19 | Leaderboard game: Best score game 20 | Limited life 21 | ex) 1, 3 22 | Limited time 23 | ex) 30 seconds 24 | Time is score 25 | 26 | Achievement 27 | Score 28 | ex) Achievement1 (Score 10) 29 | Achievement2 (Score 30) 30 | Achievement3 (Score 60) 31 | Achievement4 (Score 100) 32 | Achievement5 (Score 150) 33 | Level 34 | ex) Achievement1 (Level 1) 35 | Achievement2 (Level 2) 36 | Achievement3 (Level 4) 37 | Achievement4 (Level 6) 38 | Achievement5 (Level 8) 39 | Achievement6 (Level 10) 40 | Category 41 | ex) Achievement1 (Number) 42 | Achievement1 (Fruit) 43 | Achievement1 (Color) 44 | Achievement1 (Other) 45 | Achievement1 (Challenge (Limited time)) 46 | ``` 47 | # Change log # 48 | ```c 49 | 1.0.109 50 | Fixed crash issue when show leaderbord after logout. 51 | 1.0.112 52 | Added show leaderboards method. 53 | 1.0.113 54 | Fixed crash issue when submit score after logout. 55 | 1.0.115 56 | Refixed crash issue when submit score after logout. 57 | ``` 58 | # Install plugin # 59 | 60 | ## Cordova cli ## 61 | https://cordova.apache.org/docs/en/edge/guide_cli_index.md.html#The%20Command-Line%20Interface - npm install -g cordova@6.0.0 62 | ```c 63 | //caution: replace 1064334934918 with your google play game app id 64 | cordova plugin add cordova-plugin-game --variable APP_ID="1064334934918" 65 | ``` 66 | ## Xdk ## 67 | //caution: replace 1064334934918 with your google play game app id 68 |
69 | https://github.com/cranberrygame/cordova-plugin-game/blob/master/doc/intelxdk.config.additions.xml 70 | ```c 71 | ``` 72 | 73 | ## Cocoon ## 74 | https://cocoon.io - Create project - [specific project] - Setting - Plugins - Custom - Git Url: https://github.com/cranberrygame/cordova-plugin-game.git - INSTALL - Save
75 | //caution: replace 1064334934918 with your google play game app id
76 | https://cocoon.io - Create project - [specific project] - Setting - Plugins - Installed - Git Url https://github.com/cranberrygame/cordova-plugin-game.git - ADD PARAMETER - Name: APP_ID Value: 1064334934918 - Save
77 |
78 | 79 | 80 | ## Phonegap build service (config.xml) ## 81 | https://build.phonegap.com/ - Apps - [specific project] - Update code - Zip file including config.xml 82 | ```c 83 | //caution: replace 1064334934918 with your google play game app id 84 | 85 | 86 | 87 | ``` 88 | 89 | ## Construct2 ## 90 | Download construct2 plugin: http://www.paywithapost.de/pay?id=4ef3f2be-26e8-4a04-b826-6680db13a8c8 91 |
92 | Now all the native plugins are installed automatically: https://plus.google.com/102658703990850475314/posts/XS5jjEApJYV 93 | # Server setting # 94 | 95 | ## android (Google Play Game) ## 96 |
97 |
98 |
99 | If you migrate android app from one build system to another build system (e.g from xdk to cocoon), link Android step ~ again.
100 |
101 |
102 |
103 |
104 |
105 | ```c 106 | //add game 107 | google play developer console - Game services - Add a new game - Enter the name of your game: Test App, Category: Puzzle 108 | 109 | //get YOUR_GOOGLE_PLAY_GAME_APP_ID 110 | google play developer console - Game services - [specific app] - get YOUR_GOOGLE_PLAY_GAME_APP_ID (the number that appears beside the game name in the header of the Developer Console, e.g. "Test App - 12345678",. The YOUR_GOOGLE_PLAY_GAME_APP_ID in this case is 12345678.) 111 | 112 | //link app 113 | google play developer console - Game services - [specific app] - Linked App - Android - Save and continue - Authorize your app now - Continue - Create Client (if Signing certificate fingerprint (SHA1) is blank, do it after publishing app in alpha, beta, or normal (after publishing, it will be filled automatically)) 114 | 115 | //add leaderboard, get YOUR_GOOGLE_PLAY_GAME_LEADERBOARD_ID 116 | google play developer console - Game services - [specific app] - leaderboard - Add leaderboard - Name: Leaderboard - get YOUR_GOOGLE_PLAY_GAME_LEADERBOARD_ID 117 | 118 | //add achievements (must minimum 5 achievements), get YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID1, YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID2, YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID3, YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID4, YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID5 119 | google play developer console - Game services - [specific app] - achievement - Add achievement - Name: Achievement1 (Score 10), Description: Achievement1 (Score 10) - get YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID1 120 | google play developer console - Game services - [specific app] - achievement - Add achievement - Name: Achievement2 (Score 30), Description: Achievement2 (Score 30) - get YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID2 121 | google play developer console - Game services - [specific app] - achievement - Add achievement - Name: Achievement3 (Score 60), Description: Achievement3 (Score 60) - get YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID3 122 | google play developer console - Game services - [specific app] - achievement - Add achievement - Name: Achievement4 (Score 100), Description: Achievement4 (Score 100) - get YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID4 123 | google play developer console - Game services - [specific app] - achievement - Add achievement - Name: Achievement5 (Score 150), Description: Achievement5 (Score 150) - get YOUR_GOOGLE_PLAY_GAME_ACHIEVEMENT_ID5 124 | 125 | //publish game 126 | google play developer console - Game services - [specific app] - prepare test - publish game 127 | ``` 128 | ## ios (Game Center) ## 129 | ```c 130 | itunesconnect - 나의 App - [specific app] - Game Center - Enable for Single Game 131 | 132 | //leaderboard 133 | itunesconnect - 나의 App - [specific app] - Game Center - Add Leaderboard - 134 | 단일 순위표 - 135 | 순위표 세트 식별 정보: testapp_leaderboard - 136 | 순위표 ID: testapp_leaderboard - 137 | 점수 형식 유형: Integer - 138 | 점수 제출 유형: 가장 높은 점수 139 | 정렬 순서: 높은 점수순 - 140 | Add Language - 141 | 언어: English - 142 | 이름: Leaderboard - 143 | 점수 형식: Integer (100,000,122) - 144 | Save 145 | 146 | //achievement1 147 | itunesconnect - 나의 App - [specific app] - Game Center - 목표 달성 추가 - 148 | 목표 달성 식별 정보: testapp_achievement1 149 | 목표 달성 ID: testapp_achievement1 150 | 점수 값: 20 (max 100) 151 | 숨김: No 152 | 여러 번 달성 가능: No 153 | 언어 추가 - 154 | 언어: English 155 | 제목: Achievement1 (Score 10) 156 | 사전 획득한 목표 달성 설명: Achievement1 (Score 10) 157 | 획득한 목표 달성 설명: Achievement1 (Score 10) 158 | 이미지: 512x512 png 159 | 160 | //achievement2 161 | itunesconnect - 나의 App - [specific app] - Game Center - 목표 달성 추가 - 162 | 목표 달성 식별 정보: testapp_achievement2 163 | 목표 달성 ID: testapp_achievement2 164 | 점수 값: 40 (max 100) 165 | 숨김: No 166 | 여러 번 달성 가능: No 167 | 언어 추가 - 168 | 언어: English 169 | 제목: Achievement2 (Score 30) 170 | 사전 획득한 목표 달성 설명: Achievement2 (Score 30) 171 | 획득한 목표 달성 설명: Achievement2 (Score 30) 172 | 이미지: 512x512 png 173 | 174 | //achievement3 175 | itunesconnect - 나의 App - [specific app] - Game Center - 목표 달성 추가 - 176 | 목표 달성 식별 정보: testapp_achievement3 177 | 목표 달성 ID: testapp_achievement3 178 | 점수 값: 60 (max 100) 179 | 숨김: No 180 | 여러 번 달성 가능: No 181 | 언어 추가 - 182 | 언어: English 183 | 제목: Achievement3 (Score 60) 184 | 사전 획득한 목표 달성 설명: Achievement3 (Score 60) 185 | 획득한 목표 달성 설명: Achievement3 (Score 60) 186 | 이미지: 512x512 png 187 | 188 | //achievement4 189 | itunesconnect - 나의 App - [specific app] - Game Center - 목표 달성 추가 - 190 | 목표 달성 식별 정보: testapp_achievement4 191 | 목표 달성 ID: testapp_achievement4 192 | 점수 값: 80 (max 100) 193 | 숨김: No 194 | 여러 번 달성 가능: No 195 | 언어 추가 - 196 | 언어: English 197 | 제목: Achievement4 (Score 100) 198 | 사전 획득한 목표 달성 설명: Achievement4 (Score 100) 199 | 획득한 목표 달성 설명: Achievement4 (Score 100) 200 | 이미지: 512x512 png 201 | 202 | //achievement5 203 | itunesconnect - 나의 App - [specific app] - Game Center - 목표 달성 추가 - 204 | 목표 달성 식별 정보: testapp_achievement5 205 | 목표 달성 ID: testapp_achievement5 206 | 점수 값: 100 (leave blank, max 100) 207 | 숨김: No 208 | 여러 번 달성 가능: No 209 | 언어 추가 - 210 | 언어: English 211 | 제목: Achievement5 (Score 150) 212 | 사전 획득한 목표 달성 설명: Achievement5 (Score 150) 213 | 획득한 목표 달성 설명: Achievement5 (Score 150) 214 | 이미지: 512x512 png 215 | 216 | can test before publish 217 | ``` 218 | # API # 219 | ```javascript 220 | // 221 | var leaderboardId = "REPLACE_THIS_WITH_YOUR_LEADERBOARD_ID"; 222 | var achievementId1 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID1"; 223 | var achievementId2 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID2"; 224 | var achievementId3 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID3"; 225 | var achievementId4 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID4"; 226 | var achievementId5 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID5"; 227 | /* 228 | var leaderboardId; 229 | var achievementId1; 230 | var achievementId2; 231 | var achievementId3; 232 | var achievementId4; 233 | var achievementId5; 234 | //android 235 | if (navigator.userAgent.match(/Android/i)) { 236 | leaderboardId = "REPLACE_THIS_WITH_YOUR_LEADERBOARD_ID"; 237 | achievementId1 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID1"; 238 | achievementId2 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID2"; 239 | achievementId3 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID3"; 240 | achievementId4 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID4"; 241 | achievementId5 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID5"; 242 | } 243 | //ios 244 | else if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i)) { 245 | leaderboardId = "REPLACE_THIS_WITH_YOUR_LEADERBOARD_ID"; 246 | achievementId1 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID1"; 247 | achievementId2 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID2"; 248 | achievementId3 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID3"; 249 | achievementId4 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID4"; 250 | achievementId5 = "REPLACE_THIS_WITH_YOUR_ACHIEVEMENT_ID5"; 251 | } 252 | */ 253 | 254 | // 255 | document.addEventListener("deviceready", function(){ 256 | window.game.setUp(); 257 | 258 | //callback 259 | window.game.onLoginSucceeded = function(result) { 260 | var playerDetail = result; 261 | alert('onLoginSucceeded: ' + playerDetail['playerId'] + ' ' + playerDetail['playerDisplayName']); 262 | }; 263 | window.game.onLoginFailed = function() { 264 | alert('onLoginFailed'); 265 | }; 266 | // 267 | window.game.onSubmitScoreSucceeded = function() { 268 | alert('onSubmitScoreSucceeded'); 269 | }; 270 | window.game.onSubmitScoreFailed = function() { 271 | alert('onSubmitScoreFailed'); 272 | }; 273 | window.game.onGetPlayerScoreSucceeded = function(result) { 274 | var playerScore = result; 275 | alert('onGetPlayerScoreSucceeded: ' + playerScore); 276 | }; 277 | window.game.onGetPlayerScoreFailed = function() { 278 | alert('onGetPlayerScoreFailed'); 279 | }; 280 | // 281 | window.game.onUnlockAchievementSucceeded = function() { 282 | alert('onUnlockAchievementSucceeded'); 283 | }; 284 | window.game.onUnlockAchievementFailed = function() { 285 | alert('onUnlockAchievementFailed'); 286 | }; 287 | window.game.onIncrementAchievementSucceeded = function() { 288 | alert('onIncrementAchievementSucceeded'); 289 | }; 290 | window.game.onIncrementAchievementFailed = function() { 291 | alert('onIncrementAchievementFailed'); 292 | }; 293 | window.game.onResetAchievementsSucceeded = function() { 294 | alert('onResetAchievementsSucceeded'); 295 | }; 296 | window.game.onResetAchievementsFailed = function() { 297 | alert('onResetAchievementsFailed'); 298 | }; 299 | // 300 | window.game.onGetPlayerImageSucceeded = function(result) { 301 | var playerImageUrl = result; 302 | alert('onGetPlayerImageSucceeded: ' + playerImageUrl); 303 | }; 304 | window.game.onGetPlayerImageFailed = function() { 305 | alert('onGetPlayerImageFailed'); 306 | }; 307 | }, false); 308 | 309 | // 310 | window.game.login(); 311 | window.game.logout(); 312 | alert(window.game.isLoggedIn()); 313 | 314 | // 315 | window.game.submitScore(leaderboardId, 5);//leaderboardId, score 316 | window.game.showLeaderboard(leaderboardId); 317 | window.game.showLeaderboards(); 318 | window.game.getPlayerScore(leaderboardId); 319 | 320 | // 321 | window.game.unlockAchievement(achievementId1); 322 | window.game.unlockAchievement(achievementId2); 323 | window.game.unlockAchievement(achievementId3); 324 | window.game.unlockAchievement(achievementId4); 325 | window.game.unlockAchievement(achievementId5); 326 | window.game.incrementAchievement(achievementId1, 2); //achievementId, incrementalStepOrCurrentPercent: Incremental step (android) or current percent (ios) for incremental achievement. 327 | window.game.incrementAchievement(achievementId2, 2); 328 | window.game.incrementAchievement(achievementId3, 2); 329 | window.game.incrementAchievement(achievementId4, 2); 330 | window.game.incrementAchievement(achievementId5, 2); 331 | window.game.showAchievements(); 332 | window.game.resetAchievements();//only supported on ios 333 | 334 | // 335 | window.game.getPlayerImage(); 336 | 337 | ``` 338 | # Examples # 339 | example/index.html
340 | example_tag/index.html 341 | 342 | # Test # 343 | 344 | ## android (Google Play Game) ## 345 | 346 | ```c 347 | //publish as alpha test (recommend) or beta test instead of production. 348 | google play developer console - [specific app] - APK - Alpha test - Upload as alpha test - Drag and drop apk and publish now as alpha test. 349 | 350 | //add test user for game 351 | google play developer console - Game services - [specific game] - test - add tester (google play account) 352 | 353 | //add test community for alpha test or beta test download 354 | google play developer console - 355 | All applications - 356 | [specific app] - 357 | APK - 358 | Alpha testers - 359 | Manage list of testers - 360 | Add Google groups or Google+ community: https://plus.google.com/communities/xxx (if you want make Google+ Community, go to this: https://plus.google.com/communities) - 361 | Add - 362 | Let test user download and install apk from this url: https://play.google.com/apps/testing/YOUR_PACKAGE (invite test user in your Google+ community, wait until this url is available, take hours) 363 | ``` 364 | 365 | ```c 366 | Clear the default account so that a different account can be signed in without having to clear app data: 367 | 1.android 368 | Setting - Account - Google - Logout with previous google account and login with other google account 369 | 2.ios 370 | Setting - Game Center - Logout with previous ios account and login with other ios account 371 | ``` 372 | 373 | ## ios (Game Center) ## 374 | 375 |
376 |
377 | 378 | 379 | ```c 380 | //itunes connect sand box (Caution!) 381 | itunes connect - User and role - Sand box test - add tester (not real email but faked email) 382 | 383 | //iphone sand box (Caution!) 384 | //The requested operation could not be completed because the application is not recognized by Game Center. 385 | iphone - Setting - Game Center - activate sand box mode - login with itunes connect sand box account in the app 386 | ``` 387 | 388 | ```c 389 | the requested operation has been canceled or disabled by the user 390 | Reenabling GameCenter after user-cancelled 3 times (iOS7 only) 391 | iphone - Setting - General - Reset - Reset All Settings 392 | http://stackoverflow.com/questions/18927723/reenabling-gamecenter-after-user-cancelled-3-times-ios7-only 393 | ``` 394 | 395 | # Useful links # 396 | 397 | Cordova Plugins
398 | http://cranberrygame.github.io?referrer=github 399 | 400 | # Credits # 401 | -------------------------------------------------------------------------------- /doc/1_YOUR_GOOGLE_PLAY_GAME_APP_ID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/1_YOUR_GOOGLE_PLAY_GAME_APP_ID.png -------------------------------------------------------------------------------- /doc/2_YOUR_GOOGLE_PLAY_GAME_APP_ID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/2_YOUR_GOOGLE_PLAY_GAME_APP_ID.png -------------------------------------------------------------------------------- /doc/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/3.png -------------------------------------------------------------------------------- /doc/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/4.png -------------------------------------------------------------------------------- /doc/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/5.png -------------------------------------------------------------------------------- /doc/6_if_Signing_certificate_fingerprint_(SHA1)_is_blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/6_if_Signing_certificate_fingerprint_(SHA1)_is_blank.png -------------------------------------------------------------------------------- /doc/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/7.png -------------------------------------------------------------------------------- /doc/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/8.png -------------------------------------------------------------------------------- /doc/cocoon_APP_ID1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/cocoon_APP_ID1.png -------------------------------------------------------------------------------- /doc/cocoon_APP_ID2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/cocoon_APP_ID2.png -------------------------------------------------------------------------------- /doc/intelxdk.config.additions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /doc/iossandbox1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/iossandbox1.png -------------------------------------------------------------------------------- /doc/iossandbox2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/iossandbox2.png -------------------------------------------------------------------------------- /doc/iossandbox3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/iossandbox3.png -------------------------------------------------------------------------------- /doc/xdk_APP_ID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cranberrygame/cordova-plugin-game/f69c35efb408e8f312c27f570a914283007ae238/doc/xdk_APP_ID.png -------------------------------------------------------------------------------- /example/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 97 | 98 |

99 |

100 |

101 |
102 |

103 |

104 |

105 |

106 |
107 |

108 |

109 |

110 |

111 |

112 |

113 |

114 |

115 |

116 |

117 |

118 |

119 |
120 |

121 | 122 | 123 | -------------------------------------------------------------------------------- /example/basic_tag/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 113 | 114 |

115 |

116 |

117 |
118 |

119 |

120 |

121 |

122 |
123 |

124 |

125 |

126 |

127 |

128 |

129 |

130 |

131 |

132 |

133 |

134 |

135 |
136 |

137 | 138 | 139 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cordova-plugin-game", 3 | "version": "1.0.120", 4 | "description": "show leaderboard and achievements (google play game and game center, SDK)", 5 | "cordova": { 6 | "id": "cordova-plugin-game", 7 | "platforms": [ 8 | "android", 9 | "ios" 10 | ] 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/cranberrygame/cordova-plugin-game.git" 15 | }, 16 | "keywords": [ 17 | "cordova", 18 | "plugin", 19 | "game", 20 | "google play game", 21 | "game center", 22 | "ecosystem:cordova", 23 | "cordova-android", 24 | "cordova-ios" 25 | ], 26 | "engines": [ 27 | { 28 | "name": "cordova", 29 | "version": ">=3.0.0" 30 | } 31 | ], 32 | "author": "cranberrygame", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/cranberrygame/cordova-plugin-game/issues" 36 | }, 37 | "homepage": "https://github.com/cranberrygame/cordova-plugin-game#readme" 38 | } 39 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | Cordova Game plugin 8 | show leaderboard and achievements (google play game and game center, SDK) 9 | Sang Ki Kwon (Cranberrygame) 10 | MIT 11 | cordova,plugin,game,google play game,game center 12 | https://github.com/cranberrygame/cordova-plugin-game 13 | https://github.com/cranberrygame/cordova-plugin-game/issues 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | $APP_ID 44 | 45 | 46 | 47 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 64 | 70 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/android/Game.java: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2014 Sang Ki Kwon (Cranberrygame) 2 | //Email: cranberrygame@yahoo.com 3 | //Homepage: http://cranberrygame.github.io 4 | //License: MIT (http://opensource.org/licenses/MIT) 5 | package com.cranberrygame.cordova.plugin.game; 6 | 7 | import org.apache.cordova.CordovaPlugin; 8 | import org.apache.cordova.PluginResult; 9 | import org.apache.cordova.CallbackContext; 10 | import org.json.JSONArray; 11 | import org.json.JSONObject; 12 | import org.json.JSONException; 13 | import org.apache.cordova.CordovaInterface; 14 | import org.apache.cordova.CordovaWebView; 15 | import android.app.Activity; 16 | import android.util.Log; 17 | // 18 | //import com.google.android.gms.common.GooglePlayServicesUtil;//deprecated 19 | import com.google.android.gms.common.GoogleApiAvailability;// 20 | import android.content.Intent; 21 | import com.google.example.games.basegameutils.GameHelper; 22 | import com.google.android.gms.common.ConnectionResult; 23 | import com.google.android.gms.common.api.GoogleApiClient; 24 | //import com.google.android.gms.appstate.AppStateManager;//deprecated 25 | import android.view.Gravity; 26 | import com.google.android.gms.games.Games;//cranberrygame 27 | import android.content.IntentSender; 28 | import android.os.Bundle; 29 | import com.google.android.gms.games.achievement.Achievements; 30 | import com.google.android.gms.games.leaderboard.Leaderboards; 31 | import com.google.android.gms.common.api.*; 32 | import com.google.android.gms.games.GamesStatusCodes; 33 | import com.google.android.gms.games.leaderboard.LeaderboardScore; 34 | import com.google.android.gms.games.Player; 35 | import android.net.Uri; 36 | import com.google.android.gms.games.leaderboard.LeaderboardVariant; 37 | 38 | //Util 39 | import android.app.AlertDialog; 40 | import android.content.DialogInterface; 41 | 42 | class Util { 43 | 44 | //ex) Util.alert(cordova.getActivity(),"message"); 45 | public static void alert(Activity activity, String message) { 46 | AlertDialog ad = new AlertDialog.Builder(activity).create(); 47 | ad.setCancelable(false); // This blocks the 'BACK' button 48 | ad.setMessage(message); 49 | ad.setButton("OK", new DialogInterface.OnClickListener() { 50 | @Override 51 | public void onClick(DialogInterface dialog, int which) { 52 | dialog.dismiss(); 53 | } 54 | }); 55 | ad.show(); 56 | } 57 | } 58 | 59 | public class Game extends CordovaPlugin implements GameHelper.GameHelperListener{ 60 | private String LOG_TAG = "Game"; 61 | private GameHelper mHelper; 62 | private CallbackContext loginCC; 63 | private CallbackContext getPlayerImageCC; 64 | private CallbackContext getPlayerScoreCC; 65 | private CallbackContext submitScoreCC; 66 | private CallbackContext unlockAchievementCC; 67 | private CallbackContext incrementAchievementCC; 68 | // 69 | private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000 ;// 70 | 71 | public void initialize(CordovaInterface cordova, CordovaWebView webView) { 72 | super.initialize(cordova, webView); 73 | 74 | } 75 | @Override 76 | public boolean execute(String action, JSONArray args,CallbackContext callbackContext) throws JSONException { 77 | PluginResult result = null; 78 | 79 | /* 80 | //deprecated 81 | if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this.cordova.getActivity()) != 0) { 82 | Log.e(LOG_TAG, "Google Play Services are unavailable"); 83 | callbackContext.error("Unavailable"); 84 | return true; 85 | } 86 | else { 87 | Log.d(LOG_TAG, "** Google Play Services are available **"); 88 | } 89 | */ 90 | GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance(); 91 | int res = googleAPI.isGooglePlayServicesAvailable(this.cordova.getActivity()); 92 | if(res != ConnectionResult.SUCCESS) { 93 | //if(googleAPI.isUserResolvableError(result)) { 94 | // googleAPI.getErrorDialog(this.cordova.getActivity(), res, PLAY_SERVICES_RESOLUTION_REQUEST).show(); 95 | //} 96 | 97 | Log.e(LOG_TAG, "Google Play Services are unavailable"); 98 | callbackContext.error("Unavailable"); 99 | return true; 100 | } 101 | else { 102 | Log.d(LOG_TAG, "** Google Play Services are available **"); 103 | } 104 | 105 | //args.length() 106 | //args.getString(0) 107 | //args.getString(1) 108 | //args.getInt(0) 109 | //args.getInt(1) 110 | //args.getBoolean(0) 111 | //args.getBoolean(1) 112 | 113 | if (action.equals("setUp")) { 114 | //Activity activity=cordova.getActivity(); 115 | //webView 116 | // 117 | 118 | final CallbackContext delayedCC = callbackContext; 119 | cordova.getActivity().runOnUiThread(new Runnable(){ 120 | @Override 121 | public void run() { 122 | _setUp(); 123 | 124 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 125 | //pr.setKeepCallback(true); 126 | delayedCC.sendPluginResult(pr); 127 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 128 | //pr.setKeepCallback(true); 129 | //delayedCC.sendPluginResult(pr); 130 | } 131 | }); 132 | 133 | return true; 134 | } 135 | else if (action.equals("login")) { 136 | //Activity activity=cordova.getActivity(); 137 | //webView 138 | // 139 | 140 | loginCC = callbackContext; 141 | 142 | final CallbackContext delayedCC = callbackContext; 143 | cordova.getActivity().runOnUiThread(new Runnable(){ 144 | @Override 145 | public void run() { 146 | 147 | if (getGameHelper().isSignedIn()) { 148 | /* 149 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 150 | //pr.setKeepCallback(true); 151 | //delayedCC.sendPluginResult(pr); 152 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Already logged in"); 153 | //pr.setKeepCallback(true); 154 | delayedCC.sendPluginResult(pr); 155 | */ 156 | onSignInSucceeded(); 157 | } 158 | else { 159 | _login(); 160 | } 161 | } 162 | }); 163 | 164 | return true; 165 | } 166 | else if (action.equals("logout")) { 167 | //Activity activity=cordova.getActivity(); 168 | //webView 169 | // 170 | 171 | final CallbackContext delayedCC = callbackContext; 172 | cordova.getActivity().runOnUiThread(new Runnable(){ 173 | @Override 174 | public void run() { 175 | if (getGameHelper().isSignedIn()) { 176 | _logout(); 177 | 178 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 179 | //pr.setKeepCallback(true); 180 | delayedCC.sendPluginResult(pr); 181 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 182 | //pr.setKeepCallback(true); 183 | //delayedCC.sendPluginResult(pr); 184 | } 185 | else { 186 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 187 | //pr.setKeepCallback(true); 188 | //delayedCC.sendPluginResult(pr); 189 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Already logged out"); 190 | //pr.setKeepCallback(true); 191 | delayedCC.sendPluginResult(pr); 192 | } 193 | } 194 | }); 195 | 196 | return true; 197 | } 198 | else if (action.equals("getPlayerImage")) { 199 | //Activity activity=cordova.getActivity(); 200 | //webView 201 | // 202 | 203 | getPlayerImageCC = callbackContext; 204 | 205 | final CallbackContext delayedCC = callbackContext; 206 | cordova.getActivity().runOnUiThread(new Runnable(){ 207 | @Override 208 | public void run() { 209 | if (getGameHelper().isSignedIn()) { 210 | _getPlayerImage(); 211 | } 212 | else { 213 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 214 | //pr.setKeepCallback(true); 215 | //delayedCC.sendPluginResult(pr); 216 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 217 | //pr.setKeepCallback(true); 218 | delayedCC.sendPluginResult(pr); 219 | } 220 | } 221 | }); 222 | 223 | return true; 224 | } 225 | else if (action.equals("getPlayerScore")) { 226 | //Activity activity=cordova.getActivity(); 227 | //webView 228 | // 229 | final String leaderboardId = args.getString(0); 230 | Log.d(LOG_TAG, String.format("%s", leaderboardId)); 231 | 232 | getPlayerScoreCC = callbackContext; 233 | 234 | final CallbackContext delayedCC = callbackContext; 235 | cordova.getActivity().runOnUiThread(new Runnable(){ 236 | @Override 237 | public void run() { 238 | if (getGameHelper().isSignedIn()) { 239 | _getPlayerScore(leaderboardId); 240 | } 241 | else { 242 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 243 | //pr.setKeepCallback(true); 244 | //delayedCC.sendPluginResult(pr); 245 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 246 | //pr.setKeepCallback(true); 247 | delayedCC.sendPluginResult(pr); 248 | } 249 | } 250 | }); 251 | 252 | return true; 253 | } 254 | else if (action.equals("submitScore")) { 255 | //Activity activity=cordova.getActivity(); 256 | //webView 257 | // 258 | final String leaderboardId = args.getString(0); 259 | Log.d(LOG_TAG, String.format("%s", leaderboardId)); 260 | final int score = args.getInt(1); 261 | Log.d(LOG_TAG, String.format("%d", score)); 262 | 263 | submitScoreCC = callbackContext; 264 | 265 | final CallbackContext delayedCC = callbackContext; 266 | cordova.getActivity().runOnUiThread(new Runnable(){ 267 | @Override 268 | public void run() { 269 | if (getGameHelper().isSignedIn()) { 270 | _submitScore(leaderboardId, score); 271 | } 272 | else { 273 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 274 | //pr.setKeepCallback(true); 275 | //delayedCC.sendPluginResult(pr); 276 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 277 | //pr.setKeepCallback(true); 278 | delayedCC.sendPluginResult(pr); 279 | } 280 | } 281 | }); 282 | 283 | return true; 284 | } 285 | else if (action.equals("showLeaderboard")) { 286 | //Activity activity=cordova.getActivity(); 287 | //webView 288 | // 289 | final String leaderboardId = args.getString(0); 290 | Log.d(LOG_TAG, String.format("%s", leaderboardId)); 291 | 292 | final CallbackContext delayedCC = callbackContext; 293 | cordova.getActivity().runOnUiThread(new Runnable(){ 294 | @Override 295 | public void run() { 296 | if (getGameHelper().isSignedIn()) { 297 | _showLeaderboard(leaderboardId); 298 | 299 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 300 | //pr.setKeepCallback(true); 301 | delayedCC.sendPluginResult(pr); 302 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 303 | //pr.setKeepCallback(true); 304 | //delayedCC.sendPluginResult(pr); 305 | } 306 | else { 307 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 308 | //pr.setKeepCallback(true); 309 | //delayedCC.sendPluginResult(pr); 310 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 311 | //pr.setKeepCallback(true); 312 | delayedCC.sendPluginResult(pr); 313 | } 314 | } 315 | }); 316 | 317 | return true; 318 | } 319 | else if (action.equals("showLeaderboards")) { 320 | //Activity activity=cordova.getActivity(); 321 | //webView 322 | // 323 | 324 | final CallbackContext delayedCC = callbackContext; 325 | cordova.getActivity().runOnUiThread(new Runnable(){ 326 | @Override 327 | public void run() { 328 | if (getGameHelper().isSignedIn()) { 329 | _showLeaderboards(); 330 | 331 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 332 | //pr.setKeepCallback(true); 333 | delayedCC.sendPluginResult(pr); 334 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 335 | //pr.setKeepCallback(true); 336 | //delayedCC.sendPluginResult(pr); 337 | } 338 | else { 339 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 340 | //pr.setKeepCallback(true); 341 | //delayedCC.sendPluginResult(pr); 342 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 343 | //pr.setKeepCallback(true); 344 | delayedCC.sendPluginResult(pr); 345 | } 346 | } 347 | }); 348 | 349 | return true; 350 | } 351 | else if (action.equals("unlockAchievement")) { 352 | //Activity activity=cordova.getActivity(); 353 | //webView 354 | // 355 | final String achievementId = args.getString(0); 356 | Log.d(LOG_TAG, String.format("%s", achievementId)); 357 | 358 | unlockAchievementCC = callbackContext; 359 | 360 | final CallbackContext delayedCC = callbackContext; 361 | cordova.getActivity().runOnUiThread(new Runnable(){ 362 | @Override 363 | public void run() { 364 | if (getGameHelper().isSignedIn()) { 365 | _unlockAchievement(achievementId); 366 | } 367 | else { 368 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 369 | //pr.setKeepCallback(true); 370 | //delayedCC.sendPluginResult(pr); 371 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 372 | //pr.setKeepCallback(true); 373 | delayedCC.sendPluginResult(pr); 374 | } 375 | } 376 | }); 377 | 378 | return true; 379 | } 380 | else if (action.equals("incrementAchievement")) { 381 | //Activity activity=cordova.getActivity(); 382 | //webView 383 | // 384 | final String achievementId = args.getString(0); 385 | Log.d(LOG_TAG, String.format("%s", achievementId)); 386 | final int stepsOrPercent = args.getInt(1); 387 | Log.d(LOG_TAG, String.format("%d", stepsOrPercent)); 388 | 389 | incrementAchievementCC = callbackContext; 390 | 391 | final CallbackContext delayedCC = callbackContext; 392 | cordova.getActivity().runOnUiThread(new Runnable(){ 393 | @Override 394 | public void run() { 395 | if (getGameHelper().isSignedIn()) { 396 | _incrementAchievement(achievementId, stepsOrPercent); 397 | } 398 | else { 399 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 400 | //pr.setKeepCallback(true); 401 | //delayedCC.sendPluginResult(pr); 402 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 403 | //pr.setKeepCallback(true); 404 | delayedCC.sendPluginResult(pr); 405 | } 406 | } 407 | }); 408 | 409 | return true; 410 | } 411 | else if (action.equals("showAchievements")) { 412 | //Activity activity=cordova.getActivity(); 413 | //webView 414 | // 415 | 416 | final CallbackContext delayedCC = callbackContext; 417 | cordova.getActivity().runOnUiThread(new Runnable(){ 418 | @Override 419 | public void run() { 420 | if (getGameHelper().isSignedIn()) { 421 | _showAchievements(); 422 | 423 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 424 | //pr.setKeepCallback(true); 425 | delayedCC.sendPluginResult(pr); 426 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 427 | //pr.setKeepCallback(true); 428 | //delayedCC.sendPluginResult(pr); 429 | } 430 | else { 431 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 432 | //pr.setKeepCallback(true); 433 | //delayedCC.sendPluginResult(pr); 434 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR, "Not logged in"); 435 | //pr.setKeepCallback(true); 436 | delayedCC.sendPluginResult(pr); 437 | } 438 | } 439 | }); 440 | 441 | return true; 442 | } 443 | else if (action.equals("resetAchievements")) { 444 | //Activity activity=cordova.getActivity(); 445 | //webView 446 | // 447 | 448 | final CallbackContext delayedCC = callbackContext; 449 | cordova.getActivity().runOnUiThread(new Runnable(){ 450 | @Override 451 | public void run() { 452 | _resetAchievements(); 453 | 454 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 455 | //pr.setKeepCallback(true); 456 | delayedCC.sendPluginResult(pr); 457 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 458 | //pr.setKeepCallback(true); 459 | //delayedCC.sendPluginResult(pr); 460 | } 461 | }); 462 | 463 | return true; 464 | } 465 | 466 | return false; // Returning false results in a "MethodNotFound" error. 467 | } 468 | 469 | //------------------------------------- 470 | private void _setUp(){ 471 | getGameHelper().setup(this);//public void setup(GameHelperListener listener) { 472 | 473 | cordova.setActivityResultCallback(this); 474 | } 475 | private GameHelper getGameHelper(){ 476 | if (mHelper == null) { 477 | mHelper = new GameHelper(this.cordova.getActivity(), GameHelper.CLIENT_GAMES);//public GameHelper(Activity activity, int clientsToUse) { 478 | mHelper.enableDebugLog(true); 479 | } 480 | return mHelper; 481 | } 482 | private void _login(){ 483 | //getGameHelper().beginUserInitiatedSignIn(); 484 | getGameHelper().onStart(this.cordova.getActivity()); 485 | } 486 | private void _logout(){ 487 | //getGameHelper().signOut(); 488 | getGameHelper().onStop(); 489 | } 490 | 491 | private void _getPlayerImage() { 492 | Player player = Games.Players.getCurrentPlayer(getGameHelper().getApiClient()); 493 | if (player != null) 494 | { 495 | boolean hasH = player.hasHiResImage(); 496 | boolean hasI = player.hasIconImage(); 497 | Uri playerImageUrl = null; 498 | if (hasH) { 499 | playerImageUrl = player.getHiResImageUri(); 500 | } 501 | else if (hasI) { 502 | playerImageUrl = player.getIconImageUri(); 503 | } 504 | else { 505 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 506 | //pr.setKeepCallback(true); 507 | //getPlayerImageCC.sendPluginResult(pr); 508 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 509 | //pr.setKeepCallback(true); 510 | getPlayerImageCC.sendPluginResult(pr); 511 | 512 | return; 513 | } 514 | 515 | PluginResult pr = new PluginResult(PluginResult.Status.OK, playerImageUrl.toString()); 516 | //pr.setKeepCallback(true); 517 | getPlayerImageCC.sendPluginResult(pr); 518 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 519 | //pr.setKeepCallback(true); 520 | //getPlayerImageCC.sendPluginResult(pr); 521 | } 522 | else { 523 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 524 | //pr.setKeepCallback(true); 525 | //getPlayerImageCC.sendPluginResult(pr); 526 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 527 | //pr.setKeepCallback(true); 528 | getPlayerImageCC.sendPluginResult(pr); 529 | } 530 | } 531 | 532 | private void _getPlayerScore(String leaderboardId){ 533 | class ResultCallbackSubmitScoreResult implements ResultCallback { 534 | @Override 535 | public void onResult(Leaderboards.LoadPlayerScoreResult result) { 536 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/Leaderboards.LoadPlayerScoreResult.html 537 | if (result.getStatus().getStatusCode() == GamesStatusCodes.STATUS_OK) { 538 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/LeaderboardScore.html 539 | LeaderboardScore ls = result.getScore(); 540 | long score = 0; 541 | if (ls != null) 542 | score = ls.getRawScore(); 543 | 544 | PluginResult pr = new PluginResult(PluginResult.Status.OK, score); 545 | //pr.setKeepCallback(true); 546 | getPlayerScoreCC.sendPluginResult(pr); 547 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 548 | //pr.setKeepCallback(true); 549 | //getPlayerScoreCC.sendPluginResult(pr); 550 | } 551 | else { 552 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 553 | //pr.setKeepCallback(true); 554 | //getPlayerScoreCC.sendPluginResult(pr); 555 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 556 | //pr.setKeepCallback(true); 557 | getPlayerScoreCC.sendPluginResult(pr); 558 | } 559 | } 560 | } 561 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/Leaderboards.html 562 | //span: Time span to retrieve data for. Valid values are TIME_SPAN_DAILY, TIME_SPAN_WEEKLY, or TIME_SPAN_ALL_TIME. 563 | //leaderboardCollection: The leaderboard collection to retrieve scores for. Valid values are either COLLECTION_PUBLIC or COLLECTION_SOCIAL. 564 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/Leaderboards.html#loadCurrentPlayerLeaderboardScore(com.google.android.gms.common.api.GoogleApiClient, java.lang.String, int, int) 565 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/LeaderboardVariant.html#TIME_SPAN_DAILY 566 | //http://stackoverflow.com/questions/23248157/how-to-get-score-from-google-play-game-services-leaderboard-of-current-player 567 | Games.Leaderboards.loadCurrentPlayerLeaderboardScore(getGameHelper().getApiClient(), leaderboardId, LeaderboardVariant.TIME_SPAN_ALL_TIME, LeaderboardVariant.COLLECTION_PUBLIC).setResultCallback(new ResultCallbackSubmitScoreResult()); 568 | } 569 | 570 | private void _submitScore(String leaderboardId, int score){ 571 | /* 572 | //https://developers.google.com/games/services/android/leaderboards 573 | Games.Leaderboards.submitScore(getGameHelper().getApiClient(), leaderboardId, score); 574 | */ 575 | ///* 576 | //http://stackoverflow.com/questions/22896713/listener-for-leaderboard-in-google-game-services 577 | //https://developer.android.com/reference/gms-packages.html 578 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/package-summary.html 579 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/Leaderboards.html 580 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/Leaderboards.html#submitScoreImmediate(com.google.android.gms.common.api.GoogleApiClient, java.lang.String, long) 581 | 582 | class ResultCallbackSubmitScoreResult implements ResultCallback { 583 | @Override 584 | public void onResult(Leaderboards.SubmitScoreResult result) { 585 | //https://developer.android.com/reference/com/google/android/gms/games/leaderboard/Leaderboards.SubmitScoreResult.html 586 | if (result.getStatus().getStatusCode() == GamesStatusCodes.STATUS_OK) { 587 | // data sent successfully to server. 588 | // display toast. 589 | 590 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 591 | //pr.setKeepCallback(true); 592 | submitScoreCC.sendPluginResult(pr); 593 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 594 | //pr.setKeepCallback(true); 595 | //submitScoreCC.sendPluginResult(pr); 596 | } 597 | else { 598 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 599 | //pr.setKeepCallback(true); 600 | //submitScoreCC.sendPluginResult(pr); 601 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 602 | //pr.setKeepCallback(true); 603 | submitScoreCC.sendPluginResult(pr); 604 | } 605 | } 606 | } 607 | //Games.Leaderboards.submitScoreImmediate(getGameHelper().getApiClient(), leaderboardId, score).setResultCallback(new ResultCallbackSubmitScoreResult()); 608 | try { 609 | Games.Leaderboards.submitScoreImmediate(getGameHelper().getApiClient(), leaderboardId, score).setResultCallback(new ResultCallbackSubmitScoreResult()); 610 | } 611 | catch(SecurityException ex) { 612 | Log.d(LOG_TAG, String.format("%s", ex.getMessage())); 613 | } 614 | //*/ 615 | } 616 | 617 | private void _showLeaderboard(String leaderboardId){ 618 | try { 619 | //show a specific leaderboard 620 | this.cordova.getActivity().startActivityForResult(Games.Leaderboards.getLeaderboardIntent(getGameHelper().getApiClient(), leaderboardId), 0); 621 | } 622 | catch(SecurityException ex) { 623 | Log.d(LOG_TAG, String.format("%s", ex.getMessage())); 624 | } 625 | } 626 | 627 | private void _showLeaderboards(){ 628 | try { 629 | //show all leaderboards 630 | this.cordova.getActivity().startActivityForResult(Games.Leaderboards.getAllLeaderboardsIntent(getGameHelper().getApiClient()), 0); 631 | //this.cordova.getActivity().startActivityFor(Games.Leaderboards.getAllLeaderboardsIntent(getGameHelper().getApiClient())); 632 | } 633 | catch(SecurityException ex) { 634 | Log.d(LOG_TAG, String.format("%s", ex.getMessage())); 635 | } 636 | } 637 | 638 | private void _unlockAchievement(String achievementId){ 639 | /* 640 | //Unlocking achievements 641 | //To unlock an achievement, call the unlock() method and and pass in the achievement ID. 642 | //Games.Achievements.unlock(getApiClient(), "my_achievement_id"); 643 | //If the achievement is of the incremental type (that is, several steps are required to unlock it), call increment() instead. 644 | //Games.Achievements.increment(getApiClient(), "my_incremental_achievment_id", 10000); 645 | //You do not need to write additional code to unlock the achievement; Play Games services automatically unlocks the achievement once it reaches its required number of steps. 646 | //https://developers.google.com/games/services/android/achievements 647 | Games.Achievements.unlock(getGameHelper().getApiClient(), achievementId); 648 | */ 649 | ///* 650 | //https://developer.android.com/reference/gms-packages.html 651 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/package-summary.html 652 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.html 653 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.html#incrementImmediate(com.google.android.gms.common.api.GoogleApiClient, java.lang.String, int) 654 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.html#unlockImmediate(com.google.android.gms.common.api.GoogleApiClient, java.lang.String) 655 | class ResultCallbackUpdateAchievementResult implements ResultCallback { 656 | @Override 657 | public void onResult(Achievements.UpdateAchievementResult result) { 658 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.UpdateAchievementResult.html 659 | if (result.getStatus().getStatusCode() == GamesStatusCodes.STATUS_OK) { 660 | // data sent successfully to server. 661 | // display toast. 662 | //Log.d(LOG_TAG, String.format("%d", result.getStatus().getStatusCode())); 663 | //Util.alert(cordova.getActivity(), String.format("%d", result.getStatus().getStatusCode())); 664 | 665 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 666 | //pr.setKeepCallback(true); 667 | unlockAchievementCC.sendPluginResult(pr); 668 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 669 | //pr.setKeepCallback(true); 670 | //unlockAchievementCC.sendPluginResult(pr); 671 | } 672 | else{ 673 | //Log.d(LOG_TAG, String.format("%d", result.getStatus().getStatusCode())); 674 | //Util.alert(cordova.getActivity(), String.format("%d", result.getStatus().getStatusCode())); 675 | 676 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 677 | //pr.setKeepCallback(true); 678 | //unlockAchievementCC.sendPluginResult(pr); 679 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 680 | //pr.setKeepCallback(true); 681 | unlockAchievementCC.sendPluginResult(pr); 682 | } 683 | } 684 | } 685 | Games.Achievements.unlockImmediate(getGameHelper().getApiClient(), achievementId).setResultCallback(new ResultCallbackUpdateAchievementResult()); 686 | //*/ 687 | } 688 | 689 | private void _incrementAchievement(String achievementId, int stepsOrPercent){ 690 | /* 691 | //Unlocking achievements 692 | //To unlock an achievement, call the unlock() method and and pass in the achievement ID. 693 | //Games.Achievements.unlock(getApiClient(), "my_achievement_id"); 694 | //If the achievement is of the incremental type (that is, several steps are required to unlock it), call increment() instead. 695 | //Games.Achievements.increment(getApiClient(), "my_incremental_achievment_id", 1); 696 | //You do not need to write additional code to unlock the achievement; Play Games services automatically unlocks the achievement once it reaches its required number of steps. 697 | //https://developers.google.com/games/services/android/achievements 698 | //Games.Achievements.unlock(getGameHelper().getApiClient(), achievementId); 699 | // 700 | Games.Achievements.increment(getGameHelper().getApiClient(), achievementId, stepsOrPercent); 701 | */ 702 | ///* 703 | //https://developer.android.com/reference/gms-packages.html 704 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/package-summary.html 705 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.html 706 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.html#incrementImmediate(com.google.android.gms.common.api.GoogleApiClient, java.lang.String, int) 707 | class ResultCallbackUpdateAchievementResult implements ResultCallback { 708 | @Override 709 | public void onResult(Achievements.UpdateAchievementResult result) { 710 | //https://developer.android.com/reference/com/google/android/gms/games/achievement/Achievements.UpdateAchievementResult.html 711 | if (result.getStatus().getStatusCode() == GamesStatusCodes.STATUS_OK) { 712 | // data sent successfully to server. 713 | // display toast. 714 | //Log.d(LOG_TAG, String.format("%d", result.getStatus().getStatusCode())); 715 | //Util.alert(cordova.getActivity(), String.format("%d", result.getStatus().getStatusCode())); 716 | 717 | PluginResult pr = new PluginResult(PluginResult.Status.OK); 718 | //pr.setKeepCallback(true); 719 | incrementAchievementCC.sendPluginResult(pr); 720 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 721 | //pr.setKeepCallback(true); 722 | //incrementAchievementCC.sendPluginResult(pr); 723 | } 724 | else{ 725 | //Log.d(LOG_TAG, String.format("%d", result.getStatus().getStatusCode())); 726 | //Util.alert(cordova.getActivity(), String.format("%d", result.getStatus().getStatusCode())); 727 | 728 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 729 | //pr.setKeepCallback(true); 730 | //incrementAchievementCC.sendPluginResult(pr); 731 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 732 | //pr.setKeepCallback(true); 733 | incrementAchievementCC.sendPluginResult(pr); 734 | } 735 | } 736 | } 737 | Games.Achievements.incrementImmediate(getGameHelper().getApiClient(), achievementId, stepsOrPercent).setResultCallback(new ResultCallbackUpdateAchievementResult()); 738 | //*/ 739 | } 740 | 741 | private void _showAchievements(){ 742 | this.cordova.getActivity().startActivityForResult(Games.Achievements.getAchievementsIntent(getGameHelper().getApiClient()), 0); 743 | } 744 | 745 | private void _resetAchievements(){///////////////todo 746 | 747 | } 748 | 749 | //GameHelper.GameHelperListener 750 | @Override 751 | public void onSignInSucceeded() { 752 | //Util.alert(cordova.getActivity(), "onSignInSucceeded"); 753 | 754 | //https://github.com/freshplanet/ANE-Google-Play-Game-Services/blob/master/android/src/com/freshplanet/googleplaygames/functions/AirGooglePlayGamesGetActivePlayerName.java 755 | //https://developer.android.com/reference/com/google/android/gms/games/Games.html#Players 756 | //https://developer.android.com/reference/com/google/android/gms/games/Players.html#getCurrentPlayer(com.google.android.gms.common.api.GoogleApiClient) 757 | //https://developer.android.com/reference/com/google/android/gms/games/Player.html 758 | Player player = Games.Players.getCurrentPlayer(getGameHelper().getApiClient()); 759 | JSONObject playerDetail = new JSONObject(); 760 | try { 761 | if (player != null) 762 | { 763 | String playerId = player.getPlayerId(); 764 | String displayName = player.getDisplayName(); 765 | //String title = player.getTitle(); 766 | 767 | playerDetail.put("playerId", playerId); 768 | playerDetail.put("playerDisplayName", displayName); 769 | } 770 | } 771 | catch(JSONException ex){ 772 | } 773 | 774 | PluginResult pr = new PluginResult(PluginResult.Status.OK, playerDetail); 775 | //pr.setKeepCallback(true); 776 | loginCC.sendPluginResult(pr); 777 | //PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 778 | //pr.setKeepCallback(true); 779 | //loginCC.sendPluginResult(pr); 780 | } 781 | @Override 782 | public void onSignInFailed() { 783 | //Util.alert(cordova.getActivity(), "onSignInFailed"); 784 | 785 | //PluginResult pr = new PluginResult(PluginResult.Status.OK); 786 | //pr.setKeepCallback(true); 787 | //loginCC.sendPluginResult(pr); 788 | PluginResult pr = new PluginResult(PluginResult.Status.ERROR); 789 | //pr.setKeepCallback(true); 790 | loginCC.sendPluginResult(pr); 791 | } 792 | 793 | //CordovaPlugin 794 | @Override 795 | public void onActivityResult(int requestCode, int resultCode, Intent intent) { 796 | getGameHelper().onActivityResult(requestCode, resultCode, intent); 797 | } 798 | 799 | /* 800 | //javascript 801 | getPlayerImage: function (cb) { 802 | var self = this; 803 | //cordova.exec(function (result) { 804 | // var playerImageUrl = result; 805 | // if (self.onGetPlayerImageSucceeded) 806 | // self.onGetPlayerImageSucceeded(playerImageUrl); 807 | //}, 808 | //function (error) { 809 | // if (self.onGetPlayerImageFailed) 810 | // self.onGetPlayerImageFailed(); 811 | //}, "Game", "getPlayerImage", []); 812 | 813 | this.gapi.client.request({ 814 | path : "/games/v1/players/me", 815 | callback : function(result) { 816 | var er = result && !result.error ? success(result) : null; 817 | cb(er, result.error); 818 | } 819 | }); 820 | }, 821 | 822 | //java 823 | @SuppressWarnings("unused") 824 | public void request(CordovaArgs args, final CallbackContext ctx) throws JSONException { 825 | 826 | JSONObject params = args.getJSONObject(0); 827 | String path = params.getString("path"); 828 | JSONObject requestParams = params.optJSONObject("params"); 829 | String method = params.optString("method"); 830 | if (method == null || method.length() == 0) { 831 | method = "GET"; 832 | } 833 | HashMap headers = null; 834 | JSONObject obj = params.optJSONObject("headers"); 835 | if (obj != null) { 836 | headers = new HashMap(); 837 | Iterator keys = obj.keys(); 838 | while (keys.hasNext()) { 839 | String key = keys.next(); 840 | headers.put(key, obj.get(key).toString()); 841 | } 842 | } 843 | 844 | byte[] body = null; 845 | try 846 | { 847 | JSONObject bodyJSON = params.optJSONObject("body"); 848 | if (bodyJSON != null) { 849 | body = bodyJSON.toString().getBytes("utf-8"); 850 | } 851 | else { 852 | String bodyString = params.optString("body"); 853 | if (bodyString != null && bodyString.length() > 0) { 854 | body = bodyString.getBytes("utf-8"); 855 | } 856 | } 857 | } 858 | catch (UnsupportedEncodingException e) 859 | { 860 | e.printStackTrace(); 861 | } 862 | 863 | request_inner(path, method, requestParams, body, headers, new GPGService.RequestCallback() { 864 | @Override 865 | public void onComplete(JSONObject responseJSON, GPGService.Error error) { 866 | 867 | JSONObject data = new JSONObject(); 868 | try 869 | { 870 | if (responseJSON != null) { 871 | data.put("response", responseJSON); 872 | } 873 | if (error != null) { 874 | data.put("error", new JSONObject(error.toMap())); 875 | } 876 | 877 | } 878 | catch (JSONException ex) { 879 | ex.printStackTrace(); 880 | } 881 | ctx.sendPluginResult(new PluginResult(PluginResult.Status.OK, data)); 882 | } 883 | }); 884 | } 885 | 886 | public void request_inner(String path,final String method, JSONObject params, final byte[] body, final Map headers, final RequestCallback callback) throws JSONException { 887 | 888 | if (!this.client.isConnected()) { 889 | 890 | if (callback != null) { 891 | callback.onComplete(null, new Error("User is not logged into Google Play Game Services", 0)); 892 | } 893 | return; 894 | } 895 | 896 | 897 | if (path.startsWith("/")) { 898 | path = "https://www.googleapis.com" + path; 899 | } 900 | 901 | if (params != null) { 902 | String query = ""; 903 | Iterator it = params.keys(); 904 | while (it.hasNext()) { 905 | if (query.length() == 0) 906 | query+="&"; 907 | String key = it.next(); 908 | query+= key + "=" + params.get(key).toString(); 909 | } 910 | path+= "?" + query; 911 | } 912 | 913 | final String absolutePath = path; 914 | 915 | AsyncTask task = new AsyncTask() { 916 | 917 | @Override 918 | protected Object doInBackground(Void... params) { 919 | HttpURLConnection connection = null; 920 | 921 | try { 922 | URL url = new URL(absolutePath); 923 | connection = (HttpURLConnection) url.openConnection(); 924 | connection.setRequestProperty("Authorization", "Bearer " + GPGService.this.authToken); 925 | connection.setRequestMethod(method); 926 | 927 | if (headers != null) { 928 | for (String key : headers.keySet()) 929 | { 930 | connection.setRequestProperty(key, headers.get(key)); 931 | } 932 | } 933 | 934 | if (body != null) 935 | { 936 | connection.setFixedLengthStreamingMode(body.length); 937 | connection.setDoOutput(true); 938 | if (connection.getRequestProperty("Content-Type") == null) 939 | connection.setRequestProperty("Content-Type", "text/plain;charset=UTF-8"); 940 | 941 | connection.setRequestProperty("Content-Length", Integer.toString(body.length)); 942 | OutputStream output = null; 943 | try 944 | { 945 | output = connection.getOutputStream(); 946 | output.write(body); 947 | } 948 | finally 949 | { 950 | if (output != null) { 951 | output.close(); 952 | } 953 | } 954 | } 955 | 956 | int statusCode = connection.getResponseCode(); 957 | InputStream inputStream; 958 | if (statusCode >= 200 && statusCode < 300) { 959 | inputStream = connection.getInputStream(); 960 | } else { 961 | inputStream = connection.getErrorStream(); 962 | } 963 | 964 | String content = convertStreamToString(inputStream); 965 | 966 | JSONObject result = new JSONObject(content); 967 | return result; 968 | } 969 | catch (Exception e) { 970 | return new Error(e.getLocalizedMessage(), 0); 971 | } 972 | finally { 973 | if (connection != null) { 974 | connection.disconnect(); 975 | } 976 | } 977 | } 978 | 979 | @Override 980 | protected void onPostExecute(Object info) { 981 | if (callback == null) { 982 | return; 983 | } 984 | if (info == null) { 985 | callback.onComplete(null, null); 986 | } 987 | else if (info instanceof Error) { 988 | callback.onComplete(null, (Error)info); 989 | } 990 | else { 991 | callback.onComplete((JSONObject)info, null); 992 | } 993 | } 994 | 995 | }; 996 | 997 | if (this.executor != null) { 998 | task.executeOnExecutor(executor); 999 | } 1000 | else { 1001 | task.execute(); 1002 | } 1003 | } 1004 | 1005 | static String convertStreamToString(java.io.InputStream is) { 1006 | java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A"); 1007 | return s.hasNext() ? s.next() : ""; 1008 | } 1009 | */ 1010 | } -------------------------------------------------------------------------------- /src/android/GameHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.example.games.basegameutils; 18 | 19 | import java.util.ArrayList; 20 | 21 | import android.app.Activity; 22 | import android.app.AlertDialog; 23 | import android.app.Dialog; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.content.IntentSender.SendIntentException; 27 | import android.content.SharedPreferences; 28 | import android.os.Bundle; 29 | import android.os.Handler; 30 | import android.util.Log; 31 | 32 | //import com.google.android.gms.appstate.AppStateManager;//deprecated 33 | import com.google.android.gms.common.ConnectionResult; 34 | //import com.google.android.gms.common.GooglePlayServicesUtil;//deprecated 35 | import com.google.android.gms.common.GoogleApiAvailability;// 36 | import com.google.android.gms.common.api.Api.ApiOptions.NoOptions; 37 | import com.google.android.gms.common.api.GoogleApiClient; 38 | import com.google.android.gms.drive.Drive; 39 | import com.google.android.gms.games.Games; 40 | import com.google.android.gms.games.Games.GamesOptions; 41 | import com.google.android.gms.games.GamesActivityResultCodes; 42 | import com.google.android.gms.games.multiplayer.Invitation; 43 | import com.google.android.gms.games.multiplayer.Multiplayer; 44 | import com.google.android.gms.games.multiplayer.turnbased.TurnBasedMatch; 45 | import com.google.android.gms.games.request.GameRequest; 46 | import com.google.android.gms.plus.Plus; 47 | import com.google.android.gms.plus.Plus.PlusOptions; 48 | 49 | public class GameHelper implements GoogleApiClient.ConnectionCallbacks, 50 | GoogleApiClient.OnConnectionFailedListener { 51 | 52 | static final String TAG = "GameHelper"; 53 | // 54 | private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000 ;// 55 | 56 | /** Listener for sign-in success or failure events. */ 57 | public interface GameHelperListener { 58 | /** 59 | * Called when sign-in fails. As a result, a "Sign-In" button can be 60 | * shown to the user; when that button is clicked, call 61 | * 62 | * @link{GamesHelper#beginUserInitiatedSignIn . Note that not all calls 63 | * to this method mean an 64 | * error; it may be a result 65 | * of the fact that automatic 66 | * sign-in could not proceed 67 | * because user interaction 68 | * was required (consent 69 | * dialogs). So 70 | * implementations of this 71 | * method should NOT display 72 | * an error message unless a 73 | * call to @link{GamesHelper# 74 | * hasSignInError} indicates 75 | * that an error indeed 76 | * occurred. 77 | */ 78 | void onSignInFailed(); 79 | 80 | /** Called when sign-in succeeds. */ 81 | void onSignInSucceeded(); 82 | } 83 | 84 | // configuration done? 85 | private boolean mSetupDone = false; 86 | 87 | // are we currently connecting? 88 | private boolean mConnecting = false; 89 | 90 | // Are we expecting the result of a resolution flow? 91 | boolean mExpectingResolution = false; 92 | 93 | // was the sign-in flow cancelled when we tried it? 94 | // if true, we know not to try again automatically. 95 | boolean mSignInCancelled = false; 96 | 97 | /** 98 | * The Activity we are bound to. We need to keep a reference to the Activity 99 | * because some games methods require an Activity (a Context won't do). We 100 | * are careful not to leak these references: we release them on onStop(). 101 | */ 102 | Activity mActivity = null; 103 | 104 | // app context 105 | Context mAppContext = null; 106 | 107 | // Request code we use when invoking other Activities to complete the 108 | // sign-in flow. 109 | final static int RC_RESOLVE = 9001; 110 | 111 | // Request code when invoking Activities whose result we don't care about. 112 | final static int RC_UNUSED = 9002; 113 | 114 | // the Google API client builder we will use to create GoogleApiClient 115 | GoogleApiClient.Builder mGoogleApiClientBuilder = null; 116 | 117 | // Api options to use when adding each API, null for none 118 | GamesOptions mGamesApiOptions = GamesOptions.builder().build(); 119 | PlusOptions mPlusApiOptions = null; 120 | NoOptions mAppStateApiOptions = null; 121 | 122 | // Google API client object we manage. 123 | GoogleApiClient mGoogleApiClient = null; 124 | 125 | // Client request flags 126 | public final static int CLIENT_NONE = 0x00; 127 | public final static int CLIENT_GAMES = 0x01; 128 | public final static int CLIENT_PLUS = 0x02; 129 | public final static int CLIENT_APPSTATE = 0x04; 130 | public final static int CLIENT_SNAPSHOT = 0x08; 131 | public final static int CLIENT_ALL = CLIENT_GAMES | CLIENT_PLUS 132 | | CLIENT_APPSTATE | CLIENT_SNAPSHOT; 133 | 134 | // What clients were requested? (bit flags) 135 | int mRequestedClients = CLIENT_NONE; 136 | 137 | // Whether to automatically try to sign in on onStart(). We only set this 138 | // to true when the sign-in process fails or the user explicitly signs out. 139 | // We set it back to false when the user initiates the sign in process. 140 | boolean mConnectOnStart = true; 141 | 142 | /* 143 | * Whether user has specifically requested that the sign-in process begin. 144 | * If mUserInitiatedSignIn is false, we're in the automatic sign-in attempt 145 | * that we try once the Activity is started -- if true, then the user has 146 | * already clicked a "Sign-In" button or something similar 147 | */ 148 | boolean mUserInitiatedSignIn = false; 149 | 150 | // The connection result we got from our last attempt to sign-in. 151 | ConnectionResult mConnectionResult = null; 152 | 153 | // The error that happened during sign-in. 154 | SignInFailureReason mSignInFailureReason = null; 155 | 156 | // Should we show error dialog boxes? 157 | boolean mShowErrorDialogs = true; 158 | 159 | // Print debug logs? 160 | boolean mDebugLog = false; 161 | 162 | Handler mHandler; 163 | 164 | /* 165 | * If we got an invitation when we connected to the games client, it's here. 166 | * Otherwise, it's null. 167 | */ 168 | Invitation mInvitation; 169 | 170 | /* 171 | * If we got turn-based match when we connected to the games client, it's 172 | * here. Otherwise, it's null. 173 | */ 174 | TurnBasedMatch mTurnBasedMatch; 175 | 176 | /* 177 | * If we have incoming requests when we connected to the games client, they 178 | * are here. Otherwise, it's null. 179 | */ 180 | ArrayList mRequests; 181 | 182 | // Listener 183 | GameHelperListener mListener = null; 184 | 185 | // Should we start the flow to sign the user in automatically on startup? If 186 | // so, up to 187 | // how many times in the life of the application? 188 | static final int DEFAULT_MAX_SIGN_IN_ATTEMPTS = 3; 189 | int mMaxAutoSignInAttempts = DEFAULT_MAX_SIGN_IN_ATTEMPTS; 190 | 191 | /** 192 | * Construct a GameHelper object, initially tied to the given Activity. 193 | * After constructing this object, call @link{setup} from the onCreate() 194 | * method of your Activity. 195 | * 196 | * @param clientsToUse 197 | * the API clients to use (a combination of the CLIENT_* flags, 198 | * or CLIENT_ALL to mean all clients). 199 | */ 200 | public GameHelper(Activity activity, int clientsToUse) { 201 | mActivity = activity; 202 | mAppContext = activity.getApplicationContext(); 203 | mRequestedClients = clientsToUse; 204 | mHandler = new Handler(); 205 | } 206 | 207 | /** 208 | * Sets the maximum number of automatic sign-in attempts to be made on 209 | * application startup. This maximum is over the lifetime of the application 210 | * (it is stored in a SharedPreferences file). So, for example, if you 211 | * specify 2, then it means that the user will be prompted to sign in on app 212 | * startup the first time and, if they cancel, a second time the next time 213 | * the app starts, and, if they cancel that one, never again. Set to 0 if 214 | * you do not want the user to be prompted to sign in on application 215 | * startup. 216 | */ 217 | public void setMaxAutoSignInAttempts(int max) { 218 | mMaxAutoSignInAttempts = max; 219 | } 220 | 221 | void assertConfigured(String operation) { 222 | if (!mSetupDone) { 223 | String error = "GameHelper error: Operation attempted without setup: " 224 | + operation 225 | + ". The setup() method must be called before attempting any other operation."; 226 | logError(error); 227 | throw new IllegalStateException(error); 228 | } 229 | } 230 | 231 | private void doApiOptionsPreCheck() { 232 | if (mGoogleApiClientBuilder != null) { 233 | String error = "GameHelper: you cannot call set*ApiOptions after the client " 234 | + "builder has been created. Call it before calling createApiClientBuilder() " 235 | + "or setup()."; 236 | logError(error); 237 | throw new IllegalStateException(error); 238 | } 239 | } 240 | 241 | /** 242 | * Sets the options to pass when setting up the Games API. Call before 243 | * setup(). 244 | */ 245 | public void setGamesApiOptions(GamesOptions options) { 246 | doApiOptionsPreCheck(); 247 | mGamesApiOptions = options; 248 | } 249 | 250 | /** 251 | * Sets the options to pass when setting up the AppState API. Call before 252 | * setup(). 253 | */ 254 | public void setAppStateApiOptions(NoOptions options) { 255 | doApiOptionsPreCheck(); 256 | mAppStateApiOptions = options; 257 | } 258 | 259 | /** 260 | * Sets the options to pass when setting up the Plus API. Call before 261 | * setup(). 262 | */ 263 | public void setPlusApiOptions(PlusOptions options) { 264 | doApiOptionsPreCheck(); 265 | mPlusApiOptions = options; 266 | } 267 | 268 | /** 269 | * Creates a GoogleApiClient.Builder for use with @link{#setup}. Normally, 270 | * you do not have to do this; use this method only if you need to make 271 | * nonstandard setup (e.g. adding extra scopes for other APIs) on the 272 | * GoogleApiClient.Builder before calling @link{#setup}. 273 | */ 274 | public GoogleApiClient.Builder createApiClientBuilder() { 275 | if (mSetupDone) { 276 | String error = "GameHelper: you called GameHelper.createApiClientBuilder() after " 277 | + "calling setup. You can only get a client builder BEFORE performing setup."; 278 | logError(error); 279 | throw new IllegalStateException(error); 280 | } 281 | 282 | GoogleApiClient.Builder builder = new GoogleApiClient.Builder( 283 | mActivity, this, this); 284 | 285 | if (0 != (mRequestedClients & CLIENT_GAMES)) { 286 | builder.addApi(Games.API, mGamesApiOptions); 287 | builder.addScope(Games.SCOPE_GAMES); 288 | } 289 | 290 | if (0 != (mRequestedClients & CLIENT_PLUS)) { 291 | builder.addApi(Plus.API); 292 | builder.addScope(Plus.SCOPE_PLUS_LOGIN); 293 | } 294 | 295 | //if (0 != (mRequestedClients & CLIENT_APPSTATE)) {//deprecated 296 | // builder.addApi(AppStateManager.API); 297 | // builder.addScope(AppStateManager.SCOPE_APP_STATE); 298 | //} 299 | 300 | if (0 != (mRequestedClients & CLIENT_SNAPSHOT)) { 301 | builder.addScope(Drive.SCOPE_APPFOLDER); 302 | builder.addApi(Drive.API); 303 | } 304 | 305 | mGoogleApiClientBuilder = builder; 306 | return builder; 307 | } 308 | 309 | /** 310 | * Performs setup on this GameHelper object. Call this from the onCreate() 311 | * method of your Activity. This will create the clients and do a few other 312 | * initialization tasks. Next, call @link{#onStart} from the onStart() 313 | * method of your Activity. 314 | * 315 | * @param listener 316 | * The listener to be notified of sign-in events. 317 | */ 318 | public void setup(GameHelperListener listener) { 319 | if (mSetupDone) { 320 | String error = "GameHelper: you cannot call GameHelper.setup() more than once!"; 321 | logError(error); 322 | throw new IllegalStateException(error); 323 | } 324 | mListener = listener; 325 | debugLog("Setup: requested clients: " + mRequestedClients); 326 | 327 | if (mGoogleApiClientBuilder == null) { 328 | // we don't have a builder yet, so create one 329 | createApiClientBuilder(); 330 | } 331 | 332 | mGoogleApiClient = mGoogleApiClientBuilder.build(); 333 | mGoogleApiClientBuilder = null; 334 | mSetupDone = true; 335 | } 336 | 337 | /** 338 | * Returns the GoogleApiClient object. In order to call this method, you 339 | * must have called @link{setup}. 340 | */ 341 | public GoogleApiClient getApiClient() { 342 | if (mGoogleApiClient == null) { 343 | throw new IllegalStateException( 344 | "No GoogleApiClient. Did you call setup()?"); 345 | } 346 | return mGoogleApiClient; 347 | } 348 | 349 | /** Returns whether or not the user is signed in. */ 350 | public boolean isSignedIn() { 351 | return mGoogleApiClient != null && mGoogleApiClient.isConnected(); 352 | } 353 | 354 | /** Returns whether or not we are currently connecting */ 355 | public boolean isConnecting() { 356 | return mConnecting; 357 | } 358 | 359 | /** 360 | * Returns whether or not there was a (non-recoverable) error during the 361 | * sign-in process. 362 | */ 363 | public boolean hasSignInError() { 364 | return mSignInFailureReason != null; 365 | } 366 | 367 | /** 368 | * Returns the error that happened during the sign-in process, null if no 369 | * error occurred. 370 | */ 371 | public SignInFailureReason getSignInError() { 372 | return mSignInFailureReason; 373 | } 374 | 375 | // Set whether to show error dialogs or not. 376 | public void setShowErrorDialogs(boolean show) { 377 | mShowErrorDialogs = show; 378 | } 379 | 380 | /** Call this method from your Activity's onStart(). */ 381 | public void onStart(Activity act) { 382 | mActivity = act; 383 | mAppContext = act.getApplicationContext(); 384 | 385 | debugLog("onStart"); 386 | assertConfigured("onStart"); 387 | 388 | if (mConnectOnStart) { 389 | if (mGoogleApiClient.isConnected()) { 390 | Log.w(TAG, 391 | "GameHelper: client was already connected on onStart()"); 392 | } else { 393 | debugLog("Connecting client."); 394 | mConnecting = true; 395 | mGoogleApiClient.connect(); 396 | } 397 | } else { 398 | debugLog("Not attempting to connect becase mConnectOnStart=false"); 399 | debugLog("Instead, reporting a sign-in failure."); 400 | mHandler.postDelayed(new Runnable() { 401 | @Override 402 | public void run() { 403 | notifyListener(false); 404 | } 405 | }, 1000); 406 | } 407 | } 408 | 409 | /** Call this method from your Activity's onStop(). */ 410 | public void onStop() { 411 | debugLog("onStop"); 412 | assertConfigured("onStop"); 413 | if (mGoogleApiClient.isConnected()) { 414 | debugLog("Disconnecting client due to onStop"); 415 | mGoogleApiClient.disconnect(); 416 | } else { 417 | debugLog("Client already disconnected when we got onStop."); 418 | } 419 | mConnecting = false; 420 | mExpectingResolution = false; 421 | 422 | // let go of the Activity reference 423 | mActivity = null; 424 | } 425 | 426 | /** 427 | * Returns the invitation ID received through an invitation notification. 428 | * This should be called from your GameHelperListener's 429 | * 430 | * @link{GameHelperListener#onSignInSucceeded method, to check if there's an 431 | * invitation available. In that 432 | * case, accept the invitation. 433 | * @return The id of the invitation, or null if none was received. 434 | */ 435 | public String getInvitationId() { 436 | if (!mGoogleApiClient.isConnected()) { 437 | Log.w(TAG, 438 | "Warning: getInvitationId() should only be called when signed in, " 439 | + "that is, after getting onSignInSuceeded()"); 440 | } 441 | return mInvitation == null ? null : mInvitation.getInvitationId(); 442 | } 443 | 444 | /** 445 | * Returns the invitation received through an invitation notification. This 446 | * should be called from your GameHelperListener's 447 | * 448 | * @link{GameHelperListener#onSignInSucceeded method, to check if there's an 449 | * invitation available. In that 450 | * case, accept the invitation. 451 | * @return The invitation, or null if none was received. 452 | */ 453 | public Invitation getInvitation() { 454 | if (!mGoogleApiClient.isConnected()) { 455 | Log.w(TAG, 456 | "Warning: getInvitation() should only be called when signed in, " 457 | + "that is, after getting onSignInSuceeded()"); 458 | } 459 | return mInvitation; 460 | } 461 | 462 | public boolean hasInvitation() { 463 | return mInvitation != null; 464 | } 465 | 466 | public boolean hasTurnBasedMatch() { 467 | return mTurnBasedMatch != null; 468 | } 469 | 470 | public boolean hasRequests() { 471 | return mRequests != null; 472 | } 473 | 474 | public void clearInvitation() { 475 | mInvitation = null; 476 | } 477 | 478 | public void clearTurnBasedMatch() { 479 | mTurnBasedMatch = null; 480 | } 481 | 482 | public void clearRequests() { 483 | mRequests = null; 484 | } 485 | 486 | /** 487 | * Returns the tbmp match received through an invitation notification. This 488 | * should be called from your GameHelperListener's 489 | * 490 | * @link{GameHelperListener#onSignInSucceeded method, to check if there's a 491 | * match available. 492 | * @return The match, or null if none was received. 493 | */ 494 | public TurnBasedMatch getTurnBasedMatch() { 495 | if (!mGoogleApiClient.isConnected()) { 496 | Log.w(TAG, 497 | "Warning: getTurnBasedMatch() should only be called when signed in, " 498 | + "that is, after getting onSignInSuceeded()"); 499 | } 500 | return mTurnBasedMatch; 501 | } 502 | 503 | /** 504 | * Returns the requests received through the onConnected bundle. This should 505 | * be called from your GameHelperListener's 506 | * 507 | * @link{GameHelperListener#onSignInSucceeded method, to check if there are 508 | * incoming requests that must be 509 | * handled. 510 | * @return The requests, or null if none were received. 511 | */ 512 | public ArrayList getRequests() { 513 | if (!mGoogleApiClient.isConnected()) { 514 | Log.w(TAG, "Warning: getRequests() should only be called " 515 | + "when signed in, " 516 | + "that is, after getting onSignInSuceeded()"); 517 | } 518 | return mRequests; 519 | } 520 | 521 | /** Enables debug logging */ 522 | public void enableDebugLog(boolean enabled) { 523 | mDebugLog = enabled; 524 | if (enabled) { 525 | debugLog("Debug log enabled."); 526 | } 527 | } 528 | 529 | @Deprecated 530 | public void enableDebugLog(boolean enabled, String tag) { 531 | Log.w(TAG, "GameHelper.enableDebugLog(boolean,String) is deprecated. " 532 | + "Use GameHelper.enableDebugLog(boolean)"); 533 | enableDebugLog(enabled); 534 | } 535 | 536 | /** Sign out and disconnect from the APIs. */ 537 | public void signOut() { 538 | if (!mGoogleApiClient.isConnected()) { 539 | // nothing to do 540 | debugLog("signOut: was already disconnected, ignoring."); 541 | return; 542 | } 543 | 544 | // for Plus, "signing out" means clearing the default account and 545 | // then disconnecting 546 | if (0 != (mRequestedClients & CLIENT_PLUS)) { 547 | debugLog("Clearing default account on PlusClient."); 548 | Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); 549 | } 550 | 551 | // For the games client, signing out means calling signOut and 552 | // disconnecting 553 | if (0 != (mRequestedClients & CLIENT_GAMES)) { 554 | debugLog("Signing out from the Google API Client."); 555 | Games.signOut(mGoogleApiClient); 556 | } 557 | 558 | // Ready to disconnect 559 | debugLog("Disconnecting client."); 560 | mConnectOnStart = false; 561 | mConnecting = false; 562 | mGoogleApiClient.disconnect(); 563 | } 564 | 565 | /** 566 | * Handle activity result. Call this method from your Activity's 567 | * onActivityResult callback. If the activity result pertains to the sign-in 568 | * process, processes it appropriately. 569 | */ 570 | public void onActivityResult(int requestCode, int responseCode, 571 | Intent intent) { 572 | debugLog("onActivityResult: req=" 573 | + (requestCode == RC_RESOLVE ? "RC_RESOLVE" : String 574 | .valueOf(requestCode)) + ", resp=" 575 | + GameHelperUtils.activityResponseCodeToString(responseCode)); 576 | if (requestCode != RC_RESOLVE) { 577 | debugLog("onActivityResult: request code not meant for us. Ignoring."); 578 | return; 579 | } 580 | 581 | // no longer expecting a resolution 582 | mExpectingResolution = false; 583 | 584 | if (!mConnecting) { 585 | debugLog("onActivityResult: ignoring because we are not connecting."); 586 | return; 587 | } 588 | 589 | // We're coming back from an activity that was launched to resolve a 590 | // connection problem. For example, the sign-in UI. 591 | if (responseCode == Activity.RESULT_OK) { 592 | // Ready to try to connect again. 593 | debugLog("onAR: Resolution was RESULT_OK, so connecting current client again."); 594 | connect(); 595 | } else if (responseCode == GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED) { 596 | debugLog("onAR: Resolution was RECONNECT_REQUIRED, so reconnecting."); 597 | connect(); 598 | } else if (responseCode == Activity.RESULT_CANCELED) { 599 | // User cancelled. 600 | debugLog("onAR: Got a cancellation result, so disconnecting."); 601 | mSignInCancelled = true; 602 | mConnectOnStart = false; 603 | mUserInitiatedSignIn = false; 604 | mSignInFailureReason = null; // cancelling is not a failure! 605 | mConnecting = false; 606 | mGoogleApiClient.disconnect(); 607 | 608 | // increment # of cancellations 609 | int prevCancellations = getSignInCancellations(); 610 | int newCancellations = incrementSignInCancellations(); 611 | debugLog("onAR: # of cancellations " + prevCancellations + " --> " 612 | + newCancellations + ", max " + mMaxAutoSignInAttempts); 613 | 614 | notifyListener(false); 615 | } else { 616 | // Whatever the problem we were trying to solve, it was not 617 | // solved. So give up and show an error message. 618 | debugLog("onAR: responseCode=" 619 | + GameHelperUtils 620 | .activityResponseCodeToString(responseCode) 621 | + ", so giving up."); 622 | giveUp(new SignInFailureReason(mConnectionResult.getErrorCode(), 623 | responseCode)); 624 | } 625 | } 626 | 627 | void notifyListener(boolean success) { 628 | debugLog("Notifying LISTENER of sign-in " 629 | + (success ? "SUCCESS" 630 | : mSignInFailureReason != null ? "FAILURE (error)" 631 | : "FAILURE (no error)")); 632 | if (mListener != null) { 633 | if (success) { 634 | mListener.onSignInSucceeded(); 635 | } else { 636 | mListener.onSignInFailed(); 637 | } 638 | } 639 | } 640 | 641 | /** 642 | * Starts a user-initiated sign-in flow. This should be called when the user 643 | * clicks on a "Sign In" button. As a result, authentication/consent dialogs 644 | * may show up. At the end of the process, the GameHelperListener's 645 | * onSignInSucceeded() or onSignInFailed() methods will be called. 646 | */ 647 | public void beginUserInitiatedSignIn() { 648 | debugLog("beginUserInitiatedSignIn: resetting attempt count."); 649 | resetSignInCancellations(); 650 | mSignInCancelled = false; 651 | mConnectOnStart = true; 652 | 653 | if (mGoogleApiClient.isConnected()) { 654 | // nothing to do 655 | logWarn("beginUserInitiatedSignIn() called when already connected. " 656 | + "Calling listener directly to notify of success."); 657 | notifyListener(true); 658 | return; 659 | } else if (mConnecting) { 660 | logWarn("beginUserInitiatedSignIn() called when already connecting. " 661 | + "Be patient! You can only call this method after you get an " 662 | + "onSignInSucceeded() or onSignInFailed() callback. Suggestion: disable " 663 | + "the sign-in button on startup and also when it's clicked, and re-enable " 664 | + "when you get the callback."); 665 | // ignore call (listener will get a callback when the connection 666 | // process finishes) 667 | return; 668 | } 669 | 670 | debugLog("Starting USER-INITIATED sign-in flow."); 671 | 672 | // indicate that user is actively trying to sign in (so we know to 673 | // resolve 674 | // connection problems by showing dialogs) 675 | mUserInitiatedSignIn = true; 676 | 677 | if (mConnectionResult != null) { 678 | // We have a pending connection result from a previous failure, so 679 | // start with that. 680 | debugLog("beginUserInitiatedSignIn: continuing pending sign-in flow."); 681 | mConnecting = true; 682 | resolveConnectionResult(); 683 | } else { 684 | // We don't have a pending connection result, so start anew. 685 | debugLog("beginUserInitiatedSignIn: starting new sign-in flow."); 686 | mConnecting = true; 687 | connect(); 688 | } 689 | } 690 | 691 | void connect() { 692 | if (mGoogleApiClient.isConnected()) { 693 | debugLog("Already connected."); 694 | return; 695 | } 696 | debugLog("Starting connection."); 697 | mConnecting = true; 698 | mInvitation = null; 699 | mTurnBasedMatch = null; 700 | mGoogleApiClient.connect(); 701 | } 702 | 703 | /** 704 | * Disconnects the API client, then connects again. 705 | */ 706 | public void reconnectClient() { 707 | if (!mGoogleApiClient.isConnected()) { 708 | Log.w(TAG, "reconnectClient() called when client is not connected."); 709 | // interpret it as a request to connect 710 | connect(); 711 | } else { 712 | debugLog("Reconnecting client."); 713 | mGoogleApiClient.reconnect(); 714 | } 715 | } 716 | 717 | /** Called when we successfully obtain a connection to a client. */ 718 | @Override 719 | public void onConnected(Bundle connectionHint) { 720 | debugLog("onConnected: connected!"); 721 | 722 | if (connectionHint != null) { 723 | debugLog("onConnected: connection hint provided. Checking for invite."); 724 | Invitation inv = connectionHint 725 | .getParcelable(Multiplayer.EXTRA_INVITATION); 726 | if (inv != null && inv.getInvitationId() != null) { 727 | // retrieve and cache the invitation ID 728 | debugLog("onConnected: connection hint has a room invite!"); 729 | mInvitation = inv; 730 | debugLog("Invitation ID: " + mInvitation.getInvitationId()); 731 | } 732 | 733 | // Do we have any requests pending? 734 | mRequests = Games.Requests 735 | .getGameRequestsFromBundle(connectionHint); 736 | if (!mRequests.isEmpty()) { 737 | // We have requests in onConnected's connectionHint. 738 | debugLog("onConnected: connection hint has " + mRequests.size() 739 | + " request(s)"); 740 | } 741 | 742 | debugLog("onConnected: connection hint provided. Checking for TBMP game."); 743 | mTurnBasedMatch = connectionHint 744 | .getParcelable(Multiplayer.EXTRA_TURN_BASED_MATCH); 745 | } 746 | 747 | // we're good to go 748 | succeedSignIn(); 749 | } 750 | 751 | void succeedSignIn() { 752 | debugLog("succeedSignIn"); 753 | mSignInFailureReason = null; 754 | mConnectOnStart = true; 755 | mUserInitiatedSignIn = false; 756 | mConnecting = false; 757 | notifyListener(true); 758 | } 759 | 760 | private final String GAMEHELPER_SHARED_PREFS = "GAMEHELPER_SHARED_PREFS"; 761 | private final String KEY_SIGN_IN_CANCELLATIONS = "KEY_SIGN_IN_CANCELLATIONS"; 762 | 763 | // Return the number of times the user has cancelled the sign-in flow in the 764 | // life of the app 765 | int getSignInCancellations() { 766 | SharedPreferences sp = mAppContext.getSharedPreferences( 767 | GAMEHELPER_SHARED_PREFS, Context.MODE_PRIVATE); 768 | return sp.getInt(KEY_SIGN_IN_CANCELLATIONS, 0); 769 | } 770 | 771 | // Increments the counter that indicates how many times the user has 772 | // cancelled the sign in 773 | // flow in the life of the application 774 | int incrementSignInCancellations() { 775 | int cancellations = getSignInCancellations(); 776 | SharedPreferences.Editor editor = mAppContext.getSharedPreferences( 777 | GAMEHELPER_SHARED_PREFS, Context.MODE_PRIVATE).edit(); 778 | editor.putInt(KEY_SIGN_IN_CANCELLATIONS, cancellations + 1); 779 | editor.commit(); 780 | return cancellations + 1; 781 | } 782 | 783 | // Reset the counter of how many times the user has cancelled the sign-in 784 | // flow. 785 | void resetSignInCancellations() { 786 | SharedPreferences.Editor editor = mAppContext.getSharedPreferences( 787 | GAMEHELPER_SHARED_PREFS, Context.MODE_PRIVATE).edit(); 788 | editor.putInt(KEY_SIGN_IN_CANCELLATIONS, 0); 789 | editor.commit(); 790 | } 791 | 792 | /** Handles a connection failure. */ 793 | @Override 794 | public void onConnectionFailed(ConnectionResult result) { 795 | // save connection result for later reference 796 | debugLog("onConnectionFailed"); 797 | 798 | mConnectionResult = result; 799 | debugLog("Connection failure:"); 800 | debugLog(" - code: " 801 | + GameHelperUtils.errorCodeToString(mConnectionResult 802 | .getErrorCode())); 803 | debugLog(" - resolvable: " + mConnectionResult.hasResolution()); 804 | debugLog(" - details: " + mConnectionResult.toString()); 805 | 806 | int cancellations = getSignInCancellations(); 807 | boolean shouldResolve = false; 808 | 809 | if (mUserInitiatedSignIn) { 810 | debugLog("onConnectionFailed: WILL resolve because user initiated sign-in."); 811 | shouldResolve = true; 812 | } else if (mSignInCancelled) { 813 | debugLog("onConnectionFailed WILL NOT resolve (user already cancelled once)."); 814 | shouldResolve = false; 815 | } else if (cancellations < mMaxAutoSignInAttempts) { 816 | debugLog("onConnectionFailed: WILL resolve because we have below the max# of " 817 | + "attempts, " 818 | + cancellations 819 | + " < " 820 | + mMaxAutoSignInAttempts); 821 | shouldResolve = true; 822 | } else { 823 | shouldResolve = false; 824 | debugLog("onConnectionFailed: Will NOT resolve; not user-initiated and max attempts " 825 | + "reached: " 826 | + cancellations 827 | + " >= " 828 | + mMaxAutoSignInAttempts); 829 | } 830 | 831 | if (!shouldResolve) { 832 | // Fail and wait for the user to want to sign in. 833 | debugLog("onConnectionFailed: since we won't resolve, failing now."); 834 | mConnectionResult = result; 835 | mConnecting = false; 836 | notifyListener(false); 837 | return; 838 | } 839 | 840 | debugLog("onConnectionFailed: resolving problem..."); 841 | 842 | // Resolve the connection result. This usually means showing a dialog or 843 | // starting an Activity that will allow the user to give the appropriate 844 | // consents so that sign-in can be successful. 845 | resolveConnectionResult(); 846 | } 847 | 848 | /** 849 | * Attempts to resolve a connection failure. This will usually involve 850 | * starting a UI flow that lets the user give the appropriate consents 851 | * necessary for sign-in to work. 852 | */ 853 | void resolveConnectionResult() { 854 | // Try to resolve the problem 855 | if (mExpectingResolution) { 856 | debugLog("We're already expecting the result of a previous resolution."); 857 | return; 858 | } 859 | 860 | debugLog("resolveConnectionResult: trying to resolve result: " 861 | + mConnectionResult); 862 | if (mConnectionResult.hasResolution()) { 863 | // This problem can be fixed. So let's try to fix it. 864 | debugLog("Result has resolution. Starting it."); 865 | try { 866 | // launch appropriate UI flow (which might, for example, be the 867 | // sign-in flow) 868 | mExpectingResolution = true; 869 | mConnectionResult.startResolutionForResult(mActivity, 870 | RC_RESOLVE); 871 | } catch (SendIntentException e) { 872 | // Try connecting again 873 | debugLog("SendIntentException, so connecting again."); 874 | connect(); 875 | } 876 | } else { 877 | // It's not a problem what we can solve, so give up and show an 878 | // error. 879 | debugLog("resolveConnectionResult: result has no resolution. Giving up."); 880 | giveUp(new SignInFailureReason(mConnectionResult.getErrorCode())); 881 | } 882 | } 883 | 884 | public void disconnect() { 885 | if (mGoogleApiClient.isConnected()) { 886 | debugLog("Disconnecting client."); 887 | mGoogleApiClient.disconnect(); 888 | } else { 889 | Log.w(TAG, 890 | "disconnect() called when client was already disconnected."); 891 | } 892 | } 893 | 894 | /** 895 | * Give up on signing in due to an error. Shows the appropriate error 896 | * message to the user, using a standard error dialog as appropriate to the 897 | * cause of the error. That dialog will indicate to the user how the problem 898 | * can be solved (for example, re-enable Google Play Services, upgrade to a 899 | * new version, etc). 900 | */ 901 | void giveUp(SignInFailureReason reason) { 902 | mConnectOnStart = false; 903 | disconnect(); 904 | mSignInFailureReason = reason; 905 | 906 | if (reason.mActivityResultCode == GamesActivityResultCodes.RESULT_APP_MISCONFIGURED) { 907 | // print debug info for the developer 908 | GameHelperUtils.printMisconfiguredDebugInfo(mAppContext); 909 | } 910 | 911 | showFailureDialog(); 912 | mConnecting = false; 913 | notifyListener(false); 914 | } 915 | 916 | /** Called when we are disconnected from the Google API client. */ 917 | @Override 918 | public void onConnectionSuspended(int cause) { 919 | debugLog("onConnectionSuspended, cause=" + cause); 920 | disconnect(); 921 | mSignInFailureReason = null; 922 | debugLog("Making extraordinary call to onSignInFailed callback"); 923 | mConnecting = false; 924 | notifyListener(false); 925 | } 926 | 927 | public void showFailureDialog() { 928 | if (mSignInFailureReason != null) { 929 | int errorCode = mSignInFailureReason.getServiceErrorCode(); 930 | int actResp = mSignInFailureReason.getActivityResultCode(); 931 | 932 | if (mShowErrorDialogs) { 933 | showFailureDialog(mActivity, actResp, errorCode); 934 | } else { 935 | debugLog("Not showing error dialog because mShowErrorDialogs==false. " 936 | + "" + "Error was: " + mSignInFailureReason); 937 | } 938 | } 939 | } 940 | 941 | /** Shows an error dialog that's appropriate for the failure reason. */ 942 | public static void showFailureDialog(Activity activity, int actResp, 943 | int errorCode) { 944 | if (activity == null) { 945 | Log.e("GameHelper", "*** No Activity. Can't show failure dialog!"); 946 | return; 947 | } 948 | Dialog errorDialog = null; 949 | 950 | switch (actResp) { 951 | case GamesActivityResultCodes.RESULT_APP_MISCONFIGURED: 952 | //errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString(activity, GameHelperUtils.R_APP_MISCONFIGURED));//cranberrygame 953 | //https://developers.google.com/android/reference/com/google/android/gms/common/ConnectionResult#constants//cranberrygame 954 | errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString(activity, GameHelperUtils.R_APP_MISCONFIGURED) + " " + GameHelperUtils.errorCodeToString(errorCode));//cranberrygame 955 | break; 956 | case GamesActivityResultCodes.RESULT_SIGN_IN_FAILED://cranberrygame 957 | //errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString(activity, GameHelperUtils.R_SIGN_IN_FAILED));//cranberrygame 958 | errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString(activity, GameHelperUtils.R_SIGN_IN_FAILED) + " " + GameHelperUtils.errorCodeToString(errorCode));//cranberrygame 959 | break; 960 | case GamesActivityResultCodes.RESULT_LICENSE_FAILED://cranberrygame 961 | //errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString(activity, GameHelperUtils.R_LICENSE_FAILED));//cranberrygame 962 | errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString(activity, GameHelperUtils.R_LICENSE_FAILED) + " " + GameHelperUtils.errorCodeToString(errorCode));//cranberrygame 963 | break; 964 | default: 965 | /* 966 | //deprecated 967 | // No meaningful Activity response code, so generate default Google 968 | // Play services dialog 969 | errorDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, 970 | activity, RC_UNUSED, null); 971 | */ 972 | GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance(); 973 | int res = googleAPI.isGooglePlayServicesAvailable(activity); 974 | if(res != ConnectionResult.SUCCESS) { 975 | if(googleAPI.isUserResolvableError(res)) { 976 | errorDialog = googleAPI.getErrorDialog(activity, res, PLAY_SERVICES_RESOLUTION_REQUEST); 977 | } 978 | } 979 | 980 | if (errorDialog == null) { 981 | 982 | // get fallback dialog 983 | Log.e("GameHelper", 984 | "No standard error dialog available. Making fallback dialog."); 985 | errorDialog = makeSimpleDialog( 986 | activity, 987 | GameHelperUtils.getString(activity, 988 | GameHelperUtils.R_UNKNOWN_ERROR) 989 | + " " 990 | + GameHelperUtils.errorCodeToString(errorCode)); 991 | } 992 | } 993 | 994 | errorDialog.show(); 995 | } 996 | 997 | static Dialog makeSimpleDialog(Activity activity, String text) { 998 | return (new AlertDialog.Builder(activity)).setMessage(text) 999 | .setNeutralButton(android.R.string.ok, null).create(); 1000 | } 1001 | 1002 | static Dialog 1003 | makeSimpleDialog(Activity activity, String title, String text) { 1004 | return (new AlertDialog.Builder(activity)).setMessage(text) 1005 | .setTitle(title).setNeutralButton(android.R.string.ok, null) 1006 | .create(); 1007 | } 1008 | 1009 | public Dialog makeSimpleDialog(String text) { 1010 | if (mActivity == null) { 1011 | logError("*** makeSimpleDialog failed: no current Activity!"); 1012 | return null; 1013 | } 1014 | return makeSimpleDialog(mActivity, text); 1015 | } 1016 | 1017 | public Dialog makeSimpleDialog(String title, String text) { 1018 | if (mActivity == null) { 1019 | logError("*** makeSimpleDialog failed: no current Activity!"); 1020 | return null; 1021 | } 1022 | return makeSimpleDialog(mActivity, title, text); 1023 | } 1024 | 1025 | void debugLog(String message) { 1026 | if (mDebugLog) { 1027 | Log.d(TAG, "GameHelper: " + message); 1028 | } 1029 | } 1030 | 1031 | void logWarn(String message) { 1032 | Log.w(TAG, "!!! GameHelper WARNING: " + message); 1033 | } 1034 | 1035 | void logError(String message) { 1036 | Log.e(TAG, "*** GameHelper ERROR: " + message); 1037 | } 1038 | 1039 | // Represents the reason for a sign-in failure 1040 | public static class SignInFailureReason { 1041 | public static final int NO_ACTIVITY_RESULT_CODE = -100; 1042 | int mServiceErrorCode = 0; 1043 | int mActivityResultCode = NO_ACTIVITY_RESULT_CODE; 1044 | 1045 | public int getServiceErrorCode() { 1046 | return mServiceErrorCode; 1047 | } 1048 | 1049 | public int getActivityResultCode() { 1050 | return mActivityResultCode; 1051 | } 1052 | 1053 | public SignInFailureReason(int serviceErrorCode, int activityResultCode) { 1054 | mServiceErrorCode = serviceErrorCode; 1055 | mActivityResultCode = activityResultCode; 1056 | } 1057 | 1058 | public SignInFailureReason(int serviceErrorCode) { 1059 | this(serviceErrorCode, NO_ACTIVITY_RESULT_CODE); 1060 | } 1061 | 1062 | @Override 1063 | public String toString() { 1064 | return "SignInFailureReason(serviceErrorCode:" 1065 | + GameHelperUtils.errorCodeToString(mServiceErrorCode) 1066 | + ((mActivityResultCode == NO_ACTIVITY_RESULT_CODE) ? ")" 1067 | : (",activityResultCode:" 1068 | + GameHelperUtils 1069 | .activityResponseCodeToString(mActivityResultCode) + ")")); 1070 | } 1071 | } 1072 | 1073 | // Not recommended for general use. This method forces the 1074 | // "connect on start" flag 1075 | // to a given state. This may be useful when using GameHelper in a 1076 | // non-standard 1077 | // sign-in flow. 1078 | public void setConnectOnStart(boolean connectOnStart) { 1079 | debugLog("Forcing mConnectOnStart=" + connectOnStart); 1080 | mConnectOnStart = connectOnStart; 1081 | } 1082 | } 1083 | -------------------------------------------------------------------------------- /src/android/GameHelperUtils.java: -------------------------------------------------------------------------------- 1 | package com.google.example.games.basegameutils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.Signature; 7 | import android.content.res.Resources; 8 | import android.util.Log; 9 | 10 | import com.google.android.gms.common.ConnectionResult; 11 | import com.google.android.gms.games.GamesActivityResultCodes; 12 | 13 | import java.security.MessageDigest; 14 | import java.security.NoSuchAlgorithmException; 15 | 16 | /** 17 | * Created by btco on 2/10/14. 18 | */ 19 | class GameHelperUtils { 20 | public static final int R_UNKNOWN_ERROR = 0; 21 | public static final int R_SIGN_IN_FAILED = 1; 22 | public static final int R_APP_MISCONFIGURED = 2; 23 | public static final int R_LICENSE_FAILED = 3; 24 | 25 | private final static String[] FALLBACK_STRINGS = { 26 | "*Unknown error.", 27 | "*Failed to sign in. Please check your network connection and try again.", 28 | "*The application is incorrectly configured. Check that the package name and signing certificate match the client ID created in Developer Console. Also, if the application is not yet published, check that the account you are trying to sign in with is listed as a tester account. See logs for more information.", 29 | "*License check failed." 30 | }; 31 | 32 | private final static int[] RES_IDS = { 33 | //cranberrygame start 34 | //package r does not exist android 35 | //https://muut.com/appgyver#!/steroids#phonegap-plugin-package-r 36 | //I:\test\googleplaytest\platforms\android\ant-gen\com\cranberrygame\googleplaytest 37 | /* 38 | R.string.gamehelper_unknown_error, R.string.gamehelper_sign_in_failed, 39 | R.string.gamehelper_app_misconfigured, R.string.gamehelper_license_failed 40 | */ 41 | //cranberrygame end 42 | }; 43 | 44 | static String activityResponseCodeToString(int respCode) { 45 | switch (respCode) { 46 | case Activity.RESULT_OK: 47 | return "RESULT_OK"; 48 | case Activity.RESULT_CANCELED: 49 | return "RESULT_CANCELED"; 50 | case GamesActivityResultCodes.RESULT_APP_MISCONFIGURED: 51 | return "RESULT_APP_MISCONFIGURED"; 52 | case GamesActivityResultCodes.RESULT_LEFT_ROOM: 53 | return "RESULT_LEFT_ROOM"; 54 | case GamesActivityResultCodes.RESULT_LICENSE_FAILED: 55 | return "RESULT_LICENSE_FAILED"; 56 | case GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED: 57 | return "RESULT_RECONNECT_REQUIRED"; 58 | case GamesActivityResultCodes.RESULT_SIGN_IN_FAILED: 59 | return "SIGN_IN_FAILED"; 60 | default: 61 | return String.valueOf(respCode); 62 | } 63 | } 64 | 65 | static String errorCodeToString(int errorCode) { 66 | switch (errorCode) { 67 | case ConnectionResult.DEVELOPER_ERROR: 68 | return "DEVELOPER_ERROR(" + errorCode + ")"; 69 | case ConnectionResult.INTERNAL_ERROR: 70 | return "INTERNAL_ERROR(" + errorCode + ")"; 71 | case ConnectionResult.INVALID_ACCOUNT: 72 | return "INVALID_ACCOUNT(" + errorCode + ")"; 73 | case ConnectionResult.LICENSE_CHECK_FAILED: 74 | return "LICENSE_CHECK_FAILED(" + errorCode + ")"; 75 | case ConnectionResult.NETWORK_ERROR: 76 | return "NETWORK_ERROR(" + errorCode + ")"; 77 | case ConnectionResult.RESOLUTION_REQUIRED: 78 | return "RESOLUTION_REQUIRED(" + errorCode + ")"; 79 | case ConnectionResult.SERVICE_DISABLED: 80 | return "SERVICE_DISABLED(" + errorCode + ")"; 81 | case ConnectionResult.SERVICE_INVALID: 82 | return "SERVICE_INVALID(" + errorCode + ")"; 83 | case ConnectionResult.SERVICE_MISSING: 84 | return "SERVICE_MISSING(" + errorCode + ")"; 85 | case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED: 86 | return "SERVICE_VERSION_UPDATE_REQUIRED(" + errorCode + ")"; 87 | case ConnectionResult.SIGN_IN_REQUIRED: 88 | return "SIGN_IN_REQUIRED(" + errorCode + ")"; 89 | case ConnectionResult.SUCCESS: 90 | return "SUCCESS(" + errorCode + ")"; 91 | default: 92 | return "Unknown error code " + errorCode; 93 | } 94 | } 95 | 96 | static void printMisconfiguredDebugInfo(Context ctx) { 97 | Log.w("GameHelper", "****"); 98 | Log.w("GameHelper", "****"); 99 | Log.w("GameHelper", "**** APP NOT CORRECTLY CONFIGURED TO USE GOOGLE PLAY GAME SERVICES"); 100 | Log.w("GameHelper", "**** This is usually caused by one of these reasons:"); 101 | Log.w("GameHelper", "**** (1) Your package name and certificate fingerprint do not match"); 102 | Log.w("GameHelper", "**** the client ID you registered in Developer Console."); 103 | Log.w("GameHelper", "**** (2) Your App ID was incorrectly entered."); 104 | Log.w("GameHelper", "**** (3) Your game settings have not been published and you are "); 105 | Log.w("GameHelper", "**** trying to log in with an account that is not listed as"); 106 | Log.w("GameHelper", "**** a test account."); 107 | Log.w("GameHelper", "****"); 108 | if (ctx == null) { 109 | Log.w("GameHelper", "*** (no Context, so can't print more debug info)"); 110 | return; 111 | } 112 | 113 | Log.w("GameHelper", "**** To help you debug, here is the information about this app"); 114 | Log.w("GameHelper", "**** Package name : " + ctx.getPackageName()); 115 | Log.w("GameHelper", "**** Cert SHA1 fingerprint: " + getSHA1CertFingerprint(ctx)); 116 | Log.w("GameHelper", "**** App ID from : " + getAppIdFromResource(ctx)); 117 | Log.w("GameHelper", "****"); 118 | Log.w("GameHelper", "**** Check that the above information matches your setup in "); 119 | Log.w("GameHelper", "**** Developer Console. Also, check that you're logging in with the"); 120 | Log.w("GameHelper", "**** right account (it should be listed in the Testers section if"); 121 | Log.w("GameHelper", "**** your project is not yet published)."); 122 | Log.w("GameHelper", "****"); 123 | Log.w("GameHelper", "**** For more information, refer to the troubleshooting guide:"); 124 | Log.w("GameHelper", "**** http://developers.google.com/games/services/android/troubleshooting"); 125 | } 126 | 127 | static String getAppIdFromResource(Context ctx) { 128 | try { 129 | Resources res = ctx.getResources(); 130 | String pkgName = ctx.getPackageName(); 131 | int res_id = res.getIdentifier("app_id", "string", pkgName); 132 | return res.getString(res_id); 133 | } catch (Exception ex) { 134 | ex.printStackTrace(); 135 | return "??? (failed to retrieve APP ID)"; 136 | } 137 | } 138 | 139 | static String getSHA1CertFingerprint(Context ctx) { 140 | try { 141 | Signature[] sigs = ctx.getPackageManager().getPackageInfo( 142 | ctx.getPackageName(), PackageManager.GET_SIGNATURES).signatures; 143 | if (sigs.length == 0) { 144 | return "ERROR: NO SIGNATURE."; 145 | } else if (sigs.length > 1) { 146 | return "ERROR: MULTIPLE SIGNATURES"; 147 | } 148 | byte[] digest = MessageDigest.getInstance("SHA1").digest(sigs[0].toByteArray()); 149 | StringBuilder hexString = new StringBuilder(); 150 | for (int i = 0; i < digest.length; ++i) { 151 | if (i > 0) { 152 | hexString.append(":"); 153 | } 154 | byteToString(hexString, digest[i]); 155 | } 156 | return hexString.toString(); 157 | 158 | } catch (PackageManager.NameNotFoundException ex) { 159 | ex.printStackTrace(); 160 | return "(ERROR: package not found)"; 161 | } catch (NoSuchAlgorithmException ex) { 162 | ex.printStackTrace(); 163 | return "(ERROR: SHA1 algorithm not found)"; 164 | } 165 | } 166 | 167 | static void byteToString(StringBuilder sb, byte b) { 168 | int unsigned_byte = b < 0 ? b + 256 : b; 169 | int hi = unsigned_byte / 16; 170 | int lo = unsigned_byte % 16; 171 | sb.append("0123456789ABCDEF".substring(hi, hi + 1)); 172 | sb.append("0123456789ABCDEF".substring(lo, lo + 1)); 173 | } 174 | 175 | static String getString(Context ctx, int whichString) { 176 | //cranberrygame start 177 | /* 178 | whichString = whichString >= 0 && whichString < RES_IDS.length ? whichString : 0; 179 | int resId = RES_IDS[whichString]; 180 | try { 181 | return ctx.getString(resId); 182 | } catch (Exception ex) { 183 | ex.printStackTrace(); 184 | Log.w(GameHelper.TAG, "*** GameHelper could not found resource id #" + resId + ". " + 185 | "This probably happened because you included it as a stand-alone JAR. " + 186 | "BaseGameUtils should be compiled as a LIBRARY PROJECT, so that it can access " + 187 | "its resources. Using a fallback string."); 188 | return FALLBACK_STRINGS[whichString]; 189 | } 190 | */ 191 | try { 192 | whichString = whichString >= 0 && whichString < RES_IDS.length ? whichString : 0; 193 | int resId = RES_IDS[whichString]; 194 | 195 | return ctx.getString(resId); 196 | } catch (Exception ex) { 197 | return FALLBACK_STRINGS[whichString]; 198 | } 199 | //cranberrygame end 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/android/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/ios/Game.h: -------------------------------------------------------------------------------- 1 | // 2 | // Game.h 3 | // Detonate 4 | // 5 | // Created by Marco Piccardo on 04/02/11. 6 | // Copyright 2011 Eurotraining Engineering. All rights reserved. 7 | // 8 | /* 9 | * Modified and Updated 10 | * 11 | * Copyright 2014 Wizcorp Inc. http://www.wizcorp.jp 12 | * Author Ally Ogilvie 13 | */ 14 | //Copyright (c) 2014 Sang Ki Kwon (Cranberrygame) 15 | //Email: cranberrygame@yahoo.com 16 | //Homepage: http://www.github.com/cranberrygame 17 | //License: MIT (http://opensource.org/licenses/MIT) 18 | 19 | #import 20 | 21 | #import 22 | #import 23 | 24 | @interface Game : CDVPlugin 25 | 26 | - (void)setUp:(CDVInvokedUrlCommand *)command; 27 | - (void)login:(CDVInvokedUrlCommand *)command; 28 | - (void)logout:(CDVInvokedUrlCommand *)command; 29 | - (void)getPlayerImage:(CDVInvokedUrlCommand *)command; 30 | - (void)getPlayerScore:(CDVInvokedUrlCommand *)command; 31 | - (void)submitScore:(CDVInvokedUrlCommand *)command; 32 | - (void)showLeaderboard:(CDVInvokedUrlCommand *)command; 33 | - (void)showLeaderboards:(CDVInvokedUrlCommand *)command; 34 | - (void)unlockAchievement:(CDVInvokedUrlCommand *)command; 35 | - (void)incrementAchievement:(CDVInvokedUrlCommand *)command; 36 | - (void)showAchievements:(CDVInvokedUrlCommand *)command; 37 | - (void)resetAchievements:(CDVInvokedUrlCommand *)command; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /src/ios/Game.m: -------------------------------------------------------------------------------- 1 | // 2 | // Game.m 3 | // Detonate 4 | // 5 | // Created by Marco Piccardo on 04/02/11. 6 | // Copyright 2011 Eurotraining Engineering. All rights reserved. 7 | // 8 | /* 9 | * Modified and Updated 10 | * 11 | * Copyright 2014 Wizcorp Inc. http://www.wizcorp.jp 12 | * Author Ally Ogilvie 13 | */ 14 | //Copyright (c) 2014 Sang Ki Kwon (Cranberrygame) 15 | //Email: cranberrygame@yahoo.com 16 | //Homepage: http://www.github.com/cranberrygame 17 | //License: MIT (http://opensource.org/licenses/MIT) 18 | 19 | #import "Game.h" 20 | #import 21 | 22 | #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 23 | 24 | @interface Game () 25 | @property (nonatomic, retain) GKLeaderboardViewController *leaderboardController; 26 | @property (nonatomic, retain) GKAchievementViewController *achievementsController; 27 | @end 28 | 29 | @implementation Game 30 | 31 | - (void)setUp:(CDVInvokedUrlCommand *)command { 32 | 33 | } 34 | 35 | - (void)login:(CDVInvokedUrlCommand *)command { 36 | 37 | //[self.commandDelegate runInBackground:^{//cranberrygame 38 | [[GKLocalPlayer localPlayer] setAuthenticateHandler: ^(UIViewController *viewcontroller, NSError *error) { 39 | /* 40 | //already logged in 41 | if ([GKLocalPlayer localPlayer].authenticated) { 42 | 43 | NSString *playerID = [GKLocalPlayer localPlayer].playerID; 44 | NSString *displayName = [GKLocalPlayer localPlayer].displayName; 45 | 46 | NSDictionary* playerDetail = @{ 47 | @"playerId":playerID, 48 | @"playerDisplayName":displayName 49 | }; 50 | 51 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:playerDetail]; 52 | //[pr setKeepCallbackAsBool:YES]; 53 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 54 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 55 | //[pr setKeepCallbackAsBool:YES]; 56 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 57 | } 58 | else if (viewcontroller != nil) { 59 | CDVViewController *vc = (CDVViewController *)[super viewController]; 60 | [vc presentViewController:viewcontroller animated:YES completion:^{ 61 | }]; 62 | } 63 | else { 64 | // Called the second time with result 65 | if (error != nil) { 66 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 67 | //[pr setKeepCallbackAsBool:YES]; 68 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 69 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 70 | //[pr setKeepCallbackAsBool:YES]; 71 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 72 | } 73 | else { 74 | NSString *playerID = [GKLocalPlayer localPlayer].playerID; 75 | NSString *displayName = [GKLocalPlayer localPlayer].displayName; 76 | 77 | NSDictionary* playerDetail = @{ 78 | @"playerId":playerID, 79 | @"playerDisplayName":displayName 80 | }; 81 | 82 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:playerDetail]; 83 | //[pr setKeepCallbackAsBool:YES]; 84 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 85 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 86 | //[pr setKeepCallbackAsBool:YES]; 87 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 88 | } 89 | } 90 | */ 91 | ///* 92 | //It turns out that you need to go to Settings>Game Center and manually enable Sandbox. 93 | //http://stackoverflow.com/questions/25916055/application-is-not-recognized-by-game-center-after-building-with-xcode-6-0-1 94 | if (viewcontroller != nil) { 95 | 96 | //UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"1" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 97 | //[alert1 show]; 98 | 99 | CDVViewController *vc = (CDVViewController *)[super viewController]; 100 | [vc presentViewController:viewcontroller animated:YES completion:^{ 101 | }]; 102 | } 103 | else { 104 | //already logged in 105 | if ([GKLocalPlayer localPlayer].authenticated) { 106 | 107 | //UIAlertView *alert2 = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"2" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 108 | //[alert2 show]; 109 | 110 | NSString *playerID = [GKLocalPlayer localPlayer].playerID; 111 | NSString *displayName = [GKLocalPlayer localPlayer].displayName; 112 | 113 | NSDictionary* playerDetail = @{ 114 | @"playerId":playerID, 115 | @"playerDisplayName":displayName 116 | }; 117 | 118 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:playerDetail]; 119 | //[pr setKeepCallbackAsBool:YES]; 120 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 121 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 122 | //[pr setKeepCallbackAsBool:YES]; 123 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 124 | } 125 | else { 126 | 127 | //UIAlertView *alert3 = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"3" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 128 | //[alert3 show]; 129 | 130 | // Called the second time with result 131 | if (error != nil) { 132 | 133 | UIAlertView *alert4 = [[UIAlertView alloc] initWithTitle:@"Alert" message:[error localizedDescription] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; 134 | [alert4 show]; 135 | 136 | 137 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 138 | //[pr setKeepCallbackAsBool:YES]; 139 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 140 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 141 | //[pr setKeepCallbackAsBool:YES]; 142 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 143 | } 144 | else { 145 | NSString *playerID = [GKLocalPlayer localPlayer].playerID; 146 | NSString *displayName = [GKLocalPlayer localPlayer].displayName; 147 | 148 | NSDictionary* playerDetail = @{ 149 | @"playerId":playerID, 150 | @"playerDisplayName":displayName 151 | }; 152 | 153 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:playerDetail]; 154 | //[pr setKeepCallbackAsBool:YES]; 155 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 156 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 157 | //[pr setKeepCallbackAsBool:YES]; 158 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 159 | } 160 | } 161 | } 162 | //*/ 163 | }]; 164 | //}];//cranberrygame 165 | } 166 | 167 | - (void)logout:(CDVInvokedUrlCommand *)command { 168 | //Unfortunately, this takes the user outside your app. 169 | //http://stackoverflow.com/questions/9995576/how-to-show-game-centers-player-profile-view 170 | //[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"gamecenter:/me/account"]]; 171 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"gamecenter:/me/signout"]]; 172 | } 173 | 174 | - (void)getPlayerImage:(CDVInvokedUrlCommand *)command { 175 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 176 | NSString *documentsDirectory = [paths objectAtIndex:0]; 177 | NSString *playerImageUrl = [documentsDirectory stringByAppendingPathComponent: @"user.jpg" ]; 178 | 179 | BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:playerImageUrl]; 180 | if(fileExists){ 181 | NSLog(@"%@", playerImageUrl); 182 | 183 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:playerImageUrl]; 184 | //[pr setKeepCallbackAsBool:YES]; 185 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 186 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 187 | //[pr setKeepCallbackAsBool:YES]; 188 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 189 | } 190 | else{ 191 | GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; 192 | [localPlayer loadPhotoForSize:GKPhotoSizeSmall withCompletionHandler: ^(UIImage *photo, NSError *error) { 193 | if (photo != nil) 194 | { 195 | NSData* data = UIImageJPEGRepresentation(photo, 0.8); 196 | [data writeToFile:playerImageUrl atomically:YES]; 197 | NSLog(@"%@", playerImageUrl); 198 | 199 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:playerImageUrl]; 200 | //[pr setKeepCallbackAsBool:YES]; 201 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 202 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 203 | //[pr setKeepCallbackAsBool:YES]; 204 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 205 | } 206 | else if (error != nil) 207 | { 208 | NSLog(@"%@", [error localizedDescription]); 209 | 210 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 211 | //[pr setKeepCallbackAsBool:YES]; 212 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 213 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 214 | //[pr setKeepCallbackAsBool:YES]; 215 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 216 | } 217 | }]; 218 | } 219 | } 220 | 221 | - (void)getPlayerScore:(CDVInvokedUrlCommand *)command { 222 | NSString *leaderboardId = [command.arguments objectAtIndex:0]; 223 | 224 | //http://stackoverflow.com/questions/21591123/how-to-get-local-player-score-from-game-center 225 | GKLeaderboard *leaderboard = [[GKLeaderboard alloc] init]; 226 | leaderboard.identifier = leaderboardId; 227 | [leaderboard loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) { 228 | if (error) { 229 | NSLog(@"%@", error); 230 | 231 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 232 | //[pr setKeepCallbackAsBool:YES]; 233 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 234 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 235 | //[pr setKeepCallbackAsBool:YES]; 236 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 237 | } 238 | else if (scores) { 239 | GKScore *s = leaderboard.localPlayerScore; 240 | NSLog(@"Local player's score: %lld", s.value); 241 | 242 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[NSString stringWithFormat:@"%lld", s.value]]; 243 | //[pr setKeepCallbackAsBool:YES]; 244 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 245 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 246 | //[pr setKeepCallbackAsBool:YES]; 247 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 248 | } 249 | }]; 250 | } 251 | 252 | - (void)submitScore:(CDVInvokedUrlCommand *)command { 253 | 254 | //[self.commandDelegate runInBackground:^{//cranberrygame 255 | NSString *leaderboardId = [command.arguments objectAtIndex:0]; 256 | int64_t score = [[command.arguments objectAtIndex:1] integerValue]; 257 | 258 | GKScore *s = [[GKScore alloc] initWithLeaderboardIdentifier: leaderboardId]; 259 | s.value = score; 260 | s.context = 0; 261 | 262 | [GKScore reportScores:@[s] withCompletionHandler: ^(NSError *error) { 263 | if (error != nil) 264 | { 265 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 266 | //[pr setKeepCallbackAsBool:YES]; 267 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 268 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 269 | //[pr setKeepCallbackAsBool:YES]; 270 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 271 | } 272 | else 273 | { 274 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 275 | //[pr setKeepCallbackAsBool:YES]; 276 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 277 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 278 | //[pr setKeepCallbackAsBool:YES]; 279 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 280 | } 281 | }]; 282 | //}];//cranberrygame 283 | } 284 | 285 | - (void)showLeaderboard:(CDVInvokedUrlCommand *)command { 286 | /* 287 | //runtime error 288 | //[self.commandDelegate runInBackground:^{//cranberrygame 289 | if ( self.leaderboardController == nil ) { 290 | self.leaderboardController = [[GKLeaderboardViewController alloc] init]; 291 | self.leaderboardController.leaderboardDelegate = self;// 292 | } 293 | self.leaderboardController.category = (NSString *) [command.arguments objectAtIndex:0]; 294 | CDVViewController *vc = (CDVViewController *)[super viewController]; 295 | [vc presentViewController:self.leaderboardController animated:YES completion: ^{ 296 | }]; 297 | //}];//cranberrygame 298 | */ 299 | ///* 300 | //https://github.com/leecrossley/cordova-plugin-game-center/blob/master/src/ios/GameCenter.m 301 | NSString *leaderboardId = (NSString *) [command.arguments objectAtIndex:0]; 302 | 303 | GKGameCenterViewController *gameCenterController = [[GKGameCenterViewController alloc] init]; 304 | if (gameCenterController != nil) 305 | { 306 | gameCenterController.gameCenterDelegate = self; 307 | 308 | if (leaderboardId.length > 0) 309 | { 310 | gameCenterController.leaderboardIdentifier = leaderboardId; 311 | } 312 | 313 | gameCenterController.viewState = GKGameCenterViewControllerStateLeaderboards; 314 | 315 | [self.viewController presentViewController:gameCenterController animated:YES completion:nil]; 316 | } 317 | else 318 | { 319 | } 320 | //*/ 321 | } 322 | 323 | - (void)showLeaderboards:(CDVInvokedUrlCommand *)command { 324 | GKGameCenterViewController *gameCenterController = [[GKGameCenterViewController alloc] init]; 325 | if (gameCenterController != nil) 326 | { 327 | gameCenterController.gameCenterDelegate = self; 328 | 329 | gameCenterController.viewState = GKGameCenterViewControllerStateLeaderboards; 330 | 331 | [self.viewController presentViewController:gameCenterController animated:YES completion:nil]; 332 | } 333 | else 334 | { 335 | } 336 | } 337 | 338 | - (void)unlockAchievement:(CDVInvokedUrlCommand *)command { 339 | //[self.commandDelegate runInBackground:^{//cranberrygame 340 | NSString *achievementId = [command.arguments objectAtIndex:0]; 341 | 342 | GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier: achievementId]; 343 | if (achievement) 344 | { 345 | achievement.percentComplete = 100; 346 | 347 | [achievement reportAchievementWithCompletionHandler: ^(NSError *error) 348 | { 349 | if (error != nil) 350 | { 351 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 352 | //[pr setKeepCallbackAsBool:YES]; 353 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 354 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 355 | //[pr setKeepCallbackAsBool:YES]; 356 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 357 | } 358 | else { 359 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 360 | //[pr setKeepCallbackAsBool:YES]; 361 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 362 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 363 | //[pr setKeepCallbackAsBool:YES]; 364 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 365 | 366 | 367 | } 368 | }]; 369 | } 370 | //}];//cranberrygame 371 | } 372 | 373 | - (void)incrementAchievement:(CDVInvokedUrlCommand *)command { 374 | //[self.commandDelegate runInBackground:^{//cranberrygame 375 | NSString *achievementId = [command.arguments objectAtIndex:0]; 376 | NSString *stepsOrPercent = [command.arguments objectAtIndex:1]; 377 | float stepsOrPercentFloat = [stepsOrPercent floatValue]; 378 | 379 | GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier: achievementId]; 380 | if (achievement) 381 | { 382 | achievement.percentComplete = stepsOrPercentFloat; 383 | 384 | [achievement reportAchievementWithCompletionHandler: ^(NSError *error) 385 | { 386 | if (error != nil) 387 | { 388 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 389 | //[pr setKeepCallbackAsBool:YES]; 390 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 391 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 392 | //[pr setKeepCallbackAsBool:YES]; 393 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 394 | } 395 | else { 396 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 397 | //[pr setKeepCallbackAsBool:YES]; 398 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 399 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 400 | //[pr setKeepCallbackAsBool:YES]; 401 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 402 | 403 | 404 | } 405 | }]; 406 | } 407 | //}];//cranberrygame 408 | } 409 | 410 | - (void)showAchievements:(CDVInvokedUrlCommand *)command {//cranberrygame 411 | //[self.commandDelegate runInBackground:^{ 412 | if ( self.achievementsController == nil ) { 413 | self.achievementsController = [[GKAchievementViewController alloc] init]; 414 | self.achievementsController.achievementDelegate = self;// 415 | } 416 | CDVViewController *vc = (CDVViewController *)[super viewController]; 417 | [vc presentViewController:self.achievementsController animated:YES completion: ^{ 418 | }]; 419 | //}];//cranberrygame 420 | } 421 | 422 | - (void) resetAchievements:(CDVInvokedUrlCommand*)command; 423 | { 424 | [GKAchievement resetAchievementsWithCompletionHandler: ^(NSError *error) 425 | { 426 | if (error != nil) 427 | { 428 | 429 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 430 | //[pr setKeepCallbackAsBool:YES]; 431 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 432 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; 433 | //[pr setKeepCallbackAsBool:YES]; 434 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 435 | } else { 436 | CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; 437 | //[pr setKeepCallbackAsBool:YES]; 438 | [self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 439 | //CDVPluginResult* pr = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; 440 | //[pr setKeepCallbackAsBool:YES]; 441 | //[self.commandDelegate sendPluginResult:pr callbackId:command.callbackId]; 442 | } 443 | }]; 444 | } 445 | 446 | //GKLeaderboardViewControllerDelegate 447 | - (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController { 448 | /* 449 | CDVViewController *vc = (CDVViewController *)[super viewController]; 450 | [vc dismissViewControllerAnimated:YES completion:nil]; 451 | */ 452 | [viewController dismissViewControllerAnimated:YES completion:nil]; 453 | } 454 | 455 | //GKAchievementViewControllerDelegate 456 | - (void)achievementViewControllerDidFinish:(GKAchievementViewController *)viewController { 457 | /* 458 | CDVViewController* vc = (CDVViewController *)[super viewController]; 459 | [vc dismissViewControllerAnimated:YES completion:nil]; 460 | */ 461 | [viewController dismissViewControllerAnimated:YES completion:nil]; 462 | } 463 | 464 | //GKGameCenterControllerDelegate 465 | - (void) gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController 466 | { 467 | [gameCenterViewController dismissViewControllerAnimated:YES completion:nil]; 468 | } 469 | 470 | - (void)dealloc { 471 | self.leaderboardController = nil; 472 | self.achievementsController = nil; 473 | 474 | [super dealloc]; 475 | } 476 | 477 | @end 478 | -------------------------------------------------------------------------------- /www/game.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | _loggedin: false, 4 | tag: '', 5 | setUp: function () { 6 | cordova.exec( 7 | function (result) { 8 | }, 9 | function (error) { 10 | }, "Game", "setUp", []); 11 | }, 12 | login: function (tag) { 13 | var self = this; 14 | cordova.exec(function (result) { 15 | var playerDetail = result; 16 | self._loggedin = true; 17 | self.tag = tag; 18 | if (self.onLoginSucceeded) 19 | self.onLoginSucceeded(playerDetail); 20 | }, 21 | function (error) { 22 | self.tag = tag; 23 | if (self.onLoginFailed) 24 | self.onLoginFailed(); 25 | }, "Game", "login", []); 26 | }, 27 | logout: function () { 28 | var self = this; 29 | cordova.exec(function (result) { 30 | self._loggedin = false; 31 | }, 32 | function (error) { 33 | }, "Game", "logout", []); 34 | }, 35 | isLoggedIn: function () { 36 | return this._loggedin; 37 | }, 38 | submitScore: function (leaderboardId, score, tag) { 39 | var self = this; 40 | cordova.exec(function (result) { 41 | self.tag = tag; 42 | if (self.onSubmitScoreSucceeded) 43 | self.onSubmitScoreSucceeded(); 44 | }, 45 | function (error) { 46 | self.tag = tag; 47 | if (self.onSubmitScoreFailed) 48 | self.onSubmitScoreFailed(); 49 | }, "Game", "submitScore", [leaderboardId, score]); 50 | }, 51 | showLeaderboard: function (leaderboardId) { 52 | cordova.exec( 53 | function (result) { 54 | }, 55 | function (error) { 56 | }, "Game", "showLeaderboard", [leaderboardId]); 57 | }, 58 | showLeaderboards: function () { 59 | cordova.exec( 60 | function (result) { 61 | }, 62 | function (error) { 63 | }, "Game", "showLeaderboards", []); 64 | }, 65 | getPlayerScore: function (leaderboardId, tag) { 66 | var self = this; 67 | cordova.exec(function (result) { 68 | var playerScore = result; 69 | self.tag = tag; 70 | if (self.onGetPlayerScoreSucceeded) 71 | self.onGetPlayerScoreSucceeded(playerScore); 72 | }, 73 | function (error) { 74 | self.tag = tag; 75 | if (self.onGetPlayerScoreFailed) 76 | self.onGetPlayerScoreFailed(); 77 | }, "Game", "getPlayerScore", [leaderboardId]); 78 | }, 79 | unlockAchievement: function (achievementId, tag) { 80 | var self = this; 81 | cordova.exec(function (result) { 82 | self.tag = tag; 83 | if (self.onUnlockAchievementSucceeded) 84 | self.onUnlockAchievementSucceeded(); 85 | }, 86 | function (error) { 87 | self.tag = tag; 88 | if (self.onUnlockAchievementFailed) 89 | self.onUnlockAchievementFailed(); 90 | }, "Game", "unlockAchievement", [achievementId]); 91 | }, 92 | incrementAchievement: function (achievementId, incrementalStepOrCurrentPercent, tag) { 93 | var self = this; 94 | cordova.exec(function (result) { 95 | self.tag = tag; 96 | if (self.onIncrementAchievementSucceeded) 97 | self.onIncrementAchievementSucceeded(); 98 | }, 99 | function (error) { 100 | self.tag = tag; 101 | if (self.onIncrementAchievementFailed) 102 | self.onIncrementAchievementFailed(); 103 | }, "Game", "incrementAchievement", [achievementId, incrementalStepOrCurrentPercent]); 104 | }, 105 | showAchievements: function () { 106 | cordova.exec( 107 | function (result) { 108 | }, 109 | function (error) { 110 | }, "Game", "showAchievements", []); 111 | }, 112 | resetAchievements: function () { 113 | var self = this; 114 | cordova.exec(function (result) { 115 | if (self.onResetAchievementsSucceeded) 116 | self.onResetAchievementsSucceeded(); 117 | }, 118 | function (error) { 119 | if (self.onResetAchievementsFailed) 120 | self.onResetAchievementsFailed(); 121 | }, "Game", "resetAchievements", []); 122 | }, 123 | getPlayerImage: function () { 124 | var self = this; 125 | cordova.exec(function (result) { 126 | var playerImageUrl = result; 127 | if (self.onGetPlayerImageSucceeded) 128 | self.onGetPlayerImageSucceeded(playerImageUrl); 129 | }, 130 | function (error) { 131 | if (self.onGetPlayerImageFailed) 132 | self.onGetPlayerImageFailed(); 133 | }, "Game", "getPlayerImage", []); 134 | }, 135 | onLoginSucceeded: null, 136 | onLoginFailed: null, 137 | onSubmitScoreSucceeded: null, 138 | onSubmitScoreFailed: null, 139 | onGetPlayerScoreSucceeded: null, 140 | onGetPlayerScoreFailed: null, 141 | onUnlockAchievementSucceeded: null, 142 | onUnlockAchievementFailed: null, 143 | onIncrementAchievementSucceeded: null, 144 | onIncrementAchievementFailed: null, 145 | onResetAchievementsSucceeded: null, 146 | onResetAchievementsFailed: null, 147 | onGetPlayerImageSucceeded: null, 148 | onGetPlayerImageFailed: null 149 | }; 150 | --------------------------------------------------------------------------------