Related
I've been trying to build some functionality into my app too allow user-generated data (EEG recordings) to be sent to a central BigQuery database.
I've never done any networking code in Java before, so I shied away from doing the POST or REST-based strategies recommended here. The BigQuery Java client library seemed to be exactly what I needed, though I was completely confused why it wouldn't officially support Android.
Still, I came across this example Android app (from Google no less) that promised to do exactly what I wanted with the BigQuery Client library. I incorporated it into my app as follows:
// .... in an AsyncTask
#Override
protected String doInBackground(String... params) {
String CSV_CONTENT = params[0];
try {
AssetManager am = MainApplication.getInstance().getAssets();
InputStream isCredentialsFile = am.open(CREDENTIALS_FILE);
BigQuery bigquery = BigQueryOptions.builder()
.authCredentials(AuthCredentials.createForJson(isCredentialsFile))
.projectId( PROJECT_ID )
.build().service();
TableId tableId = TableId.of(DATASET,TABLE);
Table table = bigquery.getTable(tableId);
int num = 0;
Log.d("Main", "Sending CSV: ");
WriteChannelConfiguration configuration = WriteChannelConfiguration.builder(tableId)
.formatOptions(FormatOptions.csv())
.build();
try (WriteChannel channel = bigquery.writer(configuration)) {
num = channel.write(ByteBuffer.wrap(CSV_CONTENT.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
Log.d("Main", e.toString());
}
Log.d("Main", "Loading " + Integer.toString(num) + " bytes into table " + tableId);
} catch (Exception e) {
Log.d("Main", "Exception: " + e.toString());
}
return "Done";
}
This runs without any errors and fires off an API call that is detected by Google Cloud Storage. However, it returns error 200 (job was cancelled) every time. I don't understand how this could be since I'm not doing anything in the code to cancel the request and I don't see how the async task I put the call in could be cancelled.
Was this just a bad example app I copied and a bad usage of the BigQuery Client? If so, what's the best way to send data to BigQuery from Android?
I'm attempting to get a list of URL's from a subreddit in order to load them into universal image loader for viewing pleasure. However, I cannot figure out where my query is going wrong. Plus, I'm not familiar with android studio's logcat as compared to eclipses logcat, so I'm not exactly sure where to look for my debugging responses.
Here's the query method:
public void queryReddit()
{
// Prepare your search string to be put in a URL
// It might have reserved characters or something
// String urlString = "";
// try {
// urlString = URLEncoder.encode(searchString, "UTF-8");
// } catch (UnsupportedEncodingException e) {
//
// // if this fails for some reason, let the user know why
// e.printStackTrace();
// Toast.makeText(this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show();
// }
// Create a client to perform networking
AsyncHttpClient client = new AsyncHttpClient();
// 11. start progress bar
setProgressBarIndeterminateVisibility(true);
// Have the client get a JSONArray of data
// and define how to respond
client.get("http://www.reddit.com/r/pics/.json",
new JsonHttpResponseHandler() {
#Override
public void onSuccess(JSONObject jsonObject) {
// 11. stop progress bar
setProgressBarIndeterminateVisibility(false);
// Display a "Toast" message
// to announce your success
Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG).show();
// // 8. For now, just log results
// Log.d("omg android", jsonObject.toString());
try
{
Log.d("go reddit yay", jsonObject.toString());
JSONObject testingData = (JSONObject) jsonObject.get("data");
JSONArray testingChildren = (JSONArray) testingData.get("children");
JSONObject testingLogData = (JSONObject) testingChildren.get(0);
JSONArray children = (JSONArray) jsonObject.get("children");
JSONObject logData = (JSONObject) children.get(0);
Log.d("go reddit yay", logData.getString("url"));
Log.d("go reddit yay", testingLogData.getString("url"));
for(int i = 0; i < 10; i++)
{
JSONObject data = (JSONObject) children.get(i);
if(data.getString("url") != null)
{
System.out.println(data.getString("url"));
}
//if the url field exists and it's a picture that univ image loader understands then add it
if(data.getString("url") != null && data.getString("url").substring(data.getString("url").length()-3).equals("png") ||
data.getString("url").substring(data.getString("url").length()-3).equals("jpg"))
{
imageUrls.add(data.getString("url"));
System.out.println(data.getString("url"));
}
//TODO I found this error: this requires android.permission.INTERACT_ACROSS_USERS_FULL
}
mPagerAdapter.notifyDataSetChanged();
} catch (JSONException e)
{
e.printStackTrace();
}
//TODO Might want to put all this data in a try catch block and do it right here.
// update the data in your custom method.
//updateData()
}
#Override
public void onFailure(int statusCode, Throwable throwable, JSONObject error)
{
// 11. stop progress bar
setProgressBarIndeterminateVisibility(false);
// Display a "Toast" message
// to announce the failure
Toast.makeText(getApplicationContext(), "Error: " + statusCode + " " + throwable.getMessage(), Toast.LENGTH_LONG).show();
// Log error message
// to help solve any problems
Log.e("omg android", statusCode + " " + throwable.getMessage());
}
});
}
Within the try catch block, the only logged information that ends up being sent to logcat is the first line, Log.d("go reddit yay", jsonObject.toString());
I can't find the responses from the other log calls which is very strange to me.
Here's the response from the first log call:
06-17 06:35:29.324 17133-17133/.wallpaper D/absfr﹕ {"data":{"after":"t3_2823ou","children":[{"data":{"media_embed":{},"author_flair_css_class":null,"score":503,"created_utc":1402931529,"clicked":false,"visited":false,"id":"28a94k","author":"JamesBDW","title":"Any Bronson fans? [1920x1080]","over_18":false,"created":1402960329,"name":"t3_28a94k","selftext_html":null,"domain":"i.imgur.com","author_flair_text":null,"secure_media":null,"num_reports":null,"edited":false,"stickied":false,"link_flair_text":null,"link_flair_css_class":null,"saved":false,"secure_media_embed":{},"subreddit_id":"t5_2qmjl","distinguished":null,"gilded":0,"url":"https:\/\/i.imgur.com\/Hq1fcSm.jpg","banned_by":null,"subreddit":"wallpaper","is_self":false,"num_comments":31,"approved_by":null,"thumbnail":"http:\/\/a.thumbs.redditmedia.com\/Dh2iU7Q0rpFogkWt.jpg","permalink":"\/r\/wallpaper\/comments\/28a94k\/any_bronson_fans_1920x1080\/","hidden":false,"likes":null,"downs":188,"ups":691,"selftext":"","media":null},"kind":"t3"},{"data":{"media_embed":{},"author_flair_css_class":null,"score":8,"created_utc":1402989714,"clicked":false,"visited":false,"id":"28cnyn","author":"ZadocPaet","title":"Active Dunes on Mars (OS) [1024x768]","over_18":false,"created":1403018514,"name":"t3_28cnyn","selftext_html":null,"domain":"nasa.gov","author_flair_text":null,"secure_media":null,"num_reports":null,"edited":false,"stickied":false,"link_flair_text":null,"link_flair_css_class":null,"saved":false,"secure_media_embed":{},"subreddit_id":"t5_2qmjl","distinguished":null,"gilded":0,"url":"http:\/\/www.nasa.gov\/sites\/default\/files\/styles\/1024x768_autoletterbox\/public\/pia18244.jpg","banned_by":null,"subreddit":"wallpaper","is_self":false,"num_comments":1,"approved_by":null,"thumbnail":"http:\/\/a.thumbs.redditmedia.com\/dFTBquSWiMSjK0aZ.jpg","permalink":"\/r\/wallpaper\/comments\/28cnyn\/active_dunes_on_mars_os_1024x768\/","hidden":false,"likes":null,"downs":3,"ups":11,"selftext":"","media":null},"kind":"t3"},{"data":{"media_embed":{"content":"<iframe class=\"embedly-embed\" src=\"\/\/cdn.embedly.com\/widgets\/media.html?src=http%3A%2F%2Fimgur.com%2Fa%2F0jeZf%2Fembed&url=http%3A%2F%2Fimgur.com%2Fa%2F0jeZf&image=http%3A%2F%2Fi.imgur.com%2F2PdUiuE.jpg&key=2aa3c4d5f3de4f5b9120b660ad850dc9&type=text%2Fhtml&schema=imgur\" width=\"550\" height=\"550\" scrolling=\"no\" frameborder=\"0\" allowfullscreen><\/iframe>","scrolling":false,"height":550,"width":550},"author_flair_css_class":null,"score":1,"created_utc":1403004127,"clicked":false,"visited":false,"id":"28czid","author":"smessies","title":"I found a wallpaper album full of iconic design chairs. I tought there were some important ones missing so i started adding some myself. work in progress! [1920x1200]","over_18":false,"created":1403032927,"name":"t3_28czid","selftext_html":null,"domain":"imgur.com","author_flair_text":null,"secure_media":null,"num_reports":null,"edited":false,"stickied":false,"link_flair_text":null,"link_flair_css_class":null,"saved":false,"secure_media_embed":{},"subreddit_id":"t5_2qmjl","distinguished":null,"gilded":0,"url":"http:\/\/imgur.com\/a\/0jeZf","banned_by":null,"subreddit":"wallpaper","is_self":false,"num_comments":0,"approved_by":null,"thumbnail":"http:\/\/b.thumbs.redditmedia.com\/GYaN5fyJfY8fI8xE.jpg","permalink":"\/r\/wallpaper\/comments\/28czid\/i_found_a_wallpaper_album_full_of_iconic_design\/","hidden":false,"likes":null,"downs":0,"ups":1,"selftext":"","media":{"type":"imgur.com","oembed":{"thumbnail_height":1200,"author_url":"http:\/\/imgur.com\/user\/smessies","width":550,"type":"rich","version":"1.0","thumbnail_url":"http:\/\/i.imgur.com\/2PdUiuE.jpg","thumbnail_width":1920,"title":"imgur: the simple image sharer","height":550,"description":"Imgur is home to the web's most popular image content, curated in real time by a dedicated community through commenting, voting and sharing.","author_name":"smessies","html":"<iframe class=\"embedly-embed\" src=\"\/\/cdn.embedly.com\/widgets\/media.html?src=http%3A%2F%2Fimgur.com%2Fa%2F0jeZf%2Fembed&url=http%3A%2F%2Fimgur.com%2F
no idea what to do.
One last debugging info is that the toast for the onSuccess method does appear, so it is clearly successful in it's query, I'm just doing something wrong in interpreting the data.
From your description it seems like a statement after the first Log.d("go reddit yay", ...); call throws an exception which you can't see.
Try replacing your exception handling code:
} catch (JSONException e)
{
e.printStackTrace();
}
with this:
} catch (Throwable t)
{
Log.e("omg android", "Exception in onSuccess()", t);
}
and check if any exceptions are logged.
I am working on Android Product which supports in app purchase and have large quantity of users doing in app purchases , out of all users some users are facing "BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED" error and the response returned is 7 .
I am calling consume call after every purchase being awarded , and also verifying my inventory when the InApp is setup on the start of application and check for any owned item and call the consume on it.
Any recommendation's to solve the issue .
See here. Google says that you MUST consume managed inapp items. If you don't, the user cannot purchase another one. After successful purchase, use:
int response = mService.consumePurchase(3, getPackageName(), token);
You get the token from the purchaseData JSON object:
final String token = jo.getString("purchaseToken");
But if you for some reason did not consume a purchase, you are stuck.
I ran into the same problem because I upgraded to iap api v3 and in v2 this was not a problem.
When you get this error, try to consume all purchases of the given productId. Or just consume everything purchased during setup of the service. Note that you might want to actually provision the purchase after consumePurchase() successfully returns depending on the semantics of your application.
Call the following code after the service is connected (in onServiceConnected()) and of course execute it in background:
String continuationToken="";
boolean hasMorePurchases=false;
do {
try {
Bundle purchases = mService.getPurchases(3, activity.getPackageName(), "inapp", continuationToken);
int response = purchases.getInt("RESPONSE_CODE");
if (response == 0) {
continuationToken = purchases.getString("INAPP_CONTINUATION_TOKEN");
if(!TextUtils.isEmpty(continuationToken)) hasMorePurchases=true;
final ArrayList<String> purchaseDataList = purchases.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
for(String purchaseJSON : purchaseDataList) {
JSONObject object = new JSONObject(purchaseJSON);
String productId = object.getString("productId");
String orderId = object.getString("orderId");
String purchaseToken = object.getString("purchaseToken");
Log.i(getClass().getSimpleName(),"consuming purchase of " + productId + ", orderId " + orderId);
mService.consumePurchase(3, activity.getPackageName(), purchaseToken);
}
} else {
Log.e(getClass().getSimpleName(), "could not get purchases: " + response);
}
} catch (RemoteException e) {
Log.e(getClass().getSimpleName(), "RemoteException during getPurchases:", e);
} catch (JSONException e) {
Log.e(getClass().getSimpleName(), "JSONException during getSkuDetails:", e);
}
} while(hasMorePurchases);
Note that each call to getPurchases returns a maximum of 700 purchases, so you need to use the continuation token to get more.
You can of course just use this code if you get the ALREADY_OWNED error code and only for the productId involved. Afterwards, try start the purchase again.
edit 4/15: Catching nullpointer in IabHelper appears to have stopped this problem. I am no longer seeing the exceptions being thrown, I'm going to accept this as an answer.
edit 4/04: A little bit of a deeper dive. There are try catch blocks that handle RemoteExceptions and JSONExceptions for the queryPurchases method, but no NullPointerException handling. What I am going to try is include NullPointer Exception handling so IabHelper looks like this when trying to querySkuDetails:
catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NullPointer while refreshing inventory.", e);
}
I just filed a bug on this:
https://code.google.com/p/marketbilling/issues/detail?id=114
edit 3/25: well, still receiving this crash... now it happens while trying to get a context at line 3 of the following excerpt from IabHelper:
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
logDebug("Querying owned items, item type: " + itemType);
logDebug("Package name: " + mContext.getPackageName());
This is frustrating because in my manifest I always use the full path name of my app for "name".
Example "com.myappname.blah.ClassName"
I've also tried passing this, MyClass.this, getApplicationContext() to mHelper. However they all produce the same NullPointer results randomly from devices in the wild. I also tried name=".MyClass" in the manifest. This is what it looks like currently:
mHelper = new IabHelper(MyClass.this, myKey);
edit 3/18/13: I am still receiving exceptions, even with the new IabHelper version deployed on 3/17.
I am starting to see a pattern here, that the crashes are all when trying to get a context when executing mContext.getPackageName(). I'm curious why this works on all of my test devices, and I can't reproduce this crash, and only seems to be on a small number of devices.
Here is the new crash:
java.lang.NullPointerException
at com.myapp.util.IabHelper.queryPurchases(SourceFile:836)
at com.myapp.util.IabHelper.queryInventory(SourceFile:558)
at com.myapp.util.IabHelper.queryInventory(SourceFile:522)
at com.myapp.util.IabHelper$2.run(SourceFile:617)
at java.lang.Thread.run(Thread.java:1019)
Caused by IabHelper...
line 836: logDebug("Package name: " + mContext.getPackageName());
edit 3/17/13: I see that there have been many bug fixes published over the past several months, I will try the latest code available here and see if this resolves the problem:
https://code.google.com/p/marketbilling/source/browse/v3/src/com/example/android/trivialdrivesample/util
In one of my apps, I am using the billing API and the boilerplate code included with it.
I am using the latest version of billing API available via the SDK manager as of 3/16/2013.
In my activity, I query the inventory using the following:
final List<String> skuList = new ArrayList<String>();
skuList.add("sku1");
skuList.add("sku2");
skuList.add("sku3");
if (skuList != null) {
if (skuList.size() > 0) {
try {
mHelper.queryInventoryAsync(true, skuList, mGotInventoryListener);
} catch (Exception e) {
ACRA.getErrorReporter().handleException(e);
}
}
}
I am receiving multiple NullPointerException reports in the wild from the IabHelper class for the following devices. I can't reproduce the issue and can't find any information regarding these crashes, and is the reason why I am posting this question.
I have countless other checks for nulls and try/catch blocks in the "developer facing" part of the billing API, including within onQueryInventoryFinished, so I know this exception is not being thrown from "my code" (because I'm not capturing crashes from any of my app's classes), but instead is being thrown from within the IabHelper itself. I have not modified the IabHelper other than this recommended fix: https://stackoverflow.com/a/14737699
Crash #1 Galaxy Nexus
java.lang.NullPointerException
at com.myapp.util.IabHelper.querySkuDetails(SourceFile:802)
at com.myapp.util.IabHelper.queryInventory(SourceFile:471)
at com.myapp.util.IabHelper$2.run(SourceFile:521)
at java.lang.Thread.run(Thread.java:856)
Caused by IabHelper...
line 802: Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(), ITEM_TYPE_INAPP, querySkus);
Crash #2 Samsung GT-S5570L
java.lang.NullPointerException
at com.myapp.util.IabHelper.queryPurchases(SourceFile:735)
at com.myapp.util.IabHelper.queryInventory(SourceFile:465)
at com.myapp.util.IabHelper$2.run(SourceFile:521)
at java.lang.Thread.run(Thread.java:1019)
Caused by IabHelper...
line 735: Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(), ITEM_TYPE_INAPP, continueToken);
edit 4/15: Catching nullpointer in IabHelper appears to have stopped this problem. I am no longer seeing the exceptions being thrown, I'm going to accept this as an answer.
edit 4/04: A little bit of a deeper dive. There are try catch blocks that handle RemoteExceptions and JSONExceptions for the queryPurchases method, but no NullPointerException handling. What I am going to try is include NullPointer Exception handling so IabHelper looks like this when trying to querySkuDetails:
catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NullPointer while refreshing inventory.", e);
}
I just filed a bug on this:
https://code.google.com/p/marketbilling/issues/detail?id=114
Change
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
}
}
to
if (querySkuDetails) {
try {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
}
} catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NPE while refreshing inventory.", e);
}
}
Change
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreSubsSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
}
}
to
if (querySkuDetails) {
try {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreSubsSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
}
} catch (NullPointerException e) {
throw new IabException(IABHELPER_UNKNOWN_ERROR, "NPE while refreshing inventory.", e);
}
}
You are probably using async operations. The current IabHelper is not safe in case you use the ...async methods. The problem is that in any moment an async operation is running dispose can be called on the main thread. In this case you will get NullPointerExceptions and IllegalStateExceptions.
Here is the patch fixing it:
Index: src/com/evotegra/aCoDriver/iabUtil/IabHelper.java
===================================================================
--- src/com/evotegra/aCoDriver/iabUtil/IabHelper.java (revision 1162)
+++ src/com/evotegra/aCoDriver/iabUtil/IabHelper.java (working copy)
## -86,7 +86,10 ##
// Is an asynchronous operation in progress?
// (only one at a time can be in progress)
- boolean mAsyncInProgress = false;
+ volatile boolean mAsyncInProgress = false;
+
+ // is set to true if dispose is called while a thread is running. Allows graceful shutdown
+ volatile boolean mDisposeRequested = false;
// (for logging/debugging)
// if mAsyncInProgress == true, what asynchronous operation is in progress?
## -285,6 +288,12 ##
* disposed of, it can't be used again.
*/
public void dispose() {
+ // do not dispose while an async Thread is running. Will cause all kinds of exceptions.
+ // In this case dispose must be called from thread after setting mAsyncInProgress to true
+ if (mAsyncInProgress) {
+ mDisposeRequested = true;
+ return;
+ }
logDebug("Disposing.");
mSetupDone = false;
if (mServiceConn != null) {
## -827,6 +836,7 ##
logDebug("Ending async operation: " + mAsyncOperation);
mAsyncOperation = "";
mAsyncInProgress = false;
+ if (mDisposeRequested) IabHelper.this.dispose();
}
Or download the patch here.
http://code.google.com/p/marketbilling/issues/detail?id=139&thanks=139&ts=1375614409
Slightly modify the beginning of the queryPurchases method to look like this:
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
// Query purchases
//logDebug("Querying owned items, item type: " + itemType);
//logDebug("Package name: " + mContext.getPackageName());
boolean verificationFailed = false;
String continueToken = null;
do {
// logDebug("Calling getPurchases with continuation token: " + continueToken);
if(mDisposed || mService==null) return IABHELPER_UNKNOWN_ERROR;
Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
itemType, continueToken);
Thanks to sebastie for pointing out the cause of this.
tmanthey patch also requires
mDisposeRequested = false;
after the disposing takes place
If you're getting this error on the emulator, it may be a very simple thing which happens in more than a half cases.
Check that you're using Google API SDK and not regular SDK!!!
The IabHelper is obsolete and has been replaced by the BillingClient.
See https://developer.android.com/google/play/billing/billing_library.html
I have tried for several days to solve this problem, using the Dungeons demo code that comes with the SDK. I've tried to Google for an answer but can't find one.
In the Dungeons demo, I passed my public key from the dev console.
Signed the apk and uploaded to console without publish.
Testing for both android.test.purchased & product list created on console with published for subscription (The main feature I want for my app).
But still I get an error of Signature verification failed and then the signature does not match data. How can I solve this?
public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature)
{
if (signedData == null) {
Log.e(TAG, "data is null");
return null;
}
if (Consts.DEBUG) {
Log.i(TAG, "signedData: " + signedData);
}
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {
String base64EncodedPublicKey = "MIIBIjA....AQAB";
PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
verified = Security.verify(key, signedData, signature);
if (!verified) {
Log.w(TAG, "signature does not match data.");
return null;
}
}
}
public static boolean verify(PublicKey publicKey, String signedData, String signature)
{
if (Consts.DEBUG) {
Log.i(TAG, "signature: " + signature);
}
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
}
return false;
}
This problem is still going on in the current Google billing version. Basically the android.test.purchased is broken; After you buy android.test.purchased the verifyPurchase function in Security.java will always fail and the QueryInventoryFinishedListener will stop at the line if (result.isFailure()); this is because the android.test.purchased item always fails the TextUtils.isEmpty(signature) check in Security.java as it is not a real item and has no signature returned by the server.
My advice (from lack of any other solution) is to NEVER use "android.test.purchased". There are various code tweaks on the net but none of them work 100%.
If you have used the android.test.purchased then one way to get rid of the error is to do the following:-
Edit Security.java and change the "return false" line in the verifyPurchase to "return true" - this is temporary, we'll be putting it back in a minute.
In your QueryInventoryFinishedListener, after the "if (result.isFailure()) {...}" lines add the following to consume and get rid of your never ending android.test.purchased item:
if (inventory.hasPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD)) {
mHelper.consumeAsync(inventory.getPurchase(SKU_ANDROID_TEST_PURCHASE_GOOD),null);
}
Run your app so the consunmeAsync happens, this gets rid of the "android.test.purchased" item on the server.
Remove the consumeAsync code (or comment it out).
Back in the Security.java, change the "return true" back to "return false".
Your QueryInventoryFinishedListener will no longer error on the verify, everything is back to "normal" (if you can call it that). Remember - don't bother using android.test.purchased again as it will just cause this error again... it's broke! The only real way to test your purchasing it to upload an APK, wait for it to appear, and then test it (the same APK) on your device with logging enabled.
Yes, the problem still occurs.
After I bought android.test.purchased I start getting the error on quering the inventory.
It is possible to fix your phone by just clearing data of Google Play Store application and running Google Play one time.
When you clear data of Google Play it forgets that you bought android.test.purchased
Please check that base64EncodedPublicKey and the one from the Play Developer Console are equal.
Once you re-upload the APK in the Developer Console, the public key may change, if so update your base64EncodedPublicKey.
You can skip the verifying process for those "android.test.*" product ids. If you are using the sample code from the TrivialDrive example, open IabHelper.java, find the following line code, change it from
if (Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }
into
boolean verifySignature = !sku.startsWith("android.test."); // or inplace the condition in the following line
if (verifySignature && !Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { ... }
It's harmless, even if you forgot to rollback the code. So, you can continue to test the further workflow step.
Based on GMTDev's answer, this is what I do in order to fix the testing issues when consuming products in the simplest possible way. In Security.java, replace the verifyPurchase() method with this:
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return BuildConfig.DEBUG; // Line modified by Cristian. Original line was: return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
I only modified one line (see comment), and this way you can keep the code like that for debugging and still publish your release versions safely.
The error is caused because of the wrong license key. Maybe the license key is probably from your another app.
The solution is to use the proper license key from :
Play console > App > Development Tools > Licensing & in-app billing
What worked for me, while using In-app Billing v3 and the included utility classes, was consuming the test purchase within the returned onActivityResult call.
No changes to IabHelper, Security, or any of the In-app Billing util classes are needed to avoid this for future test purchases.
If you have already tried purchasing the test product and are now stuck on the purchase signature verification failed error, which you likely are since you are looking up answers for this error, then you should:
make the changes that GMTDev recommended
run the app to ensure that it consumes the product
remove/undo GMTDev's changes
implement the code below within onActivityResult.
Not only does this allow for the purchase testing process to be fluid but this should also avoid any conflicting issues with iab returning the " Item Already Owned " error when attempting to repurchase the test product.
If this is being called from within a fragment and your fragment's onActivityResult isn't being called then be sure to call YourFragmentName.onActivityResult(requestCode, resultCode, data) from your parent ActivityFragment if necessary. This is explained in more detail in Calling startIntentSenderForResult from Fragment (Android Billing v3).
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_PURCHASE) {
//this ensures that the mHelper.flagEndAsync() gets called
//prior to starting a new async request.
mHelper.handleActivityResult(requestCode, resultCode, data);
//get needed data from Intent extra to recreate product object
int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
// Strip out getActivity() if not being used within a fragment
if (resultCode == getActivity().RESULT_OK) {
try {
JSONObject jo = new JSONObject(purchaseData);
String sku = jo.getString("productId");
//only auto consume the android.test.purchased product
if (sku.equals("android.test.purchased")) {
//build the purchase object from the response data
Purchase purchase = new Purchase("inapp", purchaseData, dataSignature);
//consume android.test.purchased
mHelper.consumeAsync(purchase,null);
}
} catch (JSONException je) {
//failed to parse the purchase data
je.printStackTrace();
} catch (IllegalStateException ise) {
//most likely either disposed, not setup, or
//another billing async process is already running
ise.printStackTrace();
} catch (Exception e) {
//unexpected error
e.printStackTrace();
}
}
}
}
It will only remove the purchase if it's sku is "android.test.purchased" so it should be safe to use.
This Solution worked for me. I changed the new verifyPurchase method in purchase class with old one.
Signature verification fails only for the default test product.
A quick fix :
Goto IabHelper class.
Invert the if conditions of Security.verifyPurchase.
Thats it!
Remember to revert the changes when test product is replaced by actual product
Ran into the same issue (signature verification, and getting rid of the test purchase) today (Oct 30, 2018).
The signature issue is probably being caused by the fact that these test sku's are not really part of your app, and are thus do not have your app's signature. I did open a ticket with Google, but not sure if they can fix this. The workaround, as others pointed out, is to replace the code
if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature())) {
with
if (verifyValidSignature(purchase.getOriginalJson(), purchase.getSignature()) ||
(purchase.getSku().startsWith("android.test.")) ) {
Regarding "how to get rid of the purchase of android.test.purchased SKU", I found that a simple reboot of the device, followed by waiting for a minute or so and/or re-starting your app a couple of times fixed it for me (i.e. I didn't have to 'consume' the purchase by code). I am guessing that the wait is needed so that the Play store completes syncing with Google's servers. (Not sure if this will continue to work this way in the future, but if it works for you now, this might help you move forward.)
Check this answer:
Is the primary account on your test device the same as your Google
Play developer account?
If not you won't get signatures on the android.test.* static responses
unless the app has been published on Play before.
See the table at
http://developer.android.com/guide/market/billing/billing_testing.html#static-responses-table
for the full set of conditions.
And it's comment:
I don't think the static ids return signature anymore. See
https://groups.google.com/d/topic/android-developers/PCbCJdOl480/discussion
Also, previously the sample code (used by many big apps) from Google Play Billing Library allowed an empty signature. That's why it's static purchases worked there.
But it was a security hole, so when it was published, Google submitted an update.
I have the same problem and follow #Deadolus said based on https://www.gaffga.de/implementing-in-app-billing-for-android/
The key point is we need to make the SKU is consumable even the inventory query result is failed. Below is the sample how i did that.
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// Is it a failure?
if (result.isFailure()) {
try {
Purchase purchase = new Purchase("inapp", "{\"packageName\":\"PACKAGE_NAME\","+
"\"orderId\":\"transactionId.android.test.purchased\","+
"\"productId\":\"android.test.purchased\",\"developerPayload\":\"\",\"purchaseTime\":0,"+
"\"purchaseState\":0,\"purchaseToken\":\"inapp:PACKAGE_NAME :android.test.purchased\"}",
"");
} catch (JSONException e) {
e.printStackTrace();
}
mHelper.consumeAsync(purchase, null);
complain("Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/
}
};
Replace PACKAGE_NAME in the code above with the package name of your app.
This is what worked for me:
Call BillingClient.querySkuDetailsAsync to query if item if available
Wait for SkuDetailsResponseListener.onSkuDetailsResponse
Wait another 500ms
Start purchase using BillingClient.launchBillingFlow...
The step 3 shouldn't be necessary because when I received onSkuDetailsResponse it should be OK but it isn't, had to wait a little bit. After that purchase works, no more "Item not available error". This is how I tested it:
clear my app data
clear Google Play data
run app
purchase android.test.purchased
try to purchase my items (it fails with item not available)
use my solution above, it works
For Cordova and Hybrid apps you need to use this.iap.subscribe(this.productId) method to subscription InAppPurchase.
Following are the code working fine for me:
getProdutIAP() {
this.navCtrl.push('subscribeDialogPage');
this.iap
.getProducts(['productID1']).then((products: any) => {
this.buy(products);
})
.catch((err) => {
console.log(JSON.stringify(err));
alert('Finished Purchase' + JSON.stringify(err));
console.log(err);
});
}
buy(products: any) {
// this.getProdutIAP();
// alert(products[0].productId);
this.iap.subscribe(products[0].productId).then((buydata: any) => {
alert('buy Purchase' + JSON.stringify(buydata));
// this.sub();
}).catch((err) => {
// this.navCtrl.push('subscribeDialogPage');
alert('buyError' + JSON.stringify(err));
});
}
sub() {
this.platform.ready().then(() => {
this.iap
.subscribe(this.productId)
.then((data) => {
console.log('subscribe Purchase' + JSON.stringify(data));
alert('subscribe Purchase' + JSON.stringify(data));
this.getReceipt();
}).catch((err) => {
this.getReceipt();
alert('subscribeError' + JSON.stringify(err));
console.log(err);
});
})
}