/* 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.doplgangr.secrecy.Premium; import android.app.Activity; import android.app.AlertDialog; import android.content.Intent; import android.support.v4.app.Fragment; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import com.doplgangr.secrecy.R; import com.doplgangr.secrecy.Util; import com.github.jberkel.pay.me.IabHelper; import com.github.jberkel.pay.me.IabResult; import com.github.jberkel.pay.me.listener.OnIabPurchaseFinishedListener; import com.github.jberkel.pay.me.listener.OnIabSetupFinishedListener; import com.github.jberkel.pay.me.listener.QueryInventoryFinishedListener; import com.github.jberkel.pay.me.model.Inventory; import com.github.jberkel.pay.me.model.ItemType; import com.github.jberkel.pay.me.model.Purchase; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.Click; import org.androidannotations.annotations.EFragment; import org.androidannotations.annotations.ViewById; @EFragment(R.layout.activity_premium) public class PremiumFragment extends Fragment { // Debug tag, for logging private static final String TAG = "PremiumActivity"; private static final String SKU_PREMIUM = "donation.package.2"; // (arbitrary) request code for the purchase flow private static final int RC_REQUEST = 19283; @ViewById(R.id.Premium__upgrade_button) Button mUpgradeButton; // SKUs for our products: the premium upgrade (non-consumable) and gas (consumable) @ViewById(R.id.Premium__progress_bar) ProgressBar mProgressBar; private Activity context; //static final String SKU_PREMIUM = "android.test.purchased"; // Does the user have the premium upgrade? private boolean mIsPremium = false; // The helper object private IabHelper mHelper; // Callback for when a purchase is finished private final OnIabPurchaseFinishedListener mPurchaseFinishedListener = new OnIabPurchaseFinishedListener() { public void onIabPurchaseFinished(IabResult result, Purchase purchase) { Util.log("Purchase finished: " + result + ", purchase: " + purchase); // if we were disposed of in the meantime, quit. if (mHelper == null) return; if (result.isFailure()) { complain("Error purchasing: " + result); alert(getString(R.string.Error__error_processing_purchase)); setWaitScreen(false); return; } if (!verifyDeveloperPayload(purchase)) { complain("Error purchasing. Authenticity verification failed."); setWaitScreen(false); return; } Util.log("Purchase successful."); if (purchase.getSku().equals(SKU_PREMIUM)) { // bought the premium upgrade! Util.log("Purchase is premium upgrade. Congratulating user."); mIsPremium = true; updateUi(); setWaitScreen(false); } } }; // Listener that's called when we finish querying the items and subscriptions we own private final QueryInventoryFinishedListener mGotInventoryListener = new QueryInventoryFinishedListener() { public void onQueryInventoryFinished(IabResult result, Inventory inventory) { Util.log("Query inventory finished."); // Have we been disposed of in the meantime? If so, quit. if (mHelper == null) return; // Is it a failure? if (result.isFailure()) { alert(getString(R.string.Error__check_play_store)); complain("Failed to query inventory: " + result); return; } Util.log("Query inventory was successful."); /* * Check for items we own. Notice that for each purchase, we check * the developer payload to see if it's correct! See * verifyDeveloperPayload(). */ // Do we have the premium upgrade? Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM); mIsPremium = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase)); Util.log("User is " + (mIsPremium ? "PREMIUM" : "NOT PREMIUM")); updateUi(); if (mIsPremium) { Util.alert(context, getString(R.string.Dialog__purchased), getString(R.string.Dialog__purchase_message), Util.emptyClickListener, null); } setWaitScreen(false); Util.log("Initial inventory query finished; enabling main UI."); } }; @AfterViews void onCreate() { context = getActivity(); loadData(); /* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY * (that you got from the Google Play developer console). This is not your * developer public key, it's the *app-specific* public key. * * Instead of just storing the entire literal string here embedded in the * program, construct the key at runtime from pieces or * use bit manipulation (for example, XOR with some other string) to hide * the actual key. The key itself is not secret information, but we don't * want to make it easy for an attacker to replace the public key with one * of their own and then fake messages from the server. */ final String KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgbAMG82/KN7DaFV3lIVtQDepcEnI+N7MZJemXnus3kkSQ0vr+veE54l7w0Meq32alRaGBabgZuPZdjA7tsQJRa47IVF/ibHLzlBqAsefVNf+ulGEqvoeeU8oHJviIXZEdRRw3KfXrxepzKU75WLFXyMl1+ssQPWbhQaY6mLQebJz5cBivY67yd09zPjxz3SN844AFssj0+dh5D4YRIV1Qr5A0VgpNxWdbiGnDFk8WjLkfjbn3sdcJ2sCrB7pOUcjWbNRXp0jtFj0UQlmNisnbRPw9bPtrbXiWW7o745NmQfjMgg/35bJqRBlKOamU57LmJfbbpQwslpQVAQiv6dZWQIDAQAB"; // Create the helper, passing it our context and the public key to verify signatures with Util.log("Creating IAB helper."); mHelper = new IabHelper(context, KEY); // 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. Util.log("Starting setup."); mHelper.startSetup(new OnIabSetupFinishedListener() { public void onIabSetupFinished(IabResult result) { Util.log("Setup finished."); if (!result.isSuccess()) { // Oh noes, there was a problem. alert(getString(R.string.Error__cannot_set_up_IAP)); complain("Problem setting up in-app billing: " + result); return; } // Have we been disposed of in the meantime? If so, quit. if (mHelper == null) return; // IAB is fully set up. Now, let's get an inventory of stuff we own. Util.log("Setup successful. Querying inventory."); mHelper.queryInventoryAsync(mGotInventoryListener); } }); } // User clicked the "Upgrade to Premium" button. @Click(R.id.Premium__upgrade_button) public void onUpgradeAppButtonClicked() { Util.log("Upgrade button clicked; launching purchase flow for upgrade."); setWaitScreen(true); /* TODO: for security, generate your payload here for verification. See the comments on * verifyDeveloperPayload() for more info. Since this is a SAMPLE, we just use * an empty string, but on a production app you should carefully generate this. */ String payload = ""; mHelper.launchPurchaseFlow(context, SKU_PREMIUM, ItemType.INAPP, RC_REQUEST, mPurchaseFinishedListener, payload); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { Util.log("onActivityResult(" + requestCode + "," + resultCode + "," + data); if (mHelper == null) return; // 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 { Util.log("onActivityResult handled by IABUtil."); } } /** * Verifies the developer payload of a purchase. */ boolean verifyDeveloperPayload(Purchase p) { // String payload = p.getDeveloperPayload(); /* * TODO: verify that the developer payload of the purchase is correct. It will be * the same one that you sent when initiating the purchase. * * WARNING: Locally generating a random string when starting a purchase and * verifying it here might seem like a good approach, but this will fail in the * case where the user purchases an item on one device and then uses your app on * a different device, because on the other device you will not have access to the * random string you originally generated. * * So a good developer payload has these characteristics: * * 1. If two different users purchase an item, the payload is different between them, * so that one user's purchase can't be replayed to another user. * * 2. The payload must be such that you can verify it even when the app wasn't the * one who initiated the purchase flow (so that items purchased by the user on * one device work on other devices owned by the user). * * Using your own server to store and verify developer payloads across app * installations is recommended. */ return true; } // We're being destroyed. It's important to dispose of the helper here! @Override public void onDestroy() { super.onDestroy(); // very important: Util.log("Destroying helper."); if (mHelper != null) { mHelper.dispose(); mHelper = null; } } // updates UI to reflect model void updateUi() { if (!mIsPremium) { mUpgradeButton.setEnabled(true); } } // Enables or disables the "please wait" screen. void setWaitScreen(boolean set) { mProgressBar.setVisibility(set ? View.VISIBLE : View.GONE); } void complain(String message) { Log.e(TAG, "**** TrivialDrive Error: " + message); } void alert(String message) { AlertDialog.Builder bld = new AlertDialog.Builder(context); bld.setMessage(message); bld.setNeutralButton(R.string.OK, null); Util.log("Showing alert dialog: " + message); bld.create().show(); } void saveData() { /* * WARNING: on a real application, we recommend you save data in a secure way to * prevent tampering. For simplicity in this sample, we simply store the data using a * SharedPreferences. 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)); */ } }