@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;
  }