ACTION_CONFIRM_NOTIFICATION never occurs - Why? - android

In my attempts to understand the In-app Billing flow, I ran the market_billing sample, as is, plus a few Log.v() in key points, like in BillingService.handleCommand():
public void handleCommand(Intent intent, int startId) {
String action = intent.getAction();
if (Consts.DEBUG) {
Log.i(TAG, "handleCommand() action: " + action);
}
if (Consts.ACTION_CONFIRM_NOTIFICATION.equals(action)) {
String[] notifyIds = intent.getStringArrayExtra(Consts.NOTIFICATION_ID);
confirmNotifications(startId, notifyIds);
} else if (Consts.ACTION_GET_PURCHASE_INFORMATION.equals(action)) {
String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
getPurchaseInformation(startId, new String[] { notifyId });
} else if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
purchaseStateChanged(startId, signedData, signature);
} else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
ResponseCode.RESULT_ERROR.ordinal());
ResponseCode responseCode = ResponseCode.valueOf(responseCodeIndex);
checkResponseCode(requestId, responseCode);
}
}
My problem (?) is that I can see in the logs all actions being performed, but ACTION_CONFIRM_NOTIFICATION never shows up for some reason, despite the transaction being successful.
Any idea why this is?
What am I missing?

ACTION_CONFIRM_NOTIFICATION is never used in BillingReceiver and I have no idea why they declare it in handleCommand as CONFIRM_NOTIFICATION should not be done here in the first place

The BillingReceiver is waiting for the IN_APP_NOTIFY message sent from the market. Then it would start the confirmation through the service. Does your receiver receive the IN_APP_NOTIFY message?
I have a similar issue too. My application does never get back the notification from the Market application. So there's actually nothing to confirm for your app.
It seems like it is a known issue for a long time already as you can see here: http://code.google.com/p/marketbilling/issues/detail?id=14

Related

What are the reasons GCM ID is not available in some cases in android

Hello we are working on an android application in which GCM plays very important role in such as marketing purpose, push some important information to users etc.
It's working fine in 60-70% cases but other 30-40% it does not work. So rest of users never receive any notification which is useful for only to them.
This is the reason we are loosing users everyday. Below is my code to get the registration ID of GCM.
String msg = "";
int exceptionOccurRetry = 0;
while (exceptionOccurRetry < 5) {
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
int retry = 0;
while (retry < 5 && regid.length() == 0) {
regid = gcm.register(SENDER_ID);
++retry;
}
msg = "Device registered, registration ID=" + regid;
if (!regid.equals("")) {
// You should send the registration ID to your
// server
// over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your
// app.
sendRegistrationIdToBackend();
}
break;
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
exceptionOccurRetry++;
}
}
We are looking what are the reasons such that GCM id is not available for some users.
We know only one reason that If user device doesn't have a Google Play Services installed on user phone then it does not work.
We are looking some more reasons to solve this problem.
One of the most common reasons why it is not available is that the user does not have google play services installed or is using a blocker.
You should also note that the GCM id should be refreshed if your application version has changed. You should be saving a unique device id to SharedPreferences and always check if it is the same, otherwise you should initiate the registration process again.
It is also a good idea to refresh the id from time to time.
Our team members are trying to send the notification, if it fails, they wait about 60 miliseconds or seconds (i'm not sure) for this push notification to be send again, if it still does not work, they wait twice the time, and so on ...
And you have to evaluate the response from google, there is a error string under:
std::string error = response["results"][0]["error"].asString();
Which gives you the information if a users account has been moved to, if so you can use:
Json::Value newRegistrationId = response["results"][0]["registration_id"];
to get the new ID.
if gcm id is null try to start a background task and get the id.check the below code
private void registerInBackground() {
new AsyncTask() {
#Override
protected String doInBackground(Object[] params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
sendRegistrationIdToBackend();
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
#Override
protected void onPostExecute(Object msg) {
// mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
}
Check if app was updated; if so, it must clear the registration ID since the existing registration ID is not guaranteed to work with the new app version.
When the application version changes, the registration id should change too. so you should save the last registration id to backend size. Maybe your problem is this.
If your question all focus on the id registration you can ignore my answer..
Sorry my answer may be the wrong answer to your question.
I just want to show there may be some other 'factor' influence the message delivery..
Do your send all message on only single recipients?
One of the most useful features in GCM is support for up to 1,000
recipients for a single message.
http://developer.android.com/training/cloudsync/gcm.html#
Sometimes our PHP also lose message sending to the registered device...
The message must less than 4K(do your GCM contains Pictures?)
You may have already read this..
Implementing GCM Client on Android
http://developer.android.com/google/gcm/client.html
Is that piece of code inside an asyncTask if not that might be your error in certain version (dont remember which) gcm registration gives NetworkOnMainThreadException for some reason, they updated that later but I had that same problem some time ago, this is the piece of code I have used hope it helps you out:
private void performRegisterGCM(){
//Check for GCM availability
if(checkPlayServices(this)){
// If this check succeeds, proceed with normal processing.
// Otherwise, prompt user to get valid Play Services APK.
gcm = GoogleCloudMessaging.getInstance(this);
String regId = mPreferences.getGcmRegistrationId();
if (regId.isEmpty()){
RegisterGCM();
}else{
log.d("regId: "+regId);
}
} else {
// Status is a random integer
GooglePlayServicesUtil.getErrorDialog(status, this, RQS_GooglePlayServices).show();
}
}
public static boolean checkPlayServices(Activity mActivity){
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mActivity);
return resultCode == ConnectionResult.SUCCESS
}
private void RegisterGCM(){
new AsyncTask<Void,Void,String>() {
#Override
protected String doInBackground(Void... params) {
String regid = "";
try{
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(mContext);
}
regid = gcm.register(Util.SENDER_ID);
}catch(Exception e){
log.e(e.getMessage());
}
return regid;
}
#Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mPreferences.setGcmRegistrationId(s);
//TODO send regid to server with all the other info
sendGCMIDtoBackend(s);
}
}.execute(null, null, null);
}
Note that the checkPlayServices also gave me a lot of problems I had it like this:
public static boolean checkPlayServices(Activity mActivity){
int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mActivity);
if(resultCode!= ConnectionResult.SUCCESS) {
if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)){
GooglePlayServicesUtil.getErrorDialog(
resultCode,
mActivity,
PLAY_SERVICES_RESOLUTION_REQUEST
).show();
} else{
log.d("DEVICE NOT SUPPORTED");
exit(true);
}
return false;
}
return true;
}
Then changed it as you can see in the first piece of code, because for some reason when it falls in isUserRecoverableError(result) it gives a lot of headaches... Everything here is from an actual working project and the code snippets were obtained in http://developer.android.com/google/gcm/client.html and modified to work correctly. Hope this helps you out, Good Luck...
How about the backend? If you delete the ID from the server's database, the user will never receive a notification unless you update the app version?

Identify Messages using Message API Android

I have probably a simple question but has me stumped. For my Android Wear application, I have two sensors working (step counter and heartrate).The wear app then sends these values back to the mobile application. I am sending them using the Message API. My stepcount sendMessage() and heartrate sendMessage() method look the same. Here is my heartrate sendMessage method.
private void sendMessageToHandheld(final String message) {
if (mGoogleApiClient == null)
return;
Log.d(LOG_TAG,"sending a message to handheld: "+message);
// use the api client to send the heartbeat value to our handheld
final PendingResult<NodeApi.GetConnectedNodesResult> nodes = Wearable.NodeApi.getConnectedNodes(mGoogleApiClient);
nodes.setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
#Override
public void onResult(NodeApi.GetConnectedNodesResult result) {
final List<Node> nodes = result.getNodes();
if (nodes != null) {
for (int i=0; i<nodes.size(); i++) {
final Node node = nodes.get(i);
Wearable.MessageApi.sendMessage(mGoogleApiClient, node.getId(), message, bytes);
}
}
}
});
}
Problem is I am only using one messageReceived method on the mobile. So I cant differentiate from the step value coming in or the heartrate value coming in.
#Override
public void onMessageReceived(MessageEvent messageEvent) {
super.onMessageReceived(messageEvent);
Log.d(LOG_TAG, "received a message from wear: " + messageEvent.getPath());
if (messageEvent.getPath().contains("HEARTBEAT")){
// save the new heartbeat value
currentValue = Integer.parseInt(messageEvent.getPath());
if(handler!=null) {
// if a handler is registered, send the value as new message
Log.d(LOG_TAG, "received a heartbeat message from wear: " + messageEvent.getPath());
handler.sendEmptyMessage(currentValue);
}
}
else {
// save the new steps value
currentStepValue = Integer.parseInt(messageEvent.getPath());
if (handler != null) {
// if a handler is registered, send the value as new message
Log.d(LOG_TAG, "received a step message from wear: " + messageEvent.getPath());
handler.sendEmptyMessage(currentStepValue);
}
}
I tried passing in a byte array into the Heartrate sendMessage() and other strings as flags so that I could tell the values apart but no luck. Anyone know what the best way to go about this would be?
Any help would be appreciated.
Cheers
It seems you are sending the data inside the path attribute. This is not the correct use of this parameter.
Let's take a look at the MessageApi.sendMessage(GoogleApiClient client, String nodeId, String path, byte[] data method.
What you want to do is use String path to provide identifier for your message, for example in your case it would be step_counter and heartbeat. This way you can identify it on the other side, when you receive the message.
The sensor data should go into data field. You can put it raw there, but a better way is to create a DataMap and then serialize it into byte[]. This way you can enrich the data later easily.

What events cause GCM re-registration?

I have an Android app with a large user base that uses GCM. A server side misconfiguration resulted in zero backups for our GCM registration DB (really bad!), now there is no way to message these apps.
My question is: what client or server system events would cause GCM to re-register with the server? e.g. client apk version update, client google play services update, etc.
As an alternative, is there a way for the server to basically invalidate ALL the token registrations such that all of the Android clients would be forced to re-register?
You would have to release a new version of your app which (if your current logic doesn't already do it) should register to GCM and send the registration ID to your server. This, unfortunately, will give you the Registration IDs for only the devices that install the new version, but there's nothing better you can do.
You can add logic to your new app version that would be able to overcome this problem the next time it happens, this time without requiring a new version. For example, each time the app is launched, make an API call to your server that contains some unique ID of the app instance (not necessarily the Registration ID), and if your server doesn't find that ID in your DB (or doesn't find a Registration ID associated with it), you force the app instance to go through some registration process, that would require the app to re-send the Registration ID to your server. This still won't help you restore the Registration IDs of devices that don't launch your app, but at least you'll be able to restore the Registration IDs of all you active users.
I don't know how much this method is feasible, but still it may be useful.
You need to release a new version of your app.
Whenever an app registered with GCM launches it looks for a registration id and calls for getRegistrationId() function. We store that in shared prefs inside our app data.
Here's some code from the official docs :
private String getRegistrationId(Context context)
{
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty())
{
Log.i(TAG, "Registration not found.");
return "";
}
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion)
{
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
We use a specific key to store the registeration id in shared prefs (here it is PROPERTY_REG_ID which is already defined as public static final String PROPERTY_REG_ID = "registration_id";). Each time our app launches our it checks for the presence of this key.
So what i was suggesting was is to change this shared prefs key for the registration id.
This definitely would not be found in the shared prefs and would ask for a re-registration from the GCM and would call the registerInBackground() function.
private void registerInBackground()
{
new AsyncTask()
{
#Override
protected String doInBackground(Void... params)
{
String msg = "";
try
{
if (gcm == null)
{
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
sendRegistrationIdToBackend();
storeRegistrationId(context, regid);
}
catch (IOException ex)
{
msg = "Error :" + ex.getMessage();
}
return msg;
}
#Override
protected void onPostExecute(String msg)
{
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
}
After the registration you need to use the new shared prefs key to store the new registration id.
The drawback of this whole thing is that you can get the registration id of only those who would update the app.

Unlocks app even after payment decline

I develop an app with in app version 2 and publish it on developers Google now some one download the app and try to purchase it. On purchasing a message showed to him that "Your card is decline" now even after this message app got "unlock" . Now this is a very difficult situation that payment is not made while app got unlock. Is this issue with in app billing method(logic) in my app or its with Google.
If its in my app then is there any method which check that the person card is a "declined card" so in this method i can restrict my app not to unlock the app.
Any app will be appreciated.
I have this code in my BillingReceviver:
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Consts.ACTION_PURCHASE_STATE_CHANGED.equals(action)) {
String signedData = intent.getStringExtra(Consts.INAPP_SIGNED_DATA);
String signature = intent.getStringExtra(Consts.INAPP_SIGNATURE);
purchaseStateChanged(context, signedData, signature);
} else if (Consts.ACTION_NOTIFY.equals(action)) {
String notifyId = intent.getStringExtra(Consts.NOTIFICATION_ID);
if (Consts.DEBUG) {
Log.i(TAG, "notifyId: " + notifyId);
}
notify(context, notifyId);
} else if (Consts.ACTION_RESPONSE_CODE.equals(action)) {
long requestId = intent.getLongExtra(Consts.INAPP_REQUEST_ID, -1);
int responseCodeIndex = intent.getIntExtra(Consts.INAPP_RESPONSE_CODE,
ResponseCode.RESULT_ERROR.ordinal());
checkResponseCode(context, requestId, responseCodeIndex);
} else {
Log.w(TAG, "unexpected action: " + action);
}
}
After making ACTION_PURCHASE_STATE_CHANGED matches the action the my purchsedstatechanged calls in file BillingService
private void purchaseStateChanged(int startId, String signedData, String signature) {
ArrayList<Security.VerifiedPurchase> purchases;
DatabaseHandler db=new DatabaseHandler(this);
purchases = Security.verifyPurchase(signedData, signature);
if (purchases == null) {
return;
}
ArrayList<String> notifyList = new ArrayList<String>();
for (VerifiedPurchase vp : purchases) {
if (vp.notificationId != null) {
notifyList.add(vp.notificationId);
}
ResponseHandler.purchaseResponse(this, vp.purchaseState, vp.productId,
vp.orderId, vp.purchaseTime, vp.developerPayload);
db.addUser("com.example.app", "111", "1222");
Log.i("Usgdgfer",""+db.isUserPresent("com.example.app"));
}
if (!notifyList.isEmpty()) {
String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
confirmNotifications(startId, notifyIds);
}
}
Usually, the card has to be mapped to the Google Wallet account before the purchase is made. If the card is invalid, it will show an error while you map the card to your Google account.
Seems strange that the card is being validated during purchase. Even then, if the payment is not made properly , you should get an error response
BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
Check if you handle these errors properly after you receive them in the IabResult. Make sure you unlock your app only when you receive a proper response.

GCM - Unclear on how app update works?

I'm taking a look at GCM, I'm not sure what we need to do in the case of application update. The doc says:
"When an application is updated, it should invalidate its existing registration ID, as it is not guaranteed to work with the new version. Because there is no lifecycle method called when the application is updated, the best way to achieve this validation is by storing the current application version when a registration ID is stored. Then when the application is started, compare the stored value with the current application version. If they do not match, invalidate the stored data and start the registration process again."
So what should that look like? Something like:
public class MyActivity extends Activity {
#Override
public void onCreate(...) {
if (we are a new app version) {
// calling register() force-starts the process of getting a new
// gcm token?
GCMRegistrar.register(context, SENDER_ID);
saveLastVersionUpdateCodeToDisk();
}
}
so we just need to make sure we call GCMRegistrar.register() again ourselves in case we're a new app version?
Thanks
Yes, you should call GCMRegistrar.register again and in your broadcast receiver make sure to update your server with the new id.
This question is fairly old, but this is the code I found for GCMRegistrar.getRegistrationId(Context context) in the helper class source code.
Short answer: GCM code checks to see if the app is updated. You don't have to worry about it as long you call this method and make a call to register with GCM if the return value of this method is blank.
public static String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
// check if app was updated; if so, it must clear registration id to
// avoid a race condition if GCM sends a message
int oldVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int newVersion = getAppVersion(context);
if (oldVersion != Integer.MIN_VALUE && oldVersion != newVersion) {
Log.v(TAG, "App version changed from " + oldVersion + " to " +
newVersion + "; resetting registration id");
clearRegistrationId(context);
registrationId = "";
}
return registrationId;
}
Regarding to official documents' examples, one should check whether registration ID is created in current app version. If app is registered with older version, it must be registered again.
http://developer.android.com/google/gcm/client.html
Note that if app is updated then registration id will return as empty, so app will be registered again:
if (checkPlayServices()) {
gcm = GoogleCloudMessaging.getInstance(this);
regid = getRegistrationId(context);
if (regid.isEmpty()) {
registerInBackground();
}
} else {
Log.i(TAG, "No valid Google Play Services APK found.");
}
private String getRegistrationId(Context context) {
final SharedPreferences prefs = getGCMPreferences(context);
String registrationId = prefs.getString(PROPERTY_REG_ID, "");
if (registrationId.isEmpty()) {
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
int currentVersion = getAppVersion(context);
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}

Categories

Resources