Distributing Applications on Google Play Store - Pushing the Limits - Android Programming: Pushing the Limits (2014)

Android Programming: Pushing the Limits (2014)

Part III. Pushing the Limits

Chapter 20. Distributing Applications on Google Play Store

Publishing Android applications has become very easy thanks to all the improvements made to the Google

Play Store. Google provides a number of useful tools, guides, and checklists that will help you in the process

of getting your application out to your users. In order to gain access to the Google Play Console, you must

first register for a publisher account and set up a Google Wallet Merchant Account if you want to sell apps

or support in-app purchases. You do this through the Google Play Developer Console, which you can find at

https://play.google.com/apps/publish (see Figure 20-1).

Figure 20-1 The Google Play Developer Console after a successful sign-up

Google Play supports additional features such as an advanced In-app Billing service that lets you sell digital

content from within your application and provides the means by which users can sign up for a subscription for a

service within your application. Both of these features are managed by the In-app Billing APIs that I cover in this

chapter.

If you don’t want to charge for your application or for digital in-app content but still want to have some revenue

from your application, you can integrate ads. You do so by using the Google AdMob Ads SDK, which lets you

place ad banners in your application as standard Android View objects.

In some situations, you may want to verify that your paid application is licensed to run on the most current

device. To support this, Google provides the Google Play Licensing Service, which lets you perform automatic

verifications on the purchase state of an application. You’ll find a guide for integrating this service in this

chapter.

Android limits APK files distributed through Google Play to 50MB. Some games and apps require resources that

exceed this limit, which is why Google Play supports the use of APK extension files. I provide a guide for using

this service in the section, "APK Expansion Files," later in this chapter.

In-app Billing

Although selling your application is the most obvious way of monetizing your application, sometimes it’s more

efficient to provide your application for free and rely on users purchasing additional content from within your

application. This is handled by an In-app Billing service that lets users buy digital content or subscriptions for a

specific application.

To get started with in-app purchases, you need a valid Google Wallet Merchant Account, which you find in the

Google Play Developer Console. Once you get this set up, you need to enable billing for your application and

upload it to the Google Play Store. (Note that you don’t need to publish the app to enable in-app billing.)

Start by adding the following permission to your manifest, which allows your application to perform in-app

purchases:

<uses-permission android:name=”com.android.vending.BILLING” />

Next, you must copy the file IInAppBillingService.aidl from the play_billing directory in the

Android SDK extras and place it in the package com.android.vending.billing under the aidl sources

directory of your application. I also recommend that you copy the util package from the sample application

TrivialDrive found in the play_billing directory. These classes will make it much easier to implement the

In-app Billing API in your own application.

You also need to define the product IDs (also known as SKUs) for your different in-app products. Each product

that the user can purchase must have a unique SKU, and you define them in the Google Play Developer Console

as follows:

public static final String APPLE_SKU = “apple”;

public static final String BANANA_SKU = “banana”;

public static final String ORANGE_SKU = “orange”;

public static final String AVOCADO_SKU = “avocado”;

public static final String LIME_SKU = “lime”;

public static final String[] FRUIT_SKUS = new String[] {APPLE_SKU,

BANANA_SKU,

ORANGE_SKU,

AVOCADO_SKU,

LIME_SKU};

private Inventory mInventory;

private Set<String> mOwnedFruits = new HashSet<String>();

When your application starts, you create the IabHelper class that you copied from the TrivialDrive samples

application, as shown in the following code:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mHelper = new IabHelper(this, BASE_64_PUBLIC_KEY);

mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {

@Override

public void onIabSetupFinished(IabResult result) {

if(!result.isSuccess()) {

// TODO Error handling of In-App Billing integration!

}

}

});

}

Load the current Inventory for all the products, as shown next. This class is also part of the utility

package that you copied from the samples. This query also allows you to check whether the current user has

purchased a specific SKU.

private void loadFruitInventory() {

mHelper.queryInventoryAsync(true,

new IabHelper.QueryInventoryFinishedListener() {

@Override

public void onQueryInventoryFinished(IabResult result,

Inventory inventory) {

if(result.isSuccess()) {

mInventory = inventory;

mOwnedFruits.clear();

for (String fruitSku : FRUIT_SKUS) {

if(inventory.hasPurchase(fruitSku)) {

mOwnedFruits.add(fruitSku);

}

}

}

}

});

}

In-app purchases are asynchronous because they will start an Activity in the Google Play Store application.

The following is the code for launching the purchase flow for buying a fruit. Note that you also have to

implement onActivityResult() and call handleActivityResult() on the helper class.

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent

data) {

if(!mHelper.handleActivityResult(PURCHASE_FRUIT, resultCode, data)) {

super.onActivityResult(requestCode, resultCode, data);

}

}

private void purchaseFruit(String sku) {

// User doesn’t own this fruit yet

if (!mOwnedFruits.contains(sku)) {

mHelper.launchPurchaseFlow(this, sku, PURCHASE_FRUIT,

new IabHelper.OnIabPurchaseFinishedListener() {

@Override

public void onIabPurchaseFinished(IabResult result,

Purchase info) {

if (result.isSuccess()) {

mOwnedFruits.add(info.getSku());

} else {

// TODO Error handling!

}

}

}, mBase64UserId);

}

}

Consuming Products

Once a product is purchased, it can be “consumed.” Using consumable objects is useful in games—for example,

virtual coins or, in this case, virtual fruit. The following example shows how to “consume” such a virtual fruit that

the user has purchased:

private void consumeFruit(String sku) {

mHelper.consumeAsync(mInventory.getPurchase(sku),

new IabHelper.OnConsumeFinishedListener() {

@Override

public void onConsumeFinished(Purchase purchase, IabResult result)

{

if(result.isSuccess()) {

Log.d(TAG, “Purchase successful!”);

mOwnedFruits.remove(purchase.getSku());

}

}

});

}

In-app Subscriptions

It’s also possible to implement subscriptions as part of the In-app Billing API. Doing so allows users to confirm a

recurring charge to their Google Play account. The recurrence is either monthly or yearly (this might change in

the future), and the API also supports a first-time free trial for your subscriptions.

Starting the subscription purchase flow works the same way that it does with normal in-app purchases, as

shown in the following example:

private void subcribeToUnlimitedFruit() {

if (mHelper.subscriptionsSupported()) {

mHelper.launchSubscriptionPurchaseFlow(this,

UNLIMITED_FRUIT_SUBSCRIPTION,

SUBSCRIBE_UNLIMITED_FRUIT,

new IabHelper.OnIabPurchaseFinishedListener() {

@Override

public void onIabPurchaseFinished(IabResult result,

Purchase info) {

if (result.isSuccess() &&

UNLIMITED_FRUIT_SUBSCRIPTION.

equals(info.getSku())) {

mUnlimitedFruits = true;

} else {

// TODO Error handling...

}

}

});

}

}

How you decide to utilize the In-app Billing API in your application depends on what your application or games

do. The preceding example uses fruit, and you treat the fruit as consumables that will be removed from the

user’s purchased items once the fruit is consumed. If you provide an In-app Billing service for removing ads or

upgrading to a Pro version of your applications, then you should not treat the purchases as consumables.

In-app subscriptions aren’t supported in all countries, which is why you should always check whether they’re

available before presenting them to the user. Also, remember that subscriptions involve your commitment to

continue to deliver content or services for users who sign up.

Ads in Android Applications

Although selling your application or integrating in-app purchases are ways of monetizing your work, doing so

may not always be a viable choice. For example, if you wish to provide the application for free to increase the

number of users, you can instead integrate ads that will give you a way to monetize your application while still

providing it free-of-charge to your users. Google supports in-app ads through its AdMob services. To use this

service, you need to sign up with AdMob at http://www.google.com/ads/admob.

Ironically, by combining ads with an in-app purchase that allows users to get rid of the ads, you can create

an additional method for monetizing on your application. In this section, I show how you can integrate ads

through the AdMob service provided by Google and also how you can provide the option for removing those

ads through an in-app purchase.

To start, you need to download the AdMob SDK for Android, which you can find at https://developers.

google.com/mobile-ads-sdk. Unzip the archive and copy the file GoogleAdMobAdsSdk-6.4.1.jar

to the libs directory of your project. Next, add the following dependency in your Gradle build script:

compile files(‘libs/GoogleAdMobAdsSdk-6.4.1.jar’)

You now have the AdMob SDK for Android integrated into your project.

Next, you add the permissions as well as the AdActivity to your manifest, as shown here:

<uses-permission android:name=”android.permission.INTERNET” />

<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />

<application

android:allowBackup=”true”

android:icon=”@drawable/ic_launcher”

android:label=”@string/app_name”

android:theme=”@style/AppTheme”>

<activity android:name=”com.google.ads.AdActivity”

android:configChanges=”keyboard|keyboardHidden|

orientation|screenLayout|

uiMode|screenSize|

smallestScreenSize”/>

The simplest way to integrate ads in your application is by using the class com.google.ads.AdView. You

can use this class either in your XML layouts or programmatically in Java.

Here is a simple example of using the AdView in your XML layout:

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”

xmlns:ads=”http://schemas.android.com/apk/lib/com.google.

ads”

xmlns:tools=”http://schemas.android.com/tools”

android:layout_width=”match_parent”

android:layout_height=”match_parent”

tools:context=”.AdDemo”>

<com.google.ads.AdView

android:id=”@+id/adView”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

ads:adUnitId=”AD_UNIT_ID”

ads:adSize=”SMART_BANNER”

ads:testDevices=”DEVICE_ID_FOR_TESTING”

ads:loadAdOnCreate=”true”/>

</LinearLayout>

The first ads attribute, adUnitId, is your AdMob publisher ID. The adSize attribute decides how large your

ad will be. A SMART_BANNER will adapt itself to the screen size. You can find the full list of supported banner

sizes for AdMob SDK at https://developers.google.com/mobile-ads-sdk/docs/admob/

intermediate#android-sizes. You need to set the testDevices attribute to the device ID for your development device. (An example of retrieving the device ID is in the section “Application Licensing” later in

this chapter.) Doing so enables you to debug and test the AdMob integration, which otherwise wouldn’t be

possible.

In the earlier example with the XML layout, the ads are loaded automatically when the View is loaded. This

may not always be what you want, so when you need more fine-grained control over when ads are loaded, you

can use the approach shown in the following method. You can also use this method to refresh the ad manually,

which will load a new banner.

private void loadAds() {

AdView adView = (AdView) findViewById(R.id.adView);

adView.loadAd(new AdRequest());

}

Targeting Ads

You can target the ads by giving the AdRequest a number of parameters, which you may want to do if you can

define the context and user preferences. The following code shows how to define gender, location, birthday and

a set of keywords to the AdRequest:

private void targetAdd(boolean isMale,

Location location,

Date birthday,

Set<String> keywords) {

AdRequest adRequest = new AdRequest();

adRequest.setGender(isMale ?

AdRequest.Gender.MALE : AdRequest.Gender.FEMALE);

adRequest.setLocation(location);

adRequest.setBirthday(birthday);

adRequest.setKeywords(keywords);

AdView adView = (AdView) findViewById(R.id.adView);

adView.loadAd(adRequest);

}

Another important aspect of targeting ads is determining whether the ads need to comply with the Children’s

Online Privacy Protection Act (COPPA). You can use this in your application to ensure that all ads displayed are

“child safe” according to COPPA.

You can add the following code to indicate that your ads should be displayed according to COPPA. If you want

to indicate that the ads should not be treated according to COPPA, change the value from 1 to 0.

AdMobAdapterExtras adMobAdapterExtras = new AdMobAdapterExtras();

adMobAdapterExtras.addExtra(“tag_for_child_directed_treatment”, 1);

adRequest.setNetworkExtras(adMobAdapterExtras);

Ad Colors

You can also change the overall color scheme of the ads, which is especially useful when you want to adapt the

look and feel of the ads to the color scheme of your application.

The following example shows how to set a color scheme for the ads:

AdMobAdapterExtras adColor = new AdMobAdapterExtras();

adColor.addExtra(“color_bg”, “AAAAFF”);

adColor.addExtra(“color_bg_top”, “FFFFFF”);

adColor.addExtra(“color_border”, “FFFFFF”);

adColor.addExtra(“color_link”, “000080”);

adColor.addExtra(“color_text”, “808080”);

adColor.addExtra(“color_url”, “008000”);

adRequest.setNetworkExtras(adColor);

Use this with care, as a bad color combination can annoy the user and make them abandon your application.

Interstitial Ads

If you think banner ads aren’t suitable for your application, you can instead use interstitial ads. These ads allow

you to display a full-screen ad in your application that the user can either interact with or dismiss when it times

out. Although more intrusive than banner ads, interstitial ads won’t affect your own Views in the same way

banners do.

The following code illustrates how to load an interstitial ad and display it once it’s ready. All you need to do is

implement the logic in onDismissScreen() where you move to the next screen in your app or game.

private void loadInterstitialAd() {

mInterstitialAd = new InterstitialAd(this,

MY_INTERSTITIAL_AD_UNIT_ID);

mInterstitialAd.setAdListener(new AdListener() {

@Override

public void onReceiveAd(Ad ad) {

mInterstitialAd.show();

}

@Override

public void onFailedToReceiveAd(Ad ad,

AdRequest.ErrorCode errorCode) {

}

@Override

public void onPresentScreen(Ad ad) { }

@Override

public void onDismissScreen(Ad ad) {

// TODO User dismissed the ad, show next screen...

}

image

@Override

public void onLeaveApplication(Ad ad) { }

});

mInterstitialAd.loadAd(new AdRequest());

}

Application Licensing

When you sell your application on the Google Play Store, you have a clear way of monetizing on your

application. However, because it’s possible to extract an APK file from a device, there is a risk that your

application will be distributed through channels you haven’t approved. The many alternative app stores for

Android make it virtually impossible for developers to check all of them.

Once you set up the project and library correctly, you need to add the correct permission to your manifest, as

shown next. This allows your application to perform licensing checks.

<uses-permission android:name=”com.android.vending.CHECK_LICENSE” />

Performing a license check is easy:

private void checkLicense() {

String deviceId = Settings.Secure.

getString(getContentResolver(),

Settings.Secure.ANDROID_ID);

LicenseChecker licenseChecker =

new LicenseChecker(this,

new ServerManagedPolicy(this,

new AESObfuscator(SALT,

getPackageName(),

deviceId)),

BASE_64_PUBLIC_KEY);

licenseChecker.checkAccess(mLicenseCheckerCallback);

}

The SALT parameter must be a byte array of 20 random bytes. BASE_64_PUBLIC_KEY must be the full

licensing key found in your Google Play Developer Console for your application, as shown in Figure 20-2.

Figure 20-2 The Base64 encoded public RSA key for the licensing service shown in the Google Play Developer Console

In the callback for the licensing check shown here, you simply display a Toast if the user isn’t licensed to use

this application:

class MyLicenseCheckerCallback implements LicenseCheckerCallback {

@Override

public void allow(int reason) {

Log.d(TAG, “License check passed!”);

}

@Override

public void dontAllow(int reason) {

Log.e(TAG, “License check failed - notify user!”);

Toast.makeText(MainActivity.this,

R.string.licensing_failed_message,

Toast.LENGTH_LONG).show();

// TODO Open Google Play Store for this application and quit

}

@Override

public void applicationError(int errorCode) {

Log.e(TAG, “Application error: “ + errorCode);

finish();

}

}

For each application that uses the licensing service, you have a number of different choices. You can notify the

user about a missing license, open Google Play Store for your application, and finish the Activity. Another

option is to perform a graceful degradation of the enabled features.

APK Expansion Files

When your game (or application) comes with a huge amount of content, the 50MB limit of APK files may not

be sufficient. In that case, you can use the APK Expansion Files feature to distribute up to 4GB of additional

data. This data will be stored on the shared storage where it will be available to all applications, so if the data is

sensitive, you need to encrypt it yourself (refer to Chapter 12 for more on this topic). The exact location of the

expansion files is <shared-storage>/Android/obb/<package-name>, where <shared-storage>

is the same path as returned by Environment.getExternalStorageDirectory().

You can have two expansion files, each with a maximum file size of 2GB, and they can contain any kind of data.

The first file is named main, and the second file, which is optional, is named patch. The name of the file uses

the following scheme:

[main|patch].<expansion-version>.<package-name>.obb

The first component of the name determines which file (main or patch) it is. The second part must match the

attribute android:versionCode in the manifest of your application. The final part is the package name of

your application.

Creating Expansion Files

The expansion files can be of any format, but I recommend using the ZIP file format because the Expansion

Library provides some helper classes to read files from such an archive. If your expansion file will contain media

files that you’ll be loading with MediaPlayer or SoundPool, package them without additional compression; that

way, you can simply load them with the standard APIs using their offset in the ZIP file.

The following command shows how you can create a ZIP file with your additional content:

$ zip –n .mp4;.ogg <expansion-file> <media files>

The –n parameter tells the zip command not to compress files with those extensions. <expansion-file>

is the filename of your expansion file, and <media files> is a list of all the files you want to include.

Downloading Expansion Files

In most situations, the expansion files are downloaded automatically by the Google Play client and placed in

external storage. However, in some situations, the files must be downloaded by your application. This is usually

the case when the user has swapped the SD card used for the external storage or deletes the contents of the

external storage manually.

In this situation, you need to perform a request using the Application Licensing service (as shown in the earlier

section “Application Licensing”) that will respond with the URLs for the expansion files. As a result, you must

include the License Verification Library, even if your application is free and otherwise wouldn’t require the

Application Licensing service. Also, because it needs to perform some network operations, your application

must declare the android.permission.INTERNET permission.

To simplify the manual download process, a Download Library is included in the Google Play APK Expansion

Library package that makes integration of the service easier.

Start by extending the class DownloaderService, as shown here, and add the public key form the licensing

service (see previous section):

public class MyDownloaderService extends DownloaderService {

public static final String BASE64_PUBLIC_KEY = “<Base64

LicensingKey>”;

public static final byte[] SALT = new byte[32];

@Override

public void onCreate() {

super.onCreate();

new Random().nextBytes(SALT);

}

@Override

public String getPublicKey() {

return BASE64_PUBLIC_KEY;

}

@Override

public byte[] getSALT() {

return SALT;

}

@Override

public String getAlarmReceiverClassName() {

return DownloadAlarmReceiver.class.getName();

}

}

The following is an example of the broadcast receiver that is used for starting the DownloaderService in

case it is terminated unexpectedly:

public class DownloadAlarmReceiver extends BroadcastReceiver {

private static final String TAG = “DownloadAlarmReceiver”;

public void onReceive(Context context, Intent intent) {

try {

DownloaderClientMarshaller.

startDownloadServiceIfRequired(context,

intent,

MyDownloaderService.

class);

} catch (PackageManager.NameNotFoundException e) {

Log.e(TAG, “Cannot find MyDownloaderService.”, e);

}

}

}

Finally, add a method in your main Activity that checks whether the correct expansion files have been

downloaded. The following is an example of such a method that you can build upon. You should probably

extend this method with additional checks of the content, just to be sure the file isn’t modified.

private void performDownloadIfNeeded() {

String fileName = Helpers.

getExpansionAPKFileName(this, true, EXPANSION_FILE_VERSION);

if (!Helpers.doesFileExist(this, fileName,

EXPANSION_FILE_SIZE, true)) {

Intent notifierIntent =

new Intent(this, MainActivity.class);

notifierIntent.

setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |

Intent.FLAG_ACTIVITY_CLEAR_TOP);

PendingIntent pendingIntent =

PendingIntent.getActivity(this, 0,

notifierIntent,

PendingIntent.FLAG_UPDATE_CURRENT);

try {

DownloaderClientMarshaller.

startDownloadServiceIfRequired(this,

pendingIntent,

MyDownloaderService.class);

} catch (PackageManager.NameNotFoundException e) {

Log.e(TAG, “Cannot find downloader service.”, e);

}

}

}

This method shows how you can add expansion files to your application on Google Play, how to verify that

you have them available, and how to initiate download them when needed. Use the expansion files as the first

solution for dealing with content that doesn’t fit within the maximum size allowed for APK files.

Summary

In this final chapter, I covered some of the features related to the Google Play Store. The features available

for developers presented in this chapter can help you monetize your application as well as deliver additional

content that doesn’t fit within the 50MB size limit for APK files. You can generate income for your application

three ways: You can sell it, add in-app purchases, or integrate ads.

Carefully consider which strategy to use when you decide to monetize your application. Although simply selling

your application may seem like the simplest solution, you’ll probably gain a larger user base by providing the

application for free and offering users the opportunity to buy virtual goods instead. Ads are easy to include but

will consume screen space in your application or game. They might also be considered too intrusive by some

users. Consider all aspects before deciding on which solution works best for each application because changing

your strategy at a later point may be difficult.

Further Resources Websites

Distributing apps on the Google Play Store: http://developer.android.com/distribute/index.html

The Google Play Developer Console: https://play.google.com/apps/publish

Resources for Google Play distribution: http://developer.android.com/google/play/dist.html

The Google Mobile Ads SDK downloads and documentation: https://developers.google.com/mobile-ads-sdk

Google AdMob service: www.google.com/ads/admob