Search code examples
androidxamarinxamarin.androidkindle-fireamazon-appstore

Amazon IAP not calling listener call backs (Kindle Fire, Xamarin)


I am attempting to [implement Amazon In-App Purchases in an Android project using Xamarin.

The setup

Amazon's official Xamarin SDK doesn't support the newer 64-bit devices so I created my own Java library binding using Amazon's Java SDK. (Documentation for the Java version)

The binding generated by Xamarin for PurchasingService looks like this:

namespace Com.Amazon.Device.Iap
{
    [Register ("com/amazon/device/iap/PurchasingService", DoNotGenerateAcw = true)]
    public sealed class PurchasingService : Object
    {
        //
        // Static Fields
        //
        private static IntPtr IS_SANDBOX_MODE_jfieldId;

        [Register ("SDK_VERSION")]
        public const string SdkVersion = "2.0.76.4";

        internal static IntPtr java_class_handle;

        private static IntPtr id_getUserData;

        private static IntPtr id_getProductData_Ljava_util_Set_;

        private static IntPtr id_getPurchaseUpdates_Z;

        private static IntPtr id_notifyFulfillment_Ljava_lang_String_Lcom_amazon_device_iap_model_FulfillmentResult_;

        private static IntPtr id_purchase_Ljava_lang_String_;

        private static IntPtr id_registerListener_Landroid_content_Context_Lcom_amazon_device_iap_PurchasingListener_;

        //
        // Static Properties
        //
        internal static IntPtr class_ref {
            get;
        }

        [Register ("IS_SANDBOX_MODE")]
        public static bool IsSandboxMode {
            get;
        }

        public static RequestId UserData {
            [Register ("getUserData", "()Lcom/amazon/device/iap/model/RequestId;", "GetGetUserDataHandler")]
            get;
        }

        //
        // Properties
        //
        protected override IntPtr ThresholdClass {
            get;
        }

        protected override Type ThresholdType {
            get;
        }

        //
        // Constructors
        //
        internal PurchasingService (IntPtr javaReference, JniHandleOwnership transfer);

        //
        // Static Methods
        //
        [Register ("getProductData", "(Ljava/util/Set;)Lcom/amazon/device/iap/model/RequestId;", "")]
        public static RequestId GetProductData (ICollection<string> p0);

        [Register ("getPurchaseUpdates", "(Z)Lcom/amazon/device/iap/model/RequestId;", "")]
        public static RequestId GetPurchaseUpdates (bool p0);

        [Register ("notifyFulfillment", "(Ljava/lang/String;Lcom/amazon/device/iap/model/FulfillmentResult;)V", "")]
        public static void NotifyFulfillment (string p0, FulfillmentResult p1);

        [Register ("purchase", "(Ljava/lang/String;)Lcom/amazon/device/iap/model/RequestId;", "")]
        public static RequestId Purchase (string p0);

        [Register ("registerListener", "(Landroid/content/Context;Lcom/amazon/device/iap/PurchasingListener;)V", "")]
        public static void RegisterListener (Context p0, IPurchasingListener p1);
    }
}

And for the PurchasingListener interface:

namespace Com.Amazon.Device.Iap
{
    [Register ("com/amazon/device/iap/PurchasingListener", "", "Com.Amazon.Device.Iap.IPurchasingListenerInvoker")]
    public interface IPurchasingListener : IJavaObject, IDisposable
    {
        //
        // Methods
        //
        [Register ("onProductDataResponse", "(Lcom/amazon/device/iap/model/ProductDataResponse;)V", "GetOnProductDataResponse_Lcom_amazon_device_iap_model_ProductDataResponse_Handler:Com.Amazon.Device.Iap.IPurchasingListenerInvoker, AmazonIAP")]
        void OnProductDataResponse (ProductDataResponse p0);

        [Register ("onPurchaseResponse", "(Lcom/amazon/device/iap/model/PurchaseResponse;)V", "GetOnPurchaseResponse_Lcom_amazon_device_iap_model_PurchaseResponse_Handler:Com.Amazon.Device.Iap.IPurchasingListenerInvoker, AmazonIAP")]
        void OnPurchaseResponse (PurchaseResponse p0);

        [Register ("onPurchaseUpdatesResponse", "(Lcom/amazon/device/iap/model/PurchaseUpdatesResponse;)V", "GetOnPurchaseUpdatesResponse_Lcom_amazon_device_iap_model_PurchaseUpdatesResponse_Handler:Com.Amazon.Device.Iap.IPurchasingListenerInvoker, AmazonIAP")]
        void OnPurchaseUpdatesResponse (PurchaseUpdatesResponse p0);

        [Register ("onUserDataResponse", "(Lcom/amazon/device/iap/model/UserDataResponse;)V", "GetOnUserDataResponse_Lcom_amazon_device_iap_model_UserDataResponse_Handler:Com.Amazon.Device.Iap.IPurchasingListenerInvoker, AmazonIAP")]
        void OnUserDataResponse (UserDataResponse p0);
    }
}

I also made sure that the Android manifest was modified with:

<receiver android:name = "com.amazon.device.iap.ResponseReceiver" >
    <intent-filter>
        <action android:name = "com.amazon.inapp.purchasing.NOTIFY"
          android:permission = "com.amazon.inapp.purchasing.Permission.NOTIFY" />
    </intent-filter>
</receiver>

The Amazon App Tester is installed on my Kindle and configured with the following test data:

{
    "IAP_ID":
    {
      "description":"One year Subscription",
      "title":"My Subscription",
      "itemType":"SUBSCRIPTION",
      "price":20.00,
      "subscriptionParent":"org.company.appid"
    }
}

And I have a skeleton of a class to test that the system works:

public class AmazonIAPController : Java.Lang.Object, IPurchasingListener
{
    const string ProductId = "IAP_ID";
    private Activity uiActivity;

    public AmazonIAPController()
    {
    }

    public void SetupIAP(Activity activity) {
        uiActivity = activity;
        PurchasingService.RegisterListener(uiActivity, this);

        // test that the Amazon system works
        PurchasingService.Purchase(ProductId);
    }

    public void OnResume(Activity activity) {
        uiActivity = activity;
        PurchasingService.RegisterListener(uiActivity, this);

        //PurchasingService.UserData;
        PurchasingService.GetPurchaseUpdates(false);
        PurchasingService.GetProductData(new string[] { ProductId });

    }

    public void OnProductDataResponse(ProductDataResponse response) {
        var status = response.GetRequestStatus();

        Debug.WriteLine("*****OnProductDataResponse");
    }

    public void OnPurchaseResponse(PurchaseResponse response) {
        var status = response.GetRequestStatus();
        Debug.WriteLine("*****OnPurchaseResponse");
    }

    public void OnPurchaseUpdatesResponse(PurchaseUpdatesResponse response) {
        var status = response.GetRequestStatus();
        Debug.WriteLine("*****OnPurchaseUpdatesResponse");
    }

    public void OnUserDataResponse(UserDataResponse response) {
        var status = response.GetRequestStatus();
        Debug.WriteLine("*****OnUserDataResponse");
    }
}

The test run

I am attempting to run this on my 5th Gen Kindle Fire. When the app launches, the user is prompted with the Amazon IAP prompt as expected. If the purchase is performed, it goes through and the transaction correctly appears in the Amazon App Tester app.

The problem

I have breakpoints setup in the callback methods. These breakpoints are never triggered. Neither do the Debug.WriteLine calls show up in the Application Output. It appears that my callback methods are not being called.


Solution

  • The root of the problem is the Android manifest entries were conflicting with how Xamarin generates manifest entries with code attributes.

    To fix the problem, I removed the aforementioned entries from the manifest and created an addition file in the Java binding project containing a partial class of the ResponseReciever and the relevant manifest attributes as follows:

    using System;
    using Android.App;
    using Android.Content;
    
    namespace Com.Amazon.Device.Iap
    {
        [BroadcastReceiver(Name = "com.amazon.device.iap.ResponseReceiver")]
        [IntentFilter(new[] { "com.amazon.inapp.purchasing.NOTIFY" })]
        public partial class ResponseReceiver
        {
    
        }
    }
    

    After this, the manifest entries were correctly generated and the expected callbacks started to work.

    Note:

    You'll probably notice that the android:permission entry from the manifest XML isn't in the above attributes. I can't find the reference, but I recall seeing somewhere that no only is this entry not needed to get the SDK working, but it's actually not a valid attribute on action according to the official Android documentation.