@Override public Uri insert(Uri uri, ContentValues values) { // The _data column is filled internally in MmsProvider, so this check is just to avoid // it from being inadvertently set. This is not supposed to be a protection against // malicious attack, since sql injection could still be attempted to bypass the check. On // the other hand, the MmsProvider does verify that the _data column has an allowed value // before opening any uri/files. if (values != null && values.containsKey(Part._DATA)) { return null; } final int callerUid = Binder.getCallingUid(); final String callerPkg = getCallingPackage(); int msgBox = Mms.MESSAGE_BOX_ALL; boolean notify = true; int match = sURLMatcher.match(uri); if (LOCAL_LOGV) { Log.v(TAG, "Insert uri=" + uri + ", match=" + match); } String table = TABLE_PDU; switch (match) { case MMS_ALL: Object msgBoxObj = values.getAsInteger(Mms.MESSAGE_BOX); if (msgBoxObj != null) { msgBox = (Integer) msgBoxObj; } else { // default to inbox msgBox = Mms.MESSAGE_BOX_INBOX; } break; case MMS_INBOX: msgBox = Mms.MESSAGE_BOX_INBOX; break; case MMS_SENT: msgBox = Mms.MESSAGE_BOX_SENT; break; case MMS_DRAFTS: msgBox = Mms.MESSAGE_BOX_DRAFTS; break; case MMS_OUTBOX: msgBox = Mms.MESSAGE_BOX_OUTBOX; break; case MMS_MSG_PART: notify = false; table = TABLE_PART; break; case MMS_MSG_ADDR: notify = false; table = TABLE_ADDR; break; case MMS_SENDING_RATE: notify = false; table = TABLE_RATE; break; case MMS_DRM_STORAGE: notify = false; table = TABLE_DRM; break; default: Log.e(TAG, "insert: invalid request: " + uri); return null; } SQLiteDatabase db = mOpenHelper.getWritableDatabase(); ContentValues finalValues; Uri res = Mms.CONTENT_URI; long rowId; if (table.equals(TABLE_PDU)) { boolean addDate = !values.containsKey(Mms.DATE); boolean addMsgBox = !values.containsKey(Mms.MESSAGE_BOX); // Filter keys we don't support yet. filterUnsupportedKeys(values); // TODO: Should initialValues be validated, e.g. if it // missed some significant keys? finalValues = new ContentValues(values); long timeInMillis = System.currentTimeMillis(); if (addDate) { finalValues.put(Mms.DATE, timeInMillis / 1000L); } if (addMsgBox && (msgBox != Mms.MESSAGE_BOX_ALL)) { finalValues.put(Mms.MESSAGE_BOX, msgBox); } if (msgBox != Mms.MESSAGE_BOX_INBOX) { // Mark all non-inbox messages read. finalValues.put(Mms.READ, 1); } // thread_id Long threadId = values.getAsLong(Mms.THREAD_ID); String address = values.getAsString(CanonicalAddressesColumns.ADDRESS); if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) { finalValues.put(Mms.THREAD_ID, Threads.getOrCreateThreadId(getContext(), address)); } if (ProviderUtil.shouldSetCreator(finalValues, callerUid)) { // Only SYSTEM or PHONE can set CREATOR // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR // set CREATOR using the truth on caller. // Note: Inferring package name from UID may include unrelated package names finalValues.put(Telephony.Mms.CREATOR, callerPkg); } if ((rowId = db.insert(table, null, finalValues)) <= 0) { Log.e(TAG, "MmsProvider.insert: failed!"); return null; } res = Uri.parse(res + "/" + rowId); } else if (table.equals(TABLE_ADDR)) { finalValues = new ContentValues(values); finalValues.put(Addr.MSG_ID, uri.getPathSegments().get(0)); if ((rowId = db.insert(table, null, finalValues)) <= 0) { Log.e(TAG, "Failed to insert address"); return null; } res = Uri.parse(res + "/addr/" + rowId); } else if (table.equals(TABLE_PART)) { finalValues = new ContentValues(values); if (match == MMS_MSG_PART) { finalValues.put(Part.MSG_ID, uri.getPathSegments().get(0)); } 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 boolean plainText = false; boolean smilText = false; if ("text/plain".equals(contentType)) { plainText = true; } else if ("application/smil".equals(contentType)) { smilText = true; } if (!plainText && !smilText) { // Use the filename if possible, otherwise use the current time as the name. String contentLocation = values.getAsString("cl"); if (!TextUtils.isEmpty(contentLocation)) { File f = new File(contentLocation); contentLocation = "_" + f.getName(); } else { contentLocation = ""; } // Generate the '_data' field of the part with default // permission settings. String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + "/PART_" + System.currentTimeMillis() + contentLocation; if (DownloadDrmHelper.isDrmConvertNeeded(contentType)) { // Adds the .fl extension to the filename if contentType is // "application/vnd.oma.drm.message" path = DownloadDrmHelper.modifyDrmFwLockFileExtension(path); } finalValues.put(Part._DATA, path); File partFile = new File(path); if (!partFile.exists()) { try { if (!partFile.createNewFile()) { throw new IllegalStateException("Unable to create new partFile: " + path); } // Give everyone rw permission until we encrypt the file // (in PduPersister.persistData). Once the file is encrypted, the // permissions will be set to 0644. int result = FileUtils.setPermissions(path, 0666, -1, -1); if (LOCAL_LOGV) { Log.d(TAG, "MmsProvider.insert setPermissions result: " + result); } } catch (IOException e) { Log.e(TAG, "createNewFile", e); throw new IllegalStateException("Unable to create new partFile: " + path); } } } if ((rowId = db.insert(table, null, finalValues)) <= 0) { Log.e(TAG, "MmsProvider.insert: failed!"); return null; } res = Uri.parse(res + "/part/" + rowId); // Don't use a trigger for updating the words table because of a bug // in FTS3. The bug is such that the call to get the last inserted // row is incorrect. if (plainText) { // Update the words table with a corresponding row. The words table // allows us to search for words quickly, without scanning the whole // table; ContentValues cv = new ContentValues(); // we're using the row id of the part table row but we're also using ids // from the sms table so this divides the space into two large chunks. // The row ids from the part table start at 2 << 32. cv.put(Telephony.MmsSms.WordsTable.ID, (2L << 32) + rowId); cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("text")); cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowId); cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2); db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); } } else if (table.equals(TABLE_RATE)) { long now = values.getAsLong(Rate.SENT_TIME); long oneHourAgo = now - 1000 * 60 * 60; // Delete all unused rows (time earlier than one hour ago). db.delete(table, Rate.SENT_TIME + "<=" + oneHourAgo, null); db.insert(table, null, values); } else if (table.equals(TABLE_DRM)) { String path = getContext().getDir(PARTS_DIR_NAME, 0).getPath() + "/PART_" + System.currentTimeMillis(); finalValues = new ContentValues(1); finalValues.put("_data", path); File partFile = new File(path); if (!partFile.exists()) { try { if (!partFile.createNewFile()) { throw new IllegalStateException("Unable to create new file: " + path); } } catch (IOException e) { Log.e(TAG, "createNewFile", e); throw new IllegalStateException("Unable to create new file: " + path); } } if ((rowId = db.insert(table, null, finalValues)) <= 0) { Log.e(TAG, "MmsProvider.insert: failed!"); return null; } res = Uri.parse(res + "/drm/" + rowId); } else { throw new AssertionError("Unknown table type: " + table); } if (notify) { notifyChange(res); } return res; }
private Uri insertInner(Uri url, ContentValues initialValues, int callerUid) { ContentValues values; long rowID; int type = Sms.MESSAGE_TYPE_ALL; int match = sURLMatcher.match(url); String table = TABLE_SMS; switch (match) { case SMS_ALL: Integer typeObj = initialValues.getAsInteger(Sms.TYPE); if (typeObj != null) { type = typeObj.intValue(); } else { // default to inbox type = Sms.MESSAGE_TYPE_INBOX; } break; case SMS_INBOX: type = Sms.MESSAGE_TYPE_INBOX; break; case SMS_FAILED: type = Sms.MESSAGE_TYPE_FAILED; break; case SMS_QUEUED: type = Sms.MESSAGE_TYPE_QUEUED; break; case SMS_SENT: type = Sms.MESSAGE_TYPE_SENT; break; case SMS_DRAFT: type = Sms.MESSAGE_TYPE_DRAFT; break; case SMS_OUTBOX: type = Sms.MESSAGE_TYPE_OUTBOX; break; case SMS_RAW_MESSAGE: table = "raw"; break; case SMS_STATUS_PENDING: table = "sr_pending"; break; case SMS_ATTACHMENT: table = "attachments"; break; case SMS_NEW_THREAD_ID: table = "canonical_addresses"; break; default: Log.e(TAG, "Invalid request: " + url); return null; } SQLiteDatabase db = mOpenHelper.getWritableDatabase(); if (table.equals(TABLE_SMS)) { boolean addDate = false; boolean addType = false; // Make sure that the date and type are set if (initialValues == null) { values = new ContentValues(1); addDate = true; addType = true; } else { values = new ContentValues(initialValues); if (!initialValues.containsKey(Sms.DATE)) { addDate = true; } if (!initialValues.containsKey(Sms.TYPE)) { addType = true; } } if (addDate) { values.put(Sms.DATE, new Long(System.currentTimeMillis())); } if (addType && (type != Sms.MESSAGE_TYPE_ALL)) { values.put(Sms.TYPE, Integer.valueOf(type)); } // thread_id Long threadId = values.getAsLong(Sms.THREAD_ID); String address = values.getAsString(Sms.ADDRESS); if (((threadId == null) || (threadId == 0)) && (!TextUtils.isEmpty(address))) { values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId(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) && (!TextUtils.isEmpty(address))) { Cursor cursor = null; Uri uri = Uri.withAppendedPath(Contacts.Phones.CONTENT_FILTER_URL, Uri.encode(address)); try { cursor = getContext() .getContentResolver() .query(uri, CONTACT_QUERY_PROJECTION, null, null, null); if (cursor.moveToFirst()) { Long id = Long.valueOf(cursor.getLong(PERSON_ID_COLUMN)); values.put(Sms.PERSON, id); } } catch (Exception ex) { Log.e(TAG, "insert: query contact uri " + uri + " caught ", ex); } finally { if (cursor != null) { cursor.close(); } } } } else { // Mark all non-inbox messages read. values.put(Sms.READ, ONE); } if (ProviderUtil.shouldSetCreator(values, callerUid)) { // Only SYSTEM or PHONE can set CREATOR // If caller is not SYSTEM or PHONE, or SYSTEM or PHONE does not set CREATOR // set CREATOR using the truth on caller. // Note: Inferring package name from UID may include unrelated package names values.put(Sms.CREATOR, ProviderUtil.getPackageNamesByUid(getContext(), callerUid)); } } else { if (initialValues == null) { values = new ContentValues(1); } else { values = initialValues; } } rowID = db.insert(table, "body", values); // Don't use a trigger for updating the words table because of a bug // in FTS3. The bug is such that the call to get the last inserted // row is incorrect. if (table == TABLE_SMS) { // Update the words table with a corresponding row. The words table // allows us to search for words quickly, without scanning the whole // table; ContentValues cv = new ContentValues(); cv.put(Telephony.MmsSms.WordsTable.ID, rowID); cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, values.getAsString("body")); cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, rowID); cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); } if (rowID > 0) { Uri uri = Uri.parse("content://" + table + "/" + rowID); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "insert " + uri + " succeeded"); } notifyChange(uri); return uri; } else { Log.e(TAG, "insert: failed!"); } return null; }