Sqlite close() was never explicitly called on database - I tried but failed
<p>OK, so I have this service class I created that is started from the AlarmManager of a game. The purpose is to fire off this service at certain intervals and check if the remote player of the game has played. It wakes up, opens a database and queries the list of games and then checks the server for the status of each game and if a play was made hits the status bar with some goodies. That all works just fine and dandy but this little service keeps throwing an exception of 
  "Sqlite close() was never explicitly called on database" 
But I think I closed the query and the database in my code below. 
What have I missed?  I've read dozens of stackoverflow threads on this which all say I should be closing the db, and I close it in the onDestroy() and unBind() but I still get this exception. </p>

<h2>service</h2>

<pre><code>public class WakeCheck extends Service {

    private triDbAdapter mDbHelper;

@Override
public void onCreate() {
// TODO Auto-generated method stub
}



@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}



@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
mDbHelper.close();
}



@Override
public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
    //Toast.makeText(this, "MyAlarmService.onStart()", Toast.LENGTH_LONG).show();
    Cursor c;
    triDbAdapter mDbHelper = new triDbAdapter(this.getApplicationContext());
    mDbHelper.open();
    ServerCommunication sComm = new ServerCommunication(this.getApplicationContext());
    c = mDbHelper.fetchAllGames();

    if (c.getCount() &gt; 0){
        c.moveToFirst();
        do {
            int game_id = c.getInt(c.getColumnIndex(mDbHelper.KEY_ID));
            try {
                if ( sComm.checkplay(game_id, 0) ) {
                    //notify user - communicate that there has been a new play

                    String ns = this.NOTIFICATION_SERVICE;
                    NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns);
                    int icon = R.drawable.status_icon;
                    CharSequence tickerText = "TriOminoes!";
                    long when = System.currentTimeMillis();

                    Notification notification = new Notification(icon, tickerText, when);
                    Context context = getApplicationContext();
                    CharSequence contentTitle = "TriOminoes";
                    String name = c.getString(c.getColumnIndex(mDbHelper.KEY_OPPNM));
                    int tscore = c.getInt(c.getColumnIndex(mDbHelper.KEY_TSCORE));
                    CharSequence contentText = name + " played for " + tscore +
                                    " points. It's Your Turn!";
                    Intent notificationIntent = new Intent(this, SelectGames.class);
                    if (Settings.AlarmVibrate) {
                        notification.flags |= Notification.DEFAULT_VIBRATE;
                    }
                    if (Settings.AlarmSound) {
                        notification.flags |= Notification.DEFAULT_SOUND;
                    }
                    notification.flags |= Notification.FLAG_AUTO_CANCEL;


                    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent,
                            PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);


                    notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);


                    mNotificationManager.notify(1, notification);
                }
            } catch (JSONException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                c.close();
                mDbHelper.close();
                Log.d("WAKE", "Something went wrong in Communications");

            }
        } while (c.moveToNext());
    }
    c.close();
    mDbHelper.close();
}


@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
    mDbHelper.close();
return super.onUnbind(intent);


}



}
</code></pre>

<h2>triDbAdapter</h2>

<pre><code>package com.ulsanonline.triominoes;


import java.text.SimpleDateFormat;
import java.util.Calendar;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class triDbAdapter {



    /**
     * Database creation sql statement
     */
    private static final String DATABASE_CREATE1 =    
    "CREATE TABLE player (" +
              "_id              INTEGER ," +
              "username         TEXT not null," +
              "alarmtime        INTEGER not null," +
              "alarmvibrate     INTEGER not null," +
              "alarmsound       INTEGER not null," +
              "showboard        INTEGER not null," +
              "showtouch        INTEGER not null," +
              "showcenters      INTEGER not null );" ;

    private static final String DATABASE_CREATE2 = 
    "CREATE TABLE games (" +
              "_id              INTEGER, " +
              "myScore          INTEGER, " +
              "username         TEXT,    " +
              "myId             INTEGER, " +
              "opponentId       INTEGER, " +
              "opponentName     TEXT,    " +
              "opponentScore    INTEGER, " +
              "last_tile        INTEGER, " +
              "last_tile_name   TEXT,    " +
              "row              INTEGER, " +
              "col              INTEGER, " +    // grid location &amp; rotation, face tells
              "rotate           INTEGER, " +    // all scoring information
              "face_dn          INTEGER, " +    // 1=face down, 1=face pointing up
              "who              INTEGER, " +    // who played it, user id
              "tscore           INTEGER, " +    // just score per that turn
              "remain           INTEGER, " +    // tiles remain(1) or not (0)
              "complete         INTEGER, " +    // game over(1) or keep playing(0)
              "last_played      INTEGER); " ;

    private static final String DATABASE_CREATE3 = 
    "CREATE TABLE tilesPlayed (" +
             "_id               INTEGER REFERENCES games(_id) on UPDATE CASCADE," +
             "row               INTEGER, " +
             "col               INTEGER, " +    // grid location &amp; rotation, face tells
             "rotate            INTEGER, " +    // all scoring information
             "face_dn           INTEGER, " +    // 1=face down, 0=face pointing up
             "tile              INTEGER); " ;

    private static final String DATABASE_CREATE4 = 
    "CREATE TABLE myTiles  (" +
             "_id               INTEGER PRIMARY KEY AUTOINCREMENT, " +
             "game_id           INTEGER REFERENCES games(_id) on UPDATE CASCADE," +
             "tile              INTEGER); " ;

    private static final String DATABASE_NAME = "triominoes.db";
    private static final String PLAYER_TABLE  = "player";
    private static final String GAMES_TABLE   = "games";
    private static final String TILES_TABLE   = "tilesPlayed";
    private static final String MYTILES_TABLE = "myTiles";
    private static final int DATABASE_VERSION = 1;  

    public static final String KEY_NAME   = "username";
    public static final String KEY_ID     = "_id";
    public static final String KEY_GAME   = "game_id";
    public static final String KEY_USERID = "myId";
    public static final String KEY_TILE   = "tile";
    public static final String KEY_LASTT  = "last_tile"; 
    public static final String KEY_LASTN  = "last_tile_name";
    public static final String KEY_SCORE  = "myScore";
    public static final String KEY_OPP    = "opponentId";
    public static final String KEY_OPPSC  = "opponentScore";
    public static final String KEY_OPPNM  = "opponentName";
    public static final String KEY_DATE   = "last_played";
    public static final String KEY_ROW    = "row";
    public static final String KEY_COL    = "col";
    public static final String KEY_ROT    = "rotate";
    public static final String KEY_FACEDN = "face_dn";
    public static final String KEY_TSCORE = "tscore";
    public static final String KEY_WHO    = "who";
    public static final String KEY_REMAIN = "remain";
    public static final String KEY_COMPLETE = "complete";
    public static final String KEY_ATIME  = "alarmtime";
    public static final String KEY_AVIBE  = "alarmvibrate";
    public static final String KEY_ASOUND = "alarmsound";
    public static final String KEY_SHOWBOARD = "showboard";
    public static final String KEY_SHOWTOUCH = "showtouch";
    public static final String KEY_SHOWCENTERS = "showcenters";




    private static final String TAG = "dbAdapter";
    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;
    private final Context mCtx;
    private static final String DATE_FORMAT_NOW = "yyyy-MM-dd hh:mm";


    private static class DatabaseHelper extends SQLiteOpenHelper {

        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }



        @Override
        public void onCreate(SQLiteDatabase db) {

            db.execSQL(DATABASE_CREATE1);
            db.execSQL(DATABASE_CREATE2);
            db.execSQL(DATABASE_CREATE3);
            db.execSQL(DATABASE_CREATE4);


        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
                    + newVersion + ". ALL data will be destroyed");
            db.execSQL("DROP TABLE IF EXISTS user");
            db.execSQL("DROP TABLE IF EXISTS games");
            db.execSQL("DROP TABLE IF EXISTS tilesPlayed");
            onCreate(db); 

        }

    }    

    /**
     * Constructor - takes the context to allow the database to be
     * opened/created
     * 
     * @param ctx the Context within which to work
     */
    public  triDbAdapter(Context ctx) {
        this.mCtx = ctx;
    }

    /**
     * Open the student database. If it cannot be opened, try to create a new
     * instance of the database. If it cannot be created, throw an exception to
     * signal the failure
     * 
     * @return this (self reference, allowing this to be chained in an
     *         initialization call)
     * @throws SQLException if the database could be neither opened or created
     */
    public triDbAdapter open() throws SQLException {
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    public void close() {
        mDbHelper.close();
    }

    /**
     * createUserProfile inserts the local username and server-generated userid, _ID , into the database.
     * @param username the name of the local user
     * @param _id - index in player table
     * @return 
     */    
    public long createUserProfile(String username, int _id) {
        ContentValues initialValues = new ContentValues();
        initialValues.put(KEY_NAME, username);
        initialValues.put(KEY_ID, _id);
        initialValues.put(KEY_ATIME,Settings.AlarmInterval);
        initialValues.put(KEY_AVIBE,Settings.AlarmVibrate?1:0);
        initialValues.put(KEY_ASOUND,Settings.AlarmSound?1:0);
        initialValues.put(KEY_SHOWBOARD,Settings.ShowBoard?1:0);
        initialValues.put(KEY_SHOWTOUCH, Settings.ShowTouch?1:0);
        initialValues.put(KEY_SHOWCENTERS,Settings.ShowCenters?1:0);       
        return mDb.insertOrThrow(PLAYER_TABLE, null, initialValues);
    }

    /* initially upon startup, get what's in the dB */
    public void getSettings()  {
        Log.w(TAG, "getting settings");
        String sql = "SELECT * FROM player";
        Cursor c = mDb.rawQuery(sql, null);
        c.moveToLast();
        Settings.AlarmInterval = c.getInt(c.getColumnIndex(KEY_ATIME));
        Settings.AlarmVibrate = (c.getInt(c.getColumnIndex(KEY_AVIBE)) == 1);
        Settings.AlarmSound = (c.getInt(c.getColumnIndex(KEY_ASOUND)) == 1);
        Settings.ShowBoard = (c.getInt(c.getColumnIndex(KEY_SHOWBOARD)) == 1);
        Settings.ShowTouch = (c.getInt(c.getColumnIndex(KEY_SHOWTOUCH)) == 1);
        Settings.ShowCenters = (c.getInt(c.getColumnIndex(KEY_SHOWCENTERS)) == 1);
        c.close();
    }



   /**
    * updateLastPlayed - updates the game with the last tile played
    * @param game_id
    * @param user_id - local user's id
     * @param who - index to which player made the move
     * @param score - score to update
     * @param tile - tile number
     * @param name - name of the tile, used for rebuilding faces
     * @param row - row in the grid the tile was placed
     * @param col - col in the grid the tile was placed
     * @param rot - current rotation of the tile when placed
     * @param facedn - orientation of the tile as placed
     * @param tScore - score for just this turn
     * @param remain - 0 = no remaining tiles, 1 = there are tiles left.
     * @param complete - 0 = not complete, keep playing; 1=game over
     * @return 
    */
    public void UpdateLastPlayed(int game_id, int user_id, int who, int score, int tile, String name,
                                 int row, int col, int rot, Boolean facedn, int tScore, int remain, int complete) {
        ContentValues args = new ContentValues();
        ContentValues InitialValues = new ContentValues();
        int newscore;

        args.put(KEY_ID, game_id);
        args.put(KEY_WHO, who);
        args.put(KEY_LASTT, tile);
        args.put(KEY_LASTN, name);
        args.put(KEY_ROW, row);
        args.put(KEY_COL, col);
        args.put(KEY_ROT, rot);
        int face = (facedn)? 1 :0;
        args.put(KEY_FACEDN, face);
        args.put(KEY_TSCORE, tScore);

        Calendar cal = Calendar.getInstance();
        args.put(KEY_DATE, cal.getTimeInMillis());

        if (user_id == who) {   // last player was me
            if (score == -5) { 
                newscore = score + getScore(game_id, user_id); // adjust myscore
                args.put(KEY_SCORE, newscore);
                args.put(KEY_LASTT, -1);

            }
            else 
                args.put(KEY_SCORE, score);
        }
        else {                 // last player was opponent
            if (score == -5) { 
                newscore = score + getScore(game_id, who); // adjust opponent's
                args.put(KEY_OPPSC, newscore);
                args.put(KEY_LASTT, -1);

            }
            else
                args.put(KEY_OPPSC, score);

        }


        mDb.update(GAMES_TABLE, args, KEY_ID + "=" + game_id, null);    // this table goes to 
                                                                        // and comes from the main server

        InitialValues.put(KEY_ID, game_id);
        InitialValues.put(KEY_ROW, row);
        InitialValues.put(KEY_COL, col);
        InitialValues.put(KEY_ROT, rot);
        InitialValues.put(KEY_TILE, tile);
        InitialValues.put(KEY_FACEDN, face);
        mDb.insert(TILES_TABLE, null, InitialValues);                   // this table is local only 

    }

    public Cursor fetchLastPlayed(int game_id){
Cursor c;
        return mDb.query(GAMES_TABLE, new String[] {KEY_ID, KEY_SCORE,
        KEY_OPP, KEY_OPPSC, KEY_OPPNM, KEY_DATE, KEY_LASTT, KEY_LASTN, KEY_TSCORE, KEY_WHO}, 
        KEY_ID + "=" + game_id, null, null, null, KEY_DATE);

    }

}
</code></pre>
