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/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();
 }