From 7b694f3d7a07f0ed4cbcb273840b22c4bc051523 Mon Sep 17 00:00:00 2001 From: Sergii Pylypenko Date: Mon, 7 Jul 2014 23:44:50 +0300 Subject: [PATCH] Support for linking with Google Play Services, also fixed Proguard config --- .gitignore | 4 +- changeAppSettings.sh | 32 +- project/AndroidManifestTemplate.xml | 77 +- project/java/CloudSave.java | 42 + project/java/Globals.java | 1 + project/java/MainActivity.java | 20 + .../googleplaygameservices/CloudSave.java | 169 +++ .../googleplaygameservices/GameHelper.java | 1066 +++++++++++++++++ .../GameHelperUtils.java | 182 +++ .../java/translations/values-fr/strings.xml | 12 + .../java/translations/values-ru/strings.xml | 12 + .../java/translations/values-uk/strings.xml | 12 + project/java/translations/values/strings.xml | 6 + .../biniax2/AndroidAppSettings.cfg | 100 +- project/proguard.cfg | 21 +- project/project.properties | 2 +- 16 files changed, 1660 insertions(+), 98 deletions(-) create mode 100644 project/java/CloudSave.java create mode 100644 project/java/googleplaygameservices/CloudSave.java create mode 100644 project/java/googleplaygameservices/GameHelper.java create mode 100644 project/java/googleplaygameservices/GameHelperUtils.java diff --git a/.gitignore b/.gitignore index 500d6ce7e..4f505d499 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,5 @@ project/jni/application/src project/res/drawable-xhdpi/ouya_icon.png project/res/drawable/app_icon.png project/proguard-project.txt +project/proguard-local.cfg project/themes/converter - - - diff --git a/changeAppSettings.sh b/changeAppSettings.sh index a500564a3..06019034a 100755 --- a/changeAppSettings.sh +++ b/changeAppSettings.sh @@ -367,6 +367,9 @@ echo >> AndroidAppSettings.cfg echo "# Your AdMob banner size (BANNER/FULL_BANNER/LEADERBOARD/MEDIUM_RECTANGLE/SMART_BANNER/WIDE_SKYSCRAPER/FULL_WIDTH:Height/Width:AUTO_HEIGHT/Width:Height)" >> AndroidAppSettings.cfg echo AdmobBannerSize=$AdmobBannerSize >> AndroidAppSettings.cfg echo >> AndroidAppSettings.cfg +echo "# Google Play Game Services application ID, required for cloud saves to work" >> AndroidAppSettings.cfg +echo GooglePlayGameServicesId=$GooglePlayGameServicesId >> AndroidAppSettings.cfg +echo >> AndroidAppSettings.cfg fi AppShortName=`echo $AppName | sed 's/ //g'` @@ -682,7 +685,7 @@ cd $JAVA_SRC_PATH for F in *.java; do echo Patching $F echo '// DO NOT EDIT THIS FILE - it is automatically generated, ALL YOUR CHANGES WILL BE OVERWRITTEN, edit the file under $JAVA_SRC_PATH dir' > ../src/$F - cat $F | sed "s/package .*;/package $AppFullName;/" >> ../src/$F # | sed 's@$@ // THIS FILE IS AUTO-GENERATED@' >> + cat $F | sed "s/^package .*;/package $AppFullName;/" >> ../src/$F # | sed 's@$@ // THIS FILE IS AUTO-GENERATED@' >> done if [ -e ../jni/application/src/java.diff ]; then patch -d ../src --no-backup-if-mismatch < ../jni/application/src/java.diff || exit 1 ; fi @@ -710,9 +713,33 @@ else F=$JAVA_SRC_PATH/admob/Advertisement.java echo Patching $F echo '// DO NOT EDIT THIS FILE - it is automatically generated, edit file under $JAVA_SRC_PATH dir' > project/src/Advertisement.java - cat $F | sed "s/package .*;/package $AppFullName;/" >> project/src/Advertisement.java + cat $F | sed "s/^package .*;/package $AppFullName;/" >> project/src/Advertisement.java fi +if [ "$GooglePlayGameServicesId" = "n" -o -z "$GooglePlayGameServicesId" ] ; then + $SEDI "/==GOOGLEPLAYGAMESERVICES==/ d" project/AndroidManifest.xml + GooglePlayGameServicesId="" +else + for F in $JAVA_SRC_PATH/googleplaygameservices/*.java; do + OUT=`echo $F | sed 's@.*/@@'` # basename tool is not available everywhere + echo Patching $F + echo '// DO NOT EDIT THIS FILE - it is automatically generated, edit file under $JAVA_SRC_PATH dir' > project/src/$OUT + cat $F | sed "s/^package .*;/package $AppFullName;/" >> project/src/$OUT + done + $SEDI "s/==GOOGLEPLAYGAMESERVICES_APP_ID==/$GooglePlayGameServicesId/g" project/AndroidManifest.xml + SDK_DIR=`grep '^sdk.dir' project/local.properties | sed 's/.*=//'` + grep 'android.library.reference.1' project/local.properties > /dev/null || { + # Ant is way too smart, and adds current project path in front of the ${sdk.dir} + echo 'android.library.reference.1=../../../../../../../../../../../../../../${sdk.dir}/extras/google/google_play_services/libproject/google-play-services_lib' >> project/local.properties + echo 'proguard.config=proguard.cfg' >> project/local.properties + ln -s -f $SDK_DIR/extras/android/compatibility/v4/android-support-v4.jar project/libs + } + [ -e $SDK_DIR/extras/google/google_play_services/libproject/google-play-services_lib/build.xml ] || \ + android update project -p $SDK_DIR/extras/google/google_play_services/libproject/google-play-services_lib +fi + +echo "-keep class $AppFullName.** { *; }" > project/proguard-local.cfg + if [ "$AppRecordsAudio" = "n" -o -z "$AppRecordsAudio" ] ; then $SEDI "/==RECORD_AUDIO==/ d" project/AndroidManifest.xml fi @@ -803,6 +830,7 @@ $SEDI "s%public static String CommandLine = .*%public static String CommandLine $SEDI "s%public static String AdmobPublisherId = .*%public static String AdmobPublisherId = \"$AdmobPublisherId\";%" project/src/Globals.java $SEDI "s/public static String AdmobTestDeviceId = .*/public static String AdmobTestDeviceId = \"$AdmobTestDeviceId\";/" project/src/Globals.java $SEDI "s/public static String AdmobBannerSize = .*/public static String AdmobBannerSize = \"$AdmobBannerSize\";/" project/src/Globals.java +$SEDI "s%public static String GooglePlayGameServicesId = .*%public static String GooglePlayGameServicesId = \"$GooglePlayGameServicesId\";%" project/src/Globals.java $SEDI "s/public static String AppLibraries.*/public static String AppLibraries[] = { $LibrariesToLoad };/" project/src/Globals.java $SEDI "s/public static String AppMainLibraries.*/public static String AppMainLibraries[] = { $MainLibrariesToLoad };/" project/src/Globals.java diff --git a/project/AndroidManifestTemplate.xml b/project/AndroidManifestTemplate.xml index e51d030ef..0205912af 100644 --- a/project/AndroidManifestTemplate.xml +++ b/project/AndroidManifestTemplate.xml @@ -1,43 +1,48 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - + + + + + + - - - - + + + + + + diff --git a/project/java/CloudSave.java b/project/java/CloudSave.java new file mode 100644 index 000000000..4e4d72c37 --- /dev/null +++ b/project/java/CloudSave.java @@ -0,0 +1,42 @@ +/* +Simple DirectMedia Layer +Java source code (C) 2009-2014 Sergii Pylypenko + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. +*/ + +package net.sourceforge.clonekeenplus; + +import android.app.Activity; +import android.content.Intent; + +// Stub class for compiling without cloud save support +class CloudSave +{ + public CloudSave(MainActivity p) + { + } + + public void onStart() { + } + + public void onStop() { + } + + public void onActivityResult(int request, int response, Intent data) { + } +} diff --git a/project/java/Globals.java b/project/java/Globals.java index 13e4e420b..4bb21aa8b 100644 --- a/project/java/Globals.java +++ b/project/java/Globals.java @@ -73,6 +73,7 @@ class Globals public static String AdmobPublisherId = ""; public static String AdmobTestDeviceId = ""; public static String AdmobBannerSize = ""; + public static String GooglePlayGameServicesId = ""; // Phone-specific config, modified by user in "Change phone config" startup dialog public static int VideoDepthBpp = 16; diff --git a/project/java/MainActivity.java b/project/java/MainActivity.java index ce82058b0..cef4d85b3 100644 --- a/project/java/MainActivity.java +++ b/project/java/MainActivity.java @@ -220,6 +220,7 @@ public class MainActivity extends Activity Intent intent = new Intent(this, DummyService.class); startService(intent); } + _cloudSave = new CloudSave(this); } public void setUpStatusLabel() @@ -425,6 +426,24 @@ public class MainActivity extends Activity System.exit(0); } + @Override + protected void onStart() { + super.onStart(); + _cloudSave.onStart(); + } + + @Override + protected void onStop() { + super.onStart(); + _cloudSave.onStop(); + } + + @Override + public void onActivityResult(int request, int response, Intent data) { + super.onActivityResult(request, response, data); + _cloudSave.onActivityResult(request, response, data); + } + public void showScreenKeyboardWithoutTextInputField() { if( !keyboardWithoutTextInputShown ) @@ -1216,6 +1235,7 @@ public class MainActivity extends Activity private LinearLayout _layout = null; private LinearLayout _layout2 = null; private Advertisement _ad = null; + public CloudSave _cloudSave = null; private FrameLayout _videoLayout = null; private EditText _screenKeyboard = null; diff --git a/project/java/googleplaygameservices/CloudSave.java b/project/java/googleplaygameservices/CloudSave.java new file mode 100644 index 000000000..6e8a0d72e --- /dev/null +++ b/project/java/googleplaygameservices/CloudSave.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.example.games.basegameutils; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import com.google.android.gms.common.api.GoogleApiClient; + +/** + * Example base class for games. This implementation takes care of setting up + * the API client object and managing its lifecycle. Subclasses only need to + * override the @link{#onSignInSucceeded} and @link{#onSignInFailed} abstract + * methods. To initiate the sign-in flow when the user clicks the sign-in + * button, subclasses should call @link{#beginUserInitiatedSignIn}. By default, + * this class only instantiates the GoogleApiClient object. If the PlusClient or + * AppStateClient objects are also wanted, call the BaseGameActivity(int) + * constructor and specify the requested clients. For example, to request + * PlusClient and GamesClient, use BaseGameActivity(CLIENT_GAMES | CLIENT_PLUS). + * To request all available clients, use BaseGameActivity(CLIENT_ALL). + * Alternatively, you can also specify the requested clients via + * @link{#setRequestedClients}, but you must do so before @link{#onCreate} + * gets called, otherwise the call will have no effect. + * + * @author Bruno Oliveira (Google) + */ +public class CloudSave implements GameHelper.GameHelperListener { + + // The game helper object. This class is mainly a wrapper around this object. + protected GameHelper mHelper; + + // We expose these constants here because we don't want users of this class + // to have to know about GameHelper at all. + public static final int CLIENT_GAMES = GameHelper.CLIENT_GAMES; + public static final int CLIENT_APPSTATE = GameHelper.CLIENT_APPSTATE; + public static final int CLIENT_PLUS = GameHelper.CLIENT_PLUS; + public static final int CLIENT_ALL = GameHelper.CLIENT_ALL; + + // Requested clients. By default, that's just the games client. + public int mRequestedClients = CLIENT_GAMES; + + private final static String TAG = "BaseGameActivity"; + public boolean mDebugLog = false; + + MainActivity parent; + + /** Constructs a BaseGameActivity with default client (GamesClient). */ + public CloudSave(MainActivity p) + { + parent = p; + setRequestedClients(CLIENT_GAMES); + getGameHelper().setup(this); + /* + // Add the Drive API and scope to the builder: + GoogleApiClient.Builder builder = helper.getApiClientBuilder(); + GoogleApiClient.Builder builder = new GoogleApiClient.Builder(parent, this, this); + builder.addScope(Drive.SCOPE_APPFOLDER); + builder.addApi(Drive.API); + */ + } + + /** + * Sets the requested clients. The preferred way to set the requested clients is + * via the constructor, but this method is available if for some reason your code + * cannot do this in the constructor. This must be called before onCreate or getGameHelper() + * in order to have any effect. If called after onCreate()/getGameHelper(), this method + * is a no-op. + * + * @param requestedClients A combination of the flags CLIENT_GAMES, CLIENT_PLUS + * and CLIENT_APPSTATE, or CLIENT_ALL to request all available clients. + */ + public void setRequestedClients(int requestedClients) { + mRequestedClients = requestedClients; + } + + public GameHelper getGameHelper() { + if (mHelper == null) { + mHelper = new GameHelper(parent, mRequestedClients); + mHelper.enableDebugLog(mDebugLog); + } + return mHelper; + } + + public void onStart() { + mHelper.onStart(parent); + } + + public void onStop() { + mHelper.onStop(); + } + + public void onActivityResult(int request, int response, Intent data) { + mHelper.onActivityResult(request, response, data); + } + + public void onSignInSucceeded() { + // TODO + } + + public void onSignInFailed() { + // TODO + } + + public GoogleApiClient getApiClient() { + return mHelper.getApiClient(); + } + + public boolean isSignedIn() { + return mHelper.isSignedIn(); + } + + public void beginUserInitiatedSignIn() { + mHelper.beginUserInitiatedSignIn(); + } + + public void signOut() { + mHelper.signOut(); + } + + public void showAlert(String message) { + mHelper.makeSimpleDialog(message).show(); + } + + public void showAlert(String title, String message) { + mHelper.makeSimpleDialog(title, message).show(); + } + + public void enableDebugLog(boolean enabled) { + mDebugLog = true; + if (mHelper != null) { + mHelper.enableDebugLog(enabled); + } + } + + public void enableDebugLog(boolean enabled, String tag) { + enableDebugLog(enabled); + } + + public String getInvitationId() { + return mHelper.getInvitationId(); + } + + public void reconnectClient() { + mHelper.reconnectClient(); + } + + public boolean hasSignInError() { + return mHelper.hasSignInError(); + } + + public GameHelper.SignInFailureReason getSignInError() { + return mHelper.getSignInError(); + } +} diff --git a/project/java/googleplaygameservices/GameHelper.java b/project/java/googleplaygameservices/GameHelper.java new file mode 100644 index 000000000..28fa482ea --- /dev/null +++ b/project/java/googleplaygameservices/GameHelper.java @@ -0,0 +1,1066 @@ +/* + * Copyright (C) 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.example.games.basegameutils; + +import java.util.ArrayList; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender.SendIntentException; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; + +import com.google.android.gms.appstate.AppStateManager; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; +import com.google.android.gms.common.api.Api.ApiOptions.NoOptions; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.drive.Drive; +import com.google.android.gms.games.Games; +import com.google.android.gms.games.Games.GamesOptions; +import com.google.android.gms.games.GamesActivityResultCodes; +import com.google.android.gms.games.multiplayer.Invitation; +import com.google.android.gms.games.multiplayer.Multiplayer; +import com.google.android.gms.games.multiplayer.turnbased.TurnBasedMatch; +import com.google.android.gms.games.request.GameRequest; +import com.google.android.gms.plus.Plus; +import com.google.android.gms.plus.Plus.PlusOptions; + +public class GameHelper implements GoogleApiClient.ConnectionCallbacks, + GoogleApiClient.OnConnectionFailedListener { + + static final String TAG = "GameHelper"; + + /** Listener for sign-in success or failure events. */ + public interface GameHelperListener { + /** + * Called when sign-in fails. As a result, a "Sign-In" button can be + * shown to the user; when that button is clicked, call + * + * @link{GamesHelper#beginUserInitiatedSignIn . Note that not all calls + * to this method mean an + * error; it may be a result + * of the fact that automatic + * sign-in could not proceed + * because user interaction + * was required (consent + * dialogs). So + * implementations of this + * method should NOT display + * an error message unless a + * call to @link{GamesHelper# + * hasSignInError} indicates + * that an error indeed + * occurred. + */ + void onSignInFailed(); + + /** Called when sign-in succeeds. */ + void onSignInSucceeded(); + } + + // configuration done? + private boolean mSetupDone = false; + + // are we currently connecting? + private boolean mConnecting = false; + + // Are we expecting the result of a resolution flow? + boolean mExpectingResolution = false; + + // was the sign-in flow cancelled when we tried it? + // if true, we know not to try again automatically. + boolean mSignInCancelled = false; + + /** + * The Activity we are bound to. We need to keep a reference to the Activity + * because some games methods require an Activity (a Context won't do). We + * are careful not to leak these references: we release them on onStop(). + */ + Activity mActivity = null; + + // app context + Context mAppContext = null; + + // Request code we use when invoking other Activities to complete the + // sign-in flow. + final static int RC_RESOLVE = 9001; + + // Request code when invoking Activities whose result we don't care about. + final static int RC_UNUSED = 9002; + + // the Google API client builder we will use to create GoogleApiClient + GoogleApiClient.Builder mGoogleApiClientBuilder = null; + + // Api options to use when adding each API, null for none + GamesOptions mGamesApiOptions = GamesOptions.builder().build(); + PlusOptions mPlusApiOptions = null; + NoOptions mAppStateApiOptions = null; + + // Google API client object we manage. + GoogleApiClient mGoogleApiClient = null; + + // Client request flags + public final static int CLIENT_NONE = 0x00; + public final static int CLIENT_GAMES = 0x01; + public final static int CLIENT_PLUS = 0x02; + public final static int CLIENT_APPSTATE = 0x04; + public final static int CLIENT_SNAPSHOT = 0x05; + public final static int CLIENT_ALL = CLIENT_GAMES | CLIENT_PLUS + | CLIENT_APPSTATE | CLIENT_SNAPSHOT; + + // What clients were requested? (bit flags) + int mRequestedClients = CLIENT_NONE; + + // Whether to automatically try to sign in on onStart(). We only set this + // to true when the sign-in process fails or the user explicitly signs out. + // We set it back to false when the user initiates the sign in process. + boolean mConnectOnStart = true; + + /* + * Whether user has specifically requested that the sign-in process begin. + * If mUserInitiatedSignIn is false, we're in the automatic sign-in attempt + * that we try once the Activity is started -- if true, then the user has + * already clicked a "Sign-In" button or something similar + */ + boolean mUserInitiatedSignIn = false; + + // The connection result we got from our last attempt to sign-in. + ConnectionResult mConnectionResult = null; + + // The error that happened during sign-in. + SignInFailureReason mSignInFailureReason = null; + + // Should we show error dialog boxes? + boolean mShowErrorDialogs = true; + + // Print debug logs? + boolean mDebugLog = false; + + Handler mHandler; + + /* + * If we got an invitation when we connected to the games client, it's here. + * Otherwise, it's null. + */ + Invitation mInvitation; + + /* + * If we got turn-based match when we connected to the games client, it's + * here. Otherwise, it's null. + */ + TurnBasedMatch mTurnBasedMatch; + + /* + * If we have incoming requests when we connected to the games client, they + * are here. Otherwise, it's null. + */ + ArrayList mRequests; + + // Listener + GameHelperListener mListener = null; + + // Should we start the flow to sign the user in automatically on startup? If + // so, up to + // how many times in the life of the application? + static final int DEFAULT_MAX_SIGN_IN_ATTEMPTS = 3; + int mMaxAutoSignInAttempts = DEFAULT_MAX_SIGN_IN_ATTEMPTS; + + /** + * Construct a GameHelper object, initially tied to the given Activity. + * After constructing this object, call @link{setup} from the onCreate() + * method of your Activity. + * + * @param clientsToUse + * the API clients to use (a combination of the CLIENT_* flags, + * or CLIENT_ALL to mean all clients). + */ + public GameHelper(Activity activity, int clientsToUse) { + mActivity = activity; + mAppContext = activity.getApplicationContext(); + mRequestedClients = clientsToUse; + mHandler = new Handler(); + } + + /** + * Sets the maximum number of automatic sign-in attempts to be made on + * application startup. This maximum is over the lifetime of the application + * (it is stored in a SharedPreferences file). So, for example, if you + * specify 2, then it means that the user will be prompted to sign in on app + * startup the first time and, if they cancel, a second time the next time + * the app starts, and, if they cancel that one, never again. Set to 0 if + * you do not want the user to be prompted to sign in on application + * startup. + */ + public void setMaxAutoSignInAttempts(int max) { + mMaxAutoSignInAttempts = max; + } + + void assertConfigured(String operation) { + if (!mSetupDone) { + String error = "GameHelper error: Operation attempted without setup: " + + operation + + ". The setup() method must be called before attempting any other operation."; + logError(error); + throw new IllegalStateException(error); + } + } + + private void doApiOptionsPreCheck() { + if (mGoogleApiClientBuilder != null) { + String error = "GameHelper: you cannot call set*ApiOptions after the client " + + "builder has been created. Call it before calling createApiClientBuilder() " + + "or setup()."; + logError(error); + throw new IllegalStateException(error); + } + } + + /** + * Sets the options to pass when setting up the Games API. Call before + * setup(). + */ + public void setGamesApiOptions(GamesOptions options) { + doApiOptionsPreCheck(); + mGamesApiOptions = options; + } + + /** + * Sets the options to pass when setting up the AppState API. Call before + * setup(). + */ + public void setAppStateApiOptions(NoOptions options) { + doApiOptionsPreCheck(); + mAppStateApiOptions = options; + } + + /** + * Sets the options to pass when setting up the Plus API. Call before + * setup(). + */ + public void setPlusApiOptions(PlusOptions options) { + doApiOptionsPreCheck(); + mPlusApiOptions = options; + } + + /** + * Creates a GoogleApiClient.Builder for use with @link{#setup}. Normally, + * you do not have to do this; use this method only if you need to make + * nonstandard setup (e.g. adding extra scopes for other APIs) on the + * GoogleApiClient.Builder before calling @link{#setup}. + */ + public GoogleApiClient.Builder createApiClientBuilder() { + if (mSetupDone) { + String error = "GameHelper: you called GameHelper.createApiClientBuilder() after " + + "calling setup. You can only get a client builder BEFORE performing setup."; + logError(error); + throw new IllegalStateException(error); + } + + GoogleApiClient.Builder builder = new GoogleApiClient.Builder( + mActivity, this, this); + + if (0 != (mRequestedClients & CLIENT_GAMES)) { + builder.addApi(Games.API, mGamesApiOptions); + builder.addScope(Games.SCOPE_GAMES); + } + + if (0 != (mRequestedClients & CLIENT_PLUS)) { + builder.addApi(Plus.API); + builder.addScope(Plus.SCOPE_PLUS_LOGIN); + } + + if (0 != (mRequestedClients & CLIENT_APPSTATE)) { + builder.addApi(AppStateManager.API); + builder.addScope(AppStateManager.SCOPE_APP_STATE); + } + + if (0 != (mRequestedClients & CLIENT_SNAPSHOT)) { + builder.addScope(Drive.SCOPE_APPFOLDER); + builder.addApi(Drive.API); + } + + mGoogleApiClientBuilder = builder; + return builder; + } + + /** + * Performs setup on this GameHelper object. Call this from the onCreate() + * method of your Activity. This will create the clients and do a few other + * initialization tasks. Next, call @link{#onStart} from the onStart() + * method of your Activity. + * + * @param listener + * The listener to be notified of sign-in events. + */ + public void setup(GameHelperListener listener) { + if (mSetupDone) { + String error = "GameHelper: you cannot call GameHelper.setup() more than once!"; + logError(error); + throw new IllegalStateException(error); + } + mListener = listener; + debugLog("Setup: requested clients: " + mRequestedClients); + + if (mGoogleApiClientBuilder == null) { + // we don't have a builder yet, so create one + createApiClientBuilder(); + } + + mGoogleApiClient = mGoogleApiClientBuilder.build(); + mGoogleApiClientBuilder = null; + mSetupDone = true; + } + + /** + * Returns the GoogleApiClient object. In order to call this method, you + * must have called @link{setup}. + */ + public GoogleApiClient getApiClient() { + if (mGoogleApiClient == null) { + throw new IllegalStateException( + "No GoogleApiClient. Did you call setup()?"); + } + return mGoogleApiClient; + } + + /** Returns whether or not the user is signed in. */ + public boolean isSignedIn() { + return mGoogleApiClient != null && mGoogleApiClient.isConnected(); + } + + /** Returns whether or not we are currently connecting */ + public boolean isConnecting() { + return mConnecting; + } + + /** + * Returns whether or not there was a (non-recoverable) error during the + * sign-in process. + */ + public boolean hasSignInError() { + return mSignInFailureReason != null; + } + + /** + * Returns the error that happened during the sign-in process, null if no + * error occurred. + */ + public SignInFailureReason getSignInError() { + return mSignInFailureReason; + } + + // Set whether to show error dialogs or not. + public void setShowErrorDialogs(boolean show) { + mShowErrorDialogs = show; + } + + /** Call this method from your Activity's onStart(). */ + public void onStart(Activity act) { + mActivity = act; + mAppContext = act.getApplicationContext(); + + debugLog("onStart"); + assertConfigured("onStart"); + + if (mConnectOnStart) { + if (mGoogleApiClient.isConnected()) { + Log.w(TAG, + "GameHelper: client was already connected on onStart()"); + } else { + debugLog("Connecting client."); + mConnecting = true; + mGoogleApiClient.connect(); + } + } else { + debugLog("Not attempting to connect becase mConnectOnStart=false"); + debugLog("Instead, reporting a sign-in failure."); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + notifyListener(false); + } + }, 1000); + } + } + + /** Call this method from your Activity's onStop(). */ + public void onStop() { + debugLog("onStop"); + assertConfigured("onStop"); + if (mGoogleApiClient.isConnected()) { + debugLog("Disconnecting client due to onStop"); + mGoogleApiClient.disconnect(); + } else { + debugLog("Client already disconnected when we got onStop."); + } + mConnecting = false; + mExpectingResolution = false; + + // let go of the Activity reference + mActivity = null; + } + + /** + * Returns the invitation ID received through an invitation notification. + * This should be called from your GameHelperListener's + * + * @link{GameHelperListener#onSignInSucceeded method, to check if there's an + * invitation available. In that + * case, accept the invitation. + * @return The id of the invitation, or null if none was received. + */ + public String getInvitationId() { + if (!mGoogleApiClient.isConnected()) { + Log.w(TAG, + "Warning: getInvitationId() should only be called when signed in, " + + "that is, after getting onSignInSuceeded()"); + } + return mInvitation == null ? null : mInvitation.getInvitationId(); + } + + /** + * Returns the invitation received through an invitation notification. This + * should be called from your GameHelperListener's + * + * @link{GameHelperListener#onSignInSucceeded method, to check if there's an + * invitation available. In that + * case, accept the invitation. + * @return The invitation, or null if none was received. + */ + public Invitation getInvitation() { + if (!mGoogleApiClient.isConnected()) { + Log.w(TAG, + "Warning: getInvitation() should only be called when signed in, " + + "that is, after getting onSignInSuceeded()"); + } + return mInvitation; + } + + public boolean hasInvitation() { + return mInvitation != null; + } + + public boolean hasTurnBasedMatch() { + return mTurnBasedMatch != null; + } + + public boolean hasRequests() { + return mRequests != null; + } + + public void clearInvitation() { + mInvitation = null; + } + + public void clearTurnBasedMatch() { + mTurnBasedMatch = null; + } + + public void clearRequests() { + mRequests = null; + } + + /** + * Returns the tbmp match received through an invitation notification. This + * should be called from your GameHelperListener's + * + * @link{GameHelperListener#onSignInSucceeded method, to check if there's a + * match available. + * @return The match, or null if none was received. + */ + public TurnBasedMatch getTurnBasedMatch() { + if (!mGoogleApiClient.isConnected()) { + Log.w(TAG, + "Warning: getTurnBasedMatch() should only be called when signed in, " + + "that is, after getting onSignInSuceeded()"); + } + return mTurnBasedMatch; + } + + /** + * Returns the requests received through the onConnected bundle. This should + * be called from your GameHelperListener's + * + * @link{GameHelperListener#onSignInSucceeded method, to check if there are + * incoming requests that must be + * handled. + * @return The requests, or null if none were received. + */ + public ArrayList getRequests() { + if (!mGoogleApiClient.isConnected()) { + Log.w(TAG, "Warning: getRequests() should only be called " + + "when signed in, " + + "that is, after getting onSignInSuceeded()"); + } + return mRequests; + } + + /** Enables debug logging */ + public void enableDebugLog(boolean enabled) { + mDebugLog = enabled; + if (enabled) { + debugLog("Debug log enabled."); + } + } + + @Deprecated + public void enableDebugLog(boolean enabled, String tag) { + Log.w(TAG, "GameHelper.enableDebugLog(boolean,String) is deprecated. " + + "Use GameHelper.enableDebugLog(boolean)"); + enableDebugLog(enabled); + } + + /** Sign out and disconnect from the APIs. */ + public void signOut() { + if (!mGoogleApiClient.isConnected()) { + // nothing to do + debugLog("signOut: was already disconnected, ignoring."); + return; + } + + // for Plus, "signing out" means clearing the default account and + // then disconnecting + if (0 != (mRequestedClients & CLIENT_PLUS)) { + debugLog("Clearing default account on PlusClient."); + Plus.AccountApi.clearDefaultAccount(mGoogleApiClient); + } + + // For the games client, signing out means calling signOut and + // disconnecting + if (0 != (mRequestedClients & CLIENT_GAMES)) { + debugLog("Signing out from the Google API Client."); + Games.signOut(mGoogleApiClient); + } + + // Ready to disconnect + debugLog("Disconnecting client."); + mConnectOnStart = false; + mConnecting = false; + mGoogleApiClient.disconnect(); + } + + /** + * Handle activity result. Call this method from your Activity's + * onActivityResult callback. If the activity result pertains to the sign-in + * process, processes it appropriately. + */ + public void onActivityResult(int requestCode, int responseCode, + Intent intent) { + debugLog("onActivityResult: req=" + + (requestCode == RC_RESOLVE ? "RC_RESOLVE" : String + .valueOf(requestCode)) + ", resp=" + + GameHelperUtils.activityResponseCodeToString(responseCode)); + if (requestCode != RC_RESOLVE) { + debugLog("onActivityResult: request code not meant for us. Ignoring."); + return; + } + + // no longer expecting a resolution + mExpectingResolution = false; + + if (!mConnecting) { + debugLog("onActivityResult: ignoring because we are not connecting."); + return; + } + + // We're coming back from an activity that was launched to resolve a + // connection problem. For example, the sign-in UI. + if (responseCode == Activity.RESULT_OK) { + // Ready to try to connect again. + debugLog("onAR: Resolution was RESULT_OK, so connecting current client again."); + connect(); + } else if (responseCode == GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED) { + debugLog("onAR: Resolution was RECONNECT_REQUIRED, so reconnecting."); + connect(); + } else if (responseCode == Activity.RESULT_CANCELED) { + // User cancelled. + debugLog("onAR: Got a cancellation result, so disconnecting."); + mSignInCancelled = true; + mConnectOnStart = false; + mUserInitiatedSignIn = false; + mSignInFailureReason = null; // cancelling is not a failure! + mConnecting = false; + mGoogleApiClient.disconnect(); + + // increment # of cancellations + int prevCancellations = getSignInCancellations(); + int newCancellations = incrementSignInCancellations(); + debugLog("onAR: # of cancellations " + prevCancellations + " --> " + + newCancellations + ", max " + mMaxAutoSignInAttempts); + + notifyListener(false); + } else { + // Whatever the problem we were trying to solve, it was not + // solved. So give up and show an error message. + debugLog("onAR: responseCode=" + + GameHelperUtils + .activityResponseCodeToString(responseCode) + + ", so giving up."); + giveUp(new SignInFailureReason(mConnectionResult.getErrorCode(), + responseCode)); + } + } + + void notifyListener(boolean success) { + debugLog("Notifying LISTENER of sign-in " + + (success ? "SUCCESS" + : mSignInFailureReason != null ? "FAILURE (error)" + : "FAILURE (no error)")); + if (mListener != null) { + if (success) { + mListener.onSignInSucceeded(); + } else { + mListener.onSignInFailed(); + } + } + } + + /** + * Starts a user-initiated sign-in flow. This should be called when the user + * clicks on a "Sign In" button. As a result, authentication/consent dialogs + * may show up. At the end of the process, the GameHelperListener's + * onSignInSucceeded() or onSignInFailed() methods will be called. + */ + public void beginUserInitiatedSignIn() { + debugLog("beginUserInitiatedSignIn: resetting attempt count."); + resetSignInCancellations(); + mSignInCancelled = false; + mConnectOnStart = true; + + if (mGoogleApiClient.isConnected()) { + // nothing to do + logWarn("beginUserInitiatedSignIn() called when already connected. " + + "Calling listener directly to notify of success."); + notifyListener(true); + return; + } else if (mConnecting) { + logWarn("beginUserInitiatedSignIn() called when already connecting. " + + "Be patient! You can only call this method after you get an " + + "onSignInSucceeded() or onSignInFailed() callback. Suggestion: disable " + + "the sign-in button on startup and also when it's clicked, and re-enable " + + "when you get the callback."); + // ignore call (listener will get a callback when the connection + // process finishes) + return; + } + + debugLog("Starting USER-INITIATED sign-in flow."); + + // indicate that user is actively trying to sign in (so we know to + // resolve + // connection problems by showing dialogs) + mUserInitiatedSignIn = true; + + if (mConnectionResult != null) { + // We have a pending connection result from a previous failure, so + // start with that. + debugLog("beginUserInitiatedSignIn: continuing pending sign-in flow."); + mConnecting = true; + resolveConnectionResult(); + } else { + // We don't have a pending connection result, so start anew. + debugLog("beginUserInitiatedSignIn: starting new sign-in flow."); + mConnecting = true; + connect(); + } + } + + void connect() { + if (mGoogleApiClient.isConnected()) { + debugLog("Already connected."); + return; + } + debugLog("Starting connection."); + mConnecting = true; + mInvitation = null; + mTurnBasedMatch = null; + mGoogleApiClient.connect(); + } + + /** + * Disconnects the API client, then connects again. + */ + public void reconnectClient() { + if (!mGoogleApiClient.isConnected()) { + Log.w(TAG, "reconnectClient() called when client is not connected."); + // interpret it as a request to connect + connect(); + } else { + debugLog("Reconnecting client."); + mGoogleApiClient.reconnect(); + } + } + + /** Called when we successfully obtain a connection to a client. */ + @Override + public void onConnected(Bundle connectionHint) { + debugLog("onConnected: connected!"); + + if (connectionHint != null) { + debugLog("onConnected: connection hint provided. Checking for invite."); + Invitation inv = connectionHint + .getParcelable(Multiplayer.EXTRA_INVITATION); + if (inv != null && inv.getInvitationId() != null) { + // retrieve and cache the invitation ID + debugLog("onConnected: connection hint has a room invite!"); + mInvitation = inv; + debugLog("Invitation ID: " + mInvitation.getInvitationId()); + } + + // Do we have any requests pending? + mRequests = Games.Requests + .getGameRequestsFromBundle(connectionHint); + if (!mRequests.isEmpty()) { + // We have requests in onConnected's connectionHint. + debugLog("onConnected: connection hint has " + mRequests.size() + + " request(s)"); + } + + debugLog("onConnected: connection hint provided. Checking for TBMP game."); + mTurnBasedMatch = connectionHint + .getParcelable(Multiplayer.EXTRA_TURN_BASED_MATCH); + } + + // we're good to go + succeedSignIn(); + } + + void succeedSignIn() { + debugLog("succeedSignIn"); + mSignInFailureReason = null; + mConnectOnStart = true; + mUserInitiatedSignIn = false; + mConnecting = false; + notifyListener(true); + } + + private final String GAMEHELPER_SHARED_PREFS = "GAMEHELPER_SHARED_PREFS"; + private final String KEY_SIGN_IN_CANCELLATIONS = "KEY_SIGN_IN_CANCELLATIONS"; + + // Return the number of times the user has cancelled the sign-in flow in the + // life of the app + int getSignInCancellations() { + SharedPreferences sp = mAppContext.getSharedPreferences( + GAMEHELPER_SHARED_PREFS, Context.MODE_PRIVATE); + return sp.getInt(KEY_SIGN_IN_CANCELLATIONS, 0); + } + + // Increments the counter that indicates how many times the user has + // cancelled the sign in + // flow in the life of the application + int incrementSignInCancellations() { + int cancellations = getSignInCancellations(); + SharedPreferences.Editor editor = mAppContext.getSharedPreferences( + GAMEHELPER_SHARED_PREFS, Context.MODE_PRIVATE).edit(); + editor.putInt(KEY_SIGN_IN_CANCELLATIONS, cancellations + 1); + editor.commit(); + return cancellations + 1; + } + + // Reset the counter of how many times the user has cancelled the sign-in + // flow. + void resetSignInCancellations() { + SharedPreferences.Editor editor = mAppContext.getSharedPreferences( + GAMEHELPER_SHARED_PREFS, Context.MODE_PRIVATE).edit(); + editor.putInt(KEY_SIGN_IN_CANCELLATIONS, 0); + editor.commit(); + } + + /** Handles a connection failure. */ + @Override + public void onConnectionFailed(ConnectionResult result) { + // save connection result for later reference + debugLog("onConnectionFailed"); + + mConnectionResult = result; + debugLog("Connection failure:"); + debugLog(" - code: " + + GameHelperUtils.errorCodeToString(mConnectionResult + .getErrorCode())); + debugLog(" - resolvable: " + mConnectionResult.hasResolution()); + debugLog(" - details: " + mConnectionResult.toString()); + + int cancellations = getSignInCancellations(); + boolean shouldResolve = false; + + if (mUserInitiatedSignIn) { + debugLog("onConnectionFailed: WILL resolve because user initiated sign-in."); + shouldResolve = true; + } else if (mSignInCancelled) { + debugLog("onConnectionFailed WILL NOT resolve (user already cancelled once)."); + shouldResolve = false; + } else if (cancellations < mMaxAutoSignInAttempts) { + debugLog("onConnectionFailed: WILL resolve because we have below the max# of " + + "attempts, " + + cancellations + + " < " + + mMaxAutoSignInAttempts); + shouldResolve = true; + } else { + shouldResolve = false; + debugLog("onConnectionFailed: Will NOT resolve; not user-initiated and max attempts " + + "reached: " + + cancellations + + " >= " + + mMaxAutoSignInAttempts); + } + + if (!shouldResolve) { + // Fail and wait for the user to want to sign in. + debugLog("onConnectionFailed: since we won't resolve, failing now."); + mConnectionResult = result; + mConnecting = false; + notifyListener(false); + return; + } + + debugLog("onConnectionFailed: resolving problem..."); + + // Resolve the connection result. This usually means showing a dialog or + // starting an Activity that will allow the user to give the appropriate + // consents so that sign-in can be successful. + resolveConnectionResult(); + } + + /** + * Attempts to resolve a connection failure. This will usually involve + * starting a UI flow that lets the user give the appropriate consents + * necessary for sign-in to work. + */ + void resolveConnectionResult() { + // Try to resolve the problem + if (mExpectingResolution) { + debugLog("We're already expecting the result of a previous resolution."); + return; + } + + debugLog("resolveConnectionResult: trying to resolve result: " + + mConnectionResult); + if (mConnectionResult.hasResolution()) { + // This problem can be fixed. So let's try to fix it. + debugLog("Result has resolution. Starting it."); + try { + // launch appropriate UI flow (which might, for example, be the + // sign-in flow) + mExpectingResolution = true; + mConnectionResult.startResolutionForResult(mActivity, + RC_RESOLVE); + } catch (SendIntentException e) { + // Try connecting again + debugLog("SendIntentException, so connecting again."); + connect(); + } + } else { + // It's not a problem what we can solve, so give up and show an + // error. + debugLog("resolveConnectionResult: result has no resolution. Giving up."); + giveUp(new SignInFailureReason(mConnectionResult.getErrorCode())); + } + } + + public void disconnect() { + if (mGoogleApiClient.isConnected()) { + debugLog("Disconnecting client."); + mGoogleApiClient.disconnect(); + } else { + Log.w(TAG, + "disconnect() called when client was already disconnected."); + } + } + + /** + * Give up on signing in due to an error. Shows the appropriate error + * message to the user, using a standard error dialog as appropriate to the + * cause of the error. That dialog will indicate to the user how the problem + * can be solved (for example, re-enable Google Play Services, upgrade to a + * new version, etc). + */ + void giveUp(SignInFailureReason reason) { + mConnectOnStart = false; + disconnect(); + mSignInFailureReason = reason; + + if (reason.mActivityResultCode == GamesActivityResultCodes.RESULT_APP_MISCONFIGURED) { + // print debug info for the developer + GameHelperUtils.printMisconfiguredDebugInfo(mAppContext); + } + + showFailureDialog(); + mConnecting = false; + notifyListener(false); + } + + /** Called when we are disconnected from the Google API client. */ + @Override + public void onConnectionSuspended(int cause) { + debugLog("onConnectionSuspended, cause=" + cause); + disconnect(); + mSignInFailureReason = null; + debugLog("Making extraordinary call to onSignInFailed callback"); + mConnecting = false; + notifyListener(false); + } + + public void showFailureDialog() { + if (mSignInFailureReason != null) { + int errorCode = mSignInFailureReason.getServiceErrorCode(); + int actResp = mSignInFailureReason.getActivityResultCode(); + + if (mShowErrorDialogs) { + showFailureDialog(mActivity, actResp, errorCode); + } else { + debugLog("Not showing error dialog because mShowErrorDialogs==false. " + + "" + "Error was: " + mSignInFailureReason); + } + } + } + + /** Shows an error dialog that's appropriate for the failure reason. */ + public static void showFailureDialog(Activity activity, int actResp, + int errorCode) { + if (activity == null) { + Log.e("GameHelper", "*** No Activity. Can't show failure dialog!"); + return; + } + Dialog errorDialog = null; + + switch (actResp) { + case GamesActivityResultCodes.RESULT_APP_MISCONFIGURED: + errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString( + activity, GameHelperUtils.R_APP_MISCONFIGURED)); + break; + case GamesActivityResultCodes.RESULT_SIGN_IN_FAILED: + errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString( + activity, GameHelperUtils.R_SIGN_IN_FAILED)); + break; + case GamesActivityResultCodes.RESULT_LICENSE_FAILED: + errorDialog = makeSimpleDialog(activity, GameHelperUtils.getString( + activity, GameHelperUtils.R_LICENSE_FAILED)); + break; + default: + // No meaningful Activity response code, so generate default Google + // Play services dialog + errorDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, + activity, RC_UNUSED, null); + if (errorDialog == null) { + // get fallback dialog + Log.e("GameHelper", + "No standard error dialog available. Making fallback dialog."); + errorDialog = makeSimpleDialog( + activity, + GameHelperUtils.getString(activity, + GameHelperUtils.R_UNKNOWN_ERROR) + + " " + + GameHelperUtils.errorCodeToString(errorCode)); + } + } + + errorDialog.show(); + } + + static Dialog makeSimpleDialog(Activity activity, String text) { + return (new AlertDialog.Builder(activity)).setMessage(text) + .setNeutralButton(android.R.string.ok, null).create(); + } + + static Dialog + makeSimpleDialog(Activity activity, String title, String text) { + return (new AlertDialog.Builder(activity)).setMessage(text) + .setTitle(title).setNeutralButton(android.R.string.ok, null) + .create(); + } + + public Dialog makeSimpleDialog(String text) { + if (mActivity == null) { + logError("*** makeSimpleDialog failed: no current Activity!"); + return null; + } + return makeSimpleDialog(mActivity, text); + } + + public Dialog makeSimpleDialog(String title, String text) { + if (mActivity == null) { + logError("*** makeSimpleDialog failed: no current Activity!"); + return null; + } + return makeSimpleDialog(mActivity, title, text); + } + + void debugLog(String message) { + if (mDebugLog) { + Log.d(TAG, "GameHelper: " + message); + } + } + + void logWarn(String message) { + Log.w(TAG, "!!! GameHelper WARNING: " + message); + } + + void logError(String message) { + Log.e(TAG, "*** GameHelper ERROR: " + message); + } + + // Represents the reason for a sign-in failure + public static class SignInFailureReason { + public static final int NO_ACTIVITY_RESULT_CODE = -100; + int mServiceErrorCode = 0; + int mActivityResultCode = NO_ACTIVITY_RESULT_CODE; + + public int getServiceErrorCode() { + return mServiceErrorCode; + } + + public int getActivityResultCode() { + return mActivityResultCode; + } + + public SignInFailureReason(int serviceErrorCode, int activityResultCode) { + mServiceErrorCode = serviceErrorCode; + mActivityResultCode = activityResultCode; + } + + public SignInFailureReason(int serviceErrorCode) { + this(serviceErrorCode, NO_ACTIVITY_RESULT_CODE); + } + + @Override + public String toString() { + return "SignInFailureReason(serviceErrorCode:" + + GameHelperUtils.errorCodeToString(mServiceErrorCode) + + ((mActivityResultCode == NO_ACTIVITY_RESULT_CODE) ? ")" + : (",activityResultCode:" + + GameHelperUtils + .activityResponseCodeToString(mActivityResultCode) + ")")); + } + } + + // Not recommended for general use. This method forces the + // "connect on start" flag + // to a given state. This may be useful when using GameHelper in a + // non-standard + // sign-in flow. + public void setConnectOnStart(boolean connectOnStart) { + debugLog("Forcing mConnectOnStart=" + connectOnStart); + mConnectOnStart = connectOnStart; + } +} diff --git a/project/java/googleplaygameservices/GameHelperUtils.java b/project/java/googleplaygameservices/GameHelperUtils.java new file mode 100644 index 000000000..703569de3 --- /dev/null +++ b/project/java/googleplaygameservices/GameHelperUtils.java @@ -0,0 +1,182 @@ +package com.google.example.games.basegameutils; + +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.content.res.Resources; +import android.util.Log; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.games.GamesActivityResultCodes; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Created by btco on 2/10/14. + */ +class GameHelperUtils { + public static final int R_UNKNOWN_ERROR = 0; + public static final int R_SIGN_IN_FAILED = 1; + public static final int R_APP_MISCONFIGURED = 2; + public static final int R_LICENSE_FAILED = 3; + + private final static String[] FALLBACK_STRINGS = { + "*Unknown error.", + "*Failed to sign in. Please check your network connection and try again.", + "*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.", + "*License check failed." + }; + + private final static int[] RES_IDS = { + R.string.gamehelper_unknown_error, R.string.gamehelper_sign_in_failed, + R.string.gamehelper_app_misconfigured, R.string.gamehelper_license_failed + }; + + static String activityResponseCodeToString(int respCode) { + switch (respCode) { + case Activity.RESULT_OK: + return "RESULT_OK"; + case Activity.RESULT_CANCELED: + return "RESULT_CANCELED"; + case GamesActivityResultCodes.RESULT_APP_MISCONFIGURED: + return "RESULT_APP_MISCONFIGURED"; + case GamesActivityResultCodes.RESULT_LEFT_ROOM: + return "RESULT_LEFT_ROOM"; + case GamesActivityResultCodes.RESULT_LICENSE_FAILED: + return "RESULT_LICENSE_FAILED"; + case GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED: + return "RESULT_RECONNECT_REQUIRED"; + case GamesActivityResultCodes.RESULT_SIGN_IN_FAILED: + return "SIGN_IN_FAILED"; + default: + return String.valueOf(respCode); + } + } + + static String errorCodeToString(int errorCode) { + switch (errorCode) { + case ConnectionResult.DEVELOPER_ERROR: + return "DEVELOPER_ERROR(" + errorCode + ")"; + case ConnectionResult.INTERNAL_ERROR: + return "INTERNAL_ERROR(" + errorCode + ")"; + case ConnectionResult.INVALID_ACCOUNT: + return "INVALID_ACCOUNT(" + errorCode + ")"; + case ConnectionResult.LICENSE_CHECK_FAILED: + return "LICENSE_CHECK_FAILED(" + errorCode + ")"; + case ConnectionResult.NETWORK_ERROR: + return "NETWORK_ERROR(" + errorCode + ")"; + case ConnectionResult.RESOLUTION_REQUIRED: + return "RESOLUTION_REQUIRED(" + errorCode + ")"; + case ConnectionResult.SERVICE_DISABLED: + return "SERVICE_DISABLED(" + errorCode + ")"; + case ConnectionResult.SERVICE_INVALID: + return "SERVICE_INVALID(" + errorCode + ")"; + case ConnectionResult.SERVICE_MISSING: + return "SERVICE_MISSING(" + errorCode + ")"; + case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED: + return "SERVICE_VERSION_UPDATE_REQUIRED(" + errorCode + ")"; + case ConnectionResult.SIGN_IN_REQUIRED: + return "SIGN_IN_REQUIRED(" + errorCode + ")"; + case ConnectionResult.SUCCESS: + return "SUCCESS(" + errorCode + ")"; + default: + return "Unknown error code " + errorCode; + } + } + + static void printMisconfiguredDebugInfo(Context ctx) { + Log.w("GameHelper", "****"); + Log.w("GameHelper", "****"); + Log.w("GameHelper", "**** APP NOT CORRECTLY CONFIGURED TO USE GOOGLE PLAY GAME SERVICES"); + Log.w("GameHelper", "**** This is usually caused by one of these reasons:"); + Log.w("GameHelper", "**** (1) Your package name and certificate fingerprint do not match"); + Log.w("GameHelper", "**** the client ID you registered in Developer Console."); + Log.w("GameHelper", "**** (2) Your App ID was incorrectly entered."); + Log.w("GameHelper", "**** (3) Your game settings have not been published and you are "); + Log.w("GameHelper", "**** trying to log in with an account that is not listed as"); + Log.w("GameHelper", "**** a test account."); + Log.w("GameHelper", "****"); + if (ctx == null) { + Log.w("GameHelper", "*** (no Context, so can't print more debug info)"); + return; + } + + Log.w("GameHelper", "**** To help you debug, here is the information about this app"); + Log.w("GameHelper", "**** Package name : " + ctx.getPackageName()); + Log.w("GameHelper", "**** Cert SHA1 fingerprint: " + getSHA1CertFingerprint(ctx)); + Log.w("GameHelper", "**** App ID from : " + getAppIdFromResource(ctx)); + Log.w("GameHelper", "****"); + Log.w("GameHelper", "**** Check that the above information matches your setup in "); + Log.w("GameHelper", "**** Developer Console. Also, check that you're logging in with the"); + Log.w("GameHelper", "**** right account (it should be listed in the Testers section if"); + Log.w("GameHelper", "**** your project is not yet published)."); + Log.w("GameHelper", "****"); + Log.w("GameHelper", "**** For more information, refer to the troubleshooting guide:"); + Log.w("GameHelper", "**** http://developers.google.com/games/services/android/troubleshooting"); + } + + static String getAppIdFromResource(Context ctx) { + try { + Resources res = ctx.getResources(); + String pkgName = ctx.getPackageName(); + int res_id = res.getIdentifier("app_id", "string", pkgName); + return res.getString(res_id); + } catch (Exception ex) { + ex.printStackTrace(); + return "??? (failed to retrieve APP ID)"; + } + } + + static String getSHA1CertFingerprint(Context ctx) { + try { + Signature[] sigs = ctx.getPackageManager().getPackageInfo( + ctx.getPackageName(), PackageManager.GET_SIGNATURES).signatures; + if (sigs.length == 0) { + return "ERROR: NO SIGNATURE."; + } else if (sigs.length > 1) { + return "ERROR: MULTIPLE SIGNATURES"; + } + byte[] digest = MessageDigest.getInstance("SHA1").digest(sigs[0].toByteArray()); + StringBuilder hexString = new StringBuilder(); + for (int i = 0; i < digest.length; ++i) { + if (i > 0) { + hexString.append(":"); + } + byteToString(hexString, digest[i]); + } + return hexString.toString(); + + } catch (PackageManager.NameNotFoundException ex) { + ex.printStackTrace(); + return "(ERROR: package not found)"; + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + return "(ERROR: SHA1 algorithm not found)"; + } + } + + static void byteToString(StringBuilder sb, byte b) { + int unsigned_byte = b < 0 ? b + 256 : b; + int hi = unsigned_byte / 16; + int lo = unsigned_byte % 16; + sb.append("0123456789ABCDEF".substring(hi, hi + 1)); + sb.append("0123456789ABCDEF".substring(lo, lo + 1)); + } + + static String getString(Context ctx, int whichString) { + whichString = whichString >= 0 && whichString < RES_IDS.length ? whichString : 0; + int resId = RES_IDS[whichString]; + try { + return ctx.getString(resId); + } catch (Exception ex) { + ex.printStackTrace(); + Log.w(GameHelper.TAG, "*** GameHelper could not found resource id #" + resId + ". " + + "This probably happened because you included it as a stand-alone JAR. " + + "BaseGameUtils should be compiled as a LIBRARY PROJECT, so that it can access " + + "its resources. Using a fallback string."); + return FALLBACK_STRINGS[whichString]; + } + } +} diff --git a/project/java/translations/values-fr/strings.xml b/project/java/translations/values-fr/strings.xml index a1fb1ddd1..de80ea809 100644 --- a/project/java/translations/values-fr/strings.xml +++ b/project/java/translations/values-fr/strings.xml @@ -176,4 +176,16 @@ Filter jitter for stylus/finger hover Select action Show all keycodes +Control mouse with gyroscope +Gyroscope sensitivity +Finger hover +Multiple touch events per video frame +Floating joystick +Portrait/vertical screen orientation +24 bpp screen color depth +Hide system navigation buttons / immersive mode +Failed to sign in. Please check your network connection and try again. +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. +License check failed. +Unknown error. diff --git a/project/java/translations/values-ru/strings.xml b/project/java/translations/values-ru/strings.xml index b35cae47c..b699eafe9 100644 --- a/project/java/translations/values-ru/strings.xml +++ b/project/java/translations/values-ru/strings.xml @@ -149,4 +149,16 @@ Фильтровать дрожание при поднесении стилуса/пальца к экрану Выберите действие Показать все коды кнопок +Control mouse with gyroscope +Gyroscope sensitivity +Finger hover +Multiple touch events per video frame +Floating joystick +Portrait/vertical screen orientation +24 bpp screen color depth +Hide system navigation buttons / immersive mode +Failed to sign in. Please check your network connection and try again. +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. +License check failed. +Unknown error. diff --git a/project/java/translations/values-uk/strings.xml b/project/java/translations/values-uk/strings.xml index 6d953fd76..b9f5456fb 100644 --- a/project/java/translations/values-uk/strings.xml +++ b/project/java/translations/values-uk/strings.xml @@ -148,4 +148,16 @@ Фільтрувати тремтіння при піднесенні стилуса/пальця до екрану Виберіть дію Показати всі коди кнопок +Control mouse with gyroscope +Gyroscope sensitivity +Finger hover +Multiple touch events per video frame +Floating joystick +Portrait/vertical screen orientation +24 bpp screen color depth +Hide system navigation buttons / immersive mode +Failed to sign in. Please check your network connection and try again. +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. +License check failed. +Unknown error. diff --git a/project/java/translations/values/strings.xml b/project/java/translations/values/strings.xml index cccde4f67..9e1aae3d9 100644 --- a/project/java/translations/values/strings.xml +++ b/project/java/translations/values/strings.xml @@ -187,4 +187,10 @@ Yes No + + Failed to sign in. Please check your network connection and try again. + 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. + License check failed. + Unknown error. + diff --git a/project/jni/application/biniax2/AndroidAppSettings.cfg b/project/jni/application/biniax2/AndroidAppSettings.cfg index 16c749c49..32397a694 100644 --- a/project/jni/application/biniax2/AndroidAppSettings.cfg +++ b/project/jni/application/biniax2/AndroidAppSettings.cfg @@ -1,21 +1,16 @@ # The application settings for Android libSDL port -AppSettingVersion=19 - -# libSDL version to use (1.2 or 1.3, specify 1.3 for SDL2) -LibSdlVersion=1.2 - # Specify application name (e.x. My Application) AppName="Biniax2" # Specify reversed site name of application (e.x. com.mysite.myapp) AppFullName=com.biniax.sdl -# Specify screen orientation: (v)ertical/(p)ortrait or (h)orizontal/(l)andscape -ScreenOrientation=h +# Application version code (integer) +AppVersionCode=1405 -# Do not allow device to sleep when the application is in foreground, set this for video players or apps which use accelerometer -InhibitSuspend=y +# Application user-visible version name (string) +AppVersionName="1.4.05" # Specify path to download application data in zip archive in the form 'Description|URL|MirrorURL^Description2|URL2|MirrorURL2^...' # If you'll start Description with '!' symbol it will be enabled by default, other downloads should be selected by user from startup config menu @@ -25,6 +20,22 @@ InhibitSuspend=y # Also please avoid 'https://' URLs, many Android devices do not have trust certificates and will fail to connect to SF.net over HTTPS AppDataDownloadUrl="!Game data|data3.zip" +# Reset SDL config when updating application to the new version (y) / (n) +ResetSdlConfigForThisVersion=n + +# Delete application data files when upgrading (specify file/dir paths separated by spaces) +DeleteFilesOnUpgrade="%" + +# Here you may type readme text, which will be shown during startup. Format is: +# Text in English, use \\\\n to separate lines (that's four backslashes)^de:Text in Deutsch^ru:Text in Russian^button:Button that will open some URL:http://url-to-open/ +ReadmeText='^.' + +# libSDL version to use (1.2/1.3/2.0) +LibSdlVersion=1.2 + +# Specify screen orientation: (v)ertical/(p)ortrait or (h)orizontal/(l)andscape +ScreenOrientation=h + # Video color depth - 16 BPP is the fastest and supported for all modes, 24 bpp is supported only # with SwVideoMode=y, SDL_OPENGL mode supports everything. (16)/(24)/(32) VideoDepthBpp=16 @@ -49,9 +60,19 @@ SdlVideoResize=y # Application resizing will keep 4:3 aspect ratio, with black bars at sides (y)/(n) SdlVideoResizeKeepAspect=n +# Do not allow device to sleep when the application is in foreground, set this for video players or apps which use accelerometer +InhibitSuspend=y + +# Create Android service, so the app is less likely to be killed while in background +CreateService= + # Application does not call SDL_Flip() or SDL_UpdateRects() appropriately, or draws from non-main thread - # enabling the compatibility mode will force screen update every 100 milliseconds, which is laggy and inefficient (y) or (n) -CompatibilityHacks=n +CompatibilityHacksForceScreenUpdate=n + +# Application does not call SDL_Flip() or SDL_UpdateRects() after mouse click (ScummVM and all Amiga emulators do that) - +# force screen update by moving mouse cursor a little after each click (y) or (n) +CompatibilityHacksForceScreenUpdateMouseClick=y # Application initializes SDL audio/video inside static constructors (which is bad, you won't be able to run ndk-gdb) (y)/(n) CompatibilityHacksStaticInit=n @@ -85,6 +106,10 @@ AppUsesMouse=y # Application needs two-button mouse, will also enable advanced point-and-click features (y) or (n) AppNeedsTwoButtonMouse=n +# Right mouse button can do long-press/drag&drop action, necessary for some games (y) or (n) +# If you disable it, swiping with two fingers will send mouse wheel events +RightMouseButtonLongPress= + # Show SDL mouse cursor, for applications that do not draw cursor at all (y) or (n) ShowMouseCursor=n @@ -94,24 +119,35 @@ GenerateSubframeTouchEvents= # Force relative (laptop) mouse movement mode, useful when both on-screen keyboard and mouse are needed (y) or (n) ForceRelativeMouseMode=n -# Application needs arrow keys (y) or (n), will show on-screen dpad/joystick (y) or (n) +# Show on-screen dpad/joystick, that will act as arrow keys (y) or (n) AppNeedsArrowKeys=n +# On-screen dpad/joystick will appear under finger when it touches the screen (y) or (n) +# Joystick always follows finger, so moving mouse requires touching the screen with other finger +FloatingScreenJoystick= + # Application needs text input (y) or (n), enables button for text input on screen AppNeedsTextInput=n # Application uses joystick (y) or (n), the on-screen DPAD will be used as joystick 0 axes 0-1 +# This will disable AppNeedsArrowKeys option AppUsesJoystick=n # Application uses second on-screen joystick, as SDL joystick 0 axes 2-3 (y)/(n) AppUsesSecondJoystick=n +# Application uses third on-screen joystick, as SDL joystick 0 axes 20-21 (y)/(n) +AppUsesThirdJoystick= + # Application uses accelerometer (y) or (n), the accelerometer will be used as joystick 1 axes 0-1 and 5-7 AppUsesAccelerometer=n # Application uses gyroscope (y) or (n), the gyroscope will be used as joystick 1 axes 2-4 AppUsesGyroscope=n +# Use gyroscope to move mouse cursor (y) or (n), it eats battery, and can be disabled in settings, do not use with AppUsesGyroscope setting +MoveMouseWithGyroscope= + # Application uses multitouch (y) or (n), multitouch events are passed as SDL_JOYBALLMOTION events for the joystick 0 AppUsesMultitouch=n @@ -120,6 +156,15 @@ AppUsesMultitouch=n # This option will add additional permission to Android manifest (y)/(n) AppRecordsAudio=n +# Application needs to access SD card. If your data files are bigger than 5 Mb, enable it. (y) / (n) +AccessSdCard= + +# Application needs Internet access. If you disable it, you'll have to bundle all your data files inside .apk (y) / (n) +AccessInternet= + +# Immersive mode - Android will hide on-screen Home/Back keys. Looks bad if you invoke Android keyboard. (y) / (n) +ImmersiveMode= + # Application implements Android-specific routines to put to background, and will not draw anything to screen # between SDL_ACTIVEEVENT lost / gained notifications - you should check for them # rigth after SDL_Flip(), if (n) then SDL_Flip() will block till app in background (y) or (n) @@ -136,9 +181,6 @@ RedefinedKeys="RETURN" # Number of virtual keyboard keys (currently 6 is maximum) AppTouchscreenKeyboardKeysAmount=0 -# Number of virtual keyboard keys that support autofire (currently 2 is maximum) -AppTouchscreenKeyboardKeysAmountAutoFire=0 - # Redefine on-screen keyboard keys to SDL keysyms - 6 keyboard keys + 4 multitouch gestures (zoom in/out and rotate left/right) RedefinedKeysScreenKb="RETURN SPACE" @@ -160,34 +202,22 @@ RedefinedKeysGamepad="RETURN ESCAPE SPACE SPACE" StartupMenuButtonTimeout=0 # Menu items to hide from startup menu, available menu items: -# SettingsMenu.OkButton SettingsMenu.DummyMenu SettingsMenu.MainMenu SettingsMenuMisc.DownloadConfig SettingsMenuMisc.OptionalDownloadConfig SettingsMenuMisc.AudioConfig SettingsMenuMisc.VideoSettingsConfig SettingsMenuMisc.ShowReadme SettingsMenuMisc.GyroscopeCalibration SettingsMenuMisc.ResetToDefaultsConfig SettingsMenuMouse.MouseConfigMainMenu SettingsMenuMouse.DisplaySizeConfig SettingsMenuMouse.LeftClickConfig SettingsMenuMouse.RightClickConfig SettingsMenuMouse.AdditionalMouseConfig SettingsMenuMouse.JoystickMouseConfig SettingsMenuMouse.TouchPressureMeasurementTool SettingsMenuMouse.CalibrateTouchscreenMenu SettingsMenuKeyboard.KeyboardConfigMainMenu SettingsMenuKeyboard.ScreenKeyboardSizeConfig SettingsMenuKeyboard.ScreenKeyboardDrawSizeConfig SettingsMenuKeyboard.ScreenKeyboardThemeConfig SettingsMenuKeyboard.ScreenKeyboardTransparencyConfig SettingsMenuKeyboard.RemapHwKeysConfig SettingsMenuKeyboard.RemapScreenKbConfig SettingsMenuKeyboard.ScreenGesturesConfig SettingsMenuKeyboard.CustomizeScreenKbLayout +# SettingsMenu.OkButton SettingsMenu.DummyMenu SettingsMenu.MainMenu SettingsMenuMisc.DownloadConfig SettingsMenuMisc.OptionalDownloadConfig SettingsMenuMisc.AudioConfig SettingsMenuMisc.VideoSettingsConfig SettingsMenuMisc.ShowReadme SettingsMenuMisc.GyroscopeCalibration SettingsMenuMisc.ResetToDefaultsConfig SettingsMenuMouse.MouseConfigMainMenu SettingsMenuMouse.DisplaySizeConfig SettingsMenuMouse.LeftClickConfig SettingsMenuMouse.RightClickConfig SettingsMenuMouse.AdditionalMouseConfig SettingsMenuMouse.JoystickMouseConfig SettingsMenuMouse.TouchPressureMeasurementTool SettingsMenuMouse.CalibrateTouchscreenMenu SettingsMenuKeyboard.KeyboardConfigMainMenu SettingsMenuKeyboard.ScreenKeyboardSizeConfig SettingsMenuKeyboard.ScreenKeyboardDrawSizeConfig SettingsMenuKeyboard.ScreenKeyboardThemeConfig SettingsMenuKeyboard.ScreenKeyboardTransparencyConfig SettingsMenuKeyboard.RemapHwKeysConfig SettingsMenuKeyboard.RemapScreenKbConfig SettingsMenuKeyboard.ScreenGesturesConfig SettingsMenuKeyboard.CustomizeScreenKbLayout SettingsMenuKeyboard.ScreenKeyboardAdvanced HiddenMenuOptions='SettingsMenuMouse.DisplaySizeConfig SettingsMenuMouse.MouseConfigMainMenu' # Menu items to show at startup - this is Java code snippet, leave empty for default # new SettingsMenuMisc.ShowReadme(), (AppUsesMouse \&\& \! ForceRelativeMouseMode \? new SettingsMenuMouse.DisplaySizeConfig(true) : new SettingsMenu.DummyMenu()), new SettingsMenuMisc.OptionalDownloadConfig(true), new SettingsMenuMisc.GyroscopeCalibration() # Available menu items: -# SettingsMenu.OkButton SettingsMenu.DummyMenu SettingsMenu.MainMenu SettingsMenuMisc.DownloadConfig SettingsMenuMisc.OptionalDownloadConfig SettingsMenuMisc.AudioConfig SettingsMenuMisc.VideoSettingsConfig SettingsMenuMisc.ShowReadme SettingsMenuMisc.GyroscopeCalibration SettingsMenuMisc.ResetToDefaultsConfig SettingsMenuMouse.MouseConfigMainMenu SettingsMenuMouse.DisplaySizeConfig SettingsMenuMouse.LeftClickConfig SettingsMenuMouse.RightClickConfig SettingsMenuMouse.AdditionalMouseConfig SettingsMenuMouse.JoystickMouseConfig SettingsMenuMouse.TouchPressureMeasurementTool SettingsMenuMouse.CalibrateTouchscreenMenu SettingsMenuKeyboard.KeyboardConfigMainMenu SettingsMenuKeyboard.ScreenKeyboardSizeConfig SettingsMenuKeyboard.ScreenKeyboardDrawSizeConfig SettingsMenuKeyboard.ScreenKeyboardThemeConfig SettingsMenuKeyboard.ScreenKeyboardTransparencyConfig SettingsMenuKeyboard.RemapHwKeysConfig SettingsMenuKeyboard.RemapScreenKbConfig SettingsMenuKeyboard.ScreenGesturesConfig SettingsMenuKeyboard.CustomizeScreenKbLayout +# SettingsMenu.OkButton SettingsMenu.DummyMenu SettingsMenu.MainMenu SettingsMenuMisc.DownloadConfig SettingsMenuMisc.OptionalDownloadConfig SettingsMenuMisc.AudioConfig SettingsMenuMisc.VideoSettingsConfig SettingsMenuMisc.ShowReadme SettingsMenuMisc.GyroscopeCalibration SettingsMenuMisc.ResetToDefaultsConfig SettingsMenuMouse.MouseConfigMainMenu SettingsMenuMouse.DisplaySizeConfig SettingsMenuMouse.LeftClickConfig SettingsMenuMouse.RightClickConfig SettingsMenuMouse.AdditionalMouseConfig SettingsMenuMouse.JoystickMouseConfig SettingsMenuMouse.TouchPressureMeasurementTool SettingsMenuMouse.CalibrateTouchscreenMenu SettingsMenuKeyboard.KeyboardConfigMainMenu SettingsMenuKeyboard.ScreenKeyboardSizeConfig SettingsMenuKeyboard.ScreenKeyboardDrawSizeConfig SettingsMenuKeyboard.ScreenKeyboardThemeConfig SettingsMenuKeyboard.ScreenKeyboardTransparencyConfig SettingsMenuKeyboard.RemapHwKeysConfig SettingsMenuKeyboard.RemapScreenKbConfig SettingsMenuKeyboard.ScreenGesturesConfig SettingsMenuKeyboard.CustomizeScreenKbLayout SettingsMenuKeyboard.ScreenKeyboardAdvanced FirstStartMenuOptions='' -# Enable multi-ABI binary, with hardware FPU support - it will also work on old devices, -# but .apk size is 2x bigger (y) / (n) / (x86) / (all) -MultiABI=all +# Specify architectures to compile, 'all' or 'y' to compile for all architectures. +# Available architectures: armeabi armeabi-v7a armeabi-v7a-hard x86 mips +MultiABI='armeabi-v7a' # Minimum amount of RAM application requires, in Mb, SDL will print warning to user if it's lower AppMinimumRAM=0 -# Application version code (integer) -AppVersionCode=1405 - -# Application user-visible version name (string) -AppVersionName="1.4.05" - -# Reset SDL config when updating application to the new version (y) / (n) -ResetSdlConfigForThisVersion=n - -# Delete application data files when upgrading (specify file/dir paths separated by spaces) -DeleteFilesOnUpgrade="%" - # Optional shared libraries to compile - removing some of them will save space # MP3 support by libMAD is encumbered by patents and libMAD is GPL-ed # Available libraries: mad (GPL-ed!) sdl_mixer sdl_image sdl_ttf sdl_net sdl_blitpool sdl_gfx sdl_sound intl xml2 lua jpeg png ogg flac tremor vorbis freetype xerces curl theora fluidsynth lzma lzo2 mikmod openal timidity zzip bzip2 yaml-cpp python boost_date_time boost_filesystem boost_iostreams boost_program_options boost_regex boost_signals boost_system boost_thread glu avcodec avdevice avfilter avformat avresample avutil swscale swresample bzip2 @@ -214,10 +244,6 @@ AppBuildExclude='' # Application command line parameters, including app name as 0-th param AppCmdline='' -# Here you may type readme text, which will be shown during startup. Format is: -# Text in English, use \\\\n to separate lines^de:Text in Deutsch^ru:Text in Russian, and so on (that's four backslashes, nice isn't it?) -ReadmeText='^.' - # Screen size is used by Google Play to prevent an app to be installed on devices with smaller screens # Minimum screen size that application supports: (s)mall / (m)edium / (l)arge MinimumScreenSize=n @@ -228,6 +254,8 @@ AdmobPublisherId=n # Your AdMob test device ID, to receive a test ad AdmobTestDeviceId= -# Your AdMob banner size (BANNER/IAB_BANNER/IAB_LEADERBOARD/IAB_MRECT/IAB_WIDE_SKYSCRAPER/SMART_BANNER) +# Your AdMob banner size (BANNER/FULL_BANNER/LEADERBOARD/MEDIUM_RECTANGLE/SMART_BANNER/WIDE_SKYSCRAPER/FULL_WIDTH:Height/Width:AUTO_HEIGHT/Width:Height) AdmobBannerSize= +# Google Play Game Services application ID, required for cloud saves to work +GooglePlayGameServicesId=520035027247 diff --git a/project/proguard.cfg b/project/proguard.cfg index 04d92e57c..7bf261def 100644 --- a/project/proguard.cfg +++ b/project/proguard.cfg @@ -1,4 +1,4 @@ --optimizationpasses 5 +-optimizationpasses 1 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify @@ -34,22 +34,3 @@ -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } - --keepclasseswithmembers class * { - native ; -} - --keep class com..AudioThread { - int initAudio(int, int, int, int); - int deinitAudio(); - int pauseAudioPlayback(); - int resumeAudioPlayback(); - int fillBuffer(); - int initAudioThread(); - *** getBuffer(); -} - --keep class com..DemoRenderer { - int swapBuffers(); - void showScreenKeyboard(java.lang.String, int); -} diff --git a/project/project.properties b/project/project.properties index 4ab125693..3409f0811 100644 --- a/project/project.properties +++ b/project/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-19 +target=android-20