About Us

Case Studies

Blog

Subscribe

Privacy Policy

SDG Systems

330 Perry Hwy.

Suite 200

Harmony, PA 16037

 

+1.724.452.9366

info@sdgsystems.com

© 2020 by SDG Systems, LLC. All rights reserved.

  • admin

In-­App Billing: From Start to Finish

Note: We asked our summer intern, Joel, to add a couple of features to our Barcode List Manager (Barliman) app for Android: In-app billing, and GPS location recording. He recorded his experience in this article.

Introduction

Recently, I had the opportunity to convert our Barcode List Manager app* (also known as Barliman) from a simple, free app, to more feature-rich app with an in-app-­billed (IAB) pro upgrade. To do this, I used the IAB Helper class included with the Google Play Services In-­App-­Billing sample application, Trivial Drive. In this article, I will detail a brief overview of the journey Barliman went through, as well as several pitfalls in its path to In-­App-­Billing.


The Miles Before the First Foot

Before I began implementing IAB, I needed to complete two key tasks:

  • Choosing / Adding features that would be designated as "Pro" features"

  • Implementing multiple product flavors

For the first requirement, I built a location tracking class and integrated it into the existing Barliman codebase. Any access to the location features of the app would require the pro upgrade. The reason I rolled my own location services class instead of using the (very well implemented) Google Location Services tied into why the second requirement existed. Barliman, after the pro upgrade, needed to be three different versions:


A free version for Android Open Source Project (AOSP) (available for download)A paid version for AOSP (to be provided to clients of products based on AOSP)An IAB version for the Google Play Store

Because I developed for Android Open Source, I could not use Google's Location Services package. Using Android Product Flavors (as linked above), proved to be the easiest path to implementing the multiple-­version requirement. Android Product Flavors provided a simple, yet powerful tool for creating multiple versions of the same app. I used just a few of the myriad of features of this tool, specifically version-­specific source and resource sets, intuitive compiling of code, and flexible manifest-­merging (between a sub-­flavors and the main manifest). The key lesson I learned for implementing multiple flavors, however, was to let each flavor differ in simple yet powerful ways in order to let the main code handle each flavor gracefully without complications. I let the main code base dictate the app's behavior, and flavor implementation smoothly followed.


The Journey of 1000 Miles Begins...

Once I completed the prerequisites for Barliman's IAB, I began setting up the IAB Helper class provided in Google's IAB Sample Application. The two main tasks of this phase were importing the IInAppBillingService.aidl and IABHelper files into the project, and then encrypting Barliman's public key for use by the IABHelper class. Importing the IInAppBillingService.aidl required that I add an aidl folder under my IAB flavor's directory, and then adding the following to the build.gradle file for the project:

       sourceSets {
		IABFlavor { aidl.srcDirs = ['src/IABFlavor/aidl'] }
	}


At first, importing seemed to fail (i.e., the compiler was complaining about a reference being missing / the IInAppBillingService being undefined), but, after much fiddling and finagling, the build system recognized the location of the aidl file. Next, implementing the encrypted license key, was as simple as performing an XOR cipher upon the license key. The XOR cipher proved to be simple to implement, as it automatically reversed itself (simple example below).

	// In an ideal world, you would be XORing your key with a number of the same size.
        // This is an example: don't duplicate this!
	public String getPublicKey() {
		String key = "";
		char[] encodedKey = SOME_CONSTANT_CHAR_ARRAY;
		char seed = SOME_CHAR_VALUE;
		for(int i = 0; i < encodedKey.length; ++i) {
			key = key + ((char)(encodedKey[i] ^ seed));
		}
		return key;
	}


With the preliminary utilities set up, I could then begin implementing my class in charge of IAB.


Slow and Steady...

Implementing and debugging the ProUpgradeManager class, paradoxically, proved both to be incredibly easy and incredibly frustrating. My usage of Google's IABHelper class facilitated quick and simple implementation of the essential upgrade process. For instance, upgrading could be as simple as

Initializing the IabHelper instanceCalling its startSetup method (passing a listener that will query the inventory for the pro upgrade and respond if upgrade is already purchased)Generating a payload (for your personal verification purposes)Calling the IabHelper.launchPurchaseFlow method (javadoc comment below along with personal comments from me)

       /**
	* Initiate the UI flow for an in-­app purchase. Call this method to initiate an in-­app purchase,
	* which will involve bringing up the Google Play screen. The calling activity will be paused while
	* the user interacts with Google Play, and the result will be delivered via the activity's
	* {@link android.app.Activity#onActivityResult} method, at which point you must call
	* this object's {@link #handleActivityResult} method to continue the purchase flow. This
method
	* MUST be called from the UI thread of the Activity. *
	* @param act The calling activity. //or the current visible actvity
	* @param sku The sku of the item to purchase.
	* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or
ITEM_TYPE_SUBS) //some overloads don't need this
	* @param requestCode A request code (to differentiate from other responses -­-­
	* as in {@link android.app.Activity#startActivityForResult}).
	* @param listener The listener to notify when the purchase process finishes
	* @param extraData Extra data (developer payload), which will be returned with the purchase data //the payload you generated in step 1. Perform verification against this.
	* when the purchase completes. This extra data will be permanently bound to that purchase
	* and will always be returned when the purchase is queried.
	*/
	public void launchPurchaseFlow(Activity act, String sku, String itemType,
int requestCode,OnIabPurchaseFinishedListener listener,
String extraData);


To my shame, however, my implementation for displaying the upgrade prompt was, at first, not implemented very cleanly. My initial implementation involved adding menu buttons with listeners to the ActionBar. However, managing the asynchronous interaction between the IabHelper.startSetup method and the display logic for these button was unnecessarily difficult and led to a messy, buggy implementation. My final implementation, however, simply relied upon Preferences with onClick listeners in the Settings menu. The sum total of the interaction of my UpgradeManager class and the settings menu consisted of

//In SettingsActivity.java
@Override
protected void onPostCreate(Bundle savedInstanceState) {
  Preference button;
  button = (Preference) findPreference("upgrade_preference_button");
  button.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
    @Override
    public boolean onPreferenceClick(Preference preference) {
      ProUpgradeManager.upgrade();
      return true;
    }
  });
  if(ProUpgradeManager.displayPrompt()){
    button.setEnabled(true);
  }
  else{
    button.setEnabled(false);
  }
}


Another difficulty (also of my own causing) was Android Studio's buggy interaction with non-­ Oracle Java. I began development with OpenJDK on my Linux machine (Ubuntu 15.04). However, Android Studio and OpenJDK did not play well together. The debugger worked sporadically, the build system sometimes/how missed updated code, and the IDE itself would sometimes slow to a crawl for no apparent reason. The only solution I found to my problems were frequent Cleans and Rebuilds of the project as well as using File -­> Invalidate Caches / Restart . Therefore, the biggest lesson I learned is if the IDE tells you at install that you do not have Oracle Java installed, and that Oracle Java is recommended, use Oracle Java! I would have saved myself much time debugging if I had simply installed Oracle Java from the beginning.


As you well know, many times errors occur because a frighteningly small section of code was either forgotten or implemented poorly. A key section of code (that my eyes conveniently glossed over) in the sample application was the override of the activity's onActivityResult method (example below). Since I hadn't implemented this in my code, several hours were spent debugging fruitlessly before I realized my mistake.

//In the activity file
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  debug(TAG, "onActivityResult()");
  if(!ProUpgradeManager.passActivityResult(requestCode, resultCode, data)) {
    super.onActivityResult(requestCode, resultCode, data);
  }
}

//In the ProUpgradeManager file
public static boolean passActivityResult(int requestCode, int resultCode, Intent data) {
  if (mHelper == null) return false; //make sure the IABHelper hasn't been disposed
  Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
  // Pass on the activity result to the helper for handling
  if (!mHelper.handleActivityResult(requestCode, resultCode, data)) { return false; }
  else {
    Log.d(TAG, "onActivityResult handled by IABHelper.");
    return true;
  }
}


The final hurdle towards implementing IAB was a problem almost outside my control: a bug in the IabHelper class itself! For testing your IAB app, Google recommends using static responses. Simply put, you shouldn't, as the IAB static response for a successful purchase is badly broken. Numerous hours were spent poring over my code, searching for flaws, before I chanced upon this StackOverflow answer. After reading about the broken status of static responses, I made some short workarounds in the IabHelper code itself in order to continue testing Barliman. Don't do this! You would be better off submitting an apk with the billing permission tacked on (and then adding In App Products and locally testing with the IAB code and the newly made SKUs) than attempting to test with static responses.


The Journey Condensed

Implementing IAB: Some tips

  • Use product flavors to separate IAB versions from free / paid versions. Remember to let your main source code do most of the work.

  • Ensure you have imported the IInAppBillingService.aidl correctly.

  • Use an XOR cipher to encrypt your public license key.

  • Make the interface to your IAB code simplistic, and make sure to let your non-­IAB code do the grunt work.

  • Use Oracle Java.

  • Ensure you have implemented all the necessary components of the sample code (especially the onActivityResult method).

  • Don't use static responses. Debug with actual SKUs instead.

*Note: We have since renamed Barliman to "Data List Manager" because it now supports NFC in addition to barcode scanning.

8 views