add more debug info to SQL section in bugreport

after this CL, adb bugreport will the following info (under SQL section
of each app's meminfo dump)

 SQL
            heap:      344       memoryUsed:      344
pageCacheOverflo:       67  largestMemAlloc:       50

 DATABASES
  Pagesize   Dbsize  Lookaside  Dbname
      1024        7         24  googlesettings.db
      1024       26        110  talk.db
      1024       11          0    (attached) transient_talk_db
      1024       11         32  subscribedfeeds.db
      1024       20         27  gservices.db

Change-Id: Iabd13be9793d9794137c60a045b84fa632f13498
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7c49bb7..b0a59c7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -39,6 +39,7 @@
 import android.content.res.Resources;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteDebug.DbStats;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.os.Bundle;
@@ -1441,6 +1442,7 @@
         private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s";
         private static final String ONE_COUNT_COLUMN = "%17s %8d";
         private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d";
+        private static final String DB_INFO_FORMAT = "  %8d %8d %10d  %s";
 
         // Formatting for checkin service - update version if row format changes
         private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1;
@@ -1760,8 +1762,7 @@
             int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
             int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount();
             long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024;
-            SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats();
-            SQLiteDebug.getPagerStats(stats);
+            SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
 
             // Check to see if we were called by checkin server. If so, print terse format.
             boolean doCheckinFormat = false;
@@ -1835,10 +1836,15 @@
 
                 // SQL
                 pw.print(sqliteAllocated); pw.print(',');
-                pw.print(stats.databaseBytes / 1024); pw.print(',');
-                pw.print(stats.numPagers); pw.print(',');
-                pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(',');
-                pw.print(stats.referencedBytes / 1024); pw.print('\n');
+                pw.print(stats.memoryUsed / 1024); pw.print(',');
+                pw.print(stats.pageCacheOverflo / 1024); pw.print(',');
+                pw.print(stats.largestMemAlloc / 1024); pw.print(',');
+                for (int i = 0; i < stats.dbStats.size(); i++) {
+                    DbStats dbStats = stats.dbStats.get(i);
+                    printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
+                            dbStats.lookaside, dbStats.dbName);
+                    pw.print(',');
+                }
 
                 return;
             }
@@ -1879,11 +1885,21 @@
             // SQLite mem info
             pw.println(" ");
             pw.println(" SQL");
-            printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "dbFiles:",
-                    stats.databaseBytes / 1024);
-            printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:",
-                    (stats.totalBytes - stats.referencedBytes) / 1024);
-            printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024);
+            printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "memoryUsed:",
+                    stats.memoryUsed / 1024);
+            printRow(pw, TWO_COUNT_COLUMNS, "pageCacheOverflo:", stats.pageCacheOverflo / 1024,
+                    "largestMemAlloc:", stats.largestMemAlloc / 1024);
+            pw.println(" ");
+            int N = stats.dbStats.size();
+            if (N > 0) {
+                pw.println(" DATABASES");
+                printRow(pw, "  %8s %8s %10s  %s", "Pagesize", "Dbsize", "Lookaside", "Dbname");
+                for (int i = 0; i < N; i++) {
+                    DbStats dbStats = stats.dbStats.get(i);
+                    printRow(pw, DB_INFO_FORMAT, dbStats.pageSize, dbStats.dbSize,
+                            dbStats.lookaside, dbStats.dbName);
+                }
+            }
 
             // Asset details.
             String assetAlloc = AssetManager.getAssetAllocations();
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 71fa2c2..1830f6c 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -32,7 +32,7 @@
         synchronized(mLock) {
             if (mReferenceCount <= 0) {
                 throw new IllegalStateException(
-                        "attempt to acquire a reference on an already-closed " + getObjInfo());
+                        "attempt to re-open an already-closed object: " + getObjInfo());
             }
             mReferenceCount++;
         }
@@ -59,7 +59,6 @@
     private String getObjInfo() {
         StringBuilder buff = new StringBuilder();
         buff.append(this.getClass().getName());
-        buff.append(" Obj");
         buff.append(" (");
         if (this instanceof SQLiteDatabase) {
             buff.append("database = ");
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index dfd4024..b232ff9 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -23,6 +23,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.SQLException;
+import android.database.sqlite.SQLiteDebug.DbStats;
 import android.os.Debug;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -30,10 +31,14 @@
 import android.util.Config;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.Pair;
 
 import java.io.File;
+import java.lang.ref.WeakReference;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
@@ -787,25 +792,27 @@
      * @throws SQLiteException if the database cannot be opened
      */
     public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) {
-        SQLiteDatabase db = null;
+        SQLiteDatabase sqliteDatabase = null;
         try {
             // Open the database.
-            SQLiteDatabase sqliteDatabase = new SQLiteDatabase(path, factory, flags);
+            sqliteDatabase = new SQLiteDatabase(path, factory, flags);
             if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
                 sqliteDatabase.enableSqlTracing(path);
             }
             if (SQLiteDebug.DEBUG_SQL_TIME) {
                 sqliteDatabase.enableSqlProfiling(path);
             }
-            return sqliteDatabase;
         } catch (SQLiteDatabaseCorruptException e) {
             // Try to recover from this, if we can.
             // TODO: should we do this for other open failures?
             Log.e(TAG, "Deleting and re-creating corrupt database " + path, e);
             EventLog.writeEvent(EVENT_DB_CORRUPT, path);
             new File(path).delete();
-            return new SQLiteDatabase(path, factory, flags);
+            sqliteDatabase = new SQLiteDatabase(path, factory, flags);
         }
+        ActiveDatabases.getInstance().mActiveDatabases.add(
+                new WeakReference<SQLiteDatabase>(sqliteDatabase));
+        return sqliteDatabase;
     }
 
     /**
@@ -2040,6 +2047,88 @@
         mMaxSqlCacheSize = cacheSize;
     }
 
+    static class ActiveDatabases {
+        private static final ActiveDatabases activeDatabases = new ActiveDatabases();
+        private HashSet<WeakReference<SQLiteDatabase>> mActiveDatabases =
+            new HashSet<WeakReference<SQLiteDatabase>>();
+        private ActiveDatabases() {} // disable instantiation of this class
+        static ActiveDatabases getInstance() {return activeDatabases;}
+    }
+
+    /* package */ static ArrayList<DbStats> getDbStats() {
+        ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
+        for (WeakReference<SQLiteDatabase> w : ActiveDatabases.getInstance().mActiveDatabases) {
+            SQLiteDatabase db = w.get();
+            if (db == null || !db.isOpen()) {
+                continue;
+            }
+            // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
+            int lookasideUsed = db.native_getDbLookaside();
+
+            // get the lastnode of the dbname
+            String path = db.getPath();
+            int indx = path.lastIndexOf("/");
+            String lastnode = path.substring((indx != -1) ? ++indx : 0);
+
+            // get list of attached dbs and for each db, get its size and pagesize
+            ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db);
+            for (int i = 0; i < attachedDbs.size(); i++) {
+                Pair<String, String> p = attachedDbs.get(i);
+                long pageCount = getPragmaVal(db, p.first + ".page_count;");
+
+                // first entry in the attached db list is always the main database
+                // don't worry about prefixing the dbname with "main"
+                String dbName;
+                if (i == 0) {
+                    dbName = lastnode;
+                } else {
+                    // lookaside is only relevant for the main db
+                    lookasideUsed = 0;
+                    dbName = "  (attached) " + p.first;
+                    // if the attached db has a path, attach the lastnode from the path to above
+                    if (p.second.trim().length() > 0) {
+                        int idx = p.second.lastIndexOf("/");
+                        dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
+                    }
+                }
+                dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(), lookasideUsed));
+            }
+        }
+        return dbStatsList;
+    }
+
+    /**
+     * get the specified pragma value from sqlite for the specified database.
+     * only handles pragma's that return int/long.
+     * NO JAVA locks are held in this method.
+     * TODO: use this to do all pragma's in this class
+     */
+    private static long getPragmaVal(SQLiteDatabase db, String pragma) {
+        SQLiteStatement prog = null;
+        try {
+            prog = new SQLiteStatement(db, "PRAGMA " + pragma);
+            long val = prog.simpleQueryForLong();
+            return val;
+        } finally {
+            if (prog != null) prog.close();
+        }
+    }
+
+    /**
+     * returns list of full pathnames of all attached databases
+     * including the main database
+     * TODO: move this to {@link DatabaseUtils}
+     */
+    private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) {
+        ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
+        Cursor c = dbObj.rawQuery("pragma database_list;", null);
+        while (c.moveToNext()) {
+             attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2)));
+        }
+        c.close();
+        return attachedDbs;
+    }
+
     /**
      * Native call to open the database.
      *
@@ -2093,4 +2182,11 @@
      * @return the number of changes made in the last statement executed.
      */
     /* package */ native int lastChangeCount();
+
+    /**
+     * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here
+     * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
+     * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED
+     */
+    private native int native_getDbLookaside();
 }
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 4ea680e..a4db6d9 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -16,6 +16,8 @@
 
 package android.database.sqlite;
 
+import java.util.ArrayList;
+
 import android.util.Log;
 
 /**
@@ -68,14 +70,87 @@
      * @see #getPagerStats(PagerStats)
      */
     public static class PagerStats {
-        /** The total number of bytes in all pagers in the current process */
+        /** The total number of bytes in all pagers in the current process
+         * @deprecated not used any longer
+         */
+        @Deprecated
         public long totalBytes;
-        /** The number of bytes in referenced pages in all pagers in the current process */
+        /** The number of bytes in referenced pages in all pagers in the current process
+         * @deprecated not used any longer
+         * */
+        @Deprecated
         public long referencedBytes;
-        /** The number of bytes in all database files opened in the current process */
+        /** The number of bytes in all database files opened in the current process
+         * @deprecated not used any longer
+         */
+        @Deprecated
         public long databaseBytes;
-        /** The number of pagers opened in the current process */
+        /** The number of pagers opened in the current process
+         * @deprecated not used any longer
+         */
+        @Deprecated
         public int numPagers;
+
+        /** the current amount of memory checked out by sqlite using sqlite3_malloc().
+         * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+         */
+        public int memoryUsed;
+
+        /** the number of bytes of page cache allocation which could not be sattisfied by the
+         * SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc().
+         * The returned value includes allocations that overflowed because they where too large
+         * (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations
+         * that overflowed because no space was left in the page cache.
+         * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+         */
+        public int pageCacheOverflo;
+
+        /** records the largest memory allocation request handed to sqlite3.
+         * documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
+         */
+        public int largestMemAlloc;
+
+        /** a list of {@link DbStats} - one for each main database opened by the applications
+         * running on the android device
+         */
+        public ArrayList<DbStats> dbStats;
+    }
+
+    /**
+     * contains statistics about a database
+     * @author vnori@google.com (Your Name Here)
+     *
+     */
+    public static class DbStats {
+        /** name of the database */
+        public String dbName;
+
+        /** the page size for the database */
+        public long pageSize;
+
+        /** the database size */
+        public long dbSize;
+
+        /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
+        public int lookaside;
+
+        public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
+            this.dbName = dbName;
+            this.pageSize = pageSize;
+            dbSize = (pageCount * pageSize) / 1024;
+            this.lookaside = lookaside;
+        }
+    }
+
+    /**
+     * return all pager and database stats for the current process.
+     * @return {@link PagerStats}
+     */
+    public static PagerStats getDatabaseInfo() {
+        PagerStats stats = new PagerStats();
+        getPagerStats(stats);
+        stats.dbStats = SQLiteDatabase.getDbStats();
+        return stats;
     }
 
     /**
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
index 44e091d..fb93014 100644
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ b/core/jni/android_database_SQLiteDatabase.cpp
@@ -306,6 +306,16 @@
     return sqlite3_changes(handle);
 }
 
+/* native int native_getDbLookaside(); */
+static jint native_getDbLookaside(JNIEnv* env, jobject object)
+{
+    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
+    int pCur = -1;
+    int unused;
+    sqlite3_db_status(handle, SQLITE_DBSTATUS_LOOKASIDE_USED, &pCur, &unused, 0);
+    return pCur;
+}
+
 /* set locale in the android_metadata table, install localized collators, and rebuild indexes */
 static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags)
 {
@@ -442,6 +452,7 @@
     {"lastInsertRow", "()J", (void *)lastInsertRow},
     {"lastChangeCount", "()I", (void *)lastChangeCount},
     {"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale},
+    {"native_getDbLookaside", "()I", (void *)native_getDbLookaside},
     {"releaseMemory", "()I", (void *)native_releaseMemory},
 };
 
diff --git a/core/jni/android_database_SQLiteDebug.cpp b/core/jni/android_database_SQLiteDebug.cpp
index 916df35..873b2a1 100644
--- a/core/jni/android_database_SQLiteDebug.cpp
+++ b/core/jni/android_database_SQLiteDebug.cpp
@@ -29,36 +29,28 @@
 // From mem_mspace.c in libsqlite
 extern "C" mspace sqlite3_get_mspace();
 
-// From sqlite.c, hacked in for Android
-extern "C" void sqlite3_get_pager_stats(sqlite3_int64 * totalBytesOut,
-                                       sqlite3_int64 * referencedBytesOut,
-                                       sqlite3_int64 * dbBytesOut,
-                                       int * numPagersOut);
-
 namespace android {
 
-static jfieldID gTotalBytesField;
-static jfieldID gReferencedBytesField;
-static jfieldID gDbBytesField;
-static jfieldID gNumPagersField;
+static jfieldID gMemoryUsedField;
+static jfieldID gPageCacheOverfloField;
+static jfieldID gLargestMemAllocField;
 
 
 #define USE_MSPACE 0
 
 static void getPagerStats(JNIEnv *env, jobject clazz, jobject statsObj)
 {
-    sqlite3_int64 totalBytes;
-    sqlite3_int64 referencedBytes;
-    sqlite3_int64 dbBytes;
-    int numPagers;
+    int memoryUsed;
+    int pageCacheOverflo;
+    int largestMemAlloc;
+    int unused;
 
-    sqlite3_get_pager_stats(&totalBytes, &referencedBytes, &dbBytes,
-            &numPagers);
-
-    env->SetLongField(statsObj, gTotalBytesField, totalBytes);
-    env->SetLongField(statsObj, gReferencedBytesField, referencedBytes);
-    env->SetLongField(statsObj, gDbBytesField, dbBytes);
-    env->SetIntField(statsObj, gNumPagersField, numPagers);
+    sqlite3_status(SQLITE_STATUS_MEMORY_USED, &memoryUsed, &unused, 0);
+    sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &unused, &largestMemAlloc, 0);
+    sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &pageCacheOverflo, &unused, 0);
+    env->SetIntField(statsObj, gMemoryUsedField, memoryUsed);
+    env->SetIntField(statsObj, gPageCacheOverfloField, pageCacheOverflo);
+    env->SetIntField(statsObj, gLargestMemAllocField, largestMemAlloc);
 }
 
 static jlong getHeapSize(JNIEnv *env, jobject clazz)
@@ -213,27 +205,21 @@
         return -1;
     }
 
-    gTotalBytesField = env->GetFieldID(clazz, "totalBytes", "J");
-    if (gTotalBytesField == NULL) {
-        LOGE("Can't find totalBytes");
+    gMemoryUsedField = env->GetFieldID(clazz, "memoryUsed", "I");
+    if (gMemoryUsedField == NULL) {
+        LOGE("Can't find memoryUsed");
         return -1;
     }
 
-    gReferencedBytesField = env->GetFieldID(clazz, "referencedBytes", "J");
-    if (gReferencedBytesField == NULL) {
-        LOGE("Can't find referencedBytes");
+    gLargestMemAllocField = env->GetFieldID(clazz, "largestMemAlloc", "I");
+    if (gLargestMemAllocField == NULL) {
+        LOGE("Can't find largestMemAlloc");
         return -1;
     }
 
-    gDbBytesField = env->GetFieldID(clazz, "databaseBytes", "J");
-    if (gDbBytesField == NULL) {
-        LOGE("Can't find databaseBytes");
-        return -1;
-    }
-
-    gNumPagersField = env->GetFieldID(clazz, "numPagers", "I");
-    if (gNumPagersField == NULL) {
-        LOGE("Can't find numPagers");
+    gPageCacheOverfloField = env->GetFieldID(clazz, "pageCacheOverflo", "I");
+    if (gPageCacheOverfloField == NULL) {
+        LOGE("Can't find pageCacheOverflo");
         return -1;
     }