I've been struggling with this problem for days now. I know there are a lot of questions with the same problem on SO but i couldn't get it to work.
What I have done
Uploaded APK in beta phase
Created Merchant account
Added test user
Code
AndroidManifest.xml
<uses-permission android:name="com.android.vending.BILLING" />
MainActivity.java
public class MainActivity extends AppCompatActivity {
private IabHelper mHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ...
setupInAppBillings();
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
// [....]
private void setupInAppBillings() {
String base64EncodedPublicKey = "MY PUBLIC KEY";
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
Toast.makeText(getContext(), "In-app Billing setup failed: " + result, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "In-app Billing is set up OK", Toast.LENGTH_SHORT).show();
}
}
});
}
}
Tested on
Huawei P8 (Google Play Version 6.2.14)
In Switzerland, so a supported country for In-App Billing
What I've tried
Deleted cache and data from Google Play
Tutorial from Google Developer site
Went trough the checklist from user sokie: answer of sokie
The only thing I haven't done from this list is the setup of the License Verification Library (LVL). But I couldn't find any information that this step is required for an In-App Purchase. If not needed I want to do it without this library because I don't really need it as stated on the Google Site.
The Google Play Licensing service is primarily intended for paid applications that wish to verify that the current user did in fact pay for the application on Google Play.
Is there something I miss?
if you target android 31 you should add this to your manifest :
<queries>
<intent>
<action android:name="android.intent.action.MAIN" />
</intent>
</queries>
Finally I got it to work! The problem was the following: Even though I put the IInAppBillingService.aidl in the com.android.vending.billing package, the generated class was in the wrong package as you can see in the code below.
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: C:\\path\\src\\main\\aidl\\com\\android\\vending\\billing\\IInAppBillingService.aidl
*/
package billing;
public interface IInAppBillingService extends android.os.IInterface { //... }
To solve this, I deleted and recreated the com.android.vending.billing package with the IInAppBillingService.aidl. So if you have the same problem, check twice where the IInAppBillingService.java was generated.
I recently faced this problem. As Bona Fide wrote, the package declaration in IInAppBillingService.aidl must be set to "com.android.vending.billing" and the aidl file should be found inside the corresponding directory using the explorer. Furthermore (and that was the problem in my case), in the IabHelper.java, the string parameter to serviceIntent must be the same as the package name that contains the IInAppBillingService.aidl file.
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");// correct package name: com.android.vending.billing
serviceIntent.setPackage("com.android.vending");
List<ResolveInfo> intentServices = mContext.getPackageManager().queryIntentServices(serviceIntent, 0);
if (intentServices != null && !intentServices.isEmpty()) {
// service available to handle that Intent
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
}
else {
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."));
}
}
}
Related
I am trying to simple connect to Google play billing & access details such as the title & the pricing but I've been unable to do so.
What I've done so far (step-by-step):
(Added the IAP details on Google play console as well as published the app)
Added the Billing permission in the Manifest file -> <uses-permission android:name="com.android.vending.BILLING" />
Added the Billing library (v.4.0) -> implementation 'com.android.billingclient:billing:4.0.0'
Initialized the BillingClient in MainActivity & on trying to connect to it, I get no response from the onBillingSetupFinished callback method
private void initializeBillingClient(Context context) {
billingClient = BillingClient.newBuilder(context)
.enablePendingPurchases()
.setListener(this)
.build();
connectToGooglePlayBilling(context);
}
private void connectToGooglePlayBilling(Context context) {
billingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingServiceDisconnected() {
connectToGooglePlayBilling(context);
}
#Override
public void onBillingSetupFinished(#NonNull BillingResult billingResult) {
Toast.makeText(context, "connected to google play billing", Toast.LENGTH_SHORT).show();
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//I should then fetch & display premium content details
}
}
});
}
Weird enough I noticed that when I enable the Battery Saving Mode (I'm using a Samsung device) the onBillingSetupFinished method gets called as the toast is shown.
What am I doing incorrect & also if anyone can explain why enabling Battery save mode suddenly makes it work?
Make sure that onBillingSetupFinished is called in UI thread.
Try to use runOnUiThread to show the message:
runOnUiThread(() ->
Toast.makeText(context, "connected to google play billing", Toast.LENGTH_SHORT).show());
I have successfully implemented In-App-Billing into my app. The IAP is working successfully and I have tested it to make sure its working.
When a user clicks on a button, they have to make an IAP to proceed. However, everytime a user clicks on the button it starts the IAP, even though they have already made the IAP. I want my IAP to be non-consumable obviously. Currently, I'm storing the IAP in SharedPreferences, but if the user reinstalls the app, they lose their IAP.
So how can I use getPurchases() or restoreTransactions() on my onCreate or onClick method to check whether the user has purchases a specific item? I have searched all over the Internet and read through so many samples and it doesn't seem to work, perhaps I am misunderstanding though.
If you need me to post any code, please ask and I'll update my post.
Use this library:
https://github.com/anjlab/android-inapp-billing-v3
How to use?
Use this in your gradle:
repositories {
mavenCentral()
}
dependencies {
implementation 'com.anjlab.android.iab.v3:library:1.0.44'
}
Manifest permission for in-app billing:
<uses-permission android:name="com.android.vending.BILLING" />
How to use library method:
public class SomeActivity extends Activity implements BillingProcessor.IBillingHandler {
BillingProcessor bp;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bp = new BillingProcessor(this, "YOUR LICENSE KEY FROM GOOGLE PLAY CONSOLE HERE", this);
bp.initialize();
// or bp = BillingProcessor.newBillingProcessor(this, "YOUR LICENSE KEY FROM GOOGLE PLAY CONSOLE HERE", this);
// See below on why this is a useful alternative
}
// IBillingHandler implementation
#Override
public void onBillingInitialized() {
/*
* Called when BillingProcessor was initialized and it's ready to purchase
*/
}
#Override
public void onProductPurchased(String productId, TransactionDetails details) {
/*
* Called when requested PRODUCT ID was successfully purchased
*/
}
#Override
public void onBillingError(int errorCode, Throwable error) {
/*
* Called when some error occurred. See Constants class for more details
*
* Note - this includes handling the case where the user canceled the buy dialog:
* errorCode = Constants.BILLING_RESPONSE_RESULT_USER_CANCELED
*/
}
#Override
public void onPurchaseHistoryRestored() {
/*
* Called when purchase history was restored and the list of all owned PRODUCT ID's
* was loaded from Google Play
*/
}
}
Note: onPurchaseHistoryRestored called only first time when you initialize BillingProcessor
Their are lot of ways to check onPurchaseHistoryRestored() but its my opinion. And I have solved it by using..
you can check through transection detail using this code.
#Override
public void onPurchaseHistoryRestored() {
/*
* Called when purchase history was restored and the list of all owned PRODUCT ID's
* was loaded from Google Play
*/
// Check whether 'premium_id' has previously been purchased:
TransactionDetails premiumTransactionDetails = bp.getPurchaseTransactionDetails("premium_id");
if (premiumTransactionDetails == null) {
Log.i(TAG, "onPurchaseHistoryRestored(): Havn't bought premium yet.");
purchasePremiumButton.setEnabled(true);
}
else {
Log.i(TAG, "onPurchaseHistoryRestored(): Already purchases premium.");
purchasePremiumButton.setText(getString(R.string.you_have_premium));
purchasePremiumButton.setEnabled(false);
statusTextView.setVisibility(View.INVISIBLE);
}
}
And the second is to put the check on "product id".check below code.
if(bp.isPurchased(REMOVE_ID_SKU)){
if (bp.loadOwnedPurchasesFromGoogle()) {
Toast.makeText(this, R.string.subs_updated, Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, R.string.no_purchases, Toast.LENGTH_SHORT).show();
}
}
I have a skeleton class of JobIntentService
public class BackgroundRequestService extends JobIntentService {
/**
* Unique job ID for this service.
*/
static final int JOB_ID = 1234;
public static void enqueueWork(Context context, Intent work) {
BackgroundRequestService.enqueueWork(context, BackgroundRequestService.class, JOB_ID, work);
}
#Override
protected void onHandleWork(#NonNull Intent intent) {
if (intent.getExtras() != null){
String x = ";";
}
}
}
I have included the Service in the manifest
<service android:name=".BackgroundRequestService"
android:enabled="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
And calling the proper enqueue method
Intent intent = new Intent();
intent.putExtra("hardware", hardware);
BackgroundRequestService.enqueueWork(context, intent);
But the onHandleWork is never getting called. i have looked at all the pages about setting the services correctly and making sure onBind isn't overridden, but this still isn't working, wondering if another set of eyes would spot something ive missed. Thank you
Try to start service this way:
ComponentName comp = new ComponentName(context.getPackageName(), BackgroundRequestService.class.getName());
BackgroundRequestService.enqueueWork(context, (getIntent().setComponent(comp)));
Found the issue through some proper looking into the Logcat
Turns out the "hardware" that i was putting into my Intent was missing a field to be Serialized.
This caused this message to appear in the Logcat
I/UDP: no longer listening for UDP broadcasts cause of error Parcelable encountered IOException writing serializable object (name = Project.Hardware)
After fixing this Serialization issue i was able to call the onHandleWork() method.
I press a custom button to let the user give publish permissions to my Android app(Facebook SDK 4.18.0). It works. It goes to the facebook fragment in which prompts the user to give publish permissions to the app and then it comes back to the activity in which the button was pressed. The thing is that I've run the debugger and it never goes through the callback functions which I need to do what I need to do after the user has accepted.
The listener:
permissionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
permissionButton.startAnimation(rotation);
JSONObject userCredentials = new JSONObject();
//ask for facebook publishing permissions
if(facebook_connected) {
CallbackManager callbackManager = CallbackManager.Factory.create();
facebookLoginWithPublishPermissions(callbackManager);
}
}
});
And my custom function 'facebookLoginWithPublishPermissions' which intends to get a token with publish permissions(do I need a new one or the old one works now that I've been granted the "publish_actions" permissions for this user, cause I'm already logged in and have the access_token but only with the basic permissions using firebase ui auth module for android)? If I go to the facebook page of the user I can see that the proper permissions has been set so everything OK regarding that. The code:
private void facebookLoginWithPublishPermissions(CallbackManager callbackManager) {
LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback<LoginResult>() {
#Override
public void onSuccess(LoginResult loginResult) {
Log.d("FACEBOOK_LOGIN_RESULT", "facebook login success");
Log.d("LOGIN_RESULT", loginResult.getAccessToken().getToken());
}
#Override
public void onCancel() {
Log.d("FACEBOOK_LOGIN_RESULT", "facebook login canceled");
}
#Override
public void onError(FacebookException exception) {
Log.e("FACEBOOK_LOGIN_RESULT", "facebook login error");
exception.printStackTrace();
}
});
LoginManager.getInstance().logInWithPublishPermissions((Activity) mContext, Arrays.asList("publish_actions"));
}
1-How can I get a new token(in case is need to renew the token if you ask for additional permissions)?
2-How can I make the callback function work to perform actions after the publish_actions permission has been granted??
Thank you very much for your help!
EDIT: For future reference regarding Facebook API/Permission politics, Facebook provides a DIFFERENT Token from the one you are first provided when you login with the basic permissions. They have to review your app to see if you are using the permissions ONLY when you need it and also to check if you are not asking those permissions "just in case" to avoid unnecessary requests to their server. Also they want to make sure that you let the user know what's happening, they must accept the publishing permission, that's why you have to do 2 requests, first the read permissions the first time you connect to facebook, and a second request for writing(publish_actions) permissions. You CANNOT ask for publishing permissions unless you already asked for reading permissions first....
Are you adding the following method to your code ?
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
callbackManager.onActivityResult(requestCode, resultCode, data);
Log.d("TAG: 5. ", "I´m at onActivity result");
}
I use the new Google drive api and I can't get All folders from my google drive, I only get the folders that I create with the google drive api...
Anybody know why happens this?
this is my code:
#Override
protected void onResume() {
super.onResume();
if (mGoogleApiClient == null) {
// Create the API client and bind it to an instance variable.
// We use this instance as the callback for connection and connection
// failures.
// Since no account name is passed, the user is prompted to choose.
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Drive.API)
.addScope(Drive.SCOPE_FILE)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
}
// Connect the client. Once connected, the camera is launched.
mGoogleApiClient.connect();
}
/**
* Handles resolution callbacks.
*/
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_RESOLUTION && resultCode == RESULT_OK) {
mGoogleApiClient.connect();
}
}
/**
* Called when activity gets invisible. Connection to Drive service needs to
* be disconnected as soon as an activity is invisible.
*/
#Override
protected void onPause() {
if (mGoogleApiClient != null) {
mGoogleApiClient.disconnect();
}
super.onPause();
}
/**
* Called when {#code mGoogleApiClient} is connected.
*/
#Override
public void onConnected(Bundle connectionHint) {
Log.i(TAG, "GoogleApiClient connected");
rootFolder = Drive.DriveApi.getRootFolder(mGoogleApiClient);
rootFolder.listChildren(getGoogleApiClient()).setResultCallback(pruebaChildren);
}
ResultCallback<DriveApi.MetadataBufferResult> pruebaChildren = new
ResultCallback<DriveApi.MetadataBufferResult>() {
#Override
public void onResult(DriveApi.MetadataBufferResult metadataBufferResult) {
if (!metadataBufferResult.getStatus().isSuccess()) {
showMessage("Problem while retrieving files");
return;
}
Log.i(TAG,"got root folder");
MetadataBuffer buffer = metadataBufferResult.getMetadataBuffer();
Log.i(TAG,"Buffer count " + buffer.getCount());
if(buffer.getCount() == 0){
createFolderApp();
}
else{
for(Metadata m : buffer){
Log.i(TAG,"Metadata name " + m.getTitle() + "(" + (m.isFolder() ? "folder" : "file") + ")");
if (m.isFolder() && m.getTitle().equals(TAG)){
Log.i(TAG,"APP FOLDER FOUND");
Drive.DriveApi.getFolder(mGoogleApiClient, m.getDriveId())
.listChildren(mGoogleApiClient)
.setResultCallback(foreachAppAplication);
}
}
}
return;
}
};
And now I want see all folders in rootFolder Drive, I try the requestSync() but the result is same... I need help please!
And another question: How I can set the AppFolder? I only see getAppFolder but How I can set ??
Thanks
By design, GDAA supports only the FILE scope, i.e. it will find / list only folders / files created by the Android app.
There are 2 ways around it:
Use one of the intents of the GDAA, basically letting the user pick the file / folder. That way it will become available to your app.
Use a different API, the REST Api, which supports the DRIVE scope, giving your app full set of files / folders.
In case you want to study how the two APIs behave, I've put two different demos on the Github (the REST and the GDAA CRUD demo wrappers).
The second part of your question does not have answer. You don't set the app folder, you can only get it's DriveFolder id. You use it to create / retrieve objects.
DriveFolder appFldr = Drive.DriveApi.getAppFolder(mGooleApiClient);
appFldr.createFile(...);
appFldr.createFolder(...);
appFldr.listChildren(...);
appFldr.queryChildren(...);
... and don't forget to add the SCOPE_APPFOLDER scope
Good Luck