DUPLICATE_REQUEST_ID - PayPal SDK error - java

Currently making an Android app and decided to integrate Paypal payment in it, so that the user is redirected to Paypal payment inside the app.
I found the Paypal SampleApp for what I want to do:
https://github.com/paypal/PayPal-Android-SDK.
Firstly, I tested it and it worked without errors with CONFIG_ENVIRONMENT set to PayPalConfiguration.ENVIRONMENT_NO_NETWORK, and with the default sandbox sample acc (usr: sample#buy.com, pass:123123123).
Then I made an account on developer.paypal.com in order to make the payment to my Paypal account (with CONFIG_ENVIRONMENT set to PayPalConfiguration.ENVIRONMENT_PRODUCTION). I created an app and used the provided Client ID from the developer portal in the SampleApp. By doing that, the payment isn't completed, and gives the error:
request failure with http statusCode:400,exception:Bad Request
request failed with server response:{"name":"DUPLICATE_REQUEST_ID","debug_id":"9f83f9a8ce3e5","message":"The value of PayPal-Request-Id header has already been used","information_link":"https://developer.paypal.com/webapps/developer/docs/api/","details":[]}
DUPLICATE_REQUEST_ID
Note: I'm only using the "BUY A THING" option in the Sample App; not interested in the other options
The code of the class that handles the payment process:
import com.paypal.android.sdk.payments.PayPalAuthorization;
import com.paypal.android.sdk.payments.PayPalConfiguration;
import com.paypal.android.sdk.payments.PayPalFuturePaymentActivity;
import com.paypal.android.sdk.payments.PayPalItem;
import com.paypal.android.sdk.payments.PayPalOAuthScopes;
import com.paypal.android.sdk.payments.PayPalPayment;
import com.paypal.android.sdk.payments.PayPalPaymentDetails;
import com.paypal.android.sdk.payments.PayPalProfileSharingActivity;
import com.paypal.android.sdk.payments.PayPalService;
import com.paypal.android.sdk.payments.PaymentConfirmation;
import com.paypal.android.sdk.payments.ShippingAddress;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import org.json.JSONException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class PaymentActivity extends Activity{
private static final String TAG = "PAYMENT";
private static final String CONFIG_ENVIRONMENT = PayPalConfiguration.ENVIRONMENT_PRODUCTION;
private static final String CONFIG_CLIENT_ID = "i changed this field with my client id";
private static final int REQUEST_CODE_PAYMENT = 1;
private static final int REQUEST_CODE_FUTURE_PAYMENT = 2;
private static final int REQUEST_CODE_PROFILE_SHARING = 3;
private static PayPalConfiguration config = new PayPalConfiguration()
.environment(CONFIG_ENVIRONMENT)
.clientId(CONFIG_CLIENT_ID)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
Intent intent = new Intent(this, PayPalService.class);
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config);
startService(intent);
}
public void onBuyPressed(View pressed) {
PayPalPayment thingToBuy = getThingToBuy(PayPalPayment.PAYMENT_INTENT_SALE);
Intent intent = new Intent(PaymentActivity.this, com.paypal.android.sdk.payments.PaymentActivity.class);
// send the same configuration for restart resiliency
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config);
intent.putExtra(com.paypal.android.sdk.payments.PaymentActivity.EXTRA_PAYMENT, thingToBuy);
startActivityForResult(intent, REQUEST_CODE_PAYMENT);
}
private PayPalPayment getThingToBuy(String paymentIntent) {
return new PayPalPayment(new BigDecimal("1.0"), "USD", "DRINK",
paymentIntent);
}
/*
* This method shows use of optional payment details and item list.
*/
public void onFuturePaymentPressed(View pressed) {
Intent intent = new Intent(PaymentActivity.this, PayPalFuturePaymentActivity.class);
// send the same configuration for restart resiliency
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config);
startActivityForResult(intent, REQUEST_CODE_FUTURE_PAYMENT);
}
public void onProfileSharingPressed(View pressed) {
Intent intent = new Intent(PaymentActivity.this, PayPalProfileSharingActivity.class);
// send the same configuration for restart resiliency
intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config);
intent.putExtra(PayPalProfileSharingActivity.EXTRA_REQUESTED_SCOPES, getOauthScopes());
startActivityForResult(intent, REQUEST_CODE_PROFILE_SHARING);
}
private PayPalOAuthScopes getOauthScopes() {
Set<String> scopes = new HashSet<String>(
Arrays.asList(PayPalOAuthScopes.PAYPAL_SCOPE_EMAIL, PayPalOAuthScopes.PAYPAL_SCOPE_ADDRESS) );
return new PayPalOAuthScopes(scopes);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_PAYMENT) {
if (resultCode == Activity.RESULT_OK) {
PaymentConfirmation confirm =
data.getParcelableExtra(com.paypal.android.sdk.payments.PaymentActivity.EXTRA_RESULT_CONFIRMATION);
if (confirm != null) {
try {
Log.i(TAG, confirm.toJSONObject().toString(4));
Log.i(TAG, confirm.getPayment().toJSONObject().toString(4));
/**
* TODO: send 'confirm' (and possibly confirm.getPayment() to your server for verification
* or consent completion.
* See https://developer.paypal.com/webapps/developer/docs/integration/mobile/verify-mobile-payment/
* for more details.
*
* For sample mobile backend interactions, see
* https://github.com/paypal/rest-api-sdk-python/tree/master/samples/mobile_backend
*/
Toast.makeText(
getApplicationContext(),
"PaymentConfirmation info received from PayPal", Toast.LENGTH_LONG)
.show();
} catch (JSONException e) {
Log.e(TAG, "an extremely unlikely failure occurred: ", e);
}
}
} else if (resultCode == Activity.RESULT_CANCELED) {
Log.i(TAG, "The user canceled.");
} else if (resultCode == com.paypal.android.sdk.payments.PaymentActivity.RESULT_EXTRAS_INVALID) {
Log.i(
TAG,
"An invalid Payment or PayPalConfiguration was submitted. Please see the docs.");
}
} else if (requestCode == REQUEST_CODE_FUTURE_PAYMENT) {
if (resultCode == Activity.RESULT_OK) {
PayPalAuthorization auth =
data.getParcelableExtra(PayPalFuturePaymentActivity.EXTRA_RESULT_AUTHORIZATION);
if (auth != null) {
try {
Log.i("FuturePaymentExample", auth.toJSONObject().toString(4));
String authorization_code = auth.getAuthorizationCode();
Log.i("FuturePaymentExample", authorization_code);
sendAuthorizationToServer(auth);
Toast.makeText(
getApplicationContext(),
"Future Payment code received from PayPal", Toast.LENGTH_LONG)
.show();
} catch (JSONException e) {
Log.e("FuturePaymentExample", "an extremely unlikely failure occurred: ", e);
}
}
} else if (resultCode == Activity.RESULT_CANCELED) {
Log.i("FuturePaymentExample", "The user canceled.");
} else if (resultCode == PayPalFuturePaymentActivity.RESULT_EXTRAS_INVALID) {
Log.i(
"FuturePaymentExample",
"Probably the attempt to previously start the PayPalService had an invalid PayPalConfiguration. Please see the docs.");
}
} else if (requestCode == REQUEST_CODE_PROFILE_SHARING) {
if (resultCode == Activity.RESULT_OK) {
PayPalAuthorization auth =
data.getParcelableExtra(PayPalProfileSharingActivity.EXTRA_RESULT_AUTHORIZATION);
if (auth != null) {
try {
Log.i("ProfileSharingExample", auth.toJSONObject().toString(4));
String authorization_code = auth.getAuthorizationCode();
Log.i("ProfileSharingExample", authorization_code);
sendAuthorizationToServer(auth);
Toast.makeText(
getApplicationContext(),
"Profile Sharing code received from PayPal", Toast.LENGTH_LONG)
.show();
} catch (JSONException e) {
Log.e("ProfileSharingExample", "an extremely unlikely failure occurred: ", e);
}
}
} else if (resultCode == Activity.RESULT_CANCELED) {
Log.i("ProfileSharingExample", "The user canceled.");
} else if (resultCode == PayPalFuturePaymentActivity.RESULT_EXTRAS_INVALID) {
Log.i(
"ProfileSharingExample",
"Probably the attempt to previously start the PayPalService had an invalid PayPalConfiguration. Please see the docs.");
}
}
}
private void sendAuthorizationToServer(PayPalAuthorization authorization) {
/**
* TODO: Send the authorization response to your server, where it can
* exchange the authorization code for OAuth access and refresh tokens.
*
* Your server must then store these tokens, so that your server code
* can execute payments for this user in the future.
*
* A more complete example that includes the required app-server to
* PayPal-server integration is available from
* https://github.com/paypal/rest-api-sdk-python/tree/master/samples/mobile_backend
*/
}
#Override
public void onDestroy() {
// Stop service when done
stopService(new Intent(this, PayPalService.class));
super.onDestroy();
}
}
Thank you!

Turns out that the error occurs because the PayPal app is also installed on my phone. If I uninstall it, the payment is successfully done in the PRODUCTION environment.
PayPal will soon release a version of their app that patches this bug:
https://github.com/paypal/PayPal-Android-SDK/issues/272.

Related

Problem of "PERMISSION_DENIED" on Firestore get due to security rule

My Android Java application first creates a Firebase account (email with password) with AuthUI.getInstance() if the user has to sign up. When the account is created, a dialog informs the user he received a verification email and he has to click on the verification link in the email. When this is done by the user, he can then close the dialog and go on with the configuration of his account in Firestore.
But all the requests on Firestore documents protected by security rule like
allow read: if request.auth.uid != null && request.auth.token.email != null && request.auth.token.email_verified == true;
fail with
com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions
If the user closes the application, restarts it and re-authenticates, then its works (no problem of permission for the Firestore requests).
I made several tests. If I change the security rule as
allow read: if request.auth.uid != null && request.auth.token.email != null;
Everything works, but it is less secure from my point of view since there is no warranty the email is verified. It seems Firestore is still not aware the account is verified.
Here is a sample of an Activity :
package foo;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.firebase.ui.auth.AuthUI;
import com.google.android.gms.tasks.Task;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.firebase.Timestamp;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.firestore.Source;
import com.google.firebase.functions.FirebaseFunctions;
import com.google.firebase.functions.HttpsCallableReference;
import com.google.firebase.functions.HttpsCallableResult;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import fr.cinfo.planartis.R;
public class ExampleActivity extends AppCompatActivity {
private static final int SIGN_IN_REQUEST_CODE = 123;
private static final String VERIFICATION_EMAIL_SENT_TIMESTAMP_KEY = "verificationEmailSentTimestamp";
private FirebaseAuth firebaseAuth;
private FirebaseFirestore firestore;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
firebaseAuth = FirebaseAuth.getInstance();
firestore = FirebaseFirestore.getInstance();
if (firebaseAuth.getCurrentUser() == null) {
startUserSignInActivity();
} else {
performEmailVerification();
}
}
void startUserSignInActivity() {
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setAvailableProviders(Collections.singletonList(new AuthUI.IdpConfig.EmailBuilder().build()))
.setIsSmartLockEnabled(false, true)
.setTosAndPrivacyPolicyUrls("https://localhost/terms.html", "https://localhost/privacy.html")
.build(),
SIGN_IN_REQUEST_CODE);
}
#Override
protected void onActivityResult(final int requestCode, final int resultCode, #Nullable final Intent data) {
if (requestCode == SIGN_IN_REQUEST_CODE) {
if (resultCode != RESULT_OK) { // ERRORS
// ... do something
finish();
return;
}
performEmailVerification();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
void performEmailVerification() {
if (firebaseAuth.getCurrentUser().isEmailVerified()) {
// Everything is OK
checkSomethingOnFirestore();
return;
}
final DocumentReference documentReference = firestore.document("users/" + firebaseAuth.getCurrentUser().getEmail());
documentReference.get(Source.DEFAULT).addOnCompleteListener((Task<DocumentSnapshot> task) -> {
if (task.isSuccessful()) {
final DocumentSnapshot userSnapshot = task.getResult();
if (userSnapshot.exists()) {
// Check if the first verification email was sent
final Object timestamp = userSnapshot.get(VERIFICATION_EMAIL_SENT_TIMESTAMP_KEY);
if (timestamp == null) {
firebaseAuth.getCurrentUser().sendEmailVerification().addOnCompleteListener((Task<Void> validationEmailTask) -> {
if (validationEmailTask.isSuccessful()) {
final Timestamp now = Timestamp.now();
documentReference.update(VERIFICATION_EMAIL_SENT_TIMESTAMP_KEY, now).addOnCompleteListener((Task<Void> updateUserAccountTask) -> {
if (!updateUserAccountTask.isSuccessful()) {
}
displayWarningAboutValidation();
});
}
else {
// email not sent, so display message
FirebaseAuth.getInstance().signOut();
displayInformationDialog(this, "Unable to send a verification email", null).show();
finish();
}
});
}
else {
displayWarningAboutValidation();
}
}
else {
Toast.makeText(this, "We are finalizing your account creation\nPlease wait a few seconds", Toast.LENGTH_LONG).show();
final HttpsCallableReference httpsCallableReference = FirebaseFunctions.getInstance().getHttpsCallable("finalizeUserAccount");
httpsCallableReference.setTimeout(15, TimeUnit.SECONDS);
httpsCallableReference.call(firebaseAuth.getCurrentUser().getEmail()).continueWith((Task<HttpsCallableResult> task12) -> {
if (!task12.isSuccessful()) {
displayInformationDialog(this, "The finalization of your account failed.", (DialogInterface dialog, int id) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
return null;
}
displayInformationDialog(this, "A new verification email was sent", (final DialogInterface dialog, final int id) -> {
// Reload current user
firebaseAuth.getCurrentUser().reload().addOnCompleteListener((Task<Void> task1) -> {
if (task1.isSuccessful()) {
performEmailVerification();
}
else {
FirebaseAuth.getInstance().signOut();
finish();
}
});
});
return null;
});
}
}
else {
displayInformationDialog(this, "Problem with the server", (DialogInterface dialog, int id) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
}
});
}
private void checkSomethingOnFirestore() {
firestore.collection("users").document(firebaseAuth.getCurrentUser().getEmail()).collection("documents").get().addOnCompleteListener(this, (Task<QuerySnapshot> task) -> {
if (!task.isSuccessful()) { // <======================================== PERMISSION_DENIED exception !!!!!!
displayInformationDialog(this, "Problem on Firestore", (final DialogInterface dialog, final int id) -> {
FirebaseAuth.getInstance().signOut();
finish();
})
.show();
return;
}
// Go on and do the job: for instance display the GUI or anything else
});
}
private void displayWarningAboutValidation() {
new AlertDialog.Builder(this)
.setCancelable(false)
.setMessage("Read the verification email we sent and click on the link inside the email")
.setPositiveButton("I understand", (DialogInterface dialog, int id) -> firebaseAuth.getCurrentUser().reload().addOnCompleteListener((Task<Void> task) -> {
if (task.isSuccessful()) {
performEmailVerification();
}
else {
FirebaseAuth.getInstance().signOut();
finish();
}
}))
.setNeutralButton("Send back a verification email", (final DialogInterface dialog, final int which) -> firebaseAuth.getCurrentUser().sendEmailVerification().addOnCompleteListener((final Task<Void> task) -> {
dialog.dismiss();
if (task.isSuccessful()) {
// email sent
displayInformationDialog(this, "A new verification email was sent", (final DialogInterface dialog12, final int which12) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
firebaseAuth.getCurrentUser().reload().addOnCompleteListener((Task<Void> task1) -> {
if (task1.isSuccessful()) {
performEmailVerification();
}
else {
FirebaseAuth.getInstance().signOut();
finish();
}
});
}
else {
// email not sent, so display message
displayInformationDialog(this, "Unable to send a new verification email", (final DialogInterface dialog1, final int which1) -> {
FirebaseAuth.getInstance().signOut();
finish();
}).show();
}
}))
.show();
}
private AlertDialog displayInformationDialog(final Context context, final CharSequence message, final DialogInterface.OnClickListener positiveButtonOnclickListener) {
return new MaterialAlertDialogBuilder(context).setCancelable(false).setMessage(message).setPositiveButton("I understand", positiveButtonOnclickListener).setTitle("Planartis").setIcon(R.drawable.ic_logo_toolbar).show();
}
}
Is the behaviour of Firestore correct ? What can I change in order to avoid restart of the application and re-authentication (user-friendliness) ?
According to Google:
Firebase Security Rules stand between your data and malicious users. You can write simple or complex rules that protect your app's data to the level of granularity that your specific app requires.
Firebase Security Rules leverage extensible, flexible configuration languages to define what data your users can access for Realtime Database, Cloud Firestore, and Cloud Storage. Firebase Realtime Database Rules leverage JSON in rule definitions, while Cloud Firestore Security Rules and Firebase Security Rules for Cloud Storage leverage a unique language built to accommodate more complex rules-specific structures.
This is your main problem: If the user is not account verified you perform some Firestore related task. However, if he is not verified you again get the email from Firestore. But your security rules describe that you should not be able to access the database unless you're account verified. That's where the code fails. This is your error:
void performEmailVerification() {
if (firebaseAuth.getCurrentUser().isEmailVerified()) {
// Everything is OK, perform your task
checkSomethingOnFirestore();
return;
}
//User not verified, but you still get a database reference and try to get the email.
final DocumentReference documentReference = ... //Error produced
You need to perform the verification first, confirm that the user is verified then try to access the database.
Another factor could be that you need to refresh your firebase account object, like this:
FirebaseAuth.getInstance().getCurrentUser().reload();
What this does is refresh your account status. Because firebase keeps a reference to your account and that reference is cached it's a useful idea to reload your account, so it is updated. Do this before the if statement where you check if your user is verified or not.
You need to take a look at the documentation: Firebase Security Rules. There you would find how to write security rules(the syntax), how to not write insecure rules and how to deploy them.

PendingIntent with implicit intent returning cancelled exception when using OpenId AppAuth-Android library

I am trying to implement oauth2 to enable users to login with Reddit. I have created my app on reddit with the appropriate redirect uri.
What I did:
A MainActivity with a login button. Clicking the login button, starts the authorization flow. To create the authorization request, we need to pass a pending intent that the library uses to call the appropriate component that we want it to call after authorization is successful.
Problem:
When the pending intent is made using an implicit intent (setting only action string while creating intent), the library gets a cancelled exception while invoking the pending intent. I have mentioned the action string in the intent filter for the MainActivity in manifest file also.
What I have tried:
1. I tried creating pending intent using an explicit intent (defining the activity class I want to open while creating intent), my activity's onStart is getting called with the correct intent.
2. I tried by directly invoking the pending intent (with implicit intent) from the activity itself and it got called successfully.
Observation:
1. If I use an older version of the library (v0.2.0), the pending intent with implicit intent works fine.
Current version of OpenId AppAuth library - 0.7.1
Tested on Android 9 (Pie) - OnePlus 3T
Below is my MainActivity.java
package com.prateekgrover.redditline;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.prateekgrover.redditline.services.RedditAuthService;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationException;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationResponse;
import net.openid.appauth.AuthorizationService;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private String USED_INTENT = "1";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button loginButton = findViewById(R.id.reddit_login);
loginButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Intent intent = new Intent(MainActivity.this, RedditAuthService.class);
// startService(intent);
performRedditAuthAction(MainActivity.this, "com.prateekgrover.redditline.HANDLE_AUTHORIZATION_RESPONSE");
}
});
}
public void performRedditAuthAction(Context context, String actionRedirect) {
String uuid = UUID.randomUUID().toString();
AuthorizationServiceConfiguration serviceConfiguration = new AuthorizationServiceConfiguration(
Uri.parse("https://www.reddit.com/api/v1/authorize") /* auth endpoint */,
Uri.parse("https://www.reddit.com/api/v1/access_token") /* token endpoint */
);
String clientId = "<my client id>";
Uri redirectUri = Uri.parse("com.prateekgrover.redditline://oauth2callback");
AuthorizationRequest.Builder builder = new AuthorizationRequest.Builder(
serviceConfiguration,
clientId,
"code",
redirectUri
);
builder.setState(uuid);
builder.setScopes("identity", "mysubreddits", "read", "save", "submit", "subscribe", "vote");
AuthorizationRequest request = builder.build();
AuthorizationService authorizationService = new AuthorizationService(context);
String action = actionRedirect;
Intent postAuthorizationIntent = new Intent("com.prateekgrover.redditline.HANDLE_AUTHORIZATION_RESPONSE");
PendingIntent pendingIntent = PendingIntent.getActivity(this, request.hashCode(), postAuthorizationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
authorizationService.performAuthorizationRequest(request, pendingIntent);
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent != null && intent.getAction() != null) {
String action = intent.getAction();
switch (action) {
case "com.prateekgrover.redditline.HANDLE_AUTHORIZATION_RESPONSE":
redirectIntent(intent);
break;
default:
}
}
}
private void redirectIntent(#Nullable Intent intent) {
if (!intent.hasExtra(USED_INTENT)) {
handleAuthorizationResponse(intent);
intent.putExtra(USED_INTENT, true);
}
}
private void handleAuthorizationResponse(Intent intent) {
AuthorizationResponse response = AuthorizationResponse.fromIntent(intent);
AuthorizationException error = AuthorizationException.fromIntent(intent);
final AuthState authState = new AuthState(response, error);
if (response != null) {
AuthorizationService service = new AuthorizationService(this);
service.performTokenRequest(response.createTokenExchangeRequest(), new AuthorizationService.TokenResponseCallback() {
#Override
public void onTokenRequestCompleted(#Nullable TokenResponse tokenResponse, #Nullable AuthorizationException exception) {
if (exception != null) {
} else {
if (tokenResponse != null) {
authState.update(tokenResponse, exception);
System.out.println(tokenResponse.accessToken + " refresh_token " + tokenResponse.refreshToken);
}
}
}
});
}
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
#Override
protected void onStart() {
super.onStart();
Intent intent = getIntent();
if (intent != null && intent.getAction() != null) {
String action = intent.getAction();
switch (action) {
case "com.prateekgrover.redditline.HANDLE_AUTHORIZATION_RESPONSE":
redirectIntent(intent);
break;
default:
}
}
}
}
Manifest File:
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.prateekgrover.redditline.HANDLE_AUTHORIZATION_RESPONSE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
Relevant parts of the library - mCompleteIntent is the PendingIntent that I sending to the library
private void extractState(Bundle state) {
if (state == null) {
Logger.warn("No stored state - unable to handle response");
finish();
return;
}
mAuthIntent = state.getParcelable(KEY_AUTH_INTENT);
mAuthorizationStarted = state.getBoolean(KEY_AUTHORIZATION_STARTED, false);
try {
String authRequestJson = state.getString(KEY_AUTH_REQUEST, null);
mAuthRequest = authRequestJson != null
? AuthorizationRequest.jsonDeserialize(authRequestJson)
: null;
} catch (JSONException ex) {
throw new IllegalStateException("Unable to deserialize authorization request", ex);
}
mCompleteIntent = state.getParcelable(KEY_COMPLETE_INTENT);
mCancelIntent = state.getParcelable(KEY_CANCEL_INTENT);
}
private void handleAuthorizationComplete() {
Uri responseUri = getIntent().getData();
Intent responseData = extractResponseData(responseUri);
if (responseData == null) {
Logger.error("Failed to extract OAuth2 response from redirect");
return;
}
responseData.setData(responseUri);
if (mCompleteIntent != null) {
Logger.debug("Authorization complete - invoking completion intent");
try {
mCompleteIntent.send(this, 0, responseData);
} catch (CanceledException ex) {
Logger.error("Failed to send completion intent", ex);
}
} else {
setResult(RESULT_OK, responseData);
}
}
In case anybody else stumbles upon this issue.
Use the example app within app-auth android github project.
Don't use Google CodeLabs app-auth example! The code from the question above is from Google CodeLabs, it is very old and no longer works (state at July 2020).
I did the same mistake, app-auth links codelabs on their own page/readme, so I started using codelabs code and ended up with lots of problems and errors.
The new app-auth version 0.7.x uses a json configuration file and the example app shows how to handle errors around pending intents etc. .

BLE connection issues

I am working on an app that can connect to an adafruit flora BLE device to receive information from it. I want the app to display the list of found devices and when an item in the list view is clicked the connection is made and data can be received. Ultimately I want to take said data over to another activity to graph in realtime (if possible). There are a few things going on that i dont understand and I hope someone can shed light on.
When it is scanning for devices the list view shows them but there are multiples of each.
For some reason the onPause and onResume make the list view glitchy (displays devices and then removes them)
How do I know when there is a connection?
When I have getRemoteDevice in my code I get a runtime error ( Attempt to invoke virtual method 'android.bluetooth.BluetoothDevice android.bluetooth.BluetoothAdapter.getRemoteDevice(java.lang.String)' on a null object reference)
When I try and use filters and settings in the startScan method I get nothing in my list, I have also tried null filters w/ settings and still nothing.
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.content.Intent;
import android.os.Build;
import android.os.ParcelUuid;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat;
import no.nordicsemi.android.support.v18.scanner.ScanFilter;
import no.nordicsemi.android.support.v18.scanner.ScanResult;
import no.nordicsemi.android.support.v18.scanner.ScanSettings;
public class BluetoothDiscovery extends AppCompatActivity {
private String TAG = "Bluetooth Device";
private int REQUEST_ENABLE_BT = 5;
private BluetoothAdapter mBluetoothAdapter;
private BluetoothLeScannerCompat scanner;
private ScanSettings settings;
private UUID baseUUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e"); // service UUID
private UUID txUUID = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e"); // TX UUID characteristic
private UUID rxUUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e"); // RX UUID characteristic
private ScanFilter scanFilter;
private BluetoothDevice device, mdevice;
private BluetoothGatt mGatt;
private boolean mScanning = false;
private ArrayList<deviceShowFormat> foundDevices = new ArrayList<>();
formattingAdapter BTadapter;
Button scanButton;
TextView fancyWords;
ListView deviceList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bluetooth_discovery);
mBluetoothAdapter.getDefaultAdapter();
scanButton = findViewById(R.id.scanButt);
scanButton.setText(getString(R.string.notScanning));
fancyWords = findViewById(R.id.discoverText);
fancyWords.setText(getString(R.string.nonScanTitle));
deviceList = findViewById(R.id.deviceList);
BTadapter = new formattingAdapter(BluetoothDiscovery.this, foundDevices);
deviceList.setAdapter(BTadapter);
scanner = BluetoothLeScannerCompat.getScanner();
settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_BALANCED).setReportDelay(500).build();
scanFilter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(baseUUID)).build();
//scanner.startScan(Arrays.asList(scanFilter), settings, mScanCallback);
deviceList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#SuppressLint("LongLogTag")
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
deviceShowFormat mBTDevice = foundDevices.get(i);
BluetoothDevice Device = mBTDevice.get_device();
String deviceName = mBTDevice.get_device_name();
String deviceAddress = mBTDevice.get_device_address();
Log.d(TAG, "Selected device: " + Device.toString());
Log.d(TAG, "Selected device name: " + deviceName);
Log.d(TAG, "Selected device address: " + deviceAddress);
//BluetoothDevice deviceConnect = mBluetoothAdapter.getRemoteDevice(deviceAddress);
//deviceConnect.createBond();
mGatt = Device.connectGatt(BluetoothDiscovery.this, false, mGattCallback);
Toast.makeText(BluetoothDiscovery.this, "Selected device: " + deviceName, Toast.LENGTH_SHORT).show();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Device.createBond();
}
Log.d(TAG, "" + Device.getBondState());
if(Device.getBondState() == BluetoothDevice.BOND_BONDED){
Toast.makeText(BluetoothDiscovery.this, "Bluetooth device connected successfully", Toast.LENGTH_SHORT).show();
}
mGatt.getServices();
mGatt.getConnectedDevices();
Log.d("THIS IS THE DEVICES UUID", String.valueOf(Device.getUuids()));
Log.d("DEVICE SERVICES", String.valueOf(mGatt.getServices()));
}
});
}
private final no.nordicsemi.android.support.v18.scanner.ScanCallback mScanCallback = new no.nordicsemi.android.support.v18.scanner.ScanCallback() {
#Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
Log.i("onScanResult", "device detected");
device = result.getDevice();
String deviceName = device.getName();
String deviceAddress = device.getAddress();
Log.d(TAG, "Scanned device: " + device.toString());
Log.d(TAG, "Scanned device name: " + deviceName);
Log.d(TAG, "Scanned device address: " + deviceAddress);
foundDevices.add(new deviceShowFormat(device, deviceName, deviceAddress));
BTadapter.notifyDataSetChanged();
}
};
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
#Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.i("onConnectionStateChange", "State Changed from: " + status + " to " + newState);
if (newState == BluetoothProfile.STATE_CONNECTED){
Toast.makeText(BluetoothDiscovery.this, "Attempting service discovery", Toast.LENGTH_SHORT).show();
Log.i("onConnectionStateChange", "Attempting service discovery: " + gatt.discoverServices());
gatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED){
Toast.makeText(BluetoothDiscovery.this, "Connection has been terminated", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onServicesDiscovered(BluetoothGatt gatt, int status){
super.onServicesDiscovered(gatt, status);
Log.i("onServicesDiscovered", "Hey, we found a service");
if (status != BluetoothGatt.GATT_SUCCESS){
// Handle error
Log.d("onServicesDiscovered" , "" + status);
return;
}
BluetoothGattCharacteristic characteristic = gatt.getService(baseUUID).getCharacteristic(rxUUID);
gatt.setCharacteristicNotification(characteristic, true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(txUUID);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
#Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status){
Log.i("onCharacteristicRead", "Characteristic has been read");
readCounterCharacteristic(characteristic);
}
#Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
if (mGatt.writeCharacteristic(characteristic)){
Log.d("Characteristic changed", "Possibly looking for a write");
}
if (mGatt.readCharacteristic(characteristic)){
readCounterCharacteristic(characteristic);
}
}
private void readCounterCharacteristic(BluetoothGattCharacteristic characteristic){
if (mGatt.readCharacteristic(characteristic)){
byte[] data = characteristic.getValue();
Log.d("READ DATA", data.toString());
}
// if (rxUUID.equals(characteristic.getUuid())){
// //byte[] data = characteristic.getValue();
// byte[] data = mGatt.readCharacteristic(characteristic);
// //int value = Ints.fromByteArray(data);
// Log.d("READ DATA", data.toString());
// }
}
};
public void toggleScan(View view){
mScanning = !mScanning;
if(mScanning){
scanner.startScan(mScanCallback); //Arrays.asList(scanFilter) null, settings,
scanButton.setText(getString(R.string.scanInProgress));
fancyWords.setText(getString(R.string.ScanTitle));
} else {
scanner.stopScan(mScanCallback);
scanButton.setText(getString(R.string.notScanning));
}
}
// #Override
// public void onPause(){
// super.onPause();
//
//// if(mScanning){
//// mScanning = !mScanning;
// scanner.stopScan(mScanCallback);
//// }
//
// //Empty Adapter
// //BTadapter.clear();
// //BTadapter.notifyDataSetChanged();
//
// //mdevice = device;
//
// }
//
// #Override
// public void onResume(){
// super.onResume();
//
// if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
// Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
// startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
// }
//
// //device = mdevice;
// }
}
The code is the second activity in my app, in the first bluetooth is initialized and what-not. The above code works but I dont receive any data from the device and am not sure its truly connected. from the logs shown in the code I get:
Logcat
Logcat2
Resources im using:
https://github.com/NordicSemiconductor/Android-Scanner-Compat-Library
https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-uart-friend/uart-service
http://nilhcem.com/android-things/bluetooth-low-energy
You missed to initialize mBluetoothAdapter
As of (at oncreate):
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = manager.getAdapter();
instead of:
mBluetoothAdapter.getDefaultAdapter();
After that your variable is initialized and ready to use, just check if you need RuntimePermissions for using bluetooth.
Much of the stuff in android BLE communication is asynchronous.
So when you call Device.connectGatt(), you need to wait for the onConnectionStateChange callback before doing the next thing. Your attempts to enumerate the services will fail (probably quietly) because your connection state hasn't gone from 0 to 2 at that point. In your log you can see that you do the getServices() call before it has called you back to say the connection state has gone to connected.
Making things trickier, in my experience you should only be doing your BluetoothDevice interaction from the main thread. (Edit: Only sending requests from the main thread is apparently not a requirement)
So each command you do (connecting, reading characteristic values, writing, disconnecting, enumerating services, etc) needs to look like:
Main Thread: Call the (connect|read|write|enumerate) function
Callback thread: Java calls your BluetoothGattCallback, and you should find a way (Handler?) to signal the main thread to do your next thing.
Main thread: process the result from BluetoothGattCallback and trigger the next thing you'll be doing.
Mild aside: This is a lot easier in objective-c and react-native-ble-plx, because they handle the threading for you. If you design a good way to get the results from the callback thread to the main thread from the beginning, you will save yourself a lot of pain.
Example code for getting stuff back to the UI thread:
Handler m_handler;
public void onResume() {
m_handler = new Handler(); // creates a Handler bound to the UI thread, which lets us fire messages back and forth between threads.
}
// ... other stuff ...
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.i("onConnectionStateChange", "State Changed from: " + status + " to " + newState);
if (newState == BluetoothProfile.STATE_CONNECTED){
// oh boy, we're connected. Let's try to discover services!
// m_handler.post() is a good way to make sure the thing you pass runs on the UI thread.
m_handler.post(new Runnable() {
#Override
public void run() {
gatt.discoverServices();
}
})
}
}
For anyone looking this up in the future: If you receive a message similar to D/BluetoothGatt: onConnectionUpdated() - Device=xx:xx:xx:xx:xx:xx interval=6 latency=0 timeout=500 status=0 you are connecting to the device but not "enough" to recieve info. For me it turned out i had to click the device to connect to a second time to actually recieve the data. I dont know why yet
in your answer
For anyone looking this up in the future: If you receive a message
similar to D/BluetoothGatt: onConnectionUpdated() -
Device=xx:xx:xx:xx:xx:xx interval=6 latency=0 timeout=500 status=0 you
are connecting to the device but not "enough" to recieve info. For me
it turned out i had to click the device to connect to a second time to
actually recieve the data. I dont know why yet
I Unfortunately got this problem, you can see in my question
BLE onDescriptorWrite is not trigger when writeDescriptor receive true
And What do you say is the second click device, your app or other ble device.
Thanks a lot.

Integration of Google Play Services with LibGDX

I have been trying out Google Play Services Multiplayer APK, and have tried out the sample code, "Button Clicker" as shown below.
However, I am not sure how to port this over to LibGDX as currently the code is running based off a normal project without the "core" and "androidlauncher" that libGDX has. Does anyone perhaps have any advise on how should i do it? From what i know, core has a Mainclass with a method called render() which always run in a loop. However I cant think of a way of integrating it.
I have checked out several websites , However, could i ask why cant i just place the whole Buttonclicker project into the core module and call methods from it? Like sign in and sign out.
I only require the functionalities currently implemented in Button Clicker
CODE AS FOLLOWS:
/* 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.ryanhello;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.games.Games;
import com.google.android.gms.games.GamesStatusCodes;
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.OnInvitationReceivedListener;
import com.google.android.gms.games.multiplayer.Participant;
import com.google.android.gms.games.multiplayer.realtime.RealTimeMessage;
import com.google.android.gms.games.multiplayer.realtime.RealTimeMessageReceivedListener;
import com.google.android.gms.games.multiplayer.realtime.Room;
import com.google.android.gms.games.multiplayer.realtime.RoomConfig;
import com.google.android.gms.games.multiplayer.realtime.RoomStatusUpdateListener;
import com.google.android.gms.games.multiplayer.realtime.RoomUpdateListener;
import com.google.android.gms.plus.Plus;
import com.google.example.games.basegameutils.BaseGameUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Button Clicker 2000. A minimalistic game showing the multiplayer features of
* the Google Play game services API. The objective of this game is clicking a
* button. Whoever clicks the button the most times within a 20 second interval
* wins. It's that simple. This game can be played with 2, 3 or 4 players. The
* code is organized in sections in order to make understanding as clear as
* possible. We start with the integration section where we show how the game
* is integrated with the Google Play game services API, then move on to
* game-specific UI and logic.
*
* INSTRUCTIONS: To run this sample, please set up
* a project in the Developer Console. Then, place your app ID on
* res/values/ids.xml. Also, change the package name to the package name you
* used to create the client ID in Developer Console. Make sure you sign the
* APK with the certificate whose fingerprint you entered in Developer Console
* when creating your Client Id.
*
* #author Bruno Oliveira (btco), 2013-04-26
*/
public class MainActivity extends Activity
implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
View.OnClickListener, RealTimeMessageReceivedListener,
RoomStatusUpdateListener, RoomUpdateListener, OnInvitationReceivedListener {
/*
* API INTEGRATION SECTION. This section contains the code that integrates
* the game with the Google Play game services API.
*/
final static String TAG = "ButtonClicker2000";
// Request codes for the UIs that we show with startActivityForResult:
final static int RC_SELECT_PLAYERS = 10000;
final static int RC_INVITATION_INBOX = 10001;
final static int RC_WAITING_ROOM = 10002;
// Request code used to invoke sign in user interactions.
private static final int RC_SIGN_IN = 9001;
// Client used to interact with Google APIs.
private GoogleApiClient mGoogleApiClient;
// Are we currently resolving a connection failure?
private boolean mResolvingConnectionFailure = false;
// Has the user clicked the sign-in button?
private boolean mSignInClicked = false;
// Set to true to automatically start the sign in flow when the Activity starts.
// Set to false to require the user to click the button in order to sign in.
private boolean mAutoStartSignInFlow = true;
// Room ID where the currently active game is taking place; null if we're
// not playing.
String mRoomId = null;
// Are we playing in multiplayer mode?
boolean mMultiplayer = false;
// The participants in the currently active game
ArrayList<Participant> mParticipants = null;
// My participant ID in the currently active game
String mMyId = null;
// If non-null, this is the id of the invitation we received via the
// invitation listener
String mIncomingInvitationId = null;
// Message buffer for sending messages
byte[] mMsgBuf = new byte[2];
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create the Google Api Client with access to Plus and Games
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API).addScope(Plus.SCOPE_PLUS_LOGIN)
.addApi(Games.API).addScope(Games.SCOPE_GAMES)
.build();
// set up a click listener for everything we care about
for (int id : CLICKABLES) {
findViewById(id).setOnClickListener(this);
}
}
#Override
public void onClick(View v) {
Intent intent;
switch (v.getId()) {
case R.id.button_single_player:
case R.id.button_single_player_2:
// play a single-player game
resetGameVars();
startGame(false);
break;
case R.id.button_sign_in:
// user wants to sign in
// Check to see the developer who's running this sample code read the instructions :-)
// NOTE: this check is here only because this is a sample! Don't include this
// check in your actual production app.
if (!BaseGameUtils.verifySampleSetup(this, R.string.app_id)) {
Log.w(TAG, "*** Warning: setup problems detected. Sign in may not work!");
}
// start the sign-in flow
Log.d(TAG, "Sign-in button clicked");
mSignInClicked = true;
mGoogleApiClient.connect();
break;
case R.id.button_sign_out:
// user wants to sign out
// sign out.
Log.d(TAG, "Sign-out button clicked");
mSignInClicked = false;
Games.signOut(mGoogleApiClient);
mGoogleApiClient.disconnect();
switchToScreen(R.id.screen_sign_in);
break;
case R.id.button_invite_players:
// show list of invitable players
intent = Games.RealTimeMultiplayer.getSelectOpponentsIntent(mGoogleApiClient, 1, 3);
switchToScreen(R.id.screen_wait);
startActivityForResult(intent, RC_SELECT_PLAYERS);
break;
case R.id.button_see_invitations:
// show list of pending invitations
intent = Games.Invitations.getInvitationInboxIntent(mGoogleApiClient);
switchToScreen(R.id.screen_wait);
startActivityForResult(intent, RC_INVITATION_INBOX);
break;
case R.id.button_accept_popup_invitation:
// user wants to accept the invitation shown on the invitation popup
// (the one we got through the OnInvitationReceivedListener).
acceptInviteToRoom(mIncomingInvitationId);
mIncomingInvitationId = null;
break;
case R.id.button_quick_game:
// user wants to play against a random opponent right now
startQuickGame();
break;
case R.id.button_click_me:
// (gameplay) user clicked the "click me" button
scoreOnePoint();
break;
}
}
void startQuickGame() {
// quick-start a game with 1 randomly selected opponent
final int MIN_OPPONENTS = 1, MAX_OPPONENTS = 1;
Bundle autoMatchCriteria = RoomConfig.createAutoMatchCriteria(MIN_OPPONENTS,
MAX_OPPONENTS, 0);
RoomConfig.Builder rtmConfigBuilder = RoomConfig.builder(this);
rtmConfigBuilder.setMessageReceivedListener(this);
rtmConfigBuilder.setRoomStatusUpdateListener(this);
rtmConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);
switchToScreen(R.id.screen_wait);
keepScreenOn();
resetGameVars();
Games.RealTimeMultiplayer.create(mGoogleApiClient, rtmConfigBuilder.build());
}
#Override
public void onActivityResult(int requestCode, int responseCode,
Intent intent) {
super.onActivityResult(requestCode, responseCode, intent);
switch (requestCode) {
case RC_SELECT_PLAYERS:
// we got the result from the "select players" UI -- ready to create the room
handleSelectPlayersResult(responseCode, intent);
break;
case RC_INVITATION_INBOX:
// we got the result from the "select invitation" UI (invitation inbox). We're
// ready to accept the selected invitation:
handleInvitationInboxResult(responseCode, intent);
break;
case RC_WAITING_ROOM:
// we got the result from the "waiting room" UI.
if (responseCode == Activity.RESULT_OK) {
// ready to start playing
Log.d(TAG, "Starting game (waiting room returned OK).");
startGame(true);
} else if (responseCode == GamesActivityResultCodes.RESULT_LEFT_ROOM) {
// player indicated that they want to leave the room
leaveRoom();
} else if (responseCode == Activity.RESULT_CANCELED) {
// Dialog was cancelled (user pressed back key, for instance). In our game,
// this means leaving the room too. In more elaborate games, this could mean
// something else (like minimizing the waiting room UI).
leaveRoom();
}
break;
case RC_SIGN_IN:
Log.d(TAG, "onActivityResult with requestCode == RC_SIGN_IN, responseCode="
+ responseCode + ", intent=" + intent);
mSignInClicked = false;
mResolvingConnectionFailure = false;
if (responseCode == RESULT_OK) {
mGoogleApiClient.connect();
} else {
BaseGameUtils.showActivityResultError(this,requestCode,responseCode, R.string.signin_other_error);
}
break;
}
super.onActivityResult(requestCode, responseCode, intent);
}
// Handle the result of the "Select players UI" we launched when the user clicked the
// "Invite friends" button. We react by creating a room with those players.
private void handleSelectPlayersResult(int response, Intent data) {
if (response != Activity.RESULT_OK) {
Log.w(TAG, "*** select players UI cancelled, " + response);
switchToMainScreen();
return;
}
Log.d(TAG, "Select players UI succeeded.");
// get the invitee list
final ArrayList<String> invitees = data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);
Log.d(TAG, "Invitee count: " + invitees.size());
// get the automatch criteria
Bundle autoMatchCriteria = null;
int minAutoMatchPlayers = data.getIntExtra(Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
int maxAutoMatchPlayers = data.getIntExtra(Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);
if (minAutoMatchPlayers > 0 || maxAutoMatchPlayers > 0) {
autoMatchCriteria = RoomConfig.createAutoMatchCriteria(
minAutoMatchPlayers, maxAutoMatchPlayers, 0);
Log.d(TAG, "Automatch criteria: " + autoMatchCriteria);
}
// create the room
Log.d(TAG, "Creating room...");
RoomConfig.Builder rtmConfigBuilder = RoomConfig.builder(this);
rtmConfigBuilder.addPlayersToInvite(invitees);
rtmConfigBuilder.setMessageReceivedListener(this);
rtmConfigBuilder.setRoomStatusUpdateListener(this);
if (autoMatchCriteria != null) {
rtmConfigBuilder.setAutoMatchCriteria(autoMatchCriteria);
}
switchToScreen(R.id.screen_wait);
keepScreenOn();
resetGameVars();
Games.RealTimeMultiplayer.create(mGoogleApiClient, rtmConfigBuilder.build());
Log.d(TAG, "Room created, waiting for it to be ready...");
}
// Handle the result of the invitation inbox UI, where the player can pick an invitation
// to accept. We react by accepting the selected invitation, if any.
private void handleInvitationInboxResult(int response, Intent data) {
if (response != Activity.RESULT_OK) {
Log.w(TAG, "*** invitation inbox UI cancelled, " + response);
switchToMainScreen();
return;
}
Log.d(TAG, "Invitation inbox UI succeeded.");
Invitation inv = data.getExtras().getParcelable(Multiplayer.EXTRA_INVITATION);
// accept invitation
acceptInviteToRoom(inv.getInvitationId());
}
// Accept the given invitation.
void acceptInviteToRoom(String invId) {
// accept the invitation
Log.d(TAG, "Accepting invitation: " + invId);
RoomConfig.Builder roomConfigBuilder = RoomConfig.builder(this);
roomConfigBuilder.setInvitationIdToAccept(invId)
.setMessageReceivedListener(this)
.setRoomStatusUpdateListener(this);
switchToScreen(R.id.screen_wait);
keepScreenOn();
resetGameVars();
Games.RealTimeMultiplayer.join(mGoogleApiClient, roomConfigBuilder.build());
}
// Activity is going to the background. We have to leave the current room.
#Override
public void onStop() {
Log.d(TAG, "**** got onStop");
// if we're in a room, leave it.
leaveRoom();
// stop trying to keep the screen on
stopKeepingScreenOn();
if (mGoogleApiClient == null || !mGoogleApiClient.isConnected()){
switchToScreen(R.id.screen_sign_in);
}
else {
switchToScreen(R.id.screen_wait);
}
super.onStop();
}
// Activity just got to the foreground. We switch to the wait screen because we will now
// go through the sign-in flow (remember that, yes, every time the Activity comes back to the
// foreground we go through the sign-in flow -- but if the user is already authenticated,
// this flow simply succeeds and is imperceptible).
#Override
public void onStart() {
switchToScreen(R.id.screen_wait);
if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
Log.w(TAG,
"GameHelper: client was already connected on onStart()");
} else {
Log.d(TAG,"Connecting client.");
mGoogleApiClient.connect();
}
super.onStart();
}
// Handle back key to make sure we cleanly leave a game if we are in the middle of one
#Override
public boolean onKeyDown(int keyCode, KeyEvent e) {
if (keyCode == KeyEvent.KEYCODE_BACK && mCurScreen == R.id.screen_game) {
leaveRoom();
return true;
}
return super.onKeyDown(keyCode, e);
}
// Leave the room.
void leaveRoom() {
Log.d(TAG, "Leaving room.");
mSecondsLeft = 0;
stopKeepingScreenOn();
if (mRoomId != null) {
Games.RealTimeMultiplayer.leave(mGoogleApiClient, this, mRoomId);
mRoomId = null;
switchToScreen(R.id.screen_wait);
} else {
switchToMainScreen();
}
}
// Show the waiting room UI to track the progress of other players as they enter the
// room and get connected.
void showWaitingRoom(Room room) {
// minimum number of players required for our game
// For simplicity, we require everyone to join the game before we start it
// (this is signaled by Integer.MAX_VALUE).
final int MIN_PLAYERS = Integer.MAX_VALUE;
Intent i = Games.RealTimeMultiplayer.getWaitingRoomIntent(mGoogleApiClient, room, MIN_PLAYERS);
// show waiting room UI
startActivityForResult(i, RC_WAITING_ROOM);
}
// Called when we get an invitation to play a game. We react by showing that to the user.
#Override
public void onInvitationReceived(Invitation invitation) {
// We got an invitation to play a game! So, store it in
// mIncomingInvitationId
// and show the popup on the screen.
mIncomingInvitationId = invitation.getInvitationId();
((TextView) findViewById(R.id.incoming_invitation_text)).setText(
invitation.getInviter().getDisplayName() + " " +
getString(R.string.is_inviting_you));
switchToScreen(mCurScreen); // This will show the invitation popup
}
#Override
public void onInvitationRemoved(String invitationId) {
if (mIncomingInvitationId.equals(invitationId)&&mIncomingInvitationId!=null) {
mIncomingInvitationId = null;
switchToScreen(mCurScreen); // This will hide the invitation popup
}
}
/*
* CALLBACKS SECTION. This section shows how we implement the several games
* API callbacks.
*/
#Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "onConnected() called. Sign in successful!");
Log.d(TAG, "Sign-in succeeded.");
// register listener so we are notified if we receive an invitation to play
// while we are in the game
Games.Invitations.registerInvitationListener(mGoogleApiClient, this);
if (connectionHint != null) {
Log.d(TAG, "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
Log.d(TAG,"onConnected: connection hint has a room invite!");
acceptInviteToRoom(inv.getInvitationId());
return;
}
}
switchToMainScreen();
}
#Override
public void onConnectionSuspended(int i) {
Log.d(TAG, "onConnectionSuspended() called. Trying to reconnect.");
mGoogleApiClient.connect();
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.d(TAG, "onConnectionFailed() called, result: " + connectionResult);
if (mResolvingConnectionFailure) {
Log.d(TAG, "onConnectionFailed() ignoring connection failure; already resolving.");
return;
}
if (mSignInClicked || mAutoStartSignInFlow) {
mAutoStartSignInFlow = false;
mSignInClicked = false;
mResolvingConnectionFailure = BaseGameUtils.resolveConnectionFailure(this, mGoogleApiClient,
connectionResult, RC_SIGN_IN, getString(R.string.signin_other_error));
}
switchToScreen(R.id.screen_sign_in);
}
// Called when we are connected to the room. We're not ready to play yet! (maybe not everybody
// is connected yet).
#Override
public void onConnectedToRoom(Room room) {
Log.d(TAG, "onConnectedToRoom.");
//get participants and my ID:
mParticipants = room.getParticipants();
mMyId = room.getParticipantId(Games.Players.getCurrentPlayerId(mGoogleApiClient));
// save room ID if its not initialized in onRoomCreated() so we can leave cleanly before the game starts.
if(mRoomId==null)
mRoomId = room.getRoomId();
// print out the list of participants (for debug purposes)
Log.d(TAG, "Room ID: " + mRoomId);
Log.d(TAG, "My ID " + mMyId);
Log.d(TAG, "<< CONNECTED TO ROOM>>");
}
// Called when we've successfully left the room (this happens a result of voluntarily leaving
// via a call to leaveRoom(). If we get disconnected, we get onDisconnectedFromRoom()).
#Override
public void onLeftRoom(int statusCode, String roomId) {
// we have left the room; return to main screen.
Log.d(TAG, "onLeftRoom, code " + statusCode);
switchToMainScreen();
}
// Called when we get disconnected from the room. We return to the main screen.
#Override
public void onDisconnectedFromRoom(Room room) {
mRoomId = null;
showGameError();
}
// Show error message about game being cancelled and return to main screen.
void showGameError() {
BaseGameUtils.makeSimpleDialog(this, getString(R.string.game_problem));
switchToMainScreen();
}
// Called when room has been created
#Override
public void onRoomCreated(int statusCode, Room room) {
Log.d(TAG, "onRoomCreated(" + statusCode + ", " + room + ")");
if (statusCode != GamesStatusCodes.STATUS_OK) {
Log.e(TAG, "*** Error: onRoomCreated, status " + statusCode);
showGameError();
return;
}
// save room ID so we can leave cleanly before the game starts.
mRoomId = room.getRoomId();
// show the waiting room UI
showWaitingRoom(room);
}
// Called when room is fully connected.
#Override
public void onRoomConnected(int statusCode, Room room) {
Log.d(TAG, "onRoomConnected(" + statusCode + ", " + room + ")");
if (statusCode != GamesStatusCodes.STATUS_OK) {
Log.e(TAG, "*** Error: onRoomConnected, status " + statusCode);
showGameError();
return;
}
updateRoom(room);
}
#Override
public void onJoinedRoom(int statusCode, Room room) {
Log.d(TAG, "onJoinedRoom(" + statusCode + ", " + room + ")");
if (statusCode != GamesStatusCodes.STATUS_OK) {
Log.e(TAG, "*** Error: onRoomConnected, status " + statusCode);
showGameError();
return;
}
// show the waiting room UI
showWaitingRoom(room);
}
// We treat most of the room update callbacks in the same way: we update our list of
// participants and update the display. In a real game we would also have to check if that
// change requires some action like removing the corresponding player avatar from the screen,
// etc.
#Override
public void onPeerDeclined(Room room, List<String> arg1) {
updateRoom(room);
}
#Override
public void onPeerInvitedToRoom(Room room, List<String> arg1) {
updateRoom(room);
}
#Override
public void onP2PDisconnected(String participant) {
}
#Override
public void onP2PConnected(String participant) {
}
#Override
public void onPeerJoined(Room room, List<String> arg1) {
updateRoom(room);
}
#Override
public void onPeerLeft(Room room, List<String> peersWhoLeft) {
updateRoom(room);
}
#Override
public void onRoomAutoMatching(Room room) {
updateRoom(room);
}
#Override
public void onRoomConnecting(Room room) {
updateRoom(room);
}
#Override
public void onPeersConnected(Room room, List<String> peers) {
updateRoom(room);
}
#Override
public void onPeersDisconnected(Room room, List<String> peers) {
updateRoom(room);
}
void updateRoom(Room room) {
if (room != null) {
mParticipants = room.getParticipants();
}
if (mParticipants != null) {
updatePeerScoresDisplay();
}
}
My current interface code
package com.macrohard.game;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.google.example.games.basegameutils.GameHelper;
import com.macrohard.game.SomeGame;
import com.google.android.gms.games.Games;
import com.google.example.games.basegameutils.GameHelper;
import com.google.example.games.basegameutils.GameHelper.GameHelperListener;
import com.macrohard.game.ActionResolver;
import com.macrohard.game.MainMenu;
public class AndroidLauncher extends AndroidApplication implements ActionResolver {
private GameHelper gameHelper;
private final static int requestCode = 1;
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
gameHelper = new GameHelper(this, GameHelper.CLIENT_GAMES);
gameHelper.enableDebugLog(false);
GameHelper.GameHelperListener gameHelperListener = new GameHelper.GameHelperListener()
{
#Override
public void onSignInFailed(){ }
#Override
public void onSignInSucceeded(){ }
};
gameHelper.setup(gameHelperListener);
//initialize(new SomeGame(), config);
initialize(new MainMenu(this), config);
}
//...
#Override
protected void onStart()
{
super.onStart();
gameHelper.onStart(this);
}
#Override
protected void onStop()
{
super.onStop();
gameHelper.onStop();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
gameHelper.onActivityResult(requestCode, resultCode, data);
}
#Override
public void signIn()
{
try
{
runOnUiThread(new Runnable()
{
#Override
public void run()
{
gameHelper.beginUserInitiatedSignIn();
}
});
}
catch (Exception e)
{
Gdx.app.log("MainActivity", "Log in failed: " + e.getMessage() + ".");
}
}
#Override
public void signOut()
{
try
{
runOnUiThread(new Runnable()
{
#Override
public void run()
{
gameHelper.signOut();
}
});
}
catch (Exception e)
{
Gdx.app.log("MainActivity", "Log out failed: " + e.getMessage() + ".");
}
}
#Override
public void rateGame()
{
String str = "Your PlayStore Link";
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(str)));
}
#Override
public void unlockAchievement()
{
}
#Override
public void submitScore(int highScore)
{
}
#Override
public void showAchievement()
{
}
#Override
public void showScore()
{
}
#Override
public boolean isSignedIn()
{
return gameHelper.isSignedIn();
}
}
LibGDX separates platform specific codes into different modules for providing multiplatform by using the same code base (Core project). So you should implement android specific stuff in android project as you show in the code example.
You can not use any class in the package "com.google.android" in your core project. Because Google Play Services depends on android specific resources. You should read the document about platform specific code
https://github.com/libgdx/libgdx/wiki/Interfacing-with-platform-specific-code
So, you should prepare an interface which defines your methods you want to use and implement separately for each of platforms that you are planning to support in your project.
It seems that we are both trying to implement a real-time multiplayer using the Google Play Games Services with the very specific architecture of any libGDX project. I suggest you to check out this GitHub repository : GarrapuchoFootball. Tell me if it helps you.

NoSuchMethodError on a method that does exist at runtime on my android device

thanks for taking the time to read this. I am experiencing a very strange 'NoSuchMethodError' in a project I'm working on involving Android. I can't figure this out as it defies all of my logic.
package com.project.qrcode
import android.security.KeyStore;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import jim.h.common.android.lib.zxing.config.ZXingLibConfig;
import jim.h.common.android.lib.zxing.integrator.IntentIntegrator;
import jim.h.common.android.lib.zxing.integrator.IntentResult;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private ZXingLibConfig zxingLibConfig;
private Handler handler = new Handler();
private TextView txtScanResult;
KeyStore ks = KeyStore.getInstance();
SecretKeyStore secretKeyStore = new SecretKeyStore();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
byte[] hashedBytes;
String decoded;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
startActivity(new Intent("android.credentials.UNLOCK"));
} else {
startActivity(new Intent("com.android.credentials.UNLOCK"));
}
} catch (ActivityNotFoundException e) {
Log.e(getPackageName(), "No UNLOCK activity: " + e.getMessage(), e);
}
zxingLibConfig = new ZXingLibConfig();
zxingLibConfig.useFrontLight = true;
txtScanResult = (TextView) findViewById(R.id.scan_result);
Button scanButton = (Button) findViewById(R.id.scan_button);
//Set a listener on the scan button
scanButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!checkIfKeyStored()) {
Toast keyerror = Toast.makeText(getBaseContext(), "You need to complete setup first", Toast.LENGTH_SHORT);
keyerror.show();
return;
}
IntentIntegrator.initiateScan(MainActivity.this, zxingLibConfig);
}
});
Log.v(getPackageName(), "Listener set on scan button");
Button setupButton = (Button) findViewById(R.id.setup_button);
// Set a listener on the setup button
setupButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (checkIfKeyStored()) {
Log.v(getPackageName(), "Key is already stored");
Toast keyerror = Toast.makeText(getBaseContext(), "You have already completed setup", Toast.LENGTH_SHORT);
keyerror.show();
return;
}
Log.v(getPackageName(), "Key not stored, proceeding with setup");
IntentIntegrator.initiateScan(MainActivity.this, zxingLibConfig);
}
});
Log.v(getPackageName(), "Listener set on setup button");
}
protected boolean checkIfKeyStored() {
String[] keyNames = ks.saw("");
if( keyNames.length == 0 ) {
return false;
}
return true;
}
// IF setup is done i.e. key is stored send to server
// Otherwise store on phone
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.v(getPackageName(), "Scanned QRCode");
if (requestCode == IntentIntegrator.REQUEST_CODE) {
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (scanResult == null) {
Log.v(getPackageName(), "Scanned nothing");
return;
}
//Contents of the QRCode
Log.v(getPackageName(), "Scan complete, getting result");
final String result = scanResult.getContents();
Log.v(getPackageName(), "Scanned the following code "+ result);
//If there is already a secret key stored i.e. setup already done
if (checkIfKeyStored()) {
Log.v(getPackageName(), "Key already stored, encrypting");
try {
MessageDigest digest = MessageDigest.getInstance("SHA1PRNG");
Log.v(getPackageName(), "Got SHA1PRNG instance");
byte[] keyBytes = ks.get("twofactorkey");
byte[] resultBytes = result.getBytes("UTF-8");
Log.v(getPackageName(), "Got Bytes");
outputStream.write( resultBytes );
outputStream.write( keyBytes );
Log.v(getPackageName(), "Wrote Bytes to output stream");
byte[] bytesToEncrypt = outputStream.toByteArray( );
Log.v(getPackageName(), "Wrote to Byte array");
hashedBytes = digest.digest(bytesToEncrypt);
decoded = new String(hashedBytes, "UTF-8");
Log.v(getPackageName(), "Coverted bytes to String");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
#Override
public void run() {
txtScanResult.setText(decoded);
Log.v(getPackageName(), "Set TextView");
}
});
}
else //This is the first time scanning a QRCode, i.e. Setup
{
Log.v(getPackageName(), "Key not stored, first time setup");
byte[] resultBytes;
try {
resultBytes = result.getBytes("UTF-8");
Log.v(getPackageName(), "Result byte array: " + resultBytes);
boolean success = ks.put("twofactorkey", resultBytes);
if (!success) {
int errorCode = ks.getLastError();
throw new RuntimeException("Keystore error: " + errorCode);
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Log.v(getPackageName(), "Stored in keystore");
Toast setupComplete = Toast.makeText(getBaseContext(), "You have completed setup", Toast.LENGTH_SHORT);
setupComplete.show();
}
}
}
}
package android.security;
public class KeyStore {
private boolean put(byte[] key, byte[] value) {
execute('i', key, value);
return mError == NO_ERROR;
}
public boolean put(String key, byte[] value) {
Log.v("KEYSTORE", "Attempting put");
return put(getBytes(key), value);
}
}
The error I am getting is..
02-24 15:25:55.689: E/AndroidRuntime(11016): java.lang.NoSuchMethodError: android.security.KeyStore.put which occurs in the onActivityResult() method.
If you need the full logcat I can post that too.
You can see I have some Log messages planted throughout the code. The one inside put never prints out.
EDIT 24/02/14:
The above NoMethod exception has been solved by moving KeyStore.java into the same package as MainActivity.java - Thank you Lars
However I now have a new problem. Any time I try using ks.state() or ks.put() I get back a response back of AssertError: 5 - Which, according to KeyStore.java is a protocol error.
Final Edit
I figured out the above problem. Turns out the version of the KeyStore I am using from AOSP is for versions of Android below 4.2 only.
what is the reason for java.lang.NoSuchMethodError
java doc says
Thrown if an application tries to call a specified method of a class
(either static or instance), and that class no longer has a definition
of that method. Normally, this error is caught by the compiler; this
error can only occur at run time if the definition of a class has
incompatibly changed.
in your code you call put method here
boolean success = ks.put("twofactorkey", resultBytes);
and you have a required method is in the KeyStore class
public boolean put(String key, byte[] value) {
Log.v("KEYSTORE", "Attempting put");
return put(getBytes(key), value);
}
but the problem is your compiled KeyStore(KeyStore.class file) has no required put method. I assume you have mistakenly altered the above put method and compiled both classes alone and ran MainActivity class. that's why you get that error
that kind of error appear when you are running some code on a older virtual machine.
in general thisd appen when your bitcode call a function that does not exist.
As far as i know android.security; does not exist, but java.security; does.
If that your custom class, i don't think you can put it into android's path, but i exoect a compiling error

Categories