Android Licensing Server on a device - android

I'm trying to figure out the whole Android licensing thing, and getting frustrated.
In the emulator, I run the app with no account, or one that isn't in the testing environment, and it seems to work correctly, returning the not licensed response and pops up the buy the app now message.
When I try to run it on an actual Android device, it returns licensed every time, even though the device account isn't one that is in the testing environment.
Also, even though it returns licensed, the "checking license" box never goes away, unless you click cancel. Then it just lets you use the app as if it was licensed.
It's mostly C&P from the example, with a few changes. I removed the check license button and the status text box.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
// Try to use more data here. ANDROID_ID is a single point of attack.
String deviceId = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
// Library calls this when it's done.
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
// Construct the LicenseChecker with a policy.
mChecker = new LicenseChecker(
this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceId)),
BASE64_PUBLIC_KEY);
doCheck();
ArrayAdapter<String> booksAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mBooks);
this.setListAdapter(booksAdapter);
}
protected Dialog onCreateDialog(int id) {
// We have only one dialog.
return new AlertDialog.Builder(this)
.setTitle(R.string.unlicensed_dialog_title)
.setMessage(R.string.unlicensed_dialog_body)
.setPositiveButton(R.string.buy_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(
"http://market.android.com/details?id=" + getPackageName()));
startActivity(marketIntent);
finish();
}
})
.setNegativeButton(R.string.quit_button, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.create();
}
private void doCheck() {
setProgressBarIndeterminateVisibility(true);
alertbox("status", getString(R.string.checking_license));
mChecker.checkAccess(mLicenseCheckerCallback);
}
protected void alertbox(String title, String mymessage)
{
new AlertDialog.Builder(this)
.setMessage(mymessage)
.setTitle(title)
.setCancelable(true)
.setNeutralButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton){}
})
.show();
}
private void displayResult(final String result) {
mHandler.post(new Runnable() {
public void run() {
alertbox("status", result);
setProgressBarIndeterminateVisibility(false);
}
});
}
private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
public void allow() {
if (isFinishing()) {
// Don't update UI if Activity is finishing.
return;
}
// Should allow user access.
//displayResult(getString(R.string.allow));
}
public void dontAllow() {
if (isFinishing()) {
// Don't update UI if Activity is finishing.
return;
}
//displayResult(getString(R.string.dont_allow));
// Should not allow access. In most cases, the app should assume
// the user has access unless it encounters this. If it does,
// the app should inform the user of their unlicensed ways
// and then either shut down the app or limit the user to a
// restricted set of features.
// In this example, we show a dialog that takes the user to Market.
showDialog(0);
}
public void applicationError(ApplicationErrorCode errorCode) {
if (isFinishing()) {
// Don't update UI if Activity is finishing.
return;
}
// This is a polite way of saying the developer made a mistake
// while setting up or calling the license checker library.
// Please examine the error code and fix the error.
String result = String.format(getString(R.string.application_error), errorCode);
displayResult(result);
}
}
#Override
protected void onDestroy() {
super.onDestroy();
mChecker.onDestroy();
}
I just don't know what I need to change to make it work... or if the license is somehow cached (even though this is the first time I've run it on this device) and if I can uncache it without wiping the device, as that'll be nice for when I do testing on other apps.
Also, how do I remove the "checking license" message without having to click the cancel button... should I just make it so that it doesn't show up?

I'm just getting into licensing myself so don't take this as gospel but a few things stick out:
or if the license is somehow cached (even though this is the first time I've run it on this device) and if I can uncache it without wiping the device, as that'll be nice for when I do testing on other apps.
You are using the ServerManagedPolicy so the approval will be cached and obfuscated. This is the recommended way to do it. ( I assume to provide a better user experience and better response time ) In order to debug your approval you need to log into your market profile and change the "Test response" option. You need to use a device that has the same account as your publisher profile for the test response to work for a app that isn't released to the market yet.
You also have no code in your allow() method for your MyLicenseCheckerCallback class which should probably be where you clear the dialog (outside the isFinishing conditional).
if I can uncache it without wiping the device, as that'll be nice for when I do testing on other apps
Based on LicenseValidator.java It looks like the approval is stored in a prefs file at com.android.vending.licensing.ServerManagedPolicy in private mode. You can use the sharedpreferences editor to clear it from another place in the app.
Again I'm not a pro on this yet so I could be wrong but I think you might be able to troubleshoot your bug if you get it configured right.

Related

can not update Data in Kinvey Database

I am trying to update a record in kinvey collection but it keep saying insufficient credentials
i found a similar question on the forum that said i have to setGloballyWriteable to the acl of my model i have done that but still have the error
here is my code
public void loadMenuHelper(){
menu_list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Order order = my_orders[position];
order.getAcl().setGloballyWriteable(true);
order.getAcl().setGloballyReadable(true);
int requests = order.getRequests();
////////////have the handler
AlertDialog.Builder alert = new AlertDialog.Builder(
new ContextThemeWrapper(ImageTargets.this, R.style.AlertDialogCustom));
LinearLayout layout = new LinearLayout(ImageTargets.this);
layout.setOrientation(LinearLayout.VERTICAL);
alert.setTitle("There are " + requests + "Before you");
alert.setView(layout);
alert.setPositiveButton("Create", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
///////////////
order.getAcl().setGloballyWriteable(true);
order.getAcl().setGloballyReadable(true);
order.setRequests(order.getRequests() + 1);
AsyncAppData<Order> myevents = mKinveyClient.appData("Order", Order.class);
myevents.save(order, new KinveyClientCallback<Order>() {
#Override
public void onFailure(Throwable e) {
Log.i("TAG", "failed to save event data" + e.getMessage());
Log.i("TAG", sharedpreferences.getString("owner_name", ""));
}
#Override
public void onSuccess(Order r) {
Log.d("TAG", "saved data for entity " + r.getName());
Toast.makeText(getApplicationContext(), "Your Order was Created Sucessfully", Toast.LENGTH_SHORT).show();
}
});
}
////////////
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// what ever you want to do with No option.
}
});
alert.show();
////////////////
}
});
///////////////
}
Considering that you have a user properly logged in, this seems to be related to collection permissions.
Your collection is in shared mode, that's why you are able to read the order object created by somebody else. But, because only creator can modify the object, you are getting "insufficient credentials" error.
You have two options here. The first option is to change collection permissions to public so that anybody can edit any order object. You can do that in Kinvey web console by going to settings tab in the data browser for the Order collection. After this, you won't need to use setGloballyWriteable/setGloballyReadable.
Other option (better suitable according to me) is to create another collection for requests for specific orders. In this new collection, you will be able to store a reference of the order object as well as store user information about the user who requested it.
You can find necessary documentation related to collection permissions here - http://devcenter.kinvey.com/android/guides/security

Android in app purchase hanging the application

I have an Android app that launches the purchase flow from a dialog within a Fragment.
The purchase flow is launched like:
config.getIabHelper().launchPurchaseFlow(
faActivity,
sku,
Constantes.SOLICITUD_COMPRA,
faActivity.mPurchaseFinishedListener,
purchaseIdentifier);
Where:
config.getIabHelper() returns an instance of the IabHelper class as implemented in the google documentation (the same used in the trivialgame example)
faActivity is the parent activity of the fragment
Constantes.SOLICITUD_COMPRA is a positive integer
mPurchaseFinishedListener is the listener, which is implemented in the parent activity.
So the current flow should work like:
Make a purchase.
Acknowledge that the purchase was bought.
Update the UI so that it enables the user to use the purchased item rather than to buy it.
However it works like:
Make a purchase.
The application freezes and stops.
When the application is relaunched, the inventory is queried and the UI gets updated.
It seems like mPurchasedListener is never called.
To make it more weird, everything seems to work fine with test responses. I am testing the app in alpha with real responses and that is where the trouble appears.
Any ideas?
i've had this issue before my self and there are multiple things that could be causing this issue.
Firstly, please make sure that you've done the following for aplha stage testing and that you've added all the required permissions and aidl files.
1) Are you using a version of the app you've put on your device through android studio? If so, this could be your issue. Usually you'll receive a message notifying you that the version of the app you're using isn't compatible with in-app purchases. Alpha stage apps are actually downloaded from the google play store. You should have set up a google group that has access to a specific link that will allow you to open the google play store and download your alpha stage app.
2) Are you connected to the internet? Obviously launching a purchase flow with a real SKU will require an internet connection to the google play servers.
3) Did you set up this in app product up as a managed product? For simplicity, i highly recommend doing so.
if you've correctly set up everything as I mentioned above and are still having a problem, then likely it's an issue with how you setup and attempt to use your purchase flow.
Here's the steps i took to launching a purchase flow from within a fragment
Instead of setting up the IAB Helper in the fragment, set it up in the faActivity Class. We will then call the purchase flow method within the faActivity class from within the fragment in which you're viewing via Dialog Box.
This is how i set up in app purchases :
faActivity.java
IabHelper mHelper;
In the oncreate method :
String base64EncodedPublicKey = "your in app purchase key";
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.startSetup(new
IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result)
{}});
Add these methods as well :
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data)
{
if (!mHelper.handleActivityResult(requestCode,
resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result,
Purchase purchase)
{
if (result.isFailure()) {
return;
}
else if (purchase.getSku().equals(ITEM_SKU)) {
consumeItem();
}
}
};
public void consumeItem() {
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// Handle failure
} else {
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =
new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase,
IabResult result) {
/*The purchase was successfully consumed, now update the
//ui/award the user with their purchase (I suggest storing the fact that
they're premium or whatever within the shared prefrences of your app)*/
}};
String ITEM_SKU = "";
//Launching the purchase Flow
public void makeThePurchase() {
//Assign the SKU Name of your managed product
ITEM_SKU="premiumMembership";
mHelper.launchPurchaseFlow(this, ITEM_SKU, 10000,
mPurchaseFinishedListener, "mypurchasetoken");
}
Now in your fragment, use this code to show a dialogBox asking the user if they wish to purchase the product and launch the purchase flow if they wish to do so
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
builder.setView(inflater.inflate(R.layout.dialog_signin, null))
.setPositiveButton("Purchase", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
((faActivity)getActivity()).makeThePurchase();
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
builder.setTitle("Purchase");
builder.setMessage("Purchase premium membership?");
builder.create().show();
And that is how i managed to launch a purchase flow from within a fragment. I'm sure there is a better way to do so, but when setting up the purchase flow from within the fragment, i always recieved issues as well when trying to launch a purchase.
Hopefully this solved your issue!
Good Luck!!! :)
NOTE: The code i've listed above is setup to consume the purchase (meaning that the fact that they own it won't show up when querying the inventory) Like i said above although, if you're not concerned about the users having to repurchase their items/premium purchases in the occurance of them getting a new device or uninstalling your app, then just keep track of what they've purchased and consumed within the shared preferences by calling this code below from within the onConsumePurchasedFinished listener :
SharedPreferences pref = getActivity().getApplicationContext().getSharedPreferences("MyPref", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean("hasBoughtPremium",true);
editor.apply();
Then when reloading the app, check to see if they own the premium item :
SharedPreferences pref = getActivity().getApplicationContext().getSharedPreferences("MyPref", Context.MODE_PRIVATE);
Boolean isPremium = pref.getBoolean("hasBoughtPremium",false);

Android Smart Lock setResultCallback is not getting called

I have been following https://developers.google.com/identity/smartlock-passwords/android/retrieve-credentials to try to automatically sign in a user if they have saved their credentials to the new Android Smart Lock feature in chrome. I have followed the guide exactly, but my callback that I pass into setResultCallback() is not getting called. Has anyone run into this problem before?
There is no error message or anything, it just doesn't get called.
The problem is likely that the Google API client is not connected, try calling connect() in the onStart() method of your activity, or if you are using a recent version of Play Services, we added automatic management of the API client to make this easier, really simplifying things and avoiding common problems.
Just call enableAutoManage() when building the GoogleApiClient:
// "this" is a reference to your activity
mCredentialsApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.enableAutoManage(this, this)
.addApi(Auth.CREDENTIALS_API)
.build();
Then you can make an API request without having to call mCredentialsApiClient.onConnect() at any point, the Google API client's lifecycle will be managed automatically for you. e.g.
#Override
public void onStart() {
CredentialRequest request = new CredentialRequest.Builder()
.setSupportsPasswordLogin(true)
.build();
Auth.CredentialsApi.request(mCredentialsApiClient, request).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
public void onResult(CredentialRequestResult result) {
// result.getStatus(), result.getCredential() ... sign in automatically!
...
Check out a full sample app at on Github: https://github.com/googlesamples/android-credentials/blob/master/credentials-quickstart/app/src/main/java/com/google/example/credentialsbasic/MainActivity.java
I tired the official demo app here, and it worked.
Basically, the setResultCallback() will be get called when save, request and delete
For save:
Auth.CredentialsApi.save(mCredentialsApiClient, credential).setResultCallback(
new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
if (status.isSuccess()) {
Log.d(TAG, "SAVE: OK");
showToast("Credential Saved");
hideProgress();
} else {
resolveResult(status, RC_SAVE);
}
}
});
For request:
Auth.CredentialsApi.request(mCredentialsApiClient, request).setResultCallback(
new ResultCallback<CredentialRequestResult>() {
#Override
public void onResult(CredentialRequestResult credentialRequestResult) {
if (credentialRequestResult.getStatus().isSuccess()) {
// Successfully read the credential without any user interaction, this
// means there was only a single credential and the user has auto
// sign-in enabled.
processRetrievedCredential(credentialRequestResult.getCredential(), false);
hideProgress();
} else {
// Reading the credential requires a resolution, which means the user
// may be asked to pick among multiple credentials if they exist.
Status status = credentialRequestResult.getStatus();
if (status.getStatusCode() == CommonStatusCodes.SIGN_IN_REQUIRED) {
// This is a "hint" credential, which will have an ID but not
// a password. This can be used to populate the username/email
// field of a sign-up form or to initialize other services.
resolveResult(status, RC_HINT);
} else {
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one
resolveResult(status, RC_READ);
}
}
}
});
For delete:
Auth.CredentialsApi.delete(mCredentialsApiClient, mCurrentCredential).setResultCallback(
new ResultCallback<Status>() {
#Override
public void onResult(Status status) {
hideProgress();
if (status.isSuccess()) {
// Credential delete succeeded, disable the delete button because we
// cannot delete the same credential twice.
showToast("Credential Delete Success");
findViewById(R.id.button_delete_loaded_credential).setEnabled(false);
mCurrentCredential = null;
} else {
// Credential deletion either failed or was cancelled, this operation
// never gives a 'resolution' so we can display the failure message
// immediately.
Log.e(TAG, "Credential Delete: NOT OK");
showToast("Credential Delete Failed");
}
}
});
Also you can clone the project in my github here, set the SHA1 in your console here.
At this point you should be ready to go :)

Android Facebook SDK lets users not log in?

I have a screen that popups up that Facebook login thing with the following code:
//facebook stuff
if (Session.getActiveSession() == null || Session.getActiveSession().isClosed()) {
Session.openActiveSession(this, true, null);
}
If the user hits the "back" button and dismisses the login thing, it automatically sends them to the screen that was supposed to be facebook protected (and has errors because no user is logged in).
How should I handle this? I see that in the old sdk or whatever, it had:
facebook.isSessionValid()
Is there still a way to do this? If so, HOW would I actually do it? Some sort of while loop until they finally stop trying to back out? Is there a way to disable backing out of that screen? I don't generate that screen at all: facebook does. Is this just a bug I have to live with?
Edit:
Corollary to this is that I have just noticed that if I use the facebook app to logout, the facebook sdk doesn't tell me this in my own app: It still authenticates. Even worse is when it does it even after I have logged out, then logged in as an entirely different user using the facebook app. How do I handle this?
Edit: More Code
This is now how I log in to facebook (in the Activity's onCreate) and ask to get my graph user and my friends' (gets me first, then once that asynch thing is done, gets friends)
if (Session.getActiveSession() == null || Session.getActiveSession().isClosed()) {
Log.v(CLASS_NAME, "Logging into facebook");
//Session.openActiveSession(this, true, new Session.statusCallback());
final Session.StatusCallback sessionStatusCallback = new Session.StatusCallback() {
#Override
public void call(Session session, SessionState state,
Exception exception) {
// TODO Auto-generated method stub
if(exception != null)
{
// Handle fail case here.
Log.v(CLASS_NAME, "Facebook login error " + exception);
return;
}
// If session is just opened...
if(state == SessionState.OPENED)
{
// Handle success case here.
Log.v(CLASS_NAME, "Facebook login success!");
return;
}
}
};
Session.openActiveSession(this, true, sessionStatusCallback);
}
//annonymous inner class
final Callback<ArrayList<GraphUser>> inner_callback_class2 = new FacebookHelper.Callback<ArrayList<GraphUser>>() {
#Override
public void setResult(ArrayList<GraphUser> result) throws Exception {
// TODO Auto-generated method stub
for(GraphUser user : result){
friends.put(user.getId(), user);
}
friends.put(me.getId(), me); //i can see my own comments as well
//get comments
//ArrayList<String>tmp = new ArrayList<String>();
//tmp.add("Jenny");
//tmp.add("Charles");
//initial comment call so I can view them
//
getComments();
}
};
//annonymous inner class
FacebookHelper.Callback<GraphUser> inner_callback_class = new FacebookHelper.Callback<GraphUser>() {
#Override
public void setResult(GraphUser result) throws Exception {
// TODO Auto-generated method stub
me = result;
Log.v("ANYTHING", me.getName());
//make sure i have an id before I do anything
FacebookHelper.getMyFriends(Session.getActiveSession(),inner_callback_class2);
//post comment
//RailsServerHelper.postComment("Comment Testing", movie.id, myID, currentNPT);
}
};
FacebookHelper.getMyID(Session.getActiveSession(), inner_callback_class);
This is how I get my friends (in a static helper class):
public static void getMyFriends(final Session session, final Callback<ArrayList<GraphUser>> callback){
Request request = Request.newMyFriendsRequest(session, new Request.GraphUserListCallback() {
#Override
public void onCompleted(List<GraphUser> users, Response response) {
// TODO Auto-generated method stub
ArrayList<GraphUser> tmp = new ArrayList<GraphUser>(users);
try {
callback.setResult(tmp);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
request.executeAsync();
}
The only other facebook thing I have is that whole clear the session thing you suggested:
#Override
public void onDestroy(){
Session.getActiveSession().closeAndClearTokenInformation();
Log.v(CLASS_NAME, "Clearing facebook session");
pleaseStop = true;
super.onDestroy();
}
It won't let me log back in at all. I have two devices I'm testing it on.
The first device was working fine originally with one profile, but when I tried to try a second profile it wouldn't log the first one out, so I did that closeAndClearTokenInformation() thing. That resulted in NO profile, and it won't pop up the whole facebook log in stuff anymore. I think there is no profile because it always prints out "Logging into facebook", and the getMe call thing never works. (Just from describing my code here I see I can put the getMe stuff in the facebook log in callback, though) It also never prints the success statement I put in the callBack, and on the first device it prints out the error statement if I hit the back button (no matter how long I sit at that screen).
The second device never managed to get a profile logged in because the back button was hit on the login screen. Ever since that, the system seems to think its "logged in" as nobody, and nothing I do can get it to refresh. I don't ever see it attempting to log in like the first device. The closeAndClear call thing seems to do nothing on this device. If relevant, the second device is a virtual simulator running on an entirely different machine.
On the actual device, what I see is:
01-24 07:16:04.669: V/MakeAndViewCommentsActivity(3677): Logging into facebook
And then a bunch of system messages (I waited over ten minutes), none of which seem to have warnings or errors, and then:
01-24 07:29:05.548: V/MakeAndViewCommentsActivity(3677): Clearing facebook session
01-24 07:29:05.552: V/MakeAndViewCommentsActivity(3677): Facebook login error com.facebook.FacebookException: Log in attempt aborted.
Why isn't the facebook openActiveSession ever returning?
Edit: The facebook sample apps still have the original log in working, but don't care if I log out or change users in the actual facebook app. I guess they don't include any session clearing either?
Edit: When I have a print statement of:
Log.v(CLASS_NAME, "Facebook callback with state: " + state);
I see:
01-24 09:10:54.333: V/MakeAndViewCommentsActivity(4869): Facebook callback with state: OPENING
does the callback only get called once? Or is it going to be theoretically called again with a state of "OPEN" if it completes?
SOLUTION:
Edit: Looking up the fact that it never leaves the "OPENING" state helped me a lot, it turns out I had to add the code:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Session.getActiveSession()
.onActivityResult(this, requestCode, resultCode, data);
}
And then it just worked! I have no idea why. I guess I will do research on this, but I"m glad I can finally change users.
You're passing in a null callback. If you implemented the callback, you would see that the callback would be called, and if you checked state.isClosed(), it would return true (and the state should be CLOSED_LOGIN_FAILED). There should also be an exception in the exception parameter. You cannot disable the ability for users to back out of SSO, that's by design.
You're also correct in that there are no notifications if the user logs out of the facebook app. One way to have a better user experience is to always display the user's name and profile picture when you're doing something on behalf of the user (like posting a status), so it's immediately obvious that it's not the correct user. The other options is to always call session.closeAndClearTokenInformation every time your app exits. This way, you're always starting a fresh session, and if the user has the facebook app installed, it's still a very fast auth (no additional buttons to press). The drawback is that if they don't have the facebook app, then it will pop up a web dialog for login every time (but you can probably work around that by checking for the existence of the FB app).

In-App Billing test: android.test.purchased already owned

I am currently testing In-App Billing for a future app, and after I successfully "bought" the test item "android.test.purchased" the first time, I now receive the response code 7 every time I try to buy it again, which means that I already own this item.
12-15 23:02:14.149: E/IabHelper(19829): In-app billing error: Unable
to buy item, Error response: 7:Item Already Owned
From what I understand, this purchase is supposed to always be possible, right? So that the developer can test his/her app?
If not, how can I "reset" its state to not owned? I am using the util package from the Google In-App Billing Sample.
Add this code to a thread to initiate consume request.
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
Here for the purchase test, purchaseToken is
purchaseToken = "inapp:" + getPackageName() + ":android.test.purchased";
And
if (response == 0)
then the consumption is successful.
also don't forget to make mService public in
IabHelper.Java
then it would be possible to access like this:
int response = mHelper.mService.consumePurchase(3, getPackageName(), purchaseToken);
No need to write any special consumption code. Just use the adb command for clearing the Google Play Store data:
adb shell pm clear com.android.vending
It turns out that the android.test.purchased item behaves like a regular ID. It means that if you want be able to buy it again, you have to consume it somewhere in your code. I think that the Google documentation is misleading on this matter, and that they should add another static ID that you can buy endlessly for test purposes.
In-app version 3:
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
.....................
if (inventory.hasPurchase(SKU_CONTENT)) {
mHelper.consumeAsync(inventory.getPurchase(SKU_CONTENT), null);
}
}
};
Version 3 - Fastest way to solve : Clearing the cache of Google Play Store will let "android.test.purchased" available again.
This is how we can consume the Item
consume.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
String purchaseToken = "inapp:" + getPackageName() + ":android.test.purchased";
try {
Log.d("","Running");
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
if(response==0)
{
Log.d("Consumed","Consumed");
}else {
Log.d("","No"+response);
}
}catch (RemoteException e)
{
Log.d("Errorr",""+e);
}
}
});
t.start();
}
});
In my opinion if your program is not designed to consume the item you do not need to tweak the code in order to clear the memory of an outside vendor. This will make your code more fragile and you will have then to spend a lot of time to add and remove code that does not belong to your software so it is a bad design to implement a solution like that.
The best solution that worked for me to clear android.test.purchased was
adb uninstall com.yourapp.name
and then
adb shell pm clear com.android.vending
I did not need to clear cash and to browse my apps setting or to change code for that. I did need to add the adb to path variables of windows system which was pretty straight forward. So yes you need to use adb which you probably need anyway so..
You just add your C:\ ...\android-sdk\platform-tools; in windows path in environment variables, and I imagine that it is pretty simple in mac and linux os as well. Hope it helps someone to spend few days less with implementing android in app billings.
Go to the Google Play Developer Console, open Order Management menu item from the left side and select the order you want to refund. Also make sure to remove the entitlement.
The main issue is you have to consume the android.test.purchased item. But this item won't be available in your query inventory, so you can't consume using the normal flow.
So, if you are using IabHelper, in IabHelper class, you can temporarily change the IInAppBillingService mService to public so that it is accessible from your IabHelper.
Then in your class, you can consume like this,
int response = mHelper.mService.consumePurchase(3, getPackageName(), "inapp:"+getPackageName()+":android.test.purchased");
If success, the response is going to be 0.
Hope this helps.
For testing purposes I also suggest you to insert a piece of code that will be clearing all the products that you've bought before calling a method that initializes gp purchase flow. That is especially comfortable, when you test just one item at the moment. E.g. like this:
PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
for (Purchase sourcePurchase : purchasesResult.getPurchasesList()) {
if(sourcePurchase != null){
ConsumeResponseListener listener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(String outToken, #BillingResponse int responseCode) {
System.out.println("all consumed");
}
};
mBillingClient.consumeAsync(sourcePurchase.getPurchaseToken(), listener);
}else{
System.out.println("null");
}
}
// and then initiate whole process with clear "shoping basket"
BillingFlowParams.Builder builder = new BillingFlowParams.Builder()
.setSku(itemName).setType(BillingClient.SkuType.INAPP);
If you are in test environment
1) In the case of android.test.purchased, I can reset the fake payment by restarting android device(consumed the inventory).
2) In InApp util there is a file called Security.java make it as following, for temporary. Since the testing payment(fake) always return false due to security exception.
public static boolean verifyPurchase(String base64PublicKey,
String signedData, String signature) {
return true; }
Then in your OnIabPurchaseFinishedListener call fechInvForconsumeItem()
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result,
Purchase purchase)
{
if (result.isFailure()) {
// Handle error
Log.e("123","Failure");
return;
}
else if (purchase.getSku().equals(ITEM_SKU)) {
Log.e("123","PURCAsed");
fechInvForconsumeItem(); // Restart device if not consume
}
}
};
The fechInvForconsumeItem() is
public void fechInvForconsumeItem() {
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// Handle failure
Log.e("11","Failure");
} else {
Log.e("11","suc");
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
Consume Listener is
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =
new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase,
IabResult result) {
if (result.isSuccess()) {
} else {
// handle error
Log.e("11","sucConsume");
}
}
};
IabHelper.QueryInventoryFinishedListener
mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
if (result.isFailure()) {
return;
}
try {
if(inventory.hasPurchase("product_sku_id"))
{
isItemEnable= true;
mHelper.consumeAsync(inventory.getPurchase("product_sku_id"),null);
}
else
{
isItemEnable = false;
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
In my case, it appears that Google does not record a purchase for the item. Rather, the local copy of Google Play Services caches the purchase. That way, when a second request is made on the same device, android.test.purchased already owned appears. However, using another device or resetting the device clears the cache, and allows the purchase to be repeated.
In my case, I just needed to clear the apps cache. After clearing the cache, I was able to initiate the purchase flow again.
From my device (4.4.2), I navigated to "Settings->Application manager". Next, I selected the app from the "DOWNLOADED" tab, and then "Clear cache".
This is the difference between consumable and non-consumable items; non-consumable items (what you seem to be dealing with here) have their state tracked persistently, while consumable items can be purchased multiple times. You'll have to go into your Play management console and cancel/refund the sale to test it again.

Categories

Resources