• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Apps Database is locked - Android 3.2

Hello, I am developing an app that has a system to purchase products within it. Every time the app is installed or reinstalled, it restores google play all items purchased by the user and writes to the database from there always looking for items purchased through the bank, and inserting each new item purchased.
But I'm having problems with the handling of the bank, and follows the error codes:

Code:
05-29 17:10:04.445 E/AndroidRuntime(14189): FATAL EXCEPTION: Thread-20
  05-29 17:10:04.445 E/AndroidRuntime(14189): android.database.sqlite.SQLiteDatabaseLockedException: database is locked
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at android.database.sqlite.SQLiteDatabase.dbopen(Native Method)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:983)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:956)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1021)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:798)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:221)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:149)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at br.com.soyuz.moderna.DBAdapter.open(DBAdapter.java:46)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at br.com.soyuz.moderna.ResponseHandler$1.run(ResponseHandler.java:130)
  05-29 17:10:04.445 E/AndroidRuntime(14189):  at java.lang.Thread.run(Thread.java:1020)
Code:
package br.com.soyuz.moderna;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.Toast;
import br.com.soyuz.moderna.BillingService.RequestPurchase;
import br.com.soyuz.moderna.BillingService.RestoreTransactions;
import br.com.soyuz.moderna.Consts.PurchaseState;
import br.com.soyuz.moderna.Consts.ResponseCode;
import br.com.soyuz.moderna.R;


public class ModernaActivity extends Activity{
    
    private WebView webView;
    
    private static final String TAG = "Moderna";
    
    //initialize our progress dialog/bar
    private ProgressDialog mProgressDialog;
    public static final int DIALOG_DOWNLOAD_PROGRESS = 0;
    
    //initialize root directory
    File rootDir = Environment.getExternalStorageDirectory();
    
    //defining file name and url
    public String fileName = "osmaias.epub";
    public String fileURL = "demos.soyuz.com.br/osmaias.epub";
    
    /**
    * The SharedPreferences key for recording whether we initialized the
    * database.  If false, then we perform a RestoreTransactions request
    * to get all the purchases for this user.
    */
    private static final String DB_INITIALIZED = "db_initialized";

    private DungeonsPurchaseObserver mModernaPurchaseObserver;
    private Handler mHandler;
    
    private BillingService mBillingService;
    private DBAdapter Database;
    private Cursor mOwnedItemsCursor;
    private Set<String> mOwnedItems = new HashSet<String>();

    private static final int DIALOG_CANNOT_CONNECT_ID = 1;
    private static final int DIALOG_BILLING_NOT_SUPPORTED_ID = 2;
    
    /**
    * Each product in the catalog is either MANAGED or UNMANAGED.  MANAGED
    * means that the product can be purchased only once per user (such as a new
    * level in a game). The purchase is remembered by Android Market and
    * can be restored if this application is uninstalled and then
    * re-installed. UNMANAGED is used for products that can be used up and
    * purchased multiple times (such as poker chips). It is up to the
    * application to keep track of UNMANAGED products for the user.
    */
    
    private class DungeonsPurchaseObserver extends PurchaseObserver {
        public DungeonsPurchaseObserver(Handler handler) {
            super(ModernaActivity.this, handler);
        }

        @Override
        public void onBillingSupported(boolean supported) {
            if (Consts.DEBUG) {
                Log.i(TAG, "supported: " + supported);
            }
            if (supported) {
                restoreDatabase();
            } else {
                showDialog(DIALOG_BILLING_NOT_SUPPORTED_ID);
            }
        }

        @Override
        public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
                int quantity, long purchaseTime, String developerPayload) {
            if (Consts.DEBUG) {
                Log.i(TAG, "onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
            }

            if (purchaseState == PurchaseState.PURCHASED) {
                 Log.i(TAG, "Item ID: " + itemId);
                 mOwnedItems.add(itemId);
            }
            
            Log.i(TAG, "OwnedItens: " + mOwnedItems);
            Toast.makeText(ModernaActivity.this, "mOwnedItems2: " + mOwnedItems.toString(), Toast.LENGTH_LONG).show();
            mOwnedItemsCursor.requery();
        }

        @Override
        public void onRequestPurchaseResponse(RequestPurchase request,
                ResponseCode responseCode) {
            if (Consts.DEBUG) {
                Log.d(TAG, request.mProductId + ": " + responseCode);
            }
            
            if (responseCode == ResponseCode.RESULT_OK) {
                if (Consts.DEBUG) {
                    Log.i(TAG, "purchase was successfully sent to server");
                }
                //logProductActivity(request.mProductId, "sending purchase request");
            } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                if (Consts.DEBUG) {
                    Log.i(TAG, "user canceled purchase");
                }
                //logProductActivity(request.mProductId, "dismissed purchase dialog");
            } else {
                if (Consts.DEBUG) {
                    Log.i(TAG, "purchase failed");
                }
                //logProductActivity(request.mProductId, "request purchase returned " + responseCode);
            }
        }

        @Override
        public void onRestoreTransactionsResponse(RestoreTransactions request,
                ResponseCode responseCode) {
            if (responseCode == ResponseCode.RESULT_OK) {
                if (Consts.DEBUG) {
                    Log.d(TAG, "completed RestoreTransactions request");
                }
                // Update the shared preferences so that we don't perform
                // a RestoreTransactions again.
                SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
                SharedPreferences.Editor edit = prefs.edit();
                edit.putBoolean(DB_INITIALIZED, true);
                edit.commit();
            } else {
                if (Consts.DEBUG) {
                    Log.d(TAG, "RestoreTransactions error: " + responseCode);
                }
            }
        }
    }
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mHandler = new Handler();
        mModernaPurchaseObserver = new DungeonsPurchaseObserver(mHandler);
        mBillingService = new BillingService();
        mBillingService.setContext(this);

        Database = new DBAdapter(this);
        Database.open();
        
        mOwnedItemsCursor = Database.queryAllPurchasedItems();

        startManagingCursor(mOwnedItemsCursor);

        webView = (WebView)findViewById(R.id.webView);
        webView.getSettings().setUseWideViewPort(true);
        webView.getSettings().setLoadWithOverviewMode(true);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.getSettings().setDomStorageEnabled(true);  
        webView.setWebChromeClient(new WebChromeClient());
        webView.addJavascriptInterface(new DemoJavaScriptInterface(this), "demo");
        webView.loadUrl("file:///android_asset/www/index.html");
        
        //Check if billing is supported.
        ResponseHandler.register(mModernaPurchaseObserver);
        if (!mBillingService.checkBillingSupported()) {
            showDialog(DIALOG_CANNOT_CONNECT_ID);
        }   
    }
    
    final class DemoJavaScriptInterface {
        Context mContext;
        DemoJavaScriptInterface(Context c) {
            mContext = c;
        }
        
        public void showToast(String toast) {
            Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
        }

        public void comprar(String id) {            
            mBillingService.requestPurchase(id, "");
        }
        
        public void linkDownload(String URL) {
            fileURL = URL;
        }
        
        public void startDownload() {
            initializeOwnedItems();
            //making sure the download directory exists
            checkAndCreateDirectory("/Android/data/br.com.soyuz.moderna/downloads/");
           
            //executing the asynctask
            new DownloadFileAsync().execute(fileURL);
        }
        
        public void viewBook(){
            Intent myIntent = new Intent(ModernaActivity.this, ViewBook.class);
            ModernaActivity.this.startActivity(myIntent);
        }
        
        /**
         * This is not called on the UI thread. Post a runnable to invoke
         * loadUrl on the UI thread.
         */
        public void clickOnAndroid() {
            mHandler.post(new Runnable() {
                public void run() {
                    webView.loadUrl("javascript:wave('"+mOwnedItems+"')");
                }
            });

        }
    }
    
  //this is our download file asynctask
    class DownloadFileAsync extends AsyncTask<String, String, String> {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            showDialog(DIALOG_DOWNLOAD_PROGRESS);
        }

       
        @Override
        protected String doInBackground(String... aurl) {

            try {
                //connecting to url
                URL u = new URL(fileURL);
                HttpURLConnection c = (HttpURLConnection) u.openConnection();
                c.setRequestMethod("GET");
                c.setDoOutput(true);
                c.connect();
               
                //lenghtOfFile is used for calculating download progress
                int lenghtOfFile = c.getContentLength();
               
                //this is where the file will be seen after the download
                FileOutputStream f = new FileOutputStream(new File(rootDir + "/Android/data/br.com.soyuz.moderna/downloads/", fileName));
                //file input is from the url
                InputStream in = c.getInputStream();

                //here's the download code
                byte[] buffer = new byte[1024];
                int len1 = 0;
                long total = 0;
               
                while ((len1 = in.read(buffer)) > 0) {
                    total += len1; //total = total + len1
                    publishProgress("" + (int)((total*100)/lenghtOfFile));
                    f.write(buffer, 0, len1);
                }
                f.close();
               
            } catch (Exception e) {
                Log.d(TAG, e.getMessage());
            }
           
            return null;
        }
       
        protected void onProgressUpdate(String... progress) {
             Log.d(TAG,progress[0]);
             mProgressDialog.setProgress(Integer.parseInt(progress[0]));
        }

        @Override
        protected void onPostExecute(String unused) {
            //dismiss the dialog after the file was downloaded
            
            dismissDialog(DIALOG_DOWNLOAD_PROGRESS);
            Context context = getApplicationContext();
            CharSequence text = "Download concluido!";
            int duration = Toast.LENGTH_SHORT;

            Toast toast = Toast.makeText(context, text, duration);
            toast.show();
            
            String path= rootDir + "/Android/data/br.com.soyuz.moderna/downloads/" + fileName;

            Intent intent = new Intent();
            intent.setAction(android.content.Intent.ACTION_VIEW);
            File file = new File(path);

            MimeTypeMap mime = MimeTypeMap.getSingleton();
            String ext=file.getName().substring(file.getName().indexOf(".")+1);
            String type = mime.getMimeTypeFromExtension(ext);

            intent.setDataAndType(Uri.fromFile(file),type);

            startActivity(intent);
            
            //Decompress d = new Decompress(rootDir + "/Android/data/br.com.soyuz.moderna/downloads/" + fileName, rootDir + "/Android/data/br.com.soyuz.moderna/downloads/"); 
            //d.unzip();
        }
    }
    
    //function to verify if directory exists
    public void checkAndCreateDirectory(String dirName){
        File new_dir = new File( rootDir + dirName );
        if( !new_dir.exists() ){
            new_dir.mkdirs();
        }
    }
    
    /**
    * Called when this activity becomes visible.
    */
    @Override
    protected void onStart() {
        super.onStart();
        ResponseHandler.register(mModernaPurchaseObserver);
        //initializeOwnedItems();
    }

    /**
    * Called when this activity is no longer visible.
    */
    @Override
    protected void onStop() {
        super.onStop();
        ResponseHandler.unregister(mModernaPurchaseObserver);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Database.close();
        mBillingService.unbind();
    }

    /**
    * Save the context of the log so simple things like rotation will not
    * result in the log being cleared.
    */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }
    
    /**
    * Restore the contents of the log if it has previously been saved.
    */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) {
            
        }
    }
    
    @Override
    protected Dialog onCreateDialog(int id) {
        switch (id) {
            case DIALOG_CANNOT_CONNECT_ID:
                return createDialog("cannot_connect_title",
                "cannot_connect_message");
            case DIALOG_BILLING_NOT_SUPPORTED_ID:
                return createDialog("billing_not_supported_title",
                "billing_not_supported_message");
                //our progress bar settings
            case DIALOG_DOWNLOAD_PROGRESS: //we set this to 0
                mProgressDialog = new ProgressDialog(this);
                mProgressDialog.setMessage("Downloading file...");
                mProgressDialog.setIndeterminate(false);
                mProgressDialog.setMax(100);
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.setCancelable(true);
                mProgressDialog.show();
                return mProgressDialog;
            default:
                return null;
        }
    }
    
    private Dialog createDialog(String string, String string2) {
        String helpUrl = replaceLanguageAndRegion("help_url");
        if (Consts.DEBUG) {
            Log.i(TAG, helpUrl);
        }
        final Uri helpUri = Uri.parse(helpUrl);

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(string)
            .setIcon(android.R.drawable.stat_sys_warning)
            .setMessage(string2)
            .setCancelable(false)
            .setPositiveButton(android.R.string.ok, null)
            .setNegativeButton("learn_more", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Intent intent = new Intent(Intent.ACTION_VIEW, helpUri);
                    startActivity(intent);
                }
            });
        return builder.create();
    }
    
    /**
    * Replaces the language and/or country of the device into the given string.
    * The pattern "%lang%" will be replaced by the device's language code and
    * the pattern "%region%" will be replaced with the device's country code.
    *
    * @param str the string to replace the language/country within
    * @return a string containing the local language and region codes
    */
    private String replaceLanguageAndRegion(String str) {
        // Substitute language and or region if present in string
        if (str.contains("%lang%") || str.contains("%region%")) {
            Locale locale = Locale.getDefault();
            str = str.replace("%lang%", locale.getLanguage().toLowerCase());
            str = str.replace("%region%", locale.getCountry().toLowerCase());
        }
        return str;
    }

    /**
    * If the database has not been initialized, we send a
    * RESTORE_TRANSACTIONS request to Android Market to get the list of purchased items
    * for this user. This happens if the application has just been installed
    * or the user wiped data. We do not want to do this on every startup, rather, we want to do
    * only when the database needs to be initialized.
    */
    private void restoreDatabase() {
        SharedPreferences prefs = getPreferences(MODE_PRIVATE);
        boolean initialized = prefs.getBoolean(DB_INITIALIZED, false);
        if (!initialized) {
            mBillingService.restoreTransactions();
            Toast.makeText(this, "restoring_transactions", Toast.LENGTH_LONG).show();
        }
    }
    
    private void initializeOwnedItems() {
        new Thread(new Runnable() {
            public void run() {
                doInitializeOwnedItems();
           }
        }).start();
    }
    
    /**
    * Reads the set of purchased items from the database in a background thread
    * and then adds those items to the set of owned items in the main UI
    * thread.
    */
   private void doInitializeOwnedItems() {
        Cursor cursor = Database.queryAllPurchasedItems();
        //PurchaseState teste = Consts.PurchaseState.PURCHASED;
        //long numero= 10000;
        //Database.updatePurchase(
         //       "teste", "teste", teste, numero, "teste");
        
        if (cursor == null) {
            Toast.makeText(ModernaActivity.this, "Cursor: " + "teste", Toast.LENGTH_LONG).show();
        }

        final Set<String> ownedItems = new HashSet<String>();
        try {
            int productIdCol = cursor.getColumnIndexOrThrow(
                    PurchaseDatabase.PURCHASED_PRODUCT_ID_COL);
           while (cursor.moveToNext()) {
                String productId = cursor.getString(productIdCol);
                Log.d(TAG, "Produto: " + productId);
                ownedItems.add(productId);
            }
       } finally {
            cursor.close();
       }
    
        // We will add the set of owned items in a new Runnable that runs on
        // the UI thread so that we don't need to synchronize access to
        mHandler.post(new Runnable() {
            public void run() {
                Log.d(TAG, "Owned Itens: " + ownedItems);
                mOwnedItems.addAll(ownedItems);
                Toast.makeText(ModernaActivity.this, "ownedItems: " + ownedItems.toString(), Toast.LENGTH_LONG).show();
                Toast.makeText(ModernaActivity.this, "mOwnedItems: " + mOwnedItems.toString(), Toast.LENGTH_LONG).show();
            }
       });
    }
}
Code:
package br.com.soyuz.moderna;

import java.io.File;
import android.content.Context;
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 extends SQLiteOpenHelper{
    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";

    // 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";

    public boolean databaseExist()
    {
        File dbFile = new File("/data/data/br.com.soyuz.moderna/databases/"+DATABASE_NAME);
        return dbFile.exists();
    }

    /**
     * This is a standard helper class for constructing the database.
     */
    public PurchaseDatabase(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.w(TAG, "databaseExist(): " + databaseExist());
        createPurchaseTable(db);
    }
   @Override
   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
       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)");
        }
}
Code:
package br.com.soyuz.moderna;

import br.com.soyuz.moderna.Consts.PurchaseState;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;

public class DBAdapter {
    
    private SQLiteDatabase mDb;
    private PurchaseDatabase mDatabaseHelper;
    
    // 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 PURCHASE_HISTORY_TABLE_NAME = "history";
    private static final String PURCHASED_ITEMS_TABLE_NAME = "purchased";
    
    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
    };
    
    public DBAdapter(Context context) {          
        mDatabaseHelper = new PurchaseDatabase(context);
    }
    
    //Abre o banco de dados
    public void open() throws SQLException {
        mDb = mDatabaseHelper.getWritableDatabase();
    }
    
    //Fecha o banco de dados
    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);
    }

}
Code:
/*
 * Copyright (C) 2011 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
 *
 *
 * 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 br.com.soyuz.moderna;

import br.com.soyuz.moderna.BillingService.RequestPurchase;
import br.com.soyuz.moderna.BillingService.RestoreTransactions;
import br.com.soyuz.moderna.Consts.PurchaseState;
import br.com.soyuz.moderna.Consts.ResponseCode;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * This class contains the methods that handle responses from Android Market.  The
 * implementation of these methods is specific to a particular application.
 * The methods in this example update the database and, if the main application
 * has registered a {@llink PurchaseObserver}, will also update the UI.  An
 * application might also want to forward some responses on to its own server,
 * and that could be done here (in a background thread) but this example does
 * not do that.
 *
 * You should modify and obfuscate this code before using it.
 */
public class ResponseHandler {
    private static final String TAG = "ResponseHandler";

    /**
     * This is a static instance of {@link PurchaseObserver} that the
     * application creates and registers with this class. The PurchaseObserver
     * is used for updating the UI if the UI is visible.
     */
    private static PurchaseObserver sPurchaseObserver;

    /**
     * Registers an observer that updates the UI.
     * @param observer the observer to register
     */
    public static synchronized void register(PurchaseObserver observer) {
        sPurchaseObserver = observer;
    }

    /**
     * Unregisters a previously registered observer.
     * @param observer the previously registered observer.
     */
    public static synchronized void unregister(PurchaseObserver observer) {
        sPurchaseObserver = null;
    }

    /**
     * Notifies the application of the availability of the MarketBillingService.
     * This method is called in response to the application calling
     * {@link BillingService#checkBillingSupported()}.
     * @param supported true if in-app billing is supported.
     */
    public static void checkBillingSupportedResponse(boolean supported) {
        if (sPurchaseObserver != null) {
            sPurchaseObserver.onBillingSupported(supported);
        }
    }

    /**
     * Starts a new activity for the user to buy an item for sale. This method
     * forwards the intent on to the PurchaseObserver (if it exists) because
     * we need to start the activity on the activity stack of the application.
     *
     * @param pendingIntent a PendingIntent that we received from Android Market that
     *     will create the new buy page activity
     * @param intent an intent containing a request id in an extra field that
     *     will be passed to the buy page activity when it is created
     */
    public static void buyPageIntentResponse(PendingIntent pendingIntent, Intent intent) {
        if (sPurchaseObserver == null) {
            if (Consts.DEBUG) {
                Log.d(TAG, "UI is not running");
            }
            return;
        }
        sPurchaseObserver.startBuyPageActivity(pendingIntent, intent);
    }

    /**
     * Notifies the application of purchase state changes. The application
     * can offer an item for sale to the user via
     * {@link BillingService#requestPurchase(String)}. The BillingService
     * calls this method after it gets the response. Another way this method
     * can be called is if the user bought something on another device running
     * this same app. Then Android Market notifies the other devices that
     * the user has purchased an item, in which case the BillingService will
     * also call this method. Finally, this method can be called if the item
     * was refunded.
     * @param purchaseState the state of the purchase request (PURCHASED,
     *     CANCELED, or REFUNDED)
     * @param productId a string identifying a product for sale
     * @param orderId a string identifying the order
     * @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
     */
    public static void purchaseResponse(
            final Context context, final PurchaseState purchaseState, final String productId,
            final String orderId, final long purchaseTime, final String developerPayload) {

        // Update the database with the purchase state. We shouldn't do that
        // from the main thread so we do the work in a background thread.
        // We don't update the UI here. We will update the UI after we update
        // the database because we need to read and update the current quantity
        // first.
        new Thread(new Runnable() {
            public void run() {
                Log.d(TAG, "purchaseResonse");
                DBAdapter db = new DBAdapter(context);
                db.open();
                Log.d(TAG, "purchaseResonse - DB OPEN");
                int quantity = db.updatePurchase(
                        orderId, productId, purchaseState, purchaseTime, developerPayload);
                db.close();

                // This needs to be synchronized because the UI thread can change the
                // value of sPurchaseObserver.
                synchronized(ResponseHandler.class) {
                    if (sPurchaseObserver != null) {
                        sPurchaseObserver.postPurchaseStateChange(
                                purchaseState, productId, quantity, purchaseTime, developerPayload);
                    }
                }
            }
        }).start();
    }

    /**
     * This is called when we receive a response code from Android Market for a
     * RequestPurchase request that we made.  This is used for reporting various
     * errors and also for acknowledging that an order was sent successfully to
     * the server. This is NOT used for any purchase state changes. All
     * purchase state changes are received in the {@link BillingReceiver} and
     * are handled in {@link Security#verifyPurchase(String, String)}.
     * @param context the context
     * @param request the RequestPurchase request for which we received a
     *     response code
     * @param responseCode a response code from Market to indicate the state
     * of the request
     */
    public static void responseCodeReceived(Context context, RequestPurchase request,
            ResponseCode responseCode) {
        if (sPurchaseObserver != null) {
            sPurchaseObserver.onRequestPurchaseResponse(request, responseCode);
        }
    }

    /**
     * This is called when we receive a response code from Android Market for a
     * RestoreTransactions request.
     * @param context the context
     * @param request the RestoreTransactions request for which we received a
     *     response code
     * @param responseCode a response code from Market to indicate the state
     *     of the request
     */
    public static void responseCodeReceived(Context context, RestoreTransactions request,
            ResponseCode responseCode) {
        if (sPurchaseObserver != null) {
            sPurchaseObserver.onRestoreTransactionsResponse(request, responseCode);
        }
    }
}
 
Back
Top Bottom