/* * Copyright (C) 2012 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.google.android.vending.expansion.downloader.impl; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDoneException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; import android.provider.BaseColumns; import android.util.Log; public class DownloadsDB { private static final String DATABASE_NAME = "DownloadsDB"; private static final int DATABASE_VERSION = 7; public static final String LOG_TAG = DownloadsDB.class.getName(); final SQLiteOpenHelper mHelper; SQLiteStatement mGetDownloadByIndex; SQLiteStatement mUpdateCurrentBytes; private static DownloadsDB mDownloadsDB; long mMetadataRowID = -1; int mVersionCode = -1; int mStatus = -1; int mFlags; static public synchronized DownloadsDB getDB(Context paramContext) { if (null == mDownloadsDB) { return new DownloadsDB(paramContext); } return mDownloadsDB; } private SQLiteStatement getDownloadByIndexStatement() { if (null == mGetDownloadByIndex) { mGetDownloadByIndex = mHelper.getReadableDatabase().compileStatement( "SELECT " + BaseColumns._ID + " FROM " + DownloadColumns.TABLE_NAME + " WHERE " + DownloadColumns.INDEX + " = ?"); } return mGetDownloadByIndex; } private SQLiteStatement getUpdateCurrentBytesStatement() { if (null == mUpdateCurrentBytes) { mUpdateCurrentBytes = mHelper.getReadableDatabase().compileStatement( "UPDATE " + DownloadColumns.TABLE_NAME + " SET " + DownloadColumns.CURRENTBYTES + " = ?" + " WHERE " + DownloadColumns.INDEX + " = ?"); } return mUpdateCurrentBytes; } private DownloadsDB(Context paramContext) { this.mHelper = new DownloadsContentDBHelper(paramContext); final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); // Query for the version code, the row ID of the metadata (for future // updating) the status and the flags Cursor cur = sqldb.rawQuery("SELECT " + MetadataColumns.APKVERSION + "," + BaseColumns._ID + "," + MetadataColumns.DOWNLOAD_STATUS + "," + MetadataColumns.FLAGS + " FROM " + MetadataColumns.TABLE_NAME + " LIMIT 1", null); if (null != cur && cur.moveToFirst()) { mVersionCode = cur.getInt(0); mMetadataRowID = cur.getLong(1); mStatus = cur.getInt(2); mFlags = cur.getInt(3); cur.close(); } mDownloadsDB = this; } protected DownloadInfo getDownloadInfoByFileName(String fileName) { final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); Cursor itemcur = null; try { itemcur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, DownloadColumns.FILENAME + " = ?", new String[] { fileName }, null, null, null); if (null != itemcur && itemcur.moveToFirst()) { return getDownloadInfoFromCursor(itemcur); } } finally { if (null != itemcur) itemcur.close(); } return null; } public long getIDForDownloadInfo(final DownloadInfo di) { return getIDByIndex(di.mIndex); } public long getIDByIndex(int index) { SQLiteStatement downloadByIndex = getDownloadByIndexStatement(); downloadByIndex.clearBindings(); downloadByIndex.bindLong(1, index); try { return downloadByIndex.simpleQueryForLong(); } catch (SQLiteDoneException e) { return -1; } } public void updateDownloadCurrentBytes(final DownloadInfo di) { SQLiteStatement downloadCurrentBytes = getUpdateCurrentBytesStatement(); downloadCurrentBytes.clearBindings(); downloadCurrentBytes.bindLong(1, di.mCurrentBytes); downloadCurrentBytes.bindLong(2, di.mIndex); downloadCurrentBytes.execute(); } public void close() { this.mHelper.close(); } protected static class DownloadsContentDBHelper extends SQLiteOpenHelper { DownloadsContentDBHelper(Context paramContext) { super(paramContext, DATABASE_NAME, null, DATABASE_VERSION); } private String createTableQueryFromArray(String paramString, String[][] paramArrayOfString) { StringBuilder localStringBuilder = new StringBuilder(); localStringBuilder.append("CREATE TABLE "); localStringBuilder.append(paramString); localStringBuilder.append(" ("); int i = paramArrayOfString.length; for (int j = 0;; j++) { if (j >= i) { localStringBuilder .setLength(localStringBuilder.length() - 1); localStringBuilder.append(");"); return localStringBuilder.toString(); } String[] arrayOfString = paramArrayOfString[j]; localStringBuilder.append(' '); localStringBuilder.append(arrayOfString[0]); localStringBuilder.append(' '); localStringBuilder.append(arrayOfString[1]); localStringBuilder.append(','); } } /** * These two arrays must match and have the same order. For every Schema * there must be a corresponding table name. */ static final private String[][][] sSchemas = { DownloadColumns.SCHEMA, MetadataColumns.SCHEMA }; static final private String[] sTables = { DownloadColumns.TABLE_NAME, MetadataColumns.TABLE_NAME }; /** * Goes through all of the tables in sTables and drops each table if it * exists. Altered to no longer make use of reflection. */ private void dropTables(SQLiteDatabase paramSQLiteDatabase) { for (String table : sTables) { try { paramSQLiteDatabase.execSQL("DROP TABLE IF EXISTS " + table); } catch (Exception localException) { localException.printStackTrace(); } } } /** * Goes through all of the tables in sTables and creates a database with * the corresponding schema described in sSchemas. Altered to no longer * make use of reflection. */ public void onCreate(SQLiteDatabase paramSQLiteDatabase) { int numSchemas = sSchemas.length; for (int i = 0; i < numSchemas; i++) { try { String[][] schema = (String[][]) sSchemas[i]; paramSQLiteDatabase.execSQL(createTableQueryFromArray( sTables[i], schema)); } catch (Exception localException) { while (true) localException.printStackTrace(); } } } public void onUpgrade(SQLiteDatabase paramSQLiteDatabase, int paramInt1, int paramInt2) { Log.w(DownloadsContentDBHelper.class.getName(), "Upgrading database from version " + paramInt1 + " to " + paramInt2 + ", which will destroy all old data"); dropTables(paramSQLiteDatabase); onCreate(paramSQLiteDatabase); } } public static class MetadataColumns implements BaseColumns { public static final String APKVERSION = "APKVERSION"; public static final String DOWNLOAD_STATUS = "DOWNLOADSTATUS"; public static final String FLAGS = "DOWNLOADFLAGS"; public static final String[][] SCHEMA = { { BaseColumns._ID, "INTEGER PRIMARY KEY" }, { APKVERSION, "INTEGER" }, { DOWNLOAD_STATUS, "INTEGER" }, { FLAGS, "INTEGER" } }; public static final String TABLE_NAME = "MetadataColumns"; public static final String _ID = "MetadataColumns._id"; } public static class DownloadColumns implements BaseColumns { public static final String INDEX = "FILEIDX"; public static final String URI = "URI"; public static final String FILENAME = "FN"; public static final String ETAG = "ETAG"; public static final String TOTALBYTES = "TOTALBYTES"; public static final String CURRENTBYTES = "CURRENTBYTES"; public static final String LASTMOD = "LASTMOD"; public static final String STATUS = "STATUS"; public static final String CONTROL = "CONTROL"; public static final String NUM_FAILED = "FAILCOUNT"; public static final String RETRY_AFTER = "RETRYAFTER"; public static final String REDIRECT_COUNT = "REDIRECTCOUNT"; public static final String[][] SCHEMA = { { BaseColumns._ID, "INTEGER PRIMARY KEY" }, { INDEX, "INTEGER UNIQUE" }, { URI, "TEXT" }, { FILENAME, "TEXT UNIQUE" }, { ETAG, "TEXT" }, { TOTALBYTES, "INTEGER" }, { CURRENTBYTES, "INTEGER" }, { LASTMOD, "INTEGER" }, { STATUS, "INTEGER" }, { CONTROL, "INTEGER" }, { NUM_FAILED, "INTEGER" }, { RETRY_AFTER, "INTEGER" }, { REDIRECT_COUNT, "INTEGER" } }; public static final String TABLE_NAME = "DownloadColumns"; public static final String _ID = "DownloadColumns._id"; } private static final String[] DC_PROJECTION = { DownloadColumns.FILENAME, DownloadColumns.URI, DownloadColumns.ETAG, DownloadColumns.TOTALBYTES, DownloadColumns.CURRENTBYTES, DownloadColumns.LASTMOD, DownloadColumns.STATUS, DownloadColumns.CONTROL, DownloadColumns.NUM_FAILED, DownloadColumns.RETRY_AFTER, DownloadColumns.REDIRECT_COUNT, DownloadColumns.INDEX }; private static final int FILENAME_IDX = 0; private static final int URI_IDX = 1; private static final int ETAG_IDX = 2; private static final int TOTALBYTES_IDX = 3; private static final int CURRENTBYTES_IDX = 4; private static final int LASTMOD_IDX = 5; private static final int STATUS_IDX = 6; private static final int CONTROL_IDX = 7; private static final int NUM_FAILED_IDX = 8; private static final int RETRY_AFTER_IDX = 9; private static final int REDIRECT_COUNT_IDX = 10; private static final int INDEX_IDX = 11; /** * This function will add a new file to the database if it does not exist. * * @param di DownloadInfo that we wish to store * @return the row id of the record to be updated/inserted, or -1 */ public boolean updateDownload(DownloadInfo di) { ContentValues cv = new ContentValues(); cv.put(DownloadColumns.INDEX, di.mIndex); cv.put(DownloadColumns.FILENAME, di.mFileName); cv.put(DownloadColumns.URI, di.mUri); cv.put(DownloadColumns.ETAG, di.mETag); cv.put(DownloadColumns.TOTALBYTES, di.mTotalBytes); cv.put(DownloadColumns.CURRENTBYTES, di.mCurrentBytes); cv.put(DownloadColumns.LASTMOD, di.mLastMod); cv.put(DownloadColumns.STATUS, di.mStatus); cv.put(DownloadColumns.CONTROL, di.mControl); cv.put(DownloadColumns.NUM_FAILED, di.mNumFailed); cv.put(DownloadColumns.RETRY_AFTER, di.mRetryAfter); cv.put(DownloadColumns.REDIRECT_COUNT, di.mRedirectCount); return updateDownload(di, cv); } public boolean updateDownload(DownloadInfo di, ContentValues cv) { long id = di == null ? -1 : getIDForDownloadInfo(di); try { final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); if (id != -1) { if (1 != sqldb.update(DownloadColumns.TABLE_NAME, cv, DownloadColumns._ID + " = " + id, null)) { return false; } } else { return -1 != sqldb.insert(DownloadColumns.TABLE_NAME, DownloadColumns.URI, cv); } } catch (android.database.sqlite.SQLiteException ex) { ex.printStackTrace(); } return false; } public int getLastCheckedVersionCode() { return mVersionCode; } public boolean isDownloadRequired() { final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); Cursor cur = sqldb.rawQuery("SELECT Count(*) FROM " + DownloadColumns.TABLE_NAME + " WHERE " + DownloadColumns.STATUS + " <> 0", null); try { if (null != cur && cur.moveToFirst()) { return 0 == cur.getInt(0); } } finally { if (null != cur) cur.close(); } return true; } public int getFlags() { return mFlags; } public boolean updateFlags(int flags) { if (mFlags != flags) { ContentValues cv = new ContentValues(); cv.put(MetadataColumns.FLAGS, flags); if (updateMetadata(cv)) { mFlags = flags; return true; } else { return false; } } else { return true; } }; public boolean updateStatus(int status) { if (mStatus != status) { ContentValues cv = new ContentValues(); cv.put(MetadataColumns.DOWNLOAD_STATUS, status); if (updateMetadata(cv)) { mStatus = status; return true; } else { return false; } } else { return true; } }; public boolean updateMetadata(ContentValues cv) { final SQLiteDatabase sqldb = mHelper.getWritableDatabase(); if (-1 == this.mMetadataRowID) { long newID = sqldb.insert(MetadataColumns.TABLE_NAME, MetadataColumns.APKVERSION, cv); if (-1 == newID) return false; mMetadataRowID = newID; } else { if (0 == sqldb.update(MetadataColumns.TABLE_NAME, cv, BaseColumns._ID + " = " + mMetadataRowID, null)) return false; } return true; } public boolean updateMetadata(int apkVersion, int downloadStatus) { ContentValues cv = new ContentValues(); cv.put(MetadataColumns.APKVERSION, apkVersion); cv.put(MetadataColumns.DOWNLOAD_STATUS, downloadStatus); if (updateMetadata(cv)) { mVersionCode = apkVersion; mStatus = downloadStatus; return true; } else { return false; } }; public boolean updateFromDb(DownloadInfo di) { final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); Cursor cur = null; try { cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, DownloadColumns.FILENAME + "= ?", new String[] { di.mFileName }, null, null, null); if (null != cur && cur.moveToFirst()) { setDownloadInfoFromCursor(di, cur); return true; } return false; } finally { if (null != cur) { cur.close(); } } } public void setDownloadInfoFromCursor(DownloadInfo di, Cursor cur) { di.mUri = cur.getString(URI_IDX); di.mETag = cur.getString(ETAG_IDX); di.mTotalBytes = cur.getLong(TOTALBYTES_IDX); di.mCurrentBytes = cur.getLong(CURRENTBYTES_IDX); di.mLastMod = cur.getLong(LASTMOD_IDX); di.mStatus = cur.getInt(STATUS_IDX); di.mControl = cur.getInt(CONTROL_IDX); di.mNumFailed = cur.getInt(NUM_FAILED_IDX); di.mRetryAfter = cur.getInt(RETRY_AFTER_IDX); di.mRedirectCount = cur.getInt(REDIRECT_COUNT_IDX); } public DownloadInfo getDownloadInfoFromCursor(Cursor cur) { DownloadInfo di = new DownloadInfo(cur.getInt(INDEX_IDX), cur.getString(FILENAME_IDX), this.getClass().getPackage() .getName()); setDownloadInfoFromCursor(di, cur); return di; } public DownloadInfo[] getDownloads() { final SQLiteDatabase sqldb = mHelper.getReadableDatabase(); Cursor cur = null; try { cur = sqldb.query(DownloadColumns.TABLE_NAME, DC_PROJECTION, null, null, null, null, null); if (null != cur && cur.moveToFirst()) { DownloadInfo[] retInfos = new DownloadInfo[cur.getCount()]; int idx = 0; do { DownloadInfo di = getDownloadInfoFromCursor(cur); retInfos[idx++] = di; } while (cur.moveToNext()); return retInfos; } return null; } finally { if (null != cur) { cur.close(); } } } }