org.onepf.oms.appstore.SamsungAppsBillingService.java Source code

Java tutorial

Introduction

Here is the source code for org.onepf.oms.appstore.SamsungAppsBillingService.java

Source

/*******************************************************************************
 * Copyright 2013 One Platform Foundation
 *
 *        Licensed under the Apache License, Version 2.0 (the "License");
 *        you may not use this file except in compliance with the License.
 *        You may obtain a copy of the License at
 *
 *            http://www.apache.org/licenses/LICENSE-2.0
 *
 *        Unless required by applicable law or agreed to in writing, software
 *        distributed under the License is distributed on an "AS IS" BASIS,
 *        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *        See the License for the specific language governing permissions and
 *        limitations under the License.
 ******************************************************************************/

package org.onepf.oms.appstore;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.json.JSONException;
import org.json.JSONObject;
import org.onepf.oms.AppstoreInAppBillingService;
import org.onepf.oms.OpenIabHelper;
import org.onepf.oms.OpenIabHelper.Options;
import org.onepf.oms.appstore.googleUtils.IabException;
import org.onepf.oms.appstore.googleUtils.IabHelper;
import org.onepf.oms.appstore.googleUtils.IabHelper.OnIabPurchaseFinishedListener;
import org.onepf.oms.appstore.googleUtils.IabHelper.OnIabSetupFinishedListener;
import org.onepf.oms.appstore.googleUtils.IabResult;
import org.onepf.oms.appstore.googleUtils.Inventory;
import org.onepf.oms.appstore.googleUtils.Purchase;
import org.onepf.oms.appstore.googleUtils.SkuDetails;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.sec.android.iap.IAPConnector;

/**
 * @author Ruslan Sayfutdinov
 * @since 10.10.2013
 */

public class SamsungAppsBillingService implements AppstoreInAppBillingService {
    private static final int ITEM_RESPONSE_COUNT = 100;

    private static boolean isDebugLog() {
        return OpenIabHelper.isDebugLog();
    }

    private static final String TAG = SamsungAppsBillingService.class.getSimpleName();

    private static final int HONEYCOMB_MR1 = 12;

    // IAP Modes are used for IAPConnector.init() 
    public static final int IAP_MODE_COMMERCIAL = 0;
    public static final int IAP_MODE_TEST_SUCCESS = 1;
    public static final int IAP_MODE_TEST_FAIL = -1;
    private static final int CURRENT_MODE = SamsungApps.isSamsungTestMode ? IAP_MODE_TEST_SUCCESS
            : IAP_MODE_COMMERCIAL;

    public static final String IAP_SERVICE_NAME = "com.sec.android.iap.service.iapService";
    public static final String ACCOUNT_ACTIVITY_NAME = "com.sec.android.iap.activity.AccountActivity";
    public static final String PAYMENT_ACTIVITY_NAME = "com.sec.android.iap.activity.PaymentMethodListActivity";
    // ========================================================================
    // BILLING RESPONSE CODE
    // ========================================================================
    public static final int IAP_RESPONSE_RESULT_OK = 0;
    public static final int IAP_RESPONSE_RESULT_UNAVAILABLE = 2;
    // ========================================================================
    public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 32;
    // ========================================================================
    // BUNDLE KEY
    // ========================================================================
    public static final String KEY_NAME_THIRD_PARTY_NAME = "THIRD_PARTY_NAME";
    public static final String KEY_NAME_STATUS_CODE = "STATUS_CODE";
    public static final String KEY_NAME_ERROR_STRING = "ERROR_STRING";
    public static final String KEY_NAME_IAP_UPGRADE_URL = "IAP_UPGRADE_URL";
    public static final String KEY_NAME_ITEM_GROUP_ID = "ITEM_GROUP_ID";
    public static final String KEY_NAME_ITEM_ID = "ITEM_ID";
    public static final String KEY_NAME_RESULT_LIST = "RESULT_LIST";
    public static final String KEY_NAME_RESULT_OBJECT = "RESULT_OBJECT";
    // ========================================================================
    // ITEM JSON KEY
    // ========================================================================
    public static final String JSON_KEY_ITEM_ID = "mItemId";
    public static final String JSON_KEY_ITEM_NAME = "mItemName";
    public static final String JSON_KEY_ITEM_DESC = "mItemDesc";
    public static final String JSON_KEY_ITEM_PRICE = "mItemPrice";
    public static final String JSON_KEY_CURRENCY_UNIT = "mCurrencyUnit";
    public static final String JSON_KEY_ITEM_IMAGE_URL = "mItemImageUrl";
    public static final String JSON_KEY_ITEM_DOWNLOAD_URL = "mItemDownloadUrl";
    public static final String JSON_KEY_PURCHASE_DATE = "mPurchaseDate";
    public static final String JSON_KEY_PAYMENT_ID = "mPaymentId";
    public static final String JSON_KEY_PURCHASE_ID = "mPurchaseId";
    public static final String JSON_KEY_TYPE = "mType";
    public static final String JSON_KEY_ITEM_PRICE_STRING = "mItemPriceString";
    // ========================================================================
    // ITEM TYPE
    // ========================================================================
    public static final String ITEM_TYPE_CONSUMABLE = "00";
    public static final String ITEM_TYPE_NON_CONSUMABLE = "01";
    public static final String ITEM_TYPE_SUBSCRIPTION = "02";
    public static final String ITEM_TYPE_ALL = "10";

    // ========================================================================
    // define request code for IAPService.
    // ========================================================================
    public static final int REQUEST_CODE_IS_IAP_PAYMENT = 1;
    public static final int REQUEST_CODE_IS_ACCOUNT_CERTIFICATION = 899;

    // ========================================================================
    // define status code passed to 3rd party application
    // ========================================================================
    public static final int IAP_ERROR_NONE = 0;
    public static final int IAP_PAYMENT_IS_CANCELED = 1;
    public static final int IAP_ERROR_INITIALIZATION = -1000;
    public static final int IAP_ERROR_NEED_APP_UPGRADE = -1001;
    public static final int IAP_ERROR_COMMON = -1002;
    public static final int IAP_ERROR_ALREADY_PURCHASED = -1003;
    public static final int IAP_ERROR_WHILE_RUNNING = -1004;
    public static final int IAP_ERROR_PRODUCT_DOES_NOT_EXIST = -1005;
    public static final int IAP_ERROR_CONFIRM_INBOX = -1006;
    // ========================================================================

    private volatile boolean isBound;
    private IAPConnector mIapConnector;
    private Activity activity;
    private Options options;
    private ServiceConnection serviceConnection;
    private String purchasingItemType;

    private OnIabSetupFinishedListener setupListener = null;
    // The listener registered on launchPurchaseFlow, which we have to call back when
    // the purchase finishes
    private OnIabPurchaseFinishedListener mPurchaseListener = null;
    private int mRequestCode;
    private String mItemGroupId;
    private String mExtraData;

    public SamsungAppsBillingService(Activity context, Options options) {
        this.activity = context;
        this.options = options;
    }

    @Override
    public void startSetup(final OnIabSetupFinishedListener listener) {
        this.setupListener = listener;

        ComponentName com = new ComponentName(SamsungApps.IAP_PACKAGE_NAME, ACCOUNT_ACTIVITY_NAME);
        Intent intent = new Intent();
        intent.setComponent(com);
        activity.startActivityForResult(intent, options.samsungCertificationRequestCode);
    }

    @Override
    public Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus, List<String> moreSubsSkus)
            throws IabException {
        Inventory inventory = new Inventory();

        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd", Locale.getDefault());
        String today = simpleDateFormat.format(date);

        /* Get all itemGroupIds from existing skus */
        Set<String> itemGroupIds = new HashSet<String>();
        List<String> storeSkus = OpenIabHelper.getAllStoreSkus(OpenIabHelper.NAME_SAMSUNG);
        for (String sku : storeSkus) {
            itemGroupIds.add(getItemGroupId(sku));
        }

        /* Query getItemsInbox for each itemGroupId */
        for (String itemGroupId : itemGroupIds) {
            int startNum = 1;
            int endNum = ITEM_RESPONSE_COUNT;
            Bundle itemInbox;
            do {
                itemInbox = null;
                try {
                    if (isDebugLog())
                        Log.d(TAG, "getItemsInbox, startNum = " + startNum + ", endNum = " + endNum);
                    itemInbox = mIapConnector.getItemsInbox(activity.getPackageName(), itemGroupId, startNum,
                            endNum, "19700101", today);
                } catch (RemoteException e) {
                    Log.e(TAG, "Samsung getItemsInbox: " + e.getMessage());
                }
                startNum += ITEM_RESPONSE_COUNT;
                endNum += ITEM_RESPONSE_COUNT;
            } while (processItemsBundle(itemInbox, itemGroupId, inventory, querySkuDetails, true, false, null));
        }
        if (querySkuDetails) {
            Set<String> queryItemGroupIds = new HashSet<String>();
            Set<String> queryItemIds = new HashSet<String>();
            if (moreItemSkus != null) {
                for (String sku : moreItemSkus) {
                    queryItemGroupIds.add(getItemGroupId(sku));
                    queryItemIds.add(getItemId(sku));
                }
            }
            if (moreSubsSkus != null) {
                for (String sku : moreSubsSkus) {
                    queryItemGroupIds.add(getItemGroupId(sku));
                    queryItemIds.add(getItemId(sku));
                }
            }
            if (!queryItemIds.isEmpty()) {
                for (String itemGroupId : queryItemGroupIds) {
                    int startNum = 1;
                    int endNum = ITEM_RESPONSE_COUNT;
                    Bundle itemList;
                    do {
                        itemList = null;
                        try {
                            itemList = mIapConnector.getItemList(CURRENT_MODE, activity.getPackageName(),
                                    itemGroupId, startNum, endNum, ITEM_TYPE_ALL);
                        } catch (RemoteException e) {
                            Log.e(TAG, "Samsung getItemList: " + e.getMessage());
                        }
                        startNum += ITEM_RESPONSE_COUNT;
                        endNum += ITEM_RESPONSE_COUNT;
                    } while (processItemsBundle(itemList, itemGroupId, inventory, querySkuDetails, false, true,
                            queryItemIds));
                }
            }
        }
        return inventory;
    }

    @Override
    public void launchPurchaseFlow(Activity activity, String sku, String itemType, int requestCode,
            OnIabPurchaseFinishedListener listener, String extraData) {
        String itemGroupId = getItemGroupId(sku);
        String itemId = getItemId(sku);

        Bundle bundle = new Bundle();
        bundle.putString(KEY_NAME_THIRD_PARTY_NAME, activity.getPackageName());
        bundle.putString(KEY_NAME_ITEM_GROUP_ID, itemGroupId);
        bundle.putString(KEY_NAME_ITEM_ID, itemId);
        if (isDebugLog())
            Log.d(TAG, "launchPurchase: itemGroupId = " + itemGroupId + ", itemId = " + itemId);
        ComponentName cmpName = new ComponentName(SamsungApps.IAP_PACKAGE_NAME, PAYMENT_ACTIVITY_NAME);
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setComponent(cmpName);
        intent.putExtras(bundle);
        mRequestCode = requestCode;
        mPurchaseListener = listener;
        purchasingItemType = itemType;
        mItemGroupId = itemGroupId;
        mExtraData = extraData;
        if (isDebugLog())
            Log.d(TAG, "Request code: " + requestCode);
        activity.startActivityForResult(intent, requestCode);
    }

    @Override
    public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == options.samsungCertificationRequestCode) {
            if (resultCode == Activity.RESULT_OK) {
                bindIapService();
            } else if (resultCode == Activity.RESULT_CANCELED) {
                setupListener.onIabSetupFinished(new IabResult(IabHelper.BILLING_RESPONSE_RESULT_USER_CANCELED,
                        "Account certification canceled"));
            } else {
                setupListener.onIabSetupFinished(new IabResult(IabHelper.BILLING_RESPONSE_RESULT_ERROR,
                        "Unknown error. Result code: " + resultCode));
            }
            return true;
        }
        if (requestCode != mRequestCode) {
            return false;
        }
        int errorCode = IabHelper.BILLING_RESPONSE_RESULT_ERROR;
        String errorMsg = "Unknown error";
        Purchase purchase = new Purchase(OpenIabHelper.NAME_SAMSUNG);
        if (data != null) {
            Bundle extras = data.getExtras();
            if (extras != null) {
                int statusCode = extras.getInt(KEY_NAME_STATUS_CODE);
                errorMsg = extras.getString(KEY_NAME_ERROR_STRING);
                String itemId = extras.getString(KEY_NAME_ITEM_ID);
                switch (resultCode) {
                case Activity.RESULT_OK:
                    switch (statusCode) {
                    case IAP_ERROR_NONE:
                        errorCode = IabHelper.BILLING_RESPONSE_RESULT_OK;
                        break;
                    case IAP_ERROR_ALREADY_PURCHASED:
                        errorCode = IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED;
                        break;
                    case IAP_ERROR_PRODUCT_DOES_NOT_EXIST:
                        errorCode = IabHelper.BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE;
                        break;
                    }
                    break;
                case Activity.RESULT_CANCELED:
                    errorCode = IabHelper.BILLING_RESPONSE_RESULT_USER_CANCELED;
                    break;
                }
                String purchaseData = extras.getString(KEY_NAME_RESULT_OBJECT);
                try {
                    JSONObject purchaseJson = new JSONObject(purchaseData);

                    purchase.setOriginalJson(purchaseData);
                    purchase.setOrderId(purchaseJson.getString(JSON_KEY_PAYMENT_ID));
                    purchase.setPurchaseTime(Long.parseLong(purchaseJson.getString(JSON_KEY_PURCHASE_DATE)));
                    purchase.setToken(purchaseJson.getString(JSON_KEY_PURCHASE_ID));
                } catch (JSONException e) {
                    Log.e(TAG, "JSON parse error: " + e.getMessage());
                }

                purchase.setItemType(purchasingItemType);
                purchase.setSku(OpenIabHelper.getSku(OpenIabHelper.NAME_SAMSUNG, mItemGroupId + '/' + itemId));
                purchase.setPackageName(activity.getPackageName());
                purchase.setPurchaseState(0);
                purchase.setDeveloperPayload(mExtraData);
            }
        }
        if (isDebugLog())
            Log.d(TAG, "Samsung result code: " + errorCode + ", msg: " + errorMsg);
        mPurchaseListener.onIabPurchaseFinished(new IabResult(errorCode, errorMsg), purchase);
        return true;
    }

    @Override
    public void consume(Purchase itemInfo) throws IabException {
        // Nothing to do here
    }

    @Override
    public boolean subscriptionsSupported() {
        return true;
    }

    @Override
    public void dispose() {
        if (serviceConnection != null && isBound) {
            activity.getApplicationContext().unbindService(serviceConnection);
            isBound = false;
        }
        serviceConnection = null;
        mIapConnector = null;
    }

    private String getItemGroupId(String sku) {
        SamsungApps.checkSku(sku);
        String[] skuParts = sku.split("/");
        return skuParts[0];
    }

    private String getItemId(String sku) {
        SamsungApps.checkSku(sku);
        String[] skuParts = sku.split("/");
        return skuParts[1];
    }

    private void bindIapService() {
        serviceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mIapConnector = IAPConnector.Stub.asInterface(service);
                if (mIapConnector != null) {
                    initIap();
                } else {
                    setupListener.onIabSetupFinished(
                            new IabResult(IabHelper.BILLING_RESPONSE_RESULT_ERROR, "IAP service bind failed"));
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
        Intent serviceIntent = new Intent(IAP_SERVICE_NAME);
        isBound = activity.getApplicationContext().bindService(serviceIntent, serviceConnection,
                Context.BIND_AUTO_CREATE);
    }

    private void initIap() {
        int errorCode = IabHelper.BILLING_RESPONSE_RESULT_ERROR;
        String errorMsg = "Init IAP service failed";
        try {
            Bundle result = mIapConnector.init(CURRENT_MODE);
            if (result != null) {
                int statusCode = result.getInt(KEY_NAME_STATUS_CODE);
                if (isDebugLog())
                    Log.d(TAG, "Init IAP connection status code: " + statusCode);
                errorMsg = result.getString(KEY_NAME_ERROR_STRING);
                if (statusCode == IAP_ERROR_NONE) {
                    errorCode = IabHelper.BILLING_RESPONSE_RESULT_OK;
                }
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Init IAP: " + e.getMessage());
        }
        setupListener.onIabSetupFinished(new IabResult(errorCode, errorMsg));
    }

    private boolean processItemsBundle(Bundle itemsBundle, String itemGroupId, Inventory inventory,
            boolean querySkuDetails, boolean addPurchase, boolean addConsumable, Set<String> queryItemIds) {
        if (itemsBundle == null || itemsBundle.getInt(KEY_NAME_STATUS_CODE) != IAP_ERROR_NONE) {
            return false;
        }
        ArrayList<String> items = itemsBundle.getStringArrayList(KEY_NAME_RESULT_LIST);
        for (String itemString : items) {
            try {
                JSONObject item = new JSONObject(itemString);
                String itemId = item.getString(JSON_KEY_ITEM_ID);
                if (queryItemIds == null || queryItemIds.contains(itemId)) {
                    String rawType = item.getString(JSON_KEY_TYPE);
                    // Do not add consumable item into inventory
                    if (rawType.equals(ITEM_TYPE_CONSUMABLE) && !addConsumable) {
                        continue;
                    }
                    String itemType = rawType.equals(ITEM_TYPE_SUBSCRIPTION) ? IabHelper.ITEM_TYPE_SUBS
                            : IabHelper.ITEM_TYPE_INAPP;
                    String sku = OpenIabHelper.getSku(OpenIabHelper.NAME_SAMSUNG, itemGroupId + '/' + itemId);

                    if (addPurchase) {
                        Purchase purchase = new Purchase(OpenIabHelper.NAME_SAMSUNG);
                        purchase.setItemType(itemType);
                        purchase.setSku(sku);
                        purchase.setPackageName(activity.getPackageName());
                        purchase.setPurchaseState(0);
                        purchase.setDeveloperPayload("");

                        purchase.setOrderId(item.getString(JSON_KEY_PAYMENT_ID));
                        purchase.setPurchaseTime(Long.parseLong(item.getString(JSON_KEY_PURCHASE_DATE)));
                        purchase.setToken(item.getString(JSON_KEY_PURCHASE_ID));

                        inventory.addPurchase(purchase);
                    }
                    if (!addPurchase || querySkuDetails) {
                        String name = item.getString(JSON_KEY_ITEM_NAME);
                        String price = item.getString(JSON_KEY_ITEM_PRICE_STRING);
                        String desc = item.getString(JSON_KEY_ITEM_DESC);
                        inventory.addSkuDetails(new SkuDetails(itemType, sku, name, price, desc));
                    }
                }
            } catch (JSONException e) {
                Log.e(TAG, "JSON parse error: " + e.getMessage());
            }
        }
        return items.size() == ITEM_RESPONSE_COUNT;
    }

}