/* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.trivialdrivesample; import android.app.Activity; import android.app.AlertDialog; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.ImageView; import com.example.android.trivialdrivesample.R; import com.example.android.trivialdrivesample.util.Base64; import com.example.android.trivialdrivesample.util.IabHelper; import com.example.android.trivialdrivesample.util.IabResult; import com.example.android.trivialdrivesample.util.Inventory; import com.example.android.trivialdrivesample.util.Purchase; /** * Example game using in-app billing version 3. * * Before attempting to run this sample, please read the README file. It * contains important information on how to set up this project. * * All the game-specific logic is implemented here in MainActivity, while the * general-purpose boilerplate that can be reused in any game is provided in the * classes in the util/ subdirectory. When implementing your own application, * you can copy over util/*.java to make use of those utility classes. * * This game is a simple "driving" game where the player can buy gas * and drive. The car has a tank which stores gas. When the player purchases * gas, the tank fills up (1/4 tank at a time). When the player drives, the gas * in the tank diminishes (also 1/4 tank at a time). * * The user can also purchase a "premium upgrade" that gives them a red car * instead of the standard blue one (exciting!). * * It's important to note the consumption mechanics for each item. * * PREMIUM: the item is purchased and NEVER consumed. So, after the original * purchase, the player will always own that item. The application knows to * display the red car instead of the blue one because it queries whether * the premium "item" is owned or not. * * GAS: when gas is purchased, the "gas" item is then owned. We consume it * when we apply that item's effects to our app's world, which to us means * filling up 1/4 of the tank. This happens immediately after purchase! * It's at this point (and not when the user drives) that the "gas" * item is CONSUMED. Consumption should always happen when your game * world was safely updated to apply the effect of the purchase. So, * in an example scenario: * * BEFORE: tank at 1/2 * ON PURCHASE: tank at 1/2, "gas" item is owned * IMMEDIATELY: "gas" is consumed, tank goes to 3/4 * AFTER: tank at 3/4, "gas" item NOT owned any more * * Another important point to notice is that it may so happen that * the application crashed (or anything else happened) after the user * purchased the "gas" item, but before it was consumed. That's why, * on startup, we check if we own the "gas" item, and, if so, * we have to apply its effects to our world and consume it. This * is also very important! * * @author Bruno Oliveira (Google) */ public class MainActivity extends Activity { // Debug tag, for logging static final String TAG = "TrivialDrive"; // Does the user have the premium upgrade? boolean mIsPremium = false; // SKUs for our two products: the premium upgrade (non-consumable) and gas (consumable) static final String SKU_PREMIUM = "premium"; static final String SKU_GAS = "gas"; // (arbitrary) request code for the purchase flow static final int RC_REQUEST = 10001; // Graphics for the gas gauge static int[] TANK_RES_IDS = { R.drawable.gas0, R.drawable.gas1, R.drawable.gas2, R.drawable.gas3, R.drawable.gas4 }; // How many units (1/4 tank is our unit) fill in the tank. static final int TANK_MAX = 4; // Current amount of gas in tank, in units int mTank; // The helper object IabHelper mHelper; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // load game data loadData(); // We've moved the public key to the Security class. We'll let DexGuard // encrypt the string and then the class as well. //String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE"; // // Some sanity checks to see if the developer (that's you!) really followed the // instructions to run this sample (don't put these checks on your app!) //if (base64EncodedPublicKey.contains("CONSTRUCT_YOUR")) { // throw new RuntimeException("Please put your app's public key in MainActivity.java. See README."); //} //if (getPackageName().startsWith("com.example")) { // throw new RuntimeException("Please change the sample's package name! See README."); //} // Create the helper, passing it our context and the public key to verify signatures with Log.d(TAG, "Creating IAB helper."); mHelper = new IabHelper(this /*, base64EncodedPublicKey*/); // enable debug logging (for a production application, you should set this to false). mHelper.enableDebugLogging(true); // Start setup. This is asynchronous and the specified listener // will be called once setup completes. Log.d(TAG, "Starting setup."); mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { Log.d(TAG, "Setup finished."); if (!result.isSuccess()) { // Oh noes, there was a problem. complain("Problem setting up in-app billing: " + result); return; } // Hooray, IAB is fully set up. Now, let's get an inventory of stuff we own. Log.d(TAG, "Setup successful. Querying inventory."); mHelper.queryInventoryAsync(mGotInventoryListener); } }); } // Listener that's called when we finish querying the items we own IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { Log.d(TAG, "Query inventory finished."); if (result.isFailure()) { complain("Failed to query inventory: " + result); return; } Log.d(TAG, "Query inventory was successful."); // Do we have the premium upgrade? mIsPremium = inventory.hasPurchase(SKU_PREMIUM); Log.d(TAG, "User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM")); // Check for gas delivery -- if we own gas, we should fill up the tank immediately if (inventory.hasPurchase(SKU_GAS)) { Log.d(TAG, "We have gas. Consuming it."); mHelper.consumeAsync(inventory.getPurchase(SKU_GAS), mConsumeFinishedListener); return; } updateUi(); setWaitScreen(false); Log.d(TAG, "Initial inventory query finished; enabling main UI."); } }; // User clicked the "Buy Gas" button public void onBuyGasButtonClicked(View arg0) { Log.d(TAG, "Buy gas button clicked."); if (mTank >= TANK_MAX) { complain("Your tank is full. Drive around a bit!"); return; } // launch the gas purchase UI flow. // We will be notified of completion via mPurchaseFinishedListener setWaitScreen(true); Log.d(TAG, "Launching purchase flow for gas."); mHelper.launchPurchaseFlow(this, SKU_GAS, RC_REQUEST, mPurchaseFinishedListener); } // User clicked the "Upgrade to Premium" button. public void onUpgradeAppButtonClicked(View arg0) { Log.d(TAG, "Upgrade button clicked; launching purchase flow for upgrade."); setWaitScreen(true); mHelper.launchPurchaseFlow(this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data); // Pass on the activity result to the helper for handling if (!mHelper.handleActivityResult(requestCode, resultCode, data)) { // not handled, so handle it ourselves (here's where you'd // perform any handling of activity results not related to in-app // billing... super.onActivityResult(requestCode, resultCode, data); } else { Log.d(TAG, "onActivityResult handled by IABUtil."); } } // Callback for when a purchase is finished IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase); if (result.isFailure()) { // Oh noes! complain("Error purchasing: " + result); setWaitScreen(false); return; } Log.d(TAG, "Purchase successful."); if (purchase.getSku().equals(SKU_GAS)) { // bought 1/4 tank of gas. So consume it. Log.d(TAG, "Purchase is gas. Starting gas consumption."); mHelper.consumeAsync(purchase, mConsumeFinishedListener); } else if (purchase.getSku().equals(SKU_PREMIUM)) { // bought the premium upgrade! Log.d(TAG, "Purchase is premium upgrade. Congratulating user."); alert("Thank you for upgrading to premium!"); mIsPremium = true; updateUi(); setWaitScreen(false); } } }; // Called when consumption is complete IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener() { public void onConsumeFinished(Purchase purchase, IabResult result) { Log.d(TAG, "Consumption finished. Purchase: " + purchase + ", result: " + result); // We know this is the "gas" sku because it's the only one we consume, // so we don't check which sku was consumed. If you have more than one // sku, you probably should check... if (result.isSuccess()) { // successfully consumed, so we apply the effects of the item in our // game world's logic, which in our case means filling the gas tank a bit Log.d(TAG, "Consumption successful. Provisioning."); mTank = mTank == TANK_MAX ? TANK_MAX : mTank + 1; saveData(); alert("You filled 1/4 tank. Your tank is now " + String.valueOf(mTank) + "/4 full!"); } else { complain("Error while consuming: " + result); } updateUi(); setWaitScreen(false); Log.d(TAG, "End consumption flow."); } }; // Drive button clicked. Burn gas! public void onDriveButtonClicked(View arg0) { Log.d(TAG, "Drive button clicked."); if (mTank <= 0) alert("Oh, no! You are out of gas! Try buying some!"); else { --mTank; saveData(); alert("Vroooom, you drove a few miles."); updateUi(); Log.d(TAG, "Vrooom. Tank is now " + mTank); } } // We're being destroyed. It's important to dispose of the helper here! @Override public void onDestroy() { // very important: Log.d(TAG, "Destroying helper."); if (mHelper != null) mHelper.dispose(); mHelper = null; } // updates UI to reflect model public void updateUi() { // update the car color to reflect premium status or lack thereof ((ImageView)findViewById(R.id.free_or_premium)).setImageResource(mIsPremium ? R.drawable.premium : R.drawable.free); // "Upgrade" button is only visible if the user is not premium findViewById(R.id.upgrade_button).setVisibility(mIsPremium ? View.GONE : View.VISIBLE); // update gas gauge to reflect tank status int index = mTank >= TANK_RES_IDS.length ? TANK_RES_IDS.length - 1 : mTank; ((ImageView)findViewById(R.id.gas_gauge)).setImageResource(TANK_RES_IDS[index]); } // Enables or disables the "please wait" screen. void setWaitScreen(boolean set) { findViewById(R.id.screen_main).setVisibility(set ? View.GONE : View.VISIBLE); findViewById(R.id.screen_wait).setVisibility(set ? View.VISIBLE : View.GONE); } void complain(String message) { Log.e(TAG, "**** TrivialDrive Error: " + message); alert("Error: " + message); } void alert(String message) { AlertDialog.Builder bld = new AlertDialog.Builder(this); bld.setMessage(message); bld.setNeutralButton("OK", null); Log.d(TAG, "Showing alert dialog: " + message); bld.create().show(); } void saveData() { SharedPreferences.Editor spe = getPreferences(MODE_PRIVATE).edit(); spe.putInt("tank", mTank); spe.commit(); Log.d(TAG, "Saved data: tank = " + String.valueOf(mTank)); } void loadData() { SharedPreferences sp = getPreferences(MODE_PRIVATE); mTank = sp.getInt("tank", 2); Log.d(TAG, "Loaded data: tank = " + String.valueOf(mTank)); } }