mods to support searching of mms messages
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
index fc20ee2..9c3a446 100644
--- a/src/com/android/providers/telephony/MmsProvider.java
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -366,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);
                 }
             }
 
@@ -591,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);
                 }
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index 6a17f04..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 = 45;
+    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.
@@ -370,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);");
@@ -406,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);
@@ -742,7 +751,7 @@
             if (currentVersion <= 41) {
                 return;
             }
-            
+
             db.beginTransaction();
             try {
                 upgradeDatabaseToVersion42(db);
@@ -758,7 +767,7 @@
             if (currentVersion <= 42) {
                 return;
             }
-            
+
             db.beginTransaction();
             try {
                 upgradeDatabaseToVersion43(db);
@@ -802,8 +811,25 @@
                 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);
@@ -841,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");
@@ -884,4 +910,61 @@
         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 d9a1089..4382c99 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -192,7 +192,7 @@
 
         // 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:
@@ -291,27 +291,48 @@
                         null, null, sortOrder);
                 break;
             case URI_SEARCH:
-                if (sortOrder != null || selection != null || selectionArgs != null) {
-                    throw new IllegalArgumentException("do not specify sortOrder, selection, selectionArgs with this query");
+                if (       sortOrder != null 
+                        || selection != null 
+                        || selectionArgs != null 
+                        || projection != null) {
+                    throw new IllegalArgumentException(
+                            "do not specify sortOrder, selection, selectionArgs, or projection" +
+                            "with this query");
                 }
                 
-                String searchString = "%" + uri.getQueryParameter("pattern") + "%";
-                
-                // compute the projection as a merge of the passed in projection
-                // and the required projection
-                ArrayList<String> projectionToUse = new ArrayList<String>();
-                projectionToUse.addAll(Arrays.asList(projection));
+                // 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.  
 
-                for (String r : new String[] {"_id","thread_id","address","body","subject","date"}) {
-                    if (!projectionToUse.contains(r)) {
-                        projectionToUse.add(r);
-                    }
-                }
+                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(
-                        "select %s from sms where (body like ? or subject like ?) " + 
-                        "group by thread_id order by thread_id ASC,date DESC", 
-                        TextUtils.join(",", projectionToUse));
+                        "%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: {
@@ -929,7 +950,7 @@
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Context context = getContext();
         int affectedRows = 0;
-        
+
         switch(URI_MATCHER.match(uri)) {
             case URI_CONVERSATIONS_MESSAGES:
                 long threadId;