Professional Android 4 Application Development (2012)
Chapter 18. Advanced Android Development
What's in this Chapter?
Securing Android using permissions
Sending server pushes with Cloud to Device Messaging
Adding copy protection with the License Verification Library
Monetizing with In-App Billing
Using Wake Locks
Inter-process communication (IPC) using AIDL and Parcelables
Improving application performance using Strict Mode
Ensuring backward and forward hardware and software compatibility
This chapter both returns to some of the possibilities touched on in previous chapters and introduces some of the more advanced options available for Android developers.
The chapter starts by taking a closer look at security—in particular, how permissions work and how to use them to secure your own applications and the data they contain.
Next, you'll be introduced to Android's Cloud to Device Messaging (C2DM) service and learn how to use it to eliminate polling within your application, replacing it with server-initiated pushes.
You'll also be introduced to the License Verification Library (LVL) and In-App Billing services. These services enable you to protect your applications from piracy and monetize them by selling virtual content.
The chapter then examines Wake Locks and the Android Interface Definition Language (AIDL). You'll use AIDL to create rich application interfaces that support full object-based inter-process communication (IPC) between Android applications running in different processes.
Finally, you'll learn how to build applications that are backward and forward compatible across a range of hardware and software platforms, and then investigate the use of Strict Mode for discovering inefficiencies within your applications.
Paranoid Android
Much of Android's security is supplied by its underlying Linux kernel. Application files and resources are sandboxed to their owners, making them inaccessible by other applications. Android uses Intents, Services, and Content Providers to let you relax these strict process boundaries, using permissions to maintain application-level security.
You've already used the permission system to request access to native system services—including location-based services, native Content Providers, and the camera—using uses-permission manifest tags.
The following sections provide a more detailed look at the Linux security model and the Android permission system. For a comprehensive view, the Android documentation provides an excellent resource that describes the security features in depth:developer.android.com/guide/topics/security/security.html.
Linux Kernel Security
Each Android package has a unique Linux user ID assigned to it during installation. This has the effect of sandboxing the process and the resources it creates, so that it can't affect (or be affected by) other applications.
Because of this kernel-level security, you need to take additional steps to communicate between applications, or access the files and resources they contain. Content Providers, Intents, Services, and AIDL interfaces are designed to open tunnels through which information can flow between applications. To ensure information doesn't “leak” beyond the intended recipients, you can use Android permissions to act as border guards at either end to control the traffic flow.
Introducing Permissions
Permissions are an application-level security mechanism that lets you restrict access to application components. Permissions are used to prevent malicious applications from corrupting data, gaining access to sensitive information, or making excessive (or unauthorized) use of hardware resources or external communication channels.
As you learned in earlier chapters, many of Android's native components have permission requirements. The native permission strings used by native Android Activities and Services can be found as static constants in the android.Manifest.permission class.
To use permission-protected components, you need to add uses-permission tags to your application manifests, specifying the permission strings your application requires.
When a package is installed, the permissions requested in its manifest are analyzed and granted (or denied) by checks with trusted authorities and user feedback. All Android permission checks are done at installation. Once an application is installed, users will not be prompted to reevaluate those permissions.
Declaring and Enforcing Permissions
Before you can assign a permission to an application component, you need to define it within your manifest using the permission tag, as shown in the Listing 18.1.
Listing 18.1: Declaring a new permission
<permission
android:name="com.paad.DETONATE_DEVICE"
android:protectionLevel="dangerous"
android:label="Self Destruct"
android:description="@string/detonate_description">
</permission>
code snippet PA4AD_Ch18_Permissions/AndroidManifest.xml
Within the permission tag, you can specify the level of access that the permission will permit (normal, dangerous, signature, signatureOrSystem), a label, and an external resource containing the description that explains the risks of granting this permission.
To define custom permissions for components within your application, use the permission attribute in the manifest. Permission constraints can be enforced throughout your application, most usefully at application interface boundaries—for example:
· Activities—Add a permission to limit the ability of other applications to launch a particular Activity.
· Broadcast Receivers—Add a permission to control which applications can send Broadcast Intents to your Receiver.
· Intents—Add a permission to control which Broadcast Receivers can receive a Broadcast Intent.
· Content Providers—Add a permission to limit read access and/or write operations on your Content Providers.
· Services—Add a permission to limit the ability of other applications to start or bind to a Service.
In each case, you can add a permission attribute to the application component in the manifest, specifying a required permission string to access each component. Listing 18.2 shows a manifest excerpt that requires the permission defined in Listing 18.1 to start an Activity, Service, and Broadcast Receiver.
Listing 18.2: Enforcing a permission requirements
<activity
android:name=".MyActivity"
android:label="@string/app_name"
android:permission="com.paad.DETONATE_DEVICE">
</activity>
<service
android:name=".MyService"
android:permission="com.paad.DETONATE_DEVICE">
</service>
<receiver
android:name=".MyReceiver"
android:permission="com.paad.DETONATE_DEVICE">
<intent-filter>
<action android:name="com.paad.ACTION_DETONATE_DEVICE"/>
</intent-filter>
</receiver>
code snippet PA4AD_Ch18_Permissions/AndroidManifest.xml
Content Providers let you set readPermission and writePermission attributes to offer a more granular control over read/write access:
<provider
android:name=".HitListProvider"
android:authorities="com.paad.hitlistprovider"
android:writePermission="com.paad.ASSIGN_KILLER"
android:readPermission="com.paad.LICENSED_TO_KILL"
/>
Enforcing Permissions when Broadcasting Intents
In addition to requiring permissions for Intents to be received by your Broadcast Receivers, you can attach a permission requirement to each Intent you broadcast. This is good practice when broadcasting Intents that contain sensitive information, such as location updates that should only be used within your application.
In such cases it's best practice to require a signature permission to ensure that only applications signed with the same signature as the host application can receive the broadcast:
<permission
android:name="com.paad.LOCATION_DATA"
android:protectionLevel="signature"
android:label="Location Transfer"
android:description="@string/location_data_description">
</permission>
When calling sendIntent, you can supply the permission string required for a Broadcast Receivers to receive the Intent.
sendBroadcast(myIntent, "com.paad.LOCATION_DATA");
Introducing Cloud to Device Messaging
The Cloud to Device Messaging (C2DM) service provides an alternative to regularly polling a server for updates; instead, your server can “push” messages to a specific client.
The frequency of your application's background polling can have a dramatic impact on the host device's battery life, so you always need to compromise between data freshness and the resulting power drain.
Introduced in Android 2.2 (API level 8), C2DM allows you to eliminate background polling, and instead have your server notify a particular device when new data is available for it.
On the client side, C2DM is implemented using Intents and Broadcast Receivers. As a result, your application does not need to be active in order to receive C2DM messages. On the server side, C2DM messages area transmitted from your server to each target device by way of the C2DM service.
The C2DM service maintains an open TCP/IP connection with each device, allowing it to transmit information instantly whenever required. The C2DM service takes care of maintaining and restoring that connection, queuing messages, and retrying failed deliveries.
In the following sections you'll learn how to:
· Register each device on which your application is running with the Android C2DM server.
· Notify your server of the C2DM address of your application running on a particular device.
· Transmit messages from your server to the C2DM service.
· Receive your server messages within your application once they're relayed through the C2DM service.
C2DM is a Google service, so its documentation is available at http://code.google.com/android/c2dm/.
C2DM Restrictions
C2DM is not designed as a blanket replacement for background polling. It is best used in situations where only one device (or a small, distinct group of devices) requires updates at any given time—such as email or voicemail services.
The real-time nature of each push makes C2DM an ideal alternative for situations where the updates are unlikely to be at predictable intervals; however, successful message delivery, latency, and delivery order are not guaranteed. As a result, you should not rely on C2DM for critical messages or where timeliness is important. It's also good practice to implement a traditional polling mechanism at long intervals as a fail-safe.
The transmitted messages should be lightweight and are limited to 1024 bytes. They should carry very little payload, instead containing only the information required for the client application to efficiently query your server for the data directly.
C2DM is based around existing Google services and requires Google Play to be installed on the device, and for the user to have a Google account configured.
At the time of writing, new C2DM accounts receive a development quota of up to 200,000 messages per day. If your production requirements demand more, you can request an increase—details on that process will be emailed to you after you sign up.
Signing Up to Use C2DM
The first step is to view and agree to the terms of the C2DM service at http://code.google.com/android/c2dm/signup.html.
As part of the registration process, you will be asked for your application's package name, an estimate of the total number of daily messages you plan to send, and the estimated peak queries per second (QPS). The C2DM team uses this information to help identify applications that may need to be granted larger quotas.
You will also be asked to supply three email addresses: your contact details, an escalation email address for urgent issues, and a role account that will be used to authenticate with the C2DM service and send messages from your server.
The role account should be a Google account used specifically for use with the C2DM service. Because you will be providing a server with authentication details for this account, it's good practice to create a new account rather than use a personal Gmail or Google Play account.
After receiving confirmation that your account has been enabled for sending C2DM messages, you can update your application to register itself, and each device it's running on, with the C2DM service.
Registering Devices with a C2DM Server
In order for your application to receive C2DM messages, it must first register each installed instance of itself with the C2DM service. Start by adding a com.google.android.c2dm.permission.RECEIVE uses-permission node to your manifest:
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
You should also define (and request) a signature-level permission that restricts the receipt of C2DM messages targeted at your application to applications signed with the same key:
<permission android:name="com.example.myapp.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.myapp.permission.C2D_MESSAGE" />
Registering an application for C2DM is a three-step process, as shown in Figure 18.1.
Figure 18.1
The process of registering your application on each device with the C2DM service associates each installed instance of your application with the device on which it is installed. Once registered, the C2DM service returns a registration ID that uniquely identifies that particular installation. Your application should send that ID, along with a way to identify each installation (typically a username or anonymous UUID) to your server.
Begin by starting a Service using an Intent that includes the com.google.android.c2dm.intent.REGISTER action. It must include extras to identify your application and specify your sender account, as shown in Listing 18.3.
Listing 18.3: Registering an application instance with the C2DM server
Intent registrationIntent =
new Intent("com.google.android.c2dm.intent.REGISTER");
registrationIntent.putExtra("app",
PendingIntent.getBroadcast(this, 0, new Intent(), 0));
registrationIntent.putExtra("sender",
"myC2DMaccount@gmail.com");
startService(registrationIntent);
code snippet PA4AD_Ch18_C2DM/src/MyActivity.java
Your application is identified using the app extra key and a Pending Broadcast Intent that will be populated by the C2DM service to send your application messages when they are received.
The sender extra is used to specify the role account you registered when signing up for C2DM, and will be used by your server to transmit messages.
The platform will transmit this information to the C2DM server, which will return a registration ID. To receive this, you need to register a Broadcast Receiver that listens for the com.google.android.c2dm.intent.REGISTRATION action, requires the com.google.android.c2dm.permission.SENDpermission, and includes your application package name as a category, as shown in Listing 18.4.
Listing 18.4: Listening for C2DM registration IDs
<receiver
android:name=".MyC2DMReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action
android:name="com.google.android.c2dm.intent.REGISTRATION"
/>
<category android:name="com.mypackage.myc2dmAppName"/>
</intent-filter>
</receiver>
code snippet PA4AD_Ch18_C2DM/AndroidManifest.xml
The registration ID for each application/device pair may be changed at any time, so it's important that your application continue listening for new REGISTRATION Broadcast Intents.
The registration ID itself is included in the registration_id extra, as shown in Listing 18.5. If the registration process fails, the error code will be included as an error extra, and successful deregistration requests will be signaled using the unregistered extra.
Listing 18.5: Extracting the C2DM registration ID
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(
"com.google.android.c2dm.intent.REGISTRATION")) {
String registrationId = intent.getStringExtra("registration_id");
String error = intent.getStringExtra("error");
String unregistered = intent.getStringExtra("unregistered");
if (error != null) {
// Registration failed.
if (error.equals("SERVICE_NOT_AVAILABLE")) {
Log.e(TAG, "Service not available.");
// Retry using exponential back off.
}
else if (error.equals("ACCOUNT_MISSING")) {
Log.e(TAG, "No Google account on device.");
// Ask the user to create / add a Google account
}
else if (error.equals("AUTHENTICATION_FAILED")) {
Log.e(TAG, "Incorrect password.");
// Ask the user to re-enter their Google account password.
}
else if (error.equals("TOO_MANY_REGISTRATIONS")) {
Log.e(TAG, "Too many applications registered.");
// Ask the user to unregister / uninstall some applications.
}
else if (error.equals("INVALID_SENDER")) {
Log.e(TAG, "Invalid sender account.");
// The sender account specified has not been registered
// with the C2DM server.
}
else if (error.equals("PHONE_REGISTRATION_ERROR")) {
Log.e(TAG, "Phone registration failed.");
// The phone doesn't currently support C2DM.
}
} else if (unregistered != null) {
// Unregistration complete. The application should stop
// processing any further received messages.
Log.d(TAG, "Phone deregistration completed successfully.");
} else if (registrationId != null) {
Log.d(TAG, "C2DM registration ID received.");
// Send the registration ID to your server.
}
}
}
code snippet PA4AD_Ch18_C2DM/src/MyC2DMReceiver.java
The received registration ID becomes the address your server uses to target a message at this particular device/application instance. Accordingly, you need to transmit this ID to your server, along with an identifier it can use to identify the user associated with this installation. This will allow you to look up the device address based on a particular user in order to transmit data to him or her. In the case of email, this might be the username; for voicemail, the phone number; or for a game, a generated UUID.
It's good practice to create a server-side hash to simplify the lookup. Keep in mind that a single user may have multiple devices, so you may need to include a collision algorithm that determines which device should receive the message (or if multiple devices should receive them).
Also remember that the registration ID may subsequently change, so be sure to retransmit the identifier/ID pair should that happen.
You can unregister a device by calling startService, passing in an Intent that uses the com.google.android.c2dm.intent.UNREGISTER action, and including an app extra that uses a Pending Intent to identify your application:
PendingIntent pi = PendingIntent.getBroadcast(this, 0, new Intent(), 0);
Intent unregister = new Intent("com.google.android.c2dm.intent.UNREGISTER");
unregister.putExtra("app", pi);
startService(unregister);
Sending C2DM Messages to Devices
Once you've recorded a particular device's registration ID on your server, it's possible for it to transmit messages to that device. Sending messages is a two-step process, as shown in Figure 18.2.
Figure 18.2
Creating the server-side implementation of C2DM is beyond the scope of this book. If your server is an AppEngine application, the Chrome 2 Phone project (http://code.google.com/p/chrometophone/) includes a server-side implementation that can be used to greatly simplify the process of authentication and message transmission to the C2DM service.
Your server transmits a message to the C2DM service using POST requests to https://android.apis.google.com/c2dm/send that include the following parameters:
· registration_id—The address of the target device/application pair.
· collapse_key—When the target device is offline, messages transmitted to it will be queued. By specifying a collapse key, you can effectively collapse that queue, causing each message with the same key to override the previous so that only the last message gets sent to the target device.
· data.[key]—Payload data in the form of key/value pairs. They will be passed in to your application as extras within the C2DM message Intent, using the keys you specify. Each C2DM message is limited to 1024 bytes, so payload data should be kept to the bare minimum—typically only the information required for the client to perform an efficient lookup.
· delay_while_idle—By default, messages transmitted to a device will be sent as quickly as possible. By setting this parameter to true, you can delay the transmission until the device is active. This is similar to setting a non-waking alarm, and can be useful for prolonging battery life where messages don't need to be received immediately. The collapse key you specify will be used to collapse the queue of pending messages so that only one message is transmitted/received when the device becomes active.
In addition to the POST parameters, you must include a header with a Google ClientLogin auth token whose cookie must be associated with the Android C2DM service.
The auth token should be generated for the C2DM Google account that your client applications used when registering with the C2DM server.
Details for implementing a server-side ClientLogin process is beyond the scope of this book. You can find details on generating a Google auth token at http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html.
Receiving C2DM Messages
After your server transmits messages to the C2DM service, they are, in turn, sent to the device to which they are addressed. The target device then delivers each message to its recipient application as a Broadcast Intent.
To receive these Intents, you must register a Broadcast Receiver that includes the com.google.android.c2dm.permission.SEND permission, a filter for the com.google.android.c2dm.intent.RECEIVE action, and the category set to the application's package name, as shown in Listing 18.6.
Listing 18.6: Registering to receive C2DM messages
<receiver
android:name=".C2DMMessageReceiver"
android:permission="com.google.android.c2dm.permission.SEND">
<intent-filter>
<action
android:name="com.google.android.c2dm.intent.RECEIVE"
/>
<category android:name="com.mypackage.myc2dmAppName"/>
</intent-filter>
</receiver>
code snippet PA4AD_Ch18_C2DM/AndroidManifest.xml
Within the associated Broadcast Receiver implementation, you can extract any extras using the keys you specified when sending the associated server message, as shown in Listing 18.7.
Listing 18.7: Extracting C2DM message details
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(
"com.google.android.c2dm.intent.RECEIVE")) {
Bundle extras = intent.getExtras();
// Extract any extras included in the server messages.
int newVoicemailCount = extras.getInt("VOICEMAIL_COUNT", 0);
}
}
code snippet PA4AD_Ch18_C2DM/src/C2DMMessageReceiver.xml
Due to the payload data limit, it's generally considered good practice to include as little payload data as possible and to use an incoming C2DM message as a tickle to indicate that the application should perform a server update.
Implementing Copy Protection Using the License Verification Library
Android 1.5 (API level 3) introduced a network-based solution for implementing copy protection for your applications. The License Verification Library (LVL) is a Google service that works together with Google Play to allow your application to query the license status of your application for a given user.
Full details for implementing an LVL solution for copy protection is outside the scope of this book. This section aims to provide an introduction to the LVL, outlining its concepts and best-practice implementation patterns.
The Android Developer Guide has a detailed guide to using the LVL, including asample implementation: http://developer.android.com/guide/publishing/licensing.html.
Installing the License Verification Library
The LVL provides a series of APIs that handle the interaction with the licensing service to request licensing confirmation and return the results to your applications. It also simplifies and encapsulates the process of defining policies for caching and offline license verification. It includes theServerManagedPolicy implementation that encapsulates the best-practice policy settings.
The LVL is distributed as an “extras” SDK package as the “Google Market Licensing package,” and can be downloaded using the Android SDK Manager, as described in Chapter 2, “Getting Started.”
After downloading the LVL, add it to Eclipse as a library project, and then import it into your existing applications. Details for creating and using Eclipse library packages using the ADT plug-in are available at http://developer.android.com/guide/developing/projects/projects-eclipse.html.
To use the LVL, you need to add the com.android.vending.CHECK_LICENSE permission to your application manifest:
<uses-permission android:name="com.android.vending.CHECK_LICENSE"/>
Finding Your License Verification Public Key
In order to perform LVL checks, you need to include a public key for validation requests. You must first create a sign-in to the Android Developer Console.
Select the Edit Profile link from https://play.google.com/apps/publish/ and scroll down to the Licensing & In-app Billing heading, as shown in Figure 18.3.
Figure 18.3
From here, you can also specify a number of test accounts that will receive the static response you specify.
Configuring Your License Validation Policy
The license validation Policy specifies the configuration options that will be used to execute license checks and determine their effects. It should manage caching of requests, handling of error codes, retries, and offline verification checks.
Although it's possible to create your own implementation of the Policy class, the LVL includes a best-practice policy whose settings are managed by the Licensing Service—the ServerManagedPolicy.
In order to support caching and offline validation support, the Server Managed Policy requires an obfuscator to obfuscate the cached values. The LVL includes the AESObfuscator, which seeds the encryption using the following:
· A salt—An array of random bytes.
· A package name—The application's full (and unique) package name.
· A unique device identifier—Typically a UUID created the first time the application is run.
Performing License Validation Checks
Start by creating a new LicenseChecker object in the onCreate handler of your Activity, specifying the Context, a Policy instance, and your public key, as shown in Listing 18.8.
Listing 18.8: Creating a new License Checker
// Generate 20 random bytes, and put them here.
private static final byte[] SALT = new byte[] {
-56, 42, 12, -18, -10, -34, 78, -75, 54, 88,
-13, -12, 36, 17, -34, 114, 77, 12, -23, -20};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Construct the LicenseChecker with a Policy.
licenseChecker =
new LicenseChecker(this, new ServerManagedPolicy(this,
new AESObfuscator(SALT, getPackageName(), deviceID)),
PUBLIC_KEY);
}
code snippet PA4AD_Ch18_LVS/src/MyActivity.xml
To perform the license check, call the License Checker object's checkAccess method, passing in an implementation of the LicenseCheckerCallback interface, as shown in Listing 18.9. Successful validation will result in the allow handler being triggered, whereas failure will trigger dontAllow.
Listing 18.9: Performing a license check
licenseChecker.checkAccess(new LicenseCheckerCallback(){
public void allow() {
// License verified.
}
public void dontAllow() {
// License verification failed.
}
public void applicationError(ApplicationErrorCode errorCode) {
// Handle associated error code.
}
});
code snippet PA4AD_Ch18_LVS/src/MyActivity.xml
Both License Checker handlers will always return on a background thread. If you plan to update the UI based on license verification callbacks, you will first need to synchronize with the main application thread.
It's up to you to determine where in your application, and how frequently, you want to make license validation checks—and how to react to failure. It's generally considered best practice to be as unpredictable as possible. This makes it more difficult for hackers to determine where your application is making checks and whether their attempts at circumventing your checks have been successful.
Many developers have found it useful to only partially disable an application that fails the license checks—for example, by limiting the number of levels available, increasing the difficulty level, or otherwise providing a less complete product. As a result, they can then direct users to Google Play at a later point to encourage them to purchase the full version.
Introducing In-App Billing
Introduced in Android 1.6 (API level 4), In-App Billing (IAB) is a Google Play service that can be used as an alternative (or addendum) to charging up-front for an application.
Using IAB, you can charge users for digital content within your applications, including virtual in-game content, such as upgrading to the “full” version, purchasing additional levels, or buying weaponry, armor, or other in-game artifacts. You can also use IAB (though you aren't required to) when charging for downloadable content, such as music, video, books, or images.
The IAB service operates using the Google Play Store, which handles all transaction processing and operates under the same revenue-sharing model as for paid applications—specifically, requiring a 30 percent transaction fee.
IAB has proven to be a powerful new monetization option for application developers. Despite the relatively low cost of mobile games and applications, consumers are wary of paying for applications without a guarantee of their quality. By implementing an IAB solution, you provide prospective users with a risk-free way to experience the quality and usefulness of your application, along with a simple way to upgrade their experience once they're satisfied that the additional functionality is worth the cost.
Similarly, rather than charging users once for access, IAB provides an avenue for providing users with ongoing or renewable resources—particularly in games—such as the ability to skip levels or simplify their in-game experience through the purchase of virtual goods they would otherwise need to invest significant time to earn.
Full details for implementing an IAB solution is beyond the scope of this book. This section aims to provide an introduction to using IAB, outlining its concepts and best-practice implementation patterns.
The Android Developer Guide has a detailed guide to integrating IAB, including a sample implementation: http://developer.android.com/guide/market/billing/index.html.
In-App Billing Restrictions
IAB is a Google service implemented using the Google Play Store client. As a result, before using IAB within your applications, you must have Google Checkout Merchant account, and your IAB applications must be published on Google Play.
As a server-based solution, IAB is available only on devices that have a network connection.
IAB is only available for selling virtual goods, including in-game artifacts or downloadable digital content. It can't be used to sell physical goods or serves.
Installing the In-App Billing Library
The IAB library and sample source is distributed as an “extras” SDK package, and can be downloaded using the Android SDK Manager, as described in Chapter 2.
To use IAB, you need to specify the com.android.vending.BILLING permission in your application manifest:
<uses-permission android:name="com.android.vending.BILLING" />
Finding Your Public Key and Defining Your Purchasable Items
Like license verification checks, in order to perform IAB transactions, you need to include a public key.
You can find your public key from your Google Play publisher account. Once you've signed in, select the Edit Profile link from https://play.google.com/apps/publish and scroll down to the Licensing & In-app Billing heading (refer to Figure 18.3).
To specify the items that can be purchased within each of your applications, click the In-App Products link beneath its listing in the Android Developer Console. The link will be available if you have a Google Checkout Merchant account, and only for applications whose manifest includes thecom.android.vending.BILLING permission.
The product list is used to store the metadata describing each product you are selling, including its unique ID and price. The content itself must be stored either within the application or on your own servers. The product ID will be used within your application when initiating an in-app purchase.
Initiating In-App Billing Transactions
To use IAB, your application sends a billing request for a specific in-app product to the IAB service; that service then handles the transaction before sending an Intent to your application containing the purchase details.
In order to execute billing requests, your application must bind to the MarketBillingService class. The sample application included as part of the IAB library package includes the AIDL file that defines the interface with this service, so before attempting to bind to the Market Billing Service, copy the AIDL definition into your project.
It's best practice to perform all IAB transactions within a Service, ensuring that an Activity closing or restarting does not interfere with an IAB transaction.
You can bind to the Market Billing Service from your own Service. Implement a new ServiceConnection to obtain a reference to the IMarketBillingService, as shown in Listing 18.10.
Listing 18.10: Binding to the Market Billing Service
IMarketBillingService billingService;
private void bindService() {
try {
String bindString =
"com.android.vending.billing.MarketBillingService.BIND";
boolean result = context.bindService(new Intent(bindString),
serviceConnection, Context.BIND_AUTO_CREATE);
} catch (SecurityException e) {
Log.e(TAG, "Security Exception.", e);
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
billingService = IMarketBillingService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) {
billingService = null;
}
};
code snippet PA4AD_Ch18_IAB/src/MyService.xml
You can now use this Market Billing Service reference to make billing request calls using the sendBillingRequest method. Note that this must be performed on the main application thread.
To make a billing request, you must pass in a Bundle parameter that specifies the type of transaction you want to execute, the version of the IAB API you are using, your package name, and the product ID to be purchased, as shown in Listing 18.11.
Listing 18.11: Creating a billing request
protected Bundle makeRequestBundle(String transactionType,
String itemId) {
Bundle request = new Bundle();
request.putString("BILLING_REQUEST", transactionType);
request.putInt("API_VERSION", 1);
request.putString("PACKAGE_NAME", getPackageName());
if (itemId != null)
request.putString("ITEM_ID", itemId);
return request;
}
code snippet PA4AD_Ch18_IAB/src/MyService.xml
The following five billing request types are supported:
· REQUEST_PURCHASE—Initiates a purchase request.
· CHECK_BILLING_SUPPORTED—Verifies that IAB is supported on the host device.
· GET_PURCHASE_INFORMATION—Requests the transaction information for a prior purchase or a refund.
· CONFIRM_NOTIFICATIONS—Acknowledges the receipt of the transaction information related to a purchase or refund.
· RESTORE_TRANSACTIONS—Retrieves a user's transaction history for his or her managed purchases.
To initiate the billing request, call the Market Billing Service's sendBillingRequest method, passing in the Bundle:
Bundle response = billingService.sendBillingRequest(request);
The sendBillingRequest method will return a Bundle response that contains a response code, request ID, and a Pending Intent that you use to launch the checkout UI.
Handling In-App Billing Purchase Request Responses
When your billing request type is REQUEST_PURCHASE, your application must listen for two Broadcast Intents—one containing a response code and another containing an IAB notification—to determine the success of your attempted transaction:
<receiver android:name="IABReceiver">
<intent-filter>
<action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
<action android:name="com.android.vending.billing.RESPONSE_CODE" />
<action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED"/>
</intent-filter>
</receiver>
After the Market Billing Service successfully receives your billing request, it broadcasts a RESPONSE_CODE Intent whose result is set to RESULT_OK.
When the transaction itself has been executed, the Market Billing Service broadcasts an IN_APP_NOTIFY Intent. This Broadcast Intent contains a notification ID that, along with a nonce, is used to retrieve the purchase information for a given purchase request using the GET_PURCHASE_INFORMATIONrequest type.
Making a purchase information request returns a Bundle containing a response code and request ID, as well as triggering two further asynchronous Broadcasts Intents. The first, a RESPONSE_CODE Intent, returns the success and error status associated with your purchase request, using the nonce you specified in the request as an identifier.
If the purchase is successful, a PURCHASE_STATE_CHANGED broadcast will also be broadcast, containing detailed transaction information as a signed JSON string.
Using Wake Locks
In order to prolong battery life, when an Android device is left idle, it will first dim, then turn off the screen, and, finally, turn off the CPU.
WakeLocks are a Power Manager system Service feature that your application can use to control the power state of the host device.
Wake Locks can be used to keep the CPU running, prevent the screen from dimming, prevent the screen from turning off, and prevent the keyboard backlight from turning off.
Creating and holding Wake Locks can have a dramatic impact on the host device's battery life. It's good practice to use Wake Locks sparingly, creating them only when strictly necessary and holding them for as short a time as possible.
Because of the dramatic impact Wake Locks can have on battery life, your application needs to request a WAKE_LOCK permission in order to create them:
<uses-permission android:name="android.permission.WAKE_LOCK"/>
To create a Wake Lock, call newWakeLock on the Power Manager, specifying one of the following Wake Lock types.
· FULL_WAKE_LOCK—Keeps the screen at full brightness, the keyboard backlight illuminated, and the CPU running.
· SCREEN_BRIGHT_WAKE_LOCK—Keeps the screen at full brightness and the CPU running.
· SCREEN_DIM_WAKE_LOCK—Keeps the screen on (but lets it dim) and the CPU running.
· PARTIAL_WAKE_LOCK—Keeps the CPU running.
Screen dim Wake Locks typically are used to prevent the screen from dimming during applications that are likely to involve little user interaction—for example, a video player.
Partial Wake Locks (or CPU Wake Locks) are used to prevent the device from going to sleep until an action has completed. This is most commonly used by Services started within Intent Receivers, which may receive Intents while the device is asleep. It's worth noting that in this case the system will hold a CPU Wake Lock throughout the onReceive handler of the Broadcast Receiver.
If you start a Service, or broadcast an Intent within the onReceive handler of a Broadcast Receiver, it is possible that the Wake Lock it holds will be released before your Service has started or your Intent received. To ensure the Service execution is completed, you will need to put in place a separate Wake Lock policy.
After creating a Wake Lock, acquire it by calling acquire.
You can optionally specify a timeout to ensure the maximum duration the Wake Lock will be held for. When the action for which you're holding the Wake Lock completes, call release to let the system manage the power state.
Listing 18.12 shows the typical use pattern for creating, acquiring, and releasing a Wake Lock.
Listing 18.12: Using a Wake Lock
WakeLock wakeLock;
private class MyAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... parameters) {
PowerManager pm =
(PowerManager)getSystemService(Context.POWER_SERVICE);
wakeLock =
pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
wakeLock.acquire();
// TODO Do things in the background
return null;
}
@Override
protected void onPostExecute(Void parameters) {
wakeLock.release();
}
}
code snippet PA4AD_Ch18_Wakelocks/src/MyActivity.xml
Using AIDL to Support Inter-Process Communication for Services
In Chapter 9, “Working in the Background,” you learned how to create Services for your applications. Here, you'll learn how to use the Android Interface Definition Language (AIDL) to support rich inter-process communication (IPC) between Services and other application components, including components running within different applications or within separate processes. This gives your Services the capability to support multiple applications across process boundaries.
To pass objects between processes, you need to deconstruct them into OS-level primitives that the underlying OS can then marshal across application boundaries. This is done by implementing them as Parcelables.
AIDL is used to simplify the code that lets your processes exchange objects. It's similar to interfaces like COM or Corba in that it lets you create public methods within your Services that can accept and return object parameters and return values between processes.
Implementing an AIDL Interface
AIDL supports the following data types:
· Java language primitives (int, boolean, float, char, etc.).
· String and CharSequence values.
· List objects (including generics), where each element is a supported type. The receiving class will always receive the List object instantiated as an ArrayList.
· Map objects (not including generics), where every key and element is of a supported type. The receiving class will always receive the Map object instantiated as a HashMap.
· AIDL-generated interfaces (covered later). An import statement is always needed for these.
· Classes that implement the Parcelable interface (covered next). An import statement is always needed for these.
The following sections demonstrate how to make your classes Parcelable, create an AIDL Service definition, and implement and expose that Service definition for use by other application components.
Making Classes Parcelable
In order for non-native classes to be passed between processes, they must implement the Parcelable interface. This lets you decompose the properties within your classes into primitive types stored within a Parcel that can be marshaled across process boundaries.
Implement the writeToParcel method to decompose your class object, using the write* methods to save object properties into the outgoing Parcel object:
public void writeToParcel(Parcel out, int flags) {
out.writeLong(myLong);
out.writeString(myString);
out.writeDouble(myDouble);
}
To re-create an object that's been saved as a parcel, implement the public static Creator field (which implements a new Parcelable.Creator class) to create a new object based on an incoming Parcel by reading the incoming parcel using its read* methods:
private MyClass(Parcel in) {
myLong = in.readLong();
myString = in.readString();
myDouble = in.readDouble();
}
Listing 18.13 shows a basic example of using the Parcelable interface for the Quake class you've been using in the ongoing Earthquake example.
Listing 18.13: Making the Quake class a Parcelable
package com.paad.earthquake;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
public class Quake implements Parcelable {
private Date date;
private String details;
private Location location;
private double magnitude;
private String link;
public Date getDate() { return date; }
public String getDetails() { return details; }
public Location getLocation() { return location; }
public double getMagnitude() { return magnitude; }
public String getLink() { return link; }
public Quake(Date _d, String _det, Location _loc,
double _mag, String _link) {
date = _d;
details = _det;
location = _loc;
magnitude = _mag;
link = _link;
}
@Override
public String toString(){
SimpleDateFormat sdf = new SimpleDateFormat("HH.mm");
String dateString = sdf.format(date);
return dateString + ":" + magnitude + " " + details;
}
private Quake(Parcel in) {
date.setTime(in.readLong());
details = in.readString();
magnitude = in.readDouble();
Location location = new Location("generated");
location.setLatitude(in.readDouble());
location.setLongitude(in.readDouble());
link = in.readString();
}
public void writeToParcel(Parcel out, int flags) {
out.writeLong(date.getTime());
out.writeString(details);
out.writeDouble(magnitude);
out.writeDouble(location.getLatitude());
out.writeDouble(location.getLongitude());
out.writeString(link);
}
public static final Parcelable.Creator<Quake> CREATOR =
new Parcelable.Creator<Quake>() {
public Quake createFromParcel(Parcel in) {
return new Quake(in);
}
public Quake[] newArray(int size) {
return new Quake[size];
}
};
public int describeContents() {
return 0;
}
}
code snippet PA4AD_Ch18_Earthquake/src/Quake.java
Now that you have a Parcelable class, you need to create a corresponding AIDL definition to make it available when defining your Service's AIDL interface.
Listing 18.14 shows the contents of the Quake.aidl file you need to create for the Quake Parcelable defined in the preceding listing.
Listing 18.14: The Quake class AIDL definition
package com.paad.earthquake;
parcelable Quake;
code snippet PA4AD_Ch18_Earthquake/src/Quake.aidl
When passing class objects between processes, remember that AIDL objects aren't self-describing, so the client process must understand the definition of the object being passed.
Creating an AIDL Service Definition
In this section you will be defining a new AIDL interface definition for a Service you'd like to use across processes.
Start by creating a new .aidl file within your project. This will define the methods and fields to include in an interface that your Service will implement.
The syntax for creating AIDL definitions is similar to that used for standard Java interface definitions.
Specify a fully qualified package name, then import all the packages required. Unlike normal Java interfaces, AIDL definitions need to import packages for any class or interface that isn't a native Java type, even if it's defined in the same project.
Define a new interface, adding the properties and methods you want to make available. Methods can take zero or more parameters and return void or a supported type. If you define a method that takes one or more parameters, you need to use a directional tag (one of in, out, and inout) to indicate whether the each parameter is a value or reference type.
Where possible, you should limit the direction of each parameter, as marshaling parameters is an expensive operation.
Listing 18.15 shows a basic AIDL definition for the earthquake sample project you last modified in Listing 18.14. It should be implemented within the IEarthquakeService.aidl file.
Listing 18.15: An Earthquake Service AIDL interface definition
package com.paad.earthquake;
import com.paad.earthquake.Quake;
interface IEarthquakeService {
List<Quake> getEarthquakes();
void refreshEarthquakes();
}
code snippet PA4AD_Ch18_IPC/src/IEarthquakeService.aidl
Implementing and Exposing the AIDL Service Definition
If you're using the ADT plug-in, saving the AIDL file will automatically code-generate a Java Interface file. This interface will include an inner Stub class that implements the interface as an abstract class.
Have your Service extend the Stub and implement the functionality required. Typically, you'll do this using a private field variable within the Service whose functionality you'll be exposing.
Listing 18.16 shows an implementation of the IEarthquakeService AIDL definition created in Listing 18.15.
Listing 18.16: Implementing the AIDL Interface definition within a Service
IBinder myEarthquakeServiceStub = new IEarthquakeService.Stub() {
public void refreshEarthquakes() throws RemoteException {
EarthquakeUpdateService.this.refreshEarthquakes();
}
public List<Quake> getEarthquakes() throws RemoteException {
ArrayList<Quake> result = new ArrayList<Quake>();
ContentResolver cr
= EarthquakeUpdateService.this.getContentResolver();
Cursor c = cr.query(EarthquakeProvider.CONTENT_URI,
null, null, null, null);
if (c != null)
if (c.moveToFirst()) {
int latColumn = c.getColumnIndexOrThrow(
EarthquakeProvider.KEY_LOCATION_LAT);
int lngColumn = c.getColumnIndexOrThrow(
EarthquakeProvider.KEY_LOCATION_LNG);
int detailsColumn = c.getColumnIndexOrThrow(
EarthquakeProvider.KEY_DETAILS);
int dateColumn = c.getColumnIndexOrThrow(
EarthquakeProvider.KEY_DATE);
int linkColumn = c.getColumnIndexOrThrow(
EarthquakeProvider.KEY_LINK);
int magColumn = c.getColumnIndexOrThrow(
EarthquakeProvider.KEY_MAGNITUDE);
do {
Double lat = c.getDouble(latColumn);
Double lng = c.getDouble(lngColumn);
Location location = new Location("dummy");
location.setLatitude(lat);
location.setLongitude(lng);
String details =
c.getString(detailsColumn);
String link = c.getString(linkColumn);
double magnitude =
c.getDouble(magColumn);
long datems = c.getLong(dateColumn);
Date date = new Date(datems);
result.add(new Quake(date, details,
location, magnitude, link));
} while(c.moveToNext());
}
c.close();
return result;
}
};
code snippet PA4AD_Ch18_Earthquake/src/EarthquakeUpdateService.java
When implementing these methods, be aware of the following:
· All exceptions will remain local to the implementing process; they will not be propagated to the calling application.
· All IPC calls are synchronous. If you know that the process is likely to be time-consuming, you should consider wrapping the synchronous call in an asynchronous wrapper or moving the processing on the receiver side onto a background thread.
With the functionality implemented, you need to expose this interface to client applications. Expose the IPC-enabled Service interface by overriding the onBind method within your Service implementation to return an instance of the interface. Listing 18.17 demonstrates the onBindimplementation for the EarthquakeUpdateService.
Listing 18.17: Exposing an AIDL interface implementation to Service clients
@Override
public IBinder onBind(Intent intent) {
return myEarthquakeServiceStub;
}
code snippet PA4AD_Ch18_IPC/src/EarthquakeUpdateService.java
To use the AIDL-enabled Service from within an Activity, you must bind it, as shown in Listing 18.18.
Listing 18.18: Binding to an AIDL Service
IEarthquakeService earthquakeService = null;
private void bindService() {
bindService(new Intent(IEarthquakeService.class.getName()),
serviceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
earthquakeService = IEarthquakeService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) {
earthquakeService = null;
}
};
code snippet PA4AD_Ch18_IPC/src/BoundEarthquakeActivity.java
Dealing with Different Hardware and Software Availability
From smartphones to tablets to televisions, Android is now being used on an increasingly diverse collection of hardware. Each new device potentially represents a variation in hardware configuration or software platform. This flexibility is a significant factor in Android's success, but as a result, you can't make assumptions regarding the hardware or software running on the host platform.
To mitigate this, Android platform releases are forward compatible—meaning that applications designed before a particular hardware or software innovation is available will be able to take advantage of it without requiring changes.
One example of this forward-compatibility is the location-based services described in Chapter 13, “Maps, Geocoding, and Location-Based Services.” Rather than specifying a particular hardware provider, you choose a set of conditions and allow the system to select the best alternative using a generic interface. Should future hardware and software provide a better alternative, your application can take advantage without requiring an update.
Android platform releases are also backward compatible, meaning your application will continue to work on new hardware and platform releases—again without you needing to upgrade it each time.
By combining forward and backward compatibility, your Android application will continue to work, and even take advantage of new hardware and software features, as the platform evolves without requiring updates.
That said, each platform release includes new APIs and platform features. Similarly, new hardware may become available (such as NFC technology). Either advance could provide features that might improve the features and user experience of your application.
Attempting to use APIs that aren't available on a given host platform will cause a runtime exception. To take advantage of these new features without losing support for hardware running earlier platforms, you need to ensure your application is also backward compatible.
Similarly, the wide range of different Android device hardware platforms means that you can't make assumptions over what hardware might be available.
The following sections explain how to specify certain hardware as required, check for hardware availability at run time, and build applications that are backward compatible.
Specifying Hardware as Required
Application hardware requirements generally fall into two categories: hardware that is required for your application to have utility, and hardware that is useful if it is available but isn't strictly necessary. The former accounts for applications built around a particular piece of hardware—for example, a replacement camera application isn't useful on a device without a camera.
To specify a particular hardware feature as a requirement to install your application, add a uses-feature node to its manifest:
<uses-feature android:name="android.hardware.sensor.compass"/>
<uses-feature android:name="android.hardware.camera"/>
This can also be used for applications that don't necessarily require a particular piece of hardware, but which haven't been designed to support certain hardware configurations—for example, a game that requires tilt sensors or a touch screen to control.
The more hardware restrictions you place on your applications, the smaller the potential target audience becomes, so it's good practice to limit your hardware restrictions to those required to support core functionality.
Confirming Hardware Availability
For hardware that would be useful but isn't necessary, you need to query the host hardware platform at run time to determine what hardware is available. The Package Manager includes a hasSystemFeature method that accepts FEATURE_* static constants.
PackageManager pm = getPackageManager();
pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_COMPASS);
The Package Manager includes a constant for every piece of optional hardware, making it possible to customize your UI and functionality based on the hardware available.
Building Backward-Compatible Applications
Each new Android SDK release brings with it a raft of new hardware support, APIs, bug fixes, and performance improvements. It's best practice to update your applications as soon as possible following a new SDK release in order to take advantage of these new features and ensure the best possible user experience for new Android owners.
At the same time, ensuring your applications are backward compatible is critical to ensure users of devices running earlier Android platform versions can continue to use them—particularly as this is likely to be a significantly larger share of the market than that held by new devices.
Many of the convenience classes and UI improvements (such as Cursors and Fragments) are distributed as a stand-alone support library. Where features aren't available as part of the support library, this means incorporating new features and using the techniques described here to support multiple platform versions within the same package.
For each technique described, it's important to know the API level associated with the underlying platform. To find this at run time, you can use the android.os.Build.VERSION.SDK_INT constant:
Importing a class or attempting to call a method not available in the underlying platform will cause a runtime exception when the enclosing class is instantiated or the method is called.
private static boolean nfc_beam_supported =
android.os.Build.VERSION.SDK_INT > 14;
This can then be used within the techniques described below to decide which components to start or interfaces to implement.
Alternatively, you can use reflection or use exceptions—as shown in the following snippet—to check if a particular class or method is supported on the current device:
private static boolean fragmentsSupported = true;
private static void checkFragmentsSupported()throws NoClassDefFoundError {
fragmentsSupported = android.app.Fragment.class != null;
}
static {
try {
checkFragmentsSupported();
} catch (NoClassDefFoundError e) {
fragmentsSupported = false;
}
}
Both reflection and exceptions are particularly slow operations on Android, so it's best practice to use the SDK version to determine which classes are available.
The easiest way to determine which API level is required for a given class or method is to progressively lower your project's build target and note which classes break the build.
Parallel Activities
The simplest, though least efficient, alternative is to create separate sets of parallel Activities, Services, and Broadcast Receivers, based on a base class compatible with the minimum Android platform version you support.
When using explicit Intents to start Services or Activities, you can select the right set of components at run time by checking the platform version and targeting the appropriate Services and Activities accordingly:
private static boolean nfc_beam_supported =
android.os.Build.VERSION.SDK_INT > 14;
Intent startActivityIntent = null;
if (nfc_beam_supported)
startActivityIntent = new Intent(this, NFCBeamActivity.class);
else
startActivityIntent = new Intent(this, NonNFCBeamActivity.class);
startActivity(startActivityIntent);
In the case of implicit Intents and Broadcast Receivers, you can add an android:enabled tag to their manifest entries that refers to a Boolean resource:
<receiver
android:name=".MediaControlReceiver"
android:enabled="@bool/supports_remote_media_controller">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</receiver>
You can then create alternative resource entries based on API level:
res/values/bool.xml
<bool name="supports_remote_media_controller">false</bool>
res/values-v14/bool.xml
<bool name="supports_remote_media_controller">true</bool>
Interfaces and Fragments
Interfaces are the traditional way to support multiple implementations of the same functionality. For functionality that you want to implement differently based on newly available APIs, create an interface that defines the action to be performed, and then create API level-specific implementations.
At run time, check the current platform version and instantiate the appropriate class and use its methods:
IP2PDataXfer dataTransfer;
if (android.os.Build.VERSION.SDK_INT > 14)
dataTransfer = new NFCBeamP2PDataXfer();
else
dataTransfer = new NonNFCBeamP2PDataXfer();
dataTransfer.initiateP2PDataXfer();
With Fragments now available as part of the Android support library, they provide a more encapsulated alternative to parallelized components.
Rather than duplicating Activities, use Fragments—combined with the resource hierarchy—to create a consistent UI that's optimized for different platform releases and hardware configurations.
Most of the UI logic for your Activities should be contained within individual Fragments rather than the Activity itself. As a result, you need only create alternative Fragments to expose and utilize different functionality and inflate different versions of the same layout stored within their respectiveres/layout-v[API level] folders.
Interaction between and within Fragments is usually maintained within each Fragment, so only code related to missing APIs will need to be changed within the Activity. If each variation of a Fragment implements the same interface definition and ID, you shouldn't need to create multiple Activities to support multiple layouts and Fragment definitions.
Optimizing UI Performance with Strict Mode
The resource-constrained nature of mobile devices amplifies the effect of performing time-consuming operations on the main application thread. Accessing network resources, reading or writing files, or accessing databases while blocking the UI thread can have a dramatic impact on the user experience, causing your application to become less smooth, more laggy, and, in the most extreme case, unresponsive.
You learned how to move such time-consuming operations onto background threads in Chapter 9. Strict Mode (introduced in Android 2.3 (API level 9) is a tool that helps you identify cases you may have missed.
Using the Strict Mode APIs, you can assign a set of policies that monitor actions within your application and define how you should be alerted. You can define policies related to either the current application thread or to your application's virtual machine (VM) process. The former is perfect for detecting slow operations being performed on the UI thread, whereas the latter helps you detect memory and Context leaks.
To use Strict Mode, create a new ThreadPolicy class and a new VmPolicy class, using their static builder classes with the detect* methods to define the actions to monitor. The corresponding penalty* methods control how the system should react to detecting those actions.
The Thread Policy can be used to detect disk reads/writes and network access, whereas the Vm Policy can monitor your application for Activity, SQLite, and closeable object leaks.
The penalties available to both policies include logging or application death, while the Thread Policy also supports displaying an on-screen dialog or flashing screen border.
Both builder classes also include a detectAll method that includes all the possible monitoring options supported by the host platform. You can also use the StrictMode.enableDefaults method to apply the default monitoring and penalty options.
To enable Strict Mode across your entire application, you should extend the Application class, as shown in Listing 18.19.
Listing 18.19: Enabling Strict Mode for an application
public class MyApplication extends Application {
public static final boolean DEVELOPER_MODE = true;
@Override
public final void onCreate() {
super.onCreate();
if (DEVELOPER_MODE) {
StrictMode.enableDefaults();
}
}
}
code snippet PA4AD_Ch18_StrictMode/src/MyApplication.java
To enable Strict Mode (or customize its settings) for a particular Activity, Service, or other application component, simply use the same pattern within that component's onCreate method.