am b4ac04f7: Add more logging to track down a monkey bug

Merge commit 'b4ac04f7bd9d4f16ec181f368c42f89c96f83f55'

* commit 'b4ac04f7bd9d4f16ec181f368c42f89c96f83f55':
  Add more logging to track down a monkey bug
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index 37d1787..9c3a446 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -176,6 +176,9 @@
                 qb.setTables(TABLE_DRM);
                 qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
                 break;
+            case MMS_THREADS:
+                qb.setTables("pdu group by thread_id");
+                break;
             default:
                 Log.e(TAG, "Invalid request: " + uri);
                 return null;
@@ -363,24 +366,30 @@
                 finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0));
             }
 
-            // Generate the '_data' field of the part with default
-            // permission settings.
-            String path = getContext().getDir("parts", 0).getPath()
-                    + "/PART_" + System.currentTimeMillis();
+            String contentType = values.getAsString("ct");
+            
+            // text/plain and app application/smil store their "data" inline in the
+            // table so there's no need to create the file
+            if (!"text/plain".equals(contentType) && !"application/smil".equals(contentType)) {
+                // Generate the '_data' field of the part with default
+                // permission settings.
+                String path = getContext().getDir("parts", 0).getPath()
+                + "/PART_" + System.currentTimeMillis();
 
-            finalValues.put(Part._DATA, path);
+                finalValues.put(Part._DATA, path);
 
-            File partFile = new File(path);
-            if (!partFile.exists()) {
-                try {
-                    if (!partFile.createNewFile()) {
+                File partFile = new File(path);
+                if (!partFile.exists()) {
+                    try {
+                        if (!partFile.createNewFile()) {
+                            throw new IllegalStateException(
+                                    "Unable to create new partFile: " + path);
+                        }
+                    } catch (IOException e) {
+                        Log.e(TAG, "createNewFile", e);
                         throw new IllegalStateException(
                                 "Unable to create new partFile: " + path);
                     }
-                } catch (IOException e) {
-                    Log.e(TAG, "createNewFile", e);
-                    throw new IllegalStateException(
-                            "Unable to create new partFile: " + path);
                 }
             }
 
@@ -588,7 +597,10 @@
             while (cursor.moveToNext()) {
                 try {
                     // Delete the associated files saved on file-system.
-                    new File(cursor.getString(0)).delete();
+                    String path = cursor.getString(0);
+                    if (path != null) {
+                        new File(path).delete();
+                    }
                 } catch (Throwable ex) {
                     Log.e(TAG, ex.getMessage(), ex);
                 }
@@ -751,10 +763,11 @@
     private static final int MMS_PART_ID                  = 12;
     private static final int MMS_MSG_ADDR                 = 13;
     private static final int MMS_SENDING_RATE             = 14;
-    private static final int MMS_REPORT_STATUS          = 15;
-    private static final int MMS_REPORT_REQUEST = 16;
+    private static final int MMS_REPORT_STATUS            = 15;
+    private static final int MMS_REPORT_REQUEST           = 16;
     private static final int MMS_DRM_STORAGE              = 17;
     private static final int MMS_DRM_STORAGE_ID           = 18;
+    private static final int MMS_THREADS                  = 19;
 
     private static final UriMatcher
             sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@@ -779,6 +792,7 @@
         sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
         sURLMatcher.addURI("mms", "drm",        MMS_DRM_STORAGE);
         sURLMatcher.addURI("mms", "drm/#",      MMS_DRM_STORAGE_ID);
+        sURLMatcher.addURI("mms", "threads",    MMS_THREADS);
     }
 
     private SQLiteOpenHelper mOpenHelper;
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 05de829..42f8b2f 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -23,6 +23,14 @@
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF;
 import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_SEND_REQ;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.File;
+import java.util.ArrayList;
+
+import com.google.android.mms.pdu.EncodedStringValue;
+
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
@@ -176,11 +184,11 @@
                         "   ELSE 1 " +
                         "   END; " +
                         " END";
-    
+
     private static MmsSmsDatabaseHelper mInstance = null;
 
     static final String DATABASE_NAME = "mmssms.db";
-    static final int DATABASE_VERSION = 44;
+    static final int DATABASE_VERSION = 46;
 
     private MmsSmsDatabaseHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
@@ -202,7 +210,7 @@
             updateAllThreads(db, null, null);
             return;
         }
-        
+
         // Delete the row for this thread in the threads table if
         // there are no more messages attached to it in either
         // the sms or pdu tables.
@@ -234,24 +242,24 @@
         // the threads table to be that of the most recent message in
         // the thread.
         db.execSQL(
-            "  UPDATE threads" + 
-            "  SET" + 
-            "  date =" + 
-            "    (SELECT date FROM" + 
-            "        (SELECT date * 1000 AS date, thread_id FROM pdu" + 
-            "         UNION SELECT date, thread_id FROM sms)" + 
-            "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 
-            "  snippet =" + 
-            "    (SELECT snippet FROM" + 
-            "        (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" + 
-            "         UNION SELECT date, body AS snippet, thread_id FROM sms)" + 
-            "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 
-            "  snippet_cs =" + 
-            "    (SELECT snippet_cs FROM" + 
-            "        (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" + 
-            "         UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" + 
-            "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" + 
-            "  WHERE threads._id = " + thread_id + ";"); 
+            "  UPDATE threads" +
+            "  SET" +
+            "  date =" +
+            "    (SELECT date FROM" +
+            "        (SELECT date * 1000 AS date, thread_id FROM pdu" +
+            "         UNION SELECT date, thread_id FROM sms)" +
+            "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," +
+            "  snippet =" +
+            "    (SELECT snippet FROM" +
+            "        (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" +
+            "         UNION SELECT date, body AS snippet, thread_id FROM sms)" +
+            "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," +
+            "  snippet_cs =" +
+            "    (SELECT snippet_cs FROM" +
+            "        (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" +
+            "         UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" +
+            "     WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" +
+            "  WHERE threads._id = " + thread_id + ";");
 
         // Update the error column of the thread to indicate if there
         // are any messages in it that have failed to send.
@@ -261,7 +269,7 @@
             "        AND thread_id = " + thread_id + " LIMIT 1)" +
             "   WHERE threads._id = " + thread_id + ";");
     }
-    
+
     public static void updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs) {
         if (where == null) {
             where = "";
@@ -282,7 +290,7 @@
                 "_id NOT IN (SELECT DISTINCT thread_id FROM sms " +
                 "UNION SELECT DISTINCT thread_id FROM pdu)", null);
     }
-    
+
     public static int deleteOneSms(SQLiteDatabase db, int message_id) {
         int thread_id = -1;
         // Find the thread ID that the specified SMS belongs to.
@@ -345,7 +353,9 @@
                    Mms.CONTENT_CLASS + " INTEGER," +
                    Mms.RESPONSE_TEXT + " TEXT," +
                    Mms.DELIVERY_TIME + " INTEGER," +
-                   Mms.DELIVERY_REPORT + " INTEGER);");
+                   Mms.DELIVERY_REPORT + " INTEGER," +
+                   Mms.LOCKED + " INTEGER DEFAULT 0" +
+                   ");");
 
         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" +
                    Addr._ID + " INTEGER PRIMARY KEY," +
@@ -368,7 +378,8 @@
                    Part.CONTENT_LOCATION + " TEXT," +
                    Part.CT_START + " INTEGER," +
                    Part.CT_TYPE + " TEXT," +
-                   Part._DATA + " TEXT);");
+                   Part._DATA + " TEXT," +
+                   Part.TEXT + " TEXT);");
 
         db.execSQL("CREATE TABLE " + MmsProvider.TABLE_RATE + " (" +
                    Rate.SENT_TIME + " INTEGER);");
@@ -404,7 +415,7 @@
                    "    OR " + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_READ_ORIG_IND + ")" +
                    "    AND " + Mms.MESSAGE_ID + "=old." + Mms.MESSAGE_ID + "; " +
                    "END;");
-        
+
         // Update threads table to indicate whether attachments exist when
         // parts are inserted or deleted.
         db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER);
@@ -431,7 +442,9 @@
                    "reply_path_present INTEGER," +
                    "subject TEXT," +
                    "body TEXT," +
-                   "service_center TEXT);");
+                   "service_center TEXT," +
+                   "locked INTEGER DEFAULT 0" +
+                   ");");
 
         /**
          * This table is used by the SMS dispatcher to hold
@@ -738,7 +751,7 @@
             if (currentVersion <= 41) {
                 return;
             }
-            
+
             db.beginTransaction();
             try {
                 upgradeDatabaseToVersion42(db);
@@ -754,7 +767,7 @@
             if (currentVersion <= 42) {
                 return;
             }
-            
+
             db.beginTransaction();
             try {
                 upgradeDatabaseToVersion43(db);
@@ -782,8 +795,41 @@
                 db.endTransaction();
             }
             return;
+        case 44:
+            if (currentVersion <= 44) {
+                return;
+            }
+
+            db.beginTransaction();
+            try {
+                upgradeDatabaseToVersion45(db);
+                db.setTransactionSuccessful();
+            } catch (Throwable ex) {
+                Log.e(TAG, ex.getMessage(), ex);
+                break;
+            } finally {
+                db.endTransaction();
+            }
+            return;
+
+        case 45:
+            if (currentVersion <= 45) {
+                return;
+            }
+            db.beginTransaction();
+            try {
+                upgradeDatabaseToVersion46(db);
+                db.setTransactionSuccessful();
+            } catch (Throwable ex) {
+                Log.e(TAG, ex.getMessage(), ex);
+                break;
+            } finally {
+                db.endTransaction();
+            }
+            return;
         }
 
+
         Log.e(TAG, "Destroying all old data.");
         dropAll(db);
         onCreate(db);
@@ -821,13 +867,13 @@
                    "  WHERE _id = OLD.thread_id; " +
                    "END;");
     }
-    
+
     private void upgradeDatabaseToVersion42(SQLiteDatabase db) {
         db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete");
         db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_sms");
         db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_sms");
     }
-    
+
     private void upgradeDatabaseToVersion43(SQLiteDatabase db) {
         // Add 'has_attachment' column to threads table.
         db.execSQL("ALTER TABLE threads ADD COLUMN has_attachment INTEGER DEFAULT 0");
@@ -855,4 +901,70 @@
         // add the update trigger for keeping the threads up to date.
         db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER);
     }
+
+    private void upgradeDatabaseToVersion45(SQLiteDatabase db) {
+        // Add 'locked' column to sms table.
+        db.execSQL("ALTER TABLE sms ADD COLUMN locked INTEGER DEFAULT 0");
+
+        // Add 'locked' column to pdu table.
+        db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.LOCKED + " INTEGER DEFAULT 0");
+    }
+
+    private void upgradeDatabaseToVersion46(SQLiteDatabase db) {
+        // add the "text" column for caching inline text (e.g. strings) instead of
+        // putting them in an external file
+        db.execSQL("ALTER TABLE part ADD COLUMN " + Part.TEXT + " TEXT");
+
+        Cursor textRows = db.query(
+                "part",
+                new String[] { Part._ID, Part._DATA, Part.TEXT},
+                "ct = 'text/plain' OR ct == 'application/smil'",
+                null,
+                null,
+                null,
+                null);
+        ArrayList<String> filesToDelete = new ArrayList<String>();
+        try {
+            if (textRows != null) {
+                int partIdColumn = textRows.getColumnIndex(Part._ID);
+                int partDataColumn = textRows.getColumnIndex(Part._DATA);
+                int partTextColumn = textRows.getColumnIndex(Part.TEXT);
+
+                // This code is imperfect in that we can't guarantee that all the
+                // backing files get deleted.  For example if the system aborts after
+                // the database is updated but before we complete the process of
+                // deleting files.
+                while (textRows.moveToNext()) {
+                    String path = textRows.getString(partDataColumn);
+                    if (path != null) {
+                        try {
+                            InputStream is = new FileInputStream(path);
+                            byte [] data = new byte[is.available()];
+                            is.read(data);
+                            EncodedStringValue v = new EncodedStringValue(data);
+                            textRows.updateString(partTextColumn, v.getString());
+                            textRows.updateToNull(partDataColumn);
+                            is.close();
+                            filesToDelete.add(path);
+                        } catch (IOException e) {
+                            // TODO Auto-generated catch block
+                            e.printStackTrace();
+                        }
+                    }
+                }
+            }
+        } finally {
+            textRows.commitUpdates();
+            for (String pathToDelete : filesToDelete) {
+                try {
+                    (new File(pathToDelete)).delete();
+                } catch (SecurityException ex) {
+                    Log.e(TAG, "unable to clean up old mms file for " + pathToDelete, ex);
+                }
+            }
+            if (textRows != null) {
+                textRows.close();
+            }
+        }
+    }
 }
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index 81de1af..d55c4fe 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -16,7 +16,11 @@
 
 package com.android.providers.telephony;
 
-import com.google.android.mms.pdu.PduHeaders;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import android.content.ContentProvider;
 import android.content.ContentValues;
@@ -40,10 +44,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import com.google.android.mms.pdu.PduHeaders;
 
 /**
  * This class provides the ability to query the MMS and SMS databases
@@ -87,6 +88,8 @@
     private static final int URI_NOTIFICATIONS              = 10;
     private static final int URI_OBSOLETE_THREADS           = 11;
     private static final int URI_DRAFT                      = 12;
+    private static final int URI_CANONICAL_ADDRESSES        = 13;
+    private static final int URI_SEARCH                     = 14;
 
     /**
      * the name of the table that is used to store the queue of
@@ -100,7 +103,7 @@
     // These are the columns that appear in both the MMS ("pdu") and
     // SMS ("sms") message tables.
     private static final String[] MMS_SMS_COLUMNS =
-            { BaseColumns._ID, Mms.DATE, Mms.READ, Mms.THREAD_ID };
+            { BaseColumns._ID, Mms.DATE, Mms.READ, Mms.THREAD_ID, Mms.LOCKED };
 
     // These are the columns that appear only in the MMS message
     // table.
@@ -188,6 +191,11 @@
         // Use this pattern to query the canonical address by given ID.
         URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS);
 
+        // Use this pattern to query all canonical addresses.
+        URI_MATCHER.addURI(AUTHORITY, "canonical-addresses", URI_CANONICAL_ADDRESSES);
+
+        URI_MATCHER.addURI(AUTHORITY, "search", URI_SEARCH);
+
         // In this pattern, two query parameters may be supplied:
         // "protocol" and "message." For example:
         //   content://mms-sms/pending?
@@ -278,6 +286,56 @@
                         null, null, sortOrder);
                 break;
             }
+            case URI_CANONICAL_ADDRESSES:
+                cursor = db.query("canonical_addresses",
+                        new String[] {"_id", "address"}, selection, selectionArgs,
+                        null, null, sortOrder);
+                break;
+            case URI_SEARCH:
+                if (       sortOrder != null 
+                        || selection != null 
+                        || selectionArgs != null 
+                        || projection != null) {
+                    throw new IllegalArgumentException(
+                            "do not specify sortOrder, selection, selectionArgs, or projection" +
+                            "with this query");
+                }
+                
+                // This code queries the sms and mms tables and returns a unified result set
+                // of text matches.  We query the sms table which is pretty simple.  We also
+                // query the pdu, part and addr table to get the mms result.  Note that we're
+                // using a UNION so we have to have the same number of result columns from
+                // both queries.  
+
+                String searchString = "%" + uri.getQueryParameter("pattern") + "%";
+                String smsProjection = "_id,thread_id,address,body,date";
+                String mmsProjection = "pdu._id,thread_id,addr.address,part.text as body,pdu.date";
+
+                String smsQuery = String.format(
+                        "SELECT %s FROM sms WHERE (body LIKE ?) ",
+                        smsProjection);
+
+                // TODO consider whether we're really getting the right addr here (for example, if
+                // I send a message to a given phone number do I want the search result to
+                // show a match on "me" or on that phone number.  I suspect the latter.
+                String mmsQuery = String.format(
+                        "SELECT %s FROM pdu,part,addr WHERE ((part.mid=pdu._id) AND " +
+                        "(addr.msg_id=pdu._id) AND " +
+                        "(addr.type=%d) AND " +
+                        "(part.ct='text/plain') AND " +
+                        "(body like ?))",
+                        mmsProjection,
+                        PduHeaders.TO);
+
+                String rawQuery = String.format(
+                        "%s UNION %s GROUP BY %s ORDER BY %s",
+                        smsQuery,
+                        mmsQuery,
+                        "thread_id",
+                        "thread_id ASC, date DESC");
+
+                cursor = db.rawQuery(rawQuery, new String[] { searchString, searchString });
+                break;
             case URI_PENDING_MSG: {
                 String protoName = uri.getQueryParameter("protocol");
                 String msgId = uri.getQueryParameter("message");
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
index 39d7013..cd2182c 100644
--- a/src/com/android/providers/telephony/SmsProvider.java
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -394,6 +394,8 @@
                 return null;
         }
 
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
         if (table.equals(TABLE_SMS)) {
             boolean addDate = false;
             boolean addType = false;
@@ -432,6 +434,17 @@
                                    getContext(), address));
             }
 
+            // If this message is going in as a draft, it should replace any
+            // other draft messages in the thread.  Just delete all draft
+            // messages with this thread ID.  We could add an OR REPLACE to
+            // the insert below, but we'd have to query to find the old _id
+            // to produce a conflict anyway.
+            if (values.getAsInteger(Sms.TYPE) == Sms.MESSAGE_TYPE_DRAFT) {
+                db.delete(TABLE_SMS, "thread_id=? AND type=?",
+                        new String[] { values.getAsString(Sms.THREAD_ID),
+                                       Integer.toString(Sms.MESSAGE_TYPE_DRAFT) });
+            }
+
             if (type == Sms.MESSAGE_TYPE_INBOX) {
                 // Look up the person if not already filled in.
                 if ((values.getAsLong(Sms.PERSON) == null)
@@ -462,7 +475,6 @@
             }
         }
 
-        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         rowID = db.insert(table, "body", values);
         if (rowID > 0) {
             Uri uri = Uri.parse("content://" + table + "/" + rowID);
@@ -625,7 +637,7 @@
 
         where = DatabaseUtils.concatenateWhere(where, extraWhere);
         count = db.update(table, values, where, whereArgs);
-
+        
         if (count > 0) {
             notifyChange(url);
         }
@@ -712,7 +724,11 @@
         sURLMatcher.addURI("sms", "sim/#", SMS_ICC);
         
         sConversationProjectionMap.put(Sms.Conversations.SNIPPET,
-                "body AS snippet");
+            "sms.body AS snippet");
+        sConversationProjectionMap.put(Sms.Conversations.THREAD_ID,
+            "sms.thread_id AS thread_id");
+        sConversationProjectionMap.put(Sms.Conversations.MESSAGE_COUNT,
+            "groups.msg_count AS msg_count");
         sConversationProjectionMap.put("delta", null);
     }
 }