/* * Copyright (C) 2010 The Android Open Source Project * * 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.dungeons; import com.example.dungeons.Consts.PurchaseState; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; /** * An example database that records the state of each purchase. You should use * an obfuscator before storing any information to persistent storage. The * obfuscator should use a key that is specific to the device and/or user. * Otherwise an attacker could copy a database full of valid purchases and * distribute it to others. */ public class PurchaseDatabase { private static final String TAG = "PurchaseDatabase"; private static final String DATABASE_NAME = "purchase.db"; private static final int DATABASE_VERSION = 1; private static final String PURCHASE_HISTORY_TABLE_NAME = "history"; private static final String PURCHASED_ITEMS_TABLE_NAME = "purchased"; // These are the column names for the purchase history table. We need a // column named "_id" if we want to use a CursorAdapter. The primary key is // the orderId so that we can be robust against getting multiple messages // from the server for the same purchase. static final String HISTORY_ORDER_ID_COL = "_id"; static final String HISTORY_STATE_COL = "state"; static final String HISTORY_PRODUCT_ID_COL = "productId"; static final String HISTORY_PURCHASE_TIME_COL = "purchaseTime"; static final String HISTORY_DEVELOPER_PAYLOAD_COL = "developerPayload"; private static final String[] HISTORY_COLUMNS = { HISTORY_ORDER_ID_COL, HISTORY_PRODUCT_ID_COL, HISTORY_STATE_COL, HISTORY_PURCHASE_TIME_COL, HISTORY_DEVELOPER_PAYLOAD_COL }; // These are the column names for the "purchased items" table. static final String PURCHASED_PRODUCT_ID_COL = "_id"; static final String PURCHASED_QUANTITY_COL = "quantity"; private static final String[] PURCHASED_COLUMNS = { PURCHASED_PRODUCT_ID_COL, PURCHASED_QUANTITY_COL }; private SQLiteDatabase mDb; private DatabaseHelper mDatabaseHelper; public PurchaseDatabase(Context context) { mDatabaseHelper = new DatabaseHelper(context); mDb = mDatabaseHelper.getWritableDatabase(); } public void close() { mDatabaseHelper.close(); } /** * Inserts a purchased product into the database. There may be multiple * rows in the table for the same product if it was purchased multiple times * or if it was refunded. * @param orderId the order ID (matches the value in the product list) * @param productId the product ID (sku) * @param state the state of the purchase * @param purchaseTime the purchase time (in milliseconds since the epoch) * @param developerPayload the developer provided "payload" associated with * the order. */ private void insertOrder(String orderId, String productId, PurchaseState state, long purchaseTime, String developerPayload) { ContentValues values = new ContentValues(); values.put(HISTORY_ORDER_ID_COL, orderId); values.put(HISTORY_PRODUCT_ID_COL, productId); values.put(HISTORY_STATE_COL, state.ordinal()); values.put(HISTORY_PURCHASE_TIME_COL, purchaseTime); values.put(HISTORY_DEVELOPER_PAYLOAD_COL, developerPayload); mDb.replace(PURCHASE_HISTORY_TABLE_NAME, null /* nullColumnHack */, values); } /** * Updates the quantity of the given product to the given value. If the * given value is zero, then the product is removed from the table. * @param productId the product to update * @param quantity the number of times the product has been purchased */ private void updatePurchasedItem(String productId, int quantity) { if (quantity == 0) { mDb.delete(PURCHASED_ITEMS_TABLE_NAME, PURCHASED_PRODUCT_ID_COL + "=?", new String[] { productId }); return; } ContentValues values = new ContentValues(); values.put(PURCHASED_PRODUCT_ID_COL, productId); values.put(PURCHASED_QUANTITY_COL, quantity); mDb.replace(PURCHASED_ITEMS_TABLE_NAME, null /* nullColumnHack */, values); } /** * Adds the given purchase information to the database and returns the total * number of times that the given product has been purchased. * @param orderId a string identifying the order * @param productId the product ID (sku) * @param purchaseState the purchase state of the product * @param purchaseTime the time the product was purchased, in milliseconds * since the epoch (Jan 1, 1970) * @param developerPayload the developer provided "payload" associated with * the order * @return the number of times the given product has been purchased. */ public synchronized int updatePurchase(String orderId, String productId, PurchaseState purchaseState, long purchaseTime, String developerPayload) { insertOrder(orderId, productId, purchaseState, purchaseTime, developerPayload); Cursor cursor = mDb.query(PURCHASE_HISTORY_TABLE_NAME, HISTORY_COLUMNS, HISTORY_PRODUCT_ID_COL + "=?", new String[] { productId }, null, null, null, null); if (cursor == null) { return 0; } int quantity = 0; try { // Count the number of times the product was purchased while (cursor.moveToNext()) { int stateIndex = cursor.getInt(2); PurchaseState state = PurchaseState.valueOf(stateIndex); // Note that a refunded purchase is treated as a purchase. Such // a friendly refund policy is nice for the user. if (state == PurchaseState.PURCHASED || state == PurchaseState.REFUNDED) { quantity += 1; } } // Update the "purchased items" table updatePurchasedItem(productId, quantity); } finally { if (cursor != null) { cursor.close(); } } return quantity; } /** * Returns a cursor that can be used to read all the rows and columns of * the "purchased items" table. */ public Cursor queryAllPurchasedItems() { return mDb.query(PURCHASED_ITEMS_TABLE_NAME, PURCHASED_COLUMNS, null, null, null, null, null); } /** * This is a standard helper class for constructing the database. */ private class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { createPurchaseTable(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Production-quality upgrade code should modify the tables when // the database version changes instead of dropping the tables and // re-creating them. if (newVersion != DATABASE_VERSION) { Log.w(TAG, "Database upgrade from old: " + oldVersion + " to: " + newVersion); db.execSQL("DROP TABLE IF EXISTS " + PURCHASE_HISTORY_TABLE_NAME); db.execSQL("DROP TABLE IF EXISTS " + PURCHASED_ITEMS_TABLE_NAME); createPurchaseTable(db); return; } } private void createPurchaseTable(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + PURCHASE_HISTORY_TABLE_NAME + "(" + HISTORY_ORDER_ID_COL + " TEXT PRIMARY KEY, " + HISTORY_STATE_COL + " INTEGER, " + HISTORY_PRODUCT_ID_COL + " TEXT, " + HISTORY_DEVELOPER_PAYLOAD_COL + " TEXT, " + HISTORY_PURCHASE_TIME_COL + " INTEGER)"); db.execSQL("CREATE TABLE " + PURCHASED_ITEMS_TABLE_NAME + "(" + PURCHASED_PRODUCT_ID_COL + " TEXT PRIMARY KEY, " + PURCHASED_QUANTITY_COL + " INTEGER)"); } } }