Java tutorial
/** * Modified MIT License * * Copyright 2015 OneSignal * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * 1. The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * 2. All copies of substantial portions of the Software may only be used in connection * with services provided by OneSignal. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.onesignal; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.onesignal.OneSignal; import com.onesignal.OneSignal.IdsAvailableHandler; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.IBinder; class TrackGooglePurchase { static private int iapEnabled = -99; private ServiceConnection mServiceConn; private static Class<?> IInAppBillingServiceClass; private Object mIInAppBillingService; private Method getPurchasesMethod, getSkuDetailsMethod; private Context appContext; private ArrayList<String> purchaseTokens; private SharedPreferences.Editor prefsEditor; // Any new purchases found count as pre-existing. // The constructor sets it to false if we already saved any purchases or already found out there isn't any. private boolean newAsExisting = true; private boolean isWaitingForPurchasesRequest = false; TrackGooglePurchase(Context activity) { appContext = activity; SharedPreferences prefs = appContext.getSharedPreferences("GTPlayerPurchases", Context.MODE_PRIVATE); prefsEditor = prefs.edit(); purchaseTokens = new ArrayList<String>(); try { JSONArray jsonPurchaseTokens = new JSONArray(prefs.getString("purchaseTokens", "[]")); for (int i = 0; i < jsonPurchaseTokens.length(); i++) purchaseTokens.add(jsonPurchaseTokens.get(i).toString()); newAsExisting = (jsonPurchaseTokens.length() == 0); if (newAsExisting) newAsExisting = prefs.getBoolean("ExistingPurchases", true); } catch (JSONException e) { e.printStackTrace(); } trackIAP(); } static boolean CanTrack(Context context) { if (iapEnabled == -99) iapEnabled = context.checkCallingOrSelfPermission("com.android.vending.BILLING"); try { if (iapEnabled == PackageManager.PERMISSION_GRANTED) IInAppBillingServiceClass = Class.forName("com.android.vending.billing.IInAppBillingService"); } catch (Throwable t) { iapEnabled = 0; return false; } return (iapEnabled == PackageManager.PERMISSION_GRANTED); } void trackIAP() { if (mServiceConn == null) { mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { iapEnabled = -99; mIInAppBillingService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { try { Class<?> stubClass = Class.forName("com.android.vending.billing.IInAppBillingService$Stub"); Method asInterfaceMethod = getAsInterfaceMethod(stubClass); asInterfaceMethod.setAccessible(true); mIInAppBillingService = asInterfaceMethod.invoke(null, service); QueryBoughtItems(); } catch (Throwable t) { t.printStackTrace(); } } }; Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); serviceIntent.setPackage("com.android.vending"); appContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); } else if (mIInAppBillingService != null) QueryBoughtItems(); } private void QueryBoughtItems() { if (isWaitingForPurchasesRequest) return; new Thread(new Runnable() { public void run() { isWaitingForPurchasesRequest = true; try { if (getPurchasesMethod == null) { getPurchasesMethod = getGetPurchasesMethod(IInAppBillingServiceClass); getPurchasesMethod.setAccessible(true); } Bundle ownedItems = (Bundle) getPurchasesMethod.invoke(mIInAppBillingService, 3, appContext.getPackageName(), "inapp", null); if (ownedItems.getInt("RESPONSE_CODE") == 0) { ArrayList<String> skusToAdd = new ArrayList<String>(); ArrayList<String> newPurchaseTokens = new ArrayList<String>(); ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST"); ArrayList<String> purchaseDataList = ownedItems .getStringArrayList("INAPP_PURCHASE_DATA_LIST"); for (int i = 0; i < purchaseDataList.size(); i++) { String purchaseData = purchaseDataList.get(i); String sku = ownedSkus.get(i); JSONObject itemPurchased = new JSONObject(purchaseData); String purchaseToken = itemPurchased.getString("purchaseToken"); if (!purchaseTokens.contains(purchaseToken) && !newPurchaseTokens.contains(purchaseToken)) { newPurchaseTokens.add(purchaseToken); skusToAdd.add(sku); } } if (skusToAdd.size() > 0) sendPurchases(skusToAdd, newPurchaseTokens); else if (purchaseDataList.size() == 0) { newAsExisting = false; prefsEditor.putBoolean("ExistingPurchases", false); prefsEditor.commit(); } // TODO: Handle very large list. Test for continuationToken != null then call getPurchases again } } catch (Throwable e) { e.printStackTrace(); } isWaitingForPurchasesRequest = false; } }).start(); } private void sendPurchases(final ArrayList<String> skusToAdd, final ArrayList<String> newPurchaseTokens) { try { if (getSkuDetailsMethod == null) { getSkuDetailsMethod = getGetSkuDetailsMethod(IInAppBillingServiceClass); getSkuDetailsMethod.setAccessible(true); } Bundle querySkus = new Bundle(); querySkus.putStringArrayList("ITEM_ID_LIST", skusToAdd); Bundle skuDetails = (Bundle) getSkuDetailsMethod.invoke(mIInAppBillingService, 3, appContext.getPackageName(), "inapp", querySkus); int response = skuDetails.getInt("RESPONSE_CODE"); if (response == 0) { ArrayList<String> responseList = skuDetails.getStringArrayList("DETAILS_LIST"); Map<String, JSONObject> currentSkus = new HashMap<String, JSONObject>(); JSONObject jsonItem; for (String thisResponse : responseList) { JSONObject object = new JSONObject(thisResponse); String sku = object.getString("productId"); BigDecimal price = new BigDecimal(object.getString("price_amount_micros")); price = price.divide(new BigDecimal(1000000)); jsonItem = new JSONObject(); jsonItem.put("sku", sku); jsonItem.put("iso", object.getString("price_currency_code")); jsonItem.put("amount", price.toString()); currentSkus.put(sku, jsonItem); } JSONArray purchasesToReport = new JSONArray(); for (String sku : skusToAdd) { if (!currentSkus.containsKey(sku)) continue; purchasesToReport.put(currentSkus.get(sku)); } // New purchases to report. // Wait until we have a userID then send purchases to server. If successful then mark them as tracked. if (purchasesToReport.length() > 0) { final JSONArray finalPurchasesToReport = purchasesToReport; OneSignal.idsAvailable(new IdsAvailableHandler() { public void idsAvailable(String userId, String registrationId) { OneSignal.sendPurchases(finalPurchasesToReport, newAsExisting, new OneSignalRestClient.ResponseHandler() { public void onFailure(int statusCode, JSONObject response, Throwable throwable) { OneSignal.Log(OneSignal.LOG_LEVEL.WARN, "HTTP sendPurchases failed to send.", throwable); isWaitingForPurchasesRequest = false; } public void onSuccess(String response) { purchaseTokens.addAll(newPurchaseTokens); prefsEditor.putString("purchaseTokens", purchaseTokens.toString()); prefsEditor.remove("ExistingPurchases"); prefsEditor.commit(); newAsExisting = false; isWaitingForPurchasesRequest = false; } }); } }); } } } catch (Throwable t) { OneSignal.Log(OneSignal.LOG_LEVEL.WARN, "Failed to track IAP purchases", t); } } private static Method getAsInterfaceMethod(Class clazz) { for (Method method : clazz.getMethods()) { Class<?>[] args = method.getParameterTypes(); if (args.length == 1 && args[0] == android.os.IBinder.class) return method; } return null; } private static Method getGetPurchasesMethod(Class clazz) { for (Method method : clazz.getMethods()) { Class<?>[] args = method.getParameterTypes(); if (args.length == 4 && args[0] == int.class && args[1] == String.class && args[2] == String.class && args[3] == String.class) return method; } return null; } private static Method getGetSkuDetailsMethod(Class clazz) { for (Method method : clazz.getMethods()) { Class<?>[] args = method.getParameterTypes(); if (args.length == 4 && args[0] == int.class && args[1] == String.class && args[2] == String.class && args[3] == Bundle.class) return method; } return null; } }