auto import from //depot/cupcake/@135843
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..2b16664
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,13 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := user development
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAVA_LIBRARIES :=
+
+LOCAL_PACKAGE_NAME := TelephonyProvider
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..c1af548
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.providers.telephony"
+ android:sharedUserId="android.uid.phone"
+>
+
+ <application android:process="com.android.phone"
+ android:allowClearUserData="false"
+ android:label="Dialer Storage"
+ android:icon="@drawable/ic_launcher_phone">
+ <provider android:name="TelephonyProvider" android:authorities="telephony" android:multiprocess="true" />
+ <provider android:name="SmsProvider" android:authorities="sms" android:multiprocess="true"
+ android:readPermission="android.permission.READ_SMS"
+ android:writePermission="android.permission.WRITE_SMS" />
+ <provider android:name="MmsProvider" android:authorities="mms" android:multiprocess="true"
+ android:readPermission="android.permission.READ_SMS"
+ android:writePermission="android.permission.WRITE_SMS">
+ <grant-uri-permission android:pathPrefix="/part/" />
+ </provider>
+ <provider android:name="MmsSmsProvider" android:authorities="mms-sms" android:multiprocess="true"
+ android:readPermission="android.permission.READ_SMS"
+ android:writePermission="android.permission.WRITE_SMS" />
+ </application>
+</manifest>
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/res/drawable/ic_launcher_phone.png b/res/drawable/ic_launcher_phone.png
new file mode 100644
index 0000000..4e613ec
--- /dev/null
+++ b/res/drawable/ic_launcher_phone.png
Binary files differ
diff --git a/src/com/android/providers/telephony/MmsProvider.java b/src/com/android/providers/telephony/MmsProvider.java
new file mode 100644
index 0000000..37d1787
--- /dev/null
+++ b/src/com/android/providers/telephony/MmsProvider.java
@@ -0,0 +1,796 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import com.google.android.mms.pdu.PduHeaders;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Mms.Addr;
+import android.provider.Telephony.Mms.Part;
+import android.provider.Telephony.Mms.Rate;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * The class to provide base facility to access MMS related content,
+ * which is stored in a SQLite database and in the file system.
+ */
+public class MmsProvider extends ContentProvider {
+ static final String TABLE_PDU = "pdu";
+ static final String TABLE_ADDR = "addr";
+ static final String TABLE_PART = "part";
+ static final String TABLE_RATE = "rate";
+ static final String TABLE_DRM = "drm";
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ // Generate the body of the query.
+ int match = sURLMatcher.match(uri);
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Query uri=" + uri + ", match=" + match);
+ }
+
+ switch (match) {
+ case MMS_ALL:
+ constructQueryForBox(qb, Mms.MESSAGE_BOX_ALL);
+ break;
+ case MMS_INBOX:
+ constructQueryForBox(qb, Mms.MESSAGE_BOX_INBOX);
+ break;
+ case MMS_SENT:
+ constructQueryForBox(qb, Mms.MESSAGE_BOX_SENT);
+ break;
+ case MMS_DRAFTS:
+ constructQueryForBox(qb, Mms.MESSAGE_BOX_DRAFTS);
+ break;
+ case MMS_OUTBOX:
+ constructQueryForBox(qb, Mms.MESSAGE_BOX_OUTBOX);
+ break;
+ case MMS_ALL_ID:
+ qb.setTables(TABLE_PDU);
+ qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(0));
+ break;
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ qb.setTables(TABLE_PDU);
+ qb.appendWhere(Mms._ID + "=" + uri.getPathSegments().get(1));
+ qb.appendWhere(" AND " + Mms.MESSAGE_BOX + "="
+ + getMessageBoxByMatch(match));
+ break;
+ case MMS_ALL_PART:
+ qb.setTables(TABLE_PART);
+ break;
+ case MMS_MSG_PART:
+ qb.setTables(TABLE_PART);
+ qb.appendWhere(Part.MSG_ID + "=" + uri.getPathSegments().get(0));
+ break;
+ case MMS_PART_ID:
+ qb.setTables(TABLE_PART);
+ qb.appendWhere(Part._ID + "=" + uri.getPathSegments().get(1));
+ break;
+ case MMS_MSG_ADDR:
+ qb.setTables(TABLE_ADDR);
+ qb.appendWhere(Addr.MSG_ID + "=" + uri.getPathSegments().get(0));
+ break;
+ case MMS_REPORT_STATUS:
+ /*
+ SELECT DISTINCT address,
+ T.delivery_status AS delivery_status,
+ T.read_status AS read_status
+ FROM addr
+ INNER JOIN (SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
+ ifnull(P2.st, 0) AS delivery_status,
+ ifnull(P3.read_status, 0) AS read_status
+ FROM pdu P1
+ INNER JOIN pdu P2
+ ON P1.m_id = P2.m_id AND P2.m_type = 134
+ LEFT JOIN pdu P3
+ ON P1.m_id = P3.m_id AND P3.m_type = 136
+ UNION
+ SELECT P1._id AS id1, P2._id AS id2, P3._id AS id3,
+ ifnull(P2.st, 0) AS delivery_status,
+ ifnull(P3.read_status, 0) AS read_status
+ FROM pdu P1
+ INNER JOIN pdu P3
+ ON P1.m_id = P3.m_id AND P3.m_type = 136
+ LEFT JOIN pdu P2
+ ON P1.m_id = P2.m_id AND P2.m_type = 134) T
+ ON (msg_id = id2 AND type = 151)
+ OR (msg_id = id3 AND type = 137)
+ WHERE T.id1 = ?;
+ */
+ qb.setTables("addr INNER JOIN (SELECT P1._id AS id1, P2._id" +
+ " AS id2, P3._id AS id3, ifnull(P2.st, 0) AS" +
+ " delivery_status, ifnull(P3.read_status, 0) AS" +
+ " read_status FROM pdu P1 INNER JOIN pdu P2 ON" +
+ " P1.m_id=P2.m_id AND P2.m_type=134 LEFT JOIN" +
+ " pdu P3 ON P1.m_id=P3.m_id AND P3.m_type=136" +
+ " UNION SELECT P1._id AS id1, P2._id AS id2, P3._id" +
+ " AS id3, ifnull(P2.st, 0) AS delivery_status," +
+ " ifnull(P3.read_status, 0) AS read_status FROM" +
+ " pdu P1 INNER JOIN pdu P3 ON P1.m_id=P3.m_id AND" +
+ " P3.m_type=136 LEFT JOIN pdu P2 ON P1.m_id=P2.m_id" +
+ " AND P2.m_type=134) T ON (msg_id=id2 AND type=151)" +
+ " OR (msg_id=id3 AND type=137)");
+ qb.appendWhere("T.id1 = " + uri.getLastPathSegment());
+ qb.setDistinct(true);
+ break;
+ case MMS_REPORT_REQUEST:
+ /*
+ SELECT address, d_rpt, rr
+ FROM addr join pdu on pdu._id = addr.msg_id
+ WHERE pdu._id = messageId AND addr.type = 151
+ */
+ qb.setTables(TABLE_ADDR + " join " +
+ TABLE_PDU + " on pdu._id = addr.msg_id");
+ qb.appendWhere("pdu._id = " + uri.getLastPathSegment());
+ qb.appendWhere(" AND " + "addr.type = " + PduHeaders.TO);
+ break;
+ case MMS_SENDING_RATE:
+ qb.setTables(TABLE_RATE);
+ break;
+ case MMS_DRM_STORAGE_ID:
+ qb.setTables(TABLE_DRM);
+ qb.appendWhere(BaseColumns._ID + "=" + uri.getLastPathSegment());
+ break;
+ default:
+ Log.e(TAG, "Invalid request: " + uri);
+ return null;
+ }
+
+ String finalSortOrder = null;
+ if (TextUtils.isEmpty(sortOrder)) {
+ if (qb.getTables().equals(TABLE_PDU)) {
+ finalSortOrder = Mms.DATE + " DESC";
+ } else if (qb.getTables().equals(TABLE_PART)) {
+ finalSortOrder = Part.SEQ;
+ }
+ } else {
+ finalSortOrder = sortOrder;
+ }
+
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor ret = qb.query(db, projection, selection,
+ selectionArgs, null, null, finalSortOrder);
+
+ // TODO: Does this need to be a URI for this provider.
+ ret.setNotificationUri(getContext().getContentResolver(), uri);
+ return ret;
+ }
+
+ private void constructQueryForBox(SQLiteQueryBuilder qb, int msgBox) {
+ qb.setTables(TABLE_PDU);
+
+ if (msgBox != Mms.MESSAGE_BOX_ALL) {
+ qb.appendWhere(Mms.MESSAGE_BOX + "=" + msgBox);
+ }
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ int match = sURLMatcher.match(uri);
+ switch (match) {
+ case MMS_ALL:
+ case MMS_INBOX:
+ case MMS_SENT:
+ case MMS_DRAFTS:
+ case MMS_OUTBOX:
+ return VND_ANDROID_DIR_MMS;
+ case MMS_ALL_ID:
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ return VND_ANDROID_MMS;
+ case MMS_PART_ID: {
+ Cursor cursor = mOpenHelper.getReadableDatabase().query(
+ TABLE_PART, new String[] { Part.CONTENT_TYPE },
+ Part._ID + " = ?", new String[] { uri.getLastPathSegment() },
+ null, null, null);
+ if (cursor != null) {
+ try {
+ if ((cursor.getCount() == 1) && cursor.moveToFirst()) {
+ return cursor.getString(0);
+ } else {
+ Log.e(TAG, "cursor.count() != 1: " + uri);
+ }
+ } finally {
+ cursor.close();
+ }
+ } else {
+ Log.e(TAG, "cursor == null: " + uri);
+ }
+ return "*/*";
+ }
+ case MMS_ALL_PART:
+ case MMS_MSG_PART:
+ case MMS_MSG_ADDR:
+ default:
+ return "*/*";
+ }
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ 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, "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);
+ }
+
+ if ((rowId = db.insert(table, null, finalValues)) <= 0) {
+ Log.e(TAG, "MmsProvider.insert: failed! " + finalValues);
+ 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: " + finalValues);
+ 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));
+ }
+
+ // 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);
+
+ 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);
+ }
+ }
+
+ if ((rowId = db.insert(table, null, finalValues)) <= 0) {
+ Log.e(TAG, "MmsProvider.insert: failed! " + finalValues);
+ return null;
+ }
+
+ res = Uri.parse(res + "/part/" + rowId);
+ } 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", 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! " + finalValues);
+ return null;
+ }
+ res = Uri.parse(res + "/drm/" + rowId);
+ } else {
+ throw new AssertionError("Unknown table type: " + table);
+ }
+
+ if (notify) {
+ notifyChange();
+ }
+ return res;
+ }
+
+ private int getMessageBoxByMatch(int match) {
+ switch (match) {
+ case MMS_INBOX_ID:
+ case MMS_INBOX:
+ return Mms.MESSAGE_BOX_INBOX;
+ case MMS_SENT_ID:
+ case MMS_SENT:
+ return Mms.MESSAGE_BOX_SENT;
+ case MMS_DRAFTS_ID:
+ case MMS_DRAFTS:
+ return Mms.MESSAGE_BOX_DRAFTS;
+ case MMS_OUTBOX_ID:
+ case MMS_OUTBOX:
+ return Mms.MESSAGE_BOX_OUTBOX;
+ default:
+ throw new IllegalArgumentException("bad Arg: " + match);
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection,
+ String[] selectionArgs) {
+ int match = sURLMatcher.match(uri);
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Delete uri=" + uri + ", match=" + match);
+ }
+
+ String table, extraSelection = null;
+ boolean notify = false;
+
+ switch (match) {
+ case MMS_ALL_ID:
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ notify = true;
+ table = TABLE_PDU;
+ extraSelection = Mms._ID + "=" + uri.getLastPathSegment();
+ break;
+ case MMS_ALL:
+ case MMS_INBOX:
+ case MMS_SENT:
+ case MMS_DRAFTS:
+ case MMS_OUTBOX:
+ notify = true;
+ table = TABLE_PDU;
+ if (match != MMS_ALL) {
+ int msgBox = getMessageBoxByMatch(match);
+ extraSelection = Mms.MESSAGE_BOX + "=" + msgBox;
+ }
+ break;
+ case MMS_ALL_PART:
+ table = TABLE_PART;
+ break;
+ case MMS_MSG_PART:
+ table = TABLE_PART;
+ extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
+ break;
+ case MMS_PART_ID:
+ table = TABLE_PART;
+ extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
+ break;
+ case MMS_MSG_ADDR:
+ table = TABLE_ADDR;
+ extraSelection = Addr.MSG_ID + "=" + uri.getPathSegments().get(0);
+ break;
+ case MMS_DRM_STORAGE:
+ table = TABLE_DRM;
+ break;
+ default:
+ Log.w(TAG, "No match for URI '" + uri + "'");
+ return 0;
+ }
+
+ String finalSelection = concatSelections(selection, extraSelection);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int deletedRows = 0;
+
+ if (TABLE_PDU.equals(table)) {
+ deletedRows = deleteMessages(getContext(), db, finalSelection,
+ selectionArgs, uri);
+ } else if (TABLE_PART.equals(table)) {
+ deletedRows = deleteParts(db, finalSelection, selectionArgs);
+ } else if (TABLE_DRM.equals(table)) {
+ deletedRows = deleteTempDrmData(db, finalSelection, selectionArgs);
+ } else {
+ deletedRows = db.delete(table, finalSelection, selectionArgs);
+ }
+
+ if ((deletedRows > 0) && notify) {
+ notifyChange();
+ }
+ return deletedRows;
+ }
+
+ static int deleteMessages(Context context, SQLiteDatabase db,
+ String selection, String[] selectionArgs, Uri uri) {
+ Cursor cursor = db.query(TABLE_PDU, new String[] { Mms._ID },
+ selection, selectionArgs, null, null, null);
+ if (cursor == null) {
+ return 0;
+ }
+
+ try {
+ if (cursor.getCount() == 0) {
+ return 0;
+ }
+
+ while (cursor.moveToNext()) {
+ deleteParts(db, Part.MSG_ID + " = ?",
+ new String[] { String.valueOf(cursor.getLong(0)) });
+ }
+ } finally {
+ cursor.close();
+ }
+
+ int count = db.delete(TABLE_PDU, selection, selectionArgs);
+ if (count > 0) {
+ Intent intent = new Intent(Mms.Intents.CONTENT_CHANGED_ACTION);
+ intent.putExtra(Mms.Intents.DELETED_CONTENTS, uri);
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Broadcasting intent: " + intent);
+ }
+ context.sendBroadcast(intent);
+ }
+ return count;
+ }
+
+ private static int deleteParts(SQLiteDatabase db, String selection,
+ String[] selectionArgs) {
+ return deleteDataRows(db, TABLE_PART, selection, selectionArgs);
+ }
+
+ private static int deleteTempDrmData(SQLiteDatabase db, String selection,
+ String[] selectionArgs) {
+ return deleteDataRows(db, TABLE_DRM, selection, selectionArgs);
+ }
+
+ private static int deleteDataRows(SQLiteDatabase db, String table,
+ String selection, String[] selectionArgs) {
+ Cursor cursor = db.query(table, new String[] { "_data" },
+ selection, selectionArgs, null, null, null);
+ if (cursor == null) {
+ // FIXME: This might be an error, ignore it may cause
+ // unpredictable result.
+ return 0;
+ }
+
+ try {
+ if (cursor.getCount() == 0) {
+ return 0;
+ }
+
+ while (cursor.moveToNext()) {
+ try {
+ // Delete the associated files saved on file-system.
+ new File(cursor.getString(0)).delete();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ }
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return db.delete(table, selection, selectionArgs);
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values,
+ String selection, String[] selectionArgs) {
+ int match = sURLMatcher.match(uri);
+ if (LOCAL_LOGV) {
+ Log.v(TAG, "Update uri=" + uri + ", match=" + match);
+ }
+
+ boolean notify = false;
+ String msgId = null;
+ String table;
+
+ switch (match) {
+ case MMS_ALL_ID:
+ case MMS_INBOX_ID:
+ case MMS_SENT_ID:
+ case MMS_DRAFTS_ID:
+ case MMS_OUTBOX_ID:
+ msgId = uri.getLastPathSegment();
+ // fall-through
+ case MMS_ALL:
+ case MMS_INBOX:
+ case MMS_SENT:
+ case MMS_DRAFTS:
+ case MMS_OUTBOX:
+ notify = true;
+ table = TABLE_PDU;
+ break;
+ case MMS_MSG_PART:
+ case MMS_PART_ID:
+ table = TABLE_PART;
+ break;
+ default:
+ Log.w(TAG, "Update operation for '" + uri + "' not implemented.");
+ return 0;
+ }
+
+ String extraSelection = null;
+ ContentValues finalValues;
+ if (table.equals(TABLE_PDU)) {
+ // Filter keys that we don't support yet.
+ filterUnsupportedKeys(values);
+ finalValues = new ContentValues(values);
+
+ if (msgId != null) {
+ extraSelection = Mms._ID + "=" + msgId;
+ }
+ } else if (table.equals(TABLE_PART)) {
+ finalValues = new ContentValues(values);
+
+ switch (match) {
+ case MMS_MSG_PART:
+ extraSelection = Part.MSG_ID + "=" + uri.getPathSegments().get(0);
+ break;
+ case MMS_PART_ID:
+ extraSelection = Part._ID + "=" + uri.getPathSegments().get(1);
+ break;
+ default:
+ break;
+ }
+ } else {
+ return 0;
+ }
+
+ String finalSelection = concatSelections(selection, extraSelection);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int count = db.update(table, finalValues, finalSelection, selectionArgs);
+ if (notify && (count > 0)) {
+ notifyChange();
+ }
+ return count;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode)
+ throws FileNotFoundException {
+ return openFileHelper(uri, mode);
+ }
+
+ private void filterUnsupportedKeys(ContentValues values) {
+ // Some columns are unsupported. They should therefore
+ // neither be inserted nor updated. Filter them out.
+ values.remove(Mms.DELIVERY_TIME_TOKEN);
+ values.remove(Mms.SENDER_VISIBILITY);
+ values.remove(Mms.REPLY_CHARGING);
+ values.remove(Mms.REPLY_CHARGING_DEADLINE_TOKEN);
+ values.remove(Mms.REPLY_CHARGING_DEADLINE);
+ values.remove(Mms.REPLY_CHARGING_ID);
+ values.remove(Mms.REPLY_CHARGING_SIZE);
+ values.remove(Mms.PREVIOUSLY_SENT_BY);
+ values.remove(Mms.PREVIOUSLY_SENT_DATE);
+ values.remove(Mms.STORE);
+ values.remove(Mms.MM_STATE);
+ values.remove(Mms.MM_FLAGS_TOKEN);
+ values.remove(Mms.MM_FLAGS);
+ values.remove(Mms.STORE_STATUS);
+ values.remove(Mms.STORE_STATUS_TEXT);
+ values.remove(Mms.STORED);
+ values.remove(Mms.TOTALS);
+ values.remove(Mms.MBOX_TOTALS);
+ values.remove(Mms.MBOX_TOTALS_TOKEN);
+ values.remove(Mms.QUOTAS);
+ values.remove(Mms.MBOX_QUOTAS);
+ values.remove(Mms.MBOX_QUOTAS_TOKEN);
+ values.remove(Mms.MESSAGE_COUNT);
+ values.remove(Mms.START);
+ values.remove(Mms.DISTRIBUTION_INDICATOR);
+ values.remove(Mms.ELEMENT_DESCRIPTOR);
+ values.remove(Mms.LIMIT);
+ values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE);
+ values.remove(Mms.RECOMMENDED_RETRIEVAL_MODE_TEXT);
+ values.remove(Mms.STATUS_TEXT);
+ values.remove(Mms.APPLIC_ID);
+ values.remove(Mms.REPLY_APPLIC_ID);
+ values.remove(Mms.AUX_APPLIC_ID);
+ values.remove(Mms.DRM_CONTENT);
+ values.remove(Mms.ADAPTATION_ALLOWED);
+ values.remove(Mms.REPLACE_ID);
+ values.remove(Mms.CANCEL_ID);
+ values.remove(Mms.CANCEL_STATUS);
+
+ // Keys shouldn't be inserted or updated.
+ values.remove(Mms._ID);
+ }
+
+ private void notifyChange() {
+ getContext().getContentResolver().notifyChange(
+ MmsSms.CONTENT_URI, null);
+ }
+
+ private final static String TAG = "MmsProvider";
+ private final static String VND_ANDROID_MMS = "vnd.android/mms";
+ private final static String VND_ANDROID_DIR_MMS = "vnd.android-dir/mms";
+ private final static boolean DEBUG = false;
+ private final static boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
+
+ private static final int MMS_ALL = 0;
+ private static final int MMS_ALL_ID = 1;
+ private static final int MMS_INBOX = 2;
+ private static final int MMS_INBOX_ID = 3;
+ private static final int MMS_SENT = 4;
+ private static final int MMS_SENT_ID = 5;
+ private static final int MMS_DRAFTS = 6;
+ private static final int MMS_DRAFTS_ID = 7;
+ private static final int MMS_OUTBOX = 8;
+ private static final int MMS_OUTBOX_ID = 9;
+ private static final int MMS_ALL_PART = 10;
+ private static final int MMS_MSG_PART = 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_DRM_STORAGE = 17;
+ private static final int MMS_DRM_STORAGE_ID = 18;
+
+ private static final UriMatcher
+ sURLMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ static {
+ sURLMatcher.addURI("mms", null, MMS_ALL);
+ sURLMatcher.addURI("mms", "#", MMS_ALL_ID);
+ sURLMatcher.addURI("mms", "inbox", MMS_INBOX);
+ sURLMatcher.addURI("mms", "inbox/#", MMS_INBOX_ID);
+ sURLMatcher.addURI("mms", "sent", MMS_SENT);
+ sURLMatcher.addURI("mms", "sent/#", MMS_SENT_ID);
+ sURLMatcher.addURI("mms", "drafts", MMS_DRAFTS);
+ sURLMatcher.addURI("mms", "drafts/#", MMS_DRAFTS_ID);
+ sURLMatcher.addURI("mms", "outbox", MMS_OUTBOX);
+ sURLMatcher.addURI("mms", "outbox/#", MMS_OUTBOX_ID);
+ sURLMatcher.addURI("mms", "part", MMS_ALL_PART);
+ sURLMatcher.addURI("mms", "#/part", MMS_MSG_PART);
+ sURLMatcher.addURI("mms", "part/#", MMS_PART_ID);
+ sURLMatcher.addURI("mms", "#/addr", MMS_MSG_ADDR);
+ sURLMatcher.addURI("mms", "rate", MMS_SENDING_RATE);
+ sURLMatcher.addURI("mms", "report-status/#", MMS_REPORT_STATUS);
+ sURLMatcher.addURI("mms", "report-request/#", MMS_REPORT_REQUEST);
+ sURLMatcher.addURI("mms", "drm", MMS_DRM_STORAGE);
+ sURLMatcher.addURI("mms", "drm/#", MMS_DRM_STORAGE_ID);
+ }
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ private static String concatSelections(String selection1, String selection2) {
+ if (TextUtils.isEmpty(selection1)) {
+ return selection2;
+ } else if (TextUtils.isEmpty(selection2)) {
+ return selection1;
+ } else {
+ return selection1 + " AND " + selection2;
+ }
+ }
+}
+
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
new file mode 100644
index 0000000..31d7cb7
--- /dev/null
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -0,0 +1,2035 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND;
+import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
+import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND;
+import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_REC_IND;
+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 android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.provider.BaseColumns;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Threads;
+import android.provider.Telephony.Mms.Addr;
+import android.provider.Telephony.Mms.Part;
+import android.provider.Telephony.Mms.Rate;
+import android.provider.Telephony.MmsSms.PendingMessages;
+import android.util.Log;
+
+public class MmsSmsDatabaseHelper extends SQLiteOpenHelper {
+ private static final String TAG = "MmsSmsDatabaseHelper";
+
+ private static final String SMS_UPDATE_THREAD_READ_BODY =
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE " + Sms.READ + " = 0" +
+ " AND " + Sms.THREAD_ID + " = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new." + Sms.THREAD_ID + "; ";
+
+ private static final String UPDATE_THREAD_COUNT_ON_NEW =
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = " + Sms.THREAD_ID +
+ " WHERE " + Sms.THREAD_ID + " = new.thread_id" +
+ " AND sms." + Sms.TYPE + " != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = " + Mms.THREAD_ID +
+ " WHERE " + Mms.THREAD_ID + " = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND " + Mms.MESSAGE_BOX + " != 3) " +
+ " WHERE threads._id = new.thread_id; ";
+
+ private static final String UPDATE_THREAD_COUNT_ON_OLD =
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = " + Sms.THREAD_ID +
+ " WHERE " + Sms.THREAD_ID + " = old.thread_id" +
+ " AND sms." + Sms.TYPE + " != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = " + Mms.THREAD_ID +
+ " WHERE " + Mms.THREAD_ID + " = old.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND " + Mms.MESSAGE_BOX + " != 3) " +
+ " WHERE threads._id = old.thread_id; ";
+
+ private static final String SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE =
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new." + Sms.BODY + ", " +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new." + Sms.THREAD_ID + "; " +
+ UPDATE_THREAD_COUNT_ON_NEW +
+ SMS_UPDATE_THREAD_READ_BODY +
+ "END;";
+
+ private static final String PDU_UPDATE_THREAD_CONSTRAINTS =
+ " WHEN new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_RETRIEVE_CONF +
+ " OR new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_NOTIFICATION_IND +
+ " OR new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_SEND_REQ + " ";
+
+ private static final String PDU_UPDATE_THREAD_READ_BODY =
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM " + MmsProvider.TABLE_PDU +
+ " WHERE " + Mms.READ + " = 0" +
+ " AND " + Mms.THREAD_ID + " = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new." + Mms.THREAD_ID + "; ";
+
+ private static final String PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE =
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new." + Mms.SUBJECT + ", " +
+ " snippet_cs = new." + Mms.SUBJECT_CHARSET +
+ " WHERE threads._id = new." + Mms.THREAD_ID + "; " +
+ UPDATE_THREAD_COUNT_ON_NEW +
+ PDU_UPDATE_THREAD_READ_BODY +
+ "END;";
+
+ private static final String UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE =
+ " UPDATE threads SET 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 = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = OLD.thread_id; " +
+ " UPDATE threads SET 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 = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = OLD.thread_id; ";
+
+ private static MmsSmsDatabaseHelper mInstance = null;
+
+ static final String DATABASE_NAME = "mmssms.db";
+ static final int DATABASE_VERSION = 41;
+
+ private MmsSmsDatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ /**
+ * Return a singleton helper for the combined MMS and SMS
+ * database.
+ */
+ /* package */ static synchronized MmsSmsDatabaseHelper getInstance(Context context) {
+ if (mInstance == null) {
+ mInstance = new MmsSmsDatabaseHelper(context);
+ }
+ return mInstance;
+ }
+
+ public static void updateThread(SQLiteDatabase db, long thread_id) {
+ if (thread_id < 0) {
+ 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.
+ int rows = db.delete("threads",
+ "_id = ? AND _id NOT IN" +
+ " (SELECT thread_id FROM sms " +
+ " UNION SELECT thread_id FROM pdu)",
+ new String[] { String.valueOf(thread_id) });
+ if (rows > 0) {
+ // If this deleted a row, we have no more work to do.
+ return;
+ }
+ // Update the message count in the threads table as the sum
+ // of all messages in both the sms and pdu tables.
+ db.execSQL(
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = " + Sms.THREAD_ID +
+ " WHERE " + Sms.THREAD_ID + " = " + thread_id +
+ " AND sms." + Sms.TYPE + " != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = " + Mms.THREAD_ID +
+ " WHERE " + Mms.THREAD_ID + " = " + thread_id +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND " + Mms.MESSAGE_BOX + " != 3) " +
+ " WHERE threads._id = " + thread_id + ";");
+
+ // Update the date and the snippet (and its character set) in
+ // 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 the error column of the thread to indicate if there
+ // are any messages in it that have failed to send.
+ db.execSQL(
+ "UPDATE threads SET error =" +
+ " (SELECT COUNT(*) FROM sms WHERE type=5" +
+ " 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 = "";
+ } else {
+ where = "WHERE (" + where + ")";
+ }
+ String query = "SELECT _id FROM threads WHERE _id IN " +
+ "(SELECT DISTINCT thread_id FROM sms " + where + ")";
+ Cursor c = db.rawQuery(query, whereArgs);
+ if (c != null) {
+ while (c.moveToNext()) {
+ updateThread(db, c.getInt(0));
+ }
+ c.close();
+ }
+ // remove orphaned threads
+ db.delete("threads",
+ "_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.
+ Cursor c = db.query("sms", new String[] { "thread_id" },
+ "_id=" + message_id, null, null, null, null);
+ if (c != null) {
+ if (c.moveToFirst()) {
+ thread_id = c.getInt(0);
+ }
+ c.close();
+ }
+
+ // Delete the specified message.
+ int rows = db.delete("sms", "_id=" + message_id, null);
+ if (thread_id > 0) {
+ // Update its thread.
+ updateThread(db, thread_id);
+ }
+ return rows;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createMmsTables(db);
+ createSmsTables(db);
+ createCommonTables(db);
+ createCommonTriggers(db);
+ createMmsTriggers(db);
+ }
+
+ private void createMmsTables(SQLiteDatabase db) {
+ // N.B.: Whenever the columns here are changed, the columns in
+ // {@ref MmsSmsProvider} must be changed to match.
+ db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" +
+ Mms._ID + " INTEGER PRIMARY KEY," +
+ Mms.THREAD_ID + " INTEGER," +
+ Mms.DATE + " INTEGER," +
+ Mms.MESSAGE_BOX + " INTEGER," +
+ Mms.READ + " INTEGER DEFAULT 0," +
+ Mms.MESSAGE_ID + " TEXT," +
+ Mms.SUBJECT + " TEXT," +
+ Mms.SUBJECT_CHARSET + " INTEGER," +
+ Mms.CONTENT_TYPE + " TEXT," +
+ Mms.CONTENT_LOCATION + " TEXT," +
+ Mms.EXPIRY + " INTEGER," +
+ Mms.MESSAGE_CLASS + " TEXT," +
+ Mms.MESSAGE_TYPE + " INTEGER," +
+ Mms.MMS_VERSION + " INTEGER," +
+ Mms.MESSAGE_SIZE + " INTEGER," +
+ Mms.PRIORITY + " INTEGER," +
+ Mms.READ_REPORT + " INTEGER," +
+ Mms.REPORT_ALLOWED + " INTEGER," +
+ Mms.RESPONSE_STATUS + " INTEGER," +
+ Mms.STATUS + " INTEGER," +
+ Mms.TRANSACTION_ID + " TEXT," +
+ Mms.RETRIEVE_STATUS + " INTEGER," +
+ Mms.RETRIEVE_TEXT + " TEXT," +
+ Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," +
+ Mms.READ_STATUS + " INTEGER," +
+ Mms.CONTENT_CLASS + " INTEGER," +
+ Mms.RESPONSE_TEXT + " TEXT," +
+ Mms.DELIVERY_TIME + " INTEGER," +
+ Mms.DELIVERY_REPORT + " INTEGER);");
+
+ db.execSQL("CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" +
+ Addr._ID + " INTEGER PRIMARY KEY," +
+ Addr.MSG_ID + " INTEGER," +
+ Addr.CONTACT_ID + " INTEGER," +
+ Addr.ADDRESS + " TEXT," +
+ Addr.TYPE + " INTEGER," +
+ Addr.CHARSET + " INTEGER);");
+
+ db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PART + " (" +
+ Part._ID + " INTEGER PRIMARY KEY," +
+ Part.MSG_ID + " INTEGER," +
+ Part.SEQ + " INTEGER DEFAULT 0," +
+ Part.CONTENT_TYPE + " TEXT," +
+ Part.NAME + " TEXT," +
+ Part.CHARSET + " INTEGER," +
+ Part.CONTENT_DISPOSITION + " TEXT," +
+ Part.FILENAME + " TEXT," +
+ Part.CONTENT_ID + " TEXT," +
+ Part.CONTENT_LOCATION + " TEXT," +
+ Part.CT_START + " INTEGER," +
+ Part.CT_TYPE + " TEXT," +
+ Part._DATA + " TEXT);");
+
+ db.execSQL("CREATE TABLE " + MmsProvider.TABLE_RATE + " (" +
+ Rate.SENT_TIME + " INTEGER);");
+
+ db.execSQL("CREATE TABLE " + MmsProvider.TABLE_DRM + " (" +
+ BaseColumns._ID + " INTEGER PRIMARY KEY," +
+ "_data TEXT);");
+ }
+
+ private void createMmsTriggers(SQLiteDatabase db) {
+ // Cleans up parts when a MM is deleted.
+ db.execSQL("CREATE TRIGGER part_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " +
+ "BEGIN " +
+ " DELETE FROM " + MmsProvider.TABLE_PART +
+ " WHERE " + Part.MSG_ID + "=old._id;" +
+ "END;");
+
+ // Cleans up address info when a MM is deleted.
+ db.execSQL("CREATE TRIGGER addr_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " +
+ "BEGIN " +
+ " DELETE FROM " + MmsProvider.TABLE_ADDR +
+ " WHERE " + Addr.MSG_ID + "=old._id;" +
+ "END;");
+
+ // Delete obsolete delivery-report, read-report while deleting their
+ // associated Send.req.
+ db.execSQL("CREATE TRIGGER cleanup_delivery_and_read_report " +
+ "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " +
+ "WHEN old." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_SEND_REQ + " " +
+ "BEGIN " +
+ " DELETE FROM " + MmsProvider.TABLE_PDU +
+ " WHERE (" + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_DELIVERY_IND +
+ " OR " + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_READ_ORIG_IND + ")" +
+ " AND " + Mms.MESSAGE_ID + "=old." + Mms.MESSAGE_ID + "; " +
+ "END;");
+ }
+
+ private void createSmsTables(SQLiteDatabase db) {
+ // N.B.: Whenever the columns here are changed, the columns in
+ // {@ref MmsSmsProvider} must be changed to match.
+ db.execSQL("CREATE TABLE sms (" +
+ "_id INTEGER PRIMARY KEY," +
+ "thread_id INTEGER," +
+ "address TEXT," +
+ "person INTEGER," +
+ "date INTEGER," +
+ "protocol INTEGER," +
+ "read INTEGER DEFAULT 0," +
+ "status INTEGER DEFAULT -1," + // a TP-Status value
+ // or -1 if it
+ // status hasn't
+ // been received
+ "type INTEGER," +
+ "reply_path_present INTEGER," +
+ "subject TEXT," +
+ "body TEXT," +
+ "service_center TEXT);");
+
+ /**
+ * This table is used by the SMS dispatcher to hold
+ * incomplete partial messages until all the parts arrive.
+ */
+ db.execSQL("CREATE TABLE raw (" +
+ "_id INTEGER PRIMARY KEY," +
+ "date INTEGER," +
+ "reference_number INTEGER," + // one per full message
+ "count INTEGER," + // the number of parts
+ "sequence INTEGER," + // the part number of this message
+ "destination_port INTEGER," +
+ "address TEXT," +
+ "pdu TEXT);"); // the raw PDU for this part
+
+ db.execSQL("CREATE TABLE attachments (" +
+ "sms_id INTEGER," +
+ "content_url TEXT," +
+ "offset INTEGER);");
+
+ /**
+ * This table is used by the SMS dispatcher to hold pending
+ * delivery status report intents.
+ */
+ db.execSQL("CREATE TABLE sr_pending (" +
+ "reference_number INTEGER," +
+ "action TEXT," +
+ "data TEXT);");
+ }
+
+ private void createCommonTables(SQLiteDatabase db) {
+ // TODO Ensure that each entry is removed when the last use of
+ // any address equivalent to its address is removed.
+
+ /**
+ * This table maps the first instance seen of any particular
+ * MMS/SMS address to an ID, which is then used as its
+ * canonical representation. If the same address or an
+ * equivalent address (as determined by our Sqlite
+ * PHONE_NUMBERS_EQUAL extension) is seen later, this same ID
+ * will be used.
+ */
+ db.execSQL("CREATE TABLE canonical_addresses (" +
+ "_id INTEGER PRIMARY KEY," +
+ "address TEXT);");
+
+ /**
+ * This table maps the subject and an ordered set of recipient
+ * IDs, separated by spaces, to a unique thread ID. The IDs
+ * come from the canonical_addresses table. This works
+ * because messages are considered to be part of the same
+ * thread if they have the same subject (or a null subject)
+ * and the same set of recipients.
+ */
+ db.execSQL("CREATE TABLE threads (" +
+ Threads._ID + " INTEGER PRIMARY KEY," +
+ Threads.DATE + " INTEGER DEFAULT 0," +
+ Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," +
+ Threads.RECIPIENT_IDS + " TEXT," +
+ Threads.SNIPPET + " TEXT," +
+ Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," +
+ Threads.READ + " INTEGER DEFAULT 1," +
+ Threads.TYPE + " INTEGER DEFAULT 0," +
+ Threads.ERROR + " INTEGER DEFAULT 0);");
+
+ /**
+ * This table stores the queue of messages to be sent/downloaded.
+ */
+ db.execSQL("CREATE TABLE " + MmsSmsProvider.TABLE_PENDING_MSG +" (" +
+ PendingMessages._ID + " INTEGER PRIMARY KEY," +
+ PendingMessages.PROTO_TYPE + " INTEGER," +
+ PendingMessages.MSG_ID + " INTEGER," +
+ PendingMessages.MSG_TYPE + " INTEGER," +
+ PendingMessages.ERROR_TYPE + " INTEGER," +
+ PendingMessages.ERROR_CODE + " INTEGER," +
+ PendingMessages.RETRY_INDEX + " INTEGER NOT NULL DEFAULT 0," +
+ PendingMessages.DUE_TIME + " INTEGER," +
+ PendingMessages.LAST_TRY + " INTEGER);");
+
+ }
+
+ // TODO Check the query plans for these triggers.
+ private void createCommonTriggers(SQLiteDatabase db) {
+ // Updates threads table whenever a message is added to pdu.
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " +
+ MmsProvider.TABLE_PDU + " " +
+ PDU_UPDATE_THREAD_CONSTRAINTS +
+ PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
+
+ // Updates threads table whenever a message is added to sms.
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " +
+ SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
+
+ // Updates threads table whenever a message in pdu is updated.
+ db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF " + Mms.DATE + ", " + Mms.SUBJECT + ", " + Mms.MESSAGE_BOX +
+ " ON " + MmsProvider.TABLE_PDU + " " +
+ PDU_UPDATE_THREAD_CONSTRAINTS +
+ PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
+
+ // Updates threads table whenever a message in sms is updated.
+ db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF " + Sms.DATE + ", " + Sms.BODY + ", " + Sms.TYPE +
+ " ON sms " +
+ SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE);
+
+ // Updates threads table whenever a message in pdu is updated.
+ db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" +
+ " UPDATE OF " + Mms.READ +
+ " ON " + MmsProvider.TABLE_PDU + " " +
+ PDU_UPDATE_THREAD_CONSTRAINTS +
+ "BEGIN " +
+ PDU_UPDATE_THREAD_READ_BODY +
+ "END;");
+
+ // Updates threads table whenever a message in sms is updated.
+ db.execSQL("CREATE TRIGGER sms_update_thread_read_on_update AFTER" +
+ " UPDATE OF " + Sms.READ +
+ " ON sms " +
+ "BEGIN " +
+ SMS_UPDATE_THREAD_READ_BODY +
+ "END;");
+
+ // Update threads table whenever a message in pdu is deleted
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " +
+ "AFTER DELETE ON pdu " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000)" +
+ " WHERE threads._id = old." + Mms.THREAD_ID + "; " +
+ UPDATE_THREAD_COUNT_ON_OLD +
+ UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE +
+ "END;");
+
+ // When the last message in a thread is deleted, these
+ // triggers ensure that the entry for its thread ID is removed
+ // from the threads table.
+ db.execSQL("CREATE TRIGGER delete_obsolete_threads_pdu " +
+ "AFTER DELETE ON pdu " +
+ "BEGIN " +
+ " DELETE FROM threads " +
+ " WHERE " +
+ " _id = old.thread_id " +
+ " AND _id NOT IN " +
+ " (SELECT thread_id FROM sms " +
+ " UNION SELECT thread_id from pdu); " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER delete_obsolete_threads_when_update_pdu " +
+ "AFTER UPDATE OF " + Mms.THREAD_ID + " ON pdu " +
+ "WHEN old." + Mms.THREAD_ID + " != new." + Mms.THREAD_ID + " " +
+ "BEGIN " +
+ " DELETE FROM threads " +
+ " WHERE " +
+ " _id = old.thread_id " +
+ " AND _id NOT IN " +
+ " (SELECT thread_id FROM sms " +
+ " UNION SELECT thread_id from pdu); " +
+ "END;");
+ // Insert pending status for M-Notification.ind or M-ReadRec.ind
+ // when they are inserted into Inbox/Outbox.
+ db.execSQL("CREATE TRIGGER insert_mms_pending_on_insert " +
+ "AFTER INSERT ON pdu " +
+ "WHEN new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_NOTIFICATION_IND +
+ " OR new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_READ_REC_IND + " " +
+ "BEGIN " +
+ " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG +
+ " (" + PendingMessages.PROTO_TYPE + "," +
+ " " + PendingMessages.MSG_ID + "," +
+ " " + PendingMessages.MSG_TYPE + "," +
+ " " + PendingMessages.ERROR_TYPE + "," +
+ " " + PendingMessages.ERROR_CODE + "," +
+ " " + PendingMessages.RETRY_INDEX + "," +
+ " " + PendingMessages.DUE_TIME + ") " +
+ " VALUES " +
+ " (" + MmsSms.MMS_PROTO + "," +
+ " new." + BaseColumns._ID + "," +
+ " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" +
+ "END;");
+
+ // Insert pending status for M-Send.req when it is moved into Outbox.
+ db.execSQL("CREATE TRIGGER insert_mms_pending_on_update " +
+ "AFTER UPDATE ON pdu " +
+ "WHEN new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_SEND_REQ +
+ " AND new." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX +
+ " AND old." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " +
+ "BEGIN " +
+ " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG +
+ " (" + PendingMessages.PROTO_TYPE + "," +
+ " " + PendingMessages.MSG_ID + "," +
+ " " + PendingMessages.MSG_TYPE + "," +
+ " " + PendingMessages.ERROR_TYPE + "," +
+ " " + PendingMessages.ERROR_CODE + "," +
+ " " + PendingMessages.RETRY_INDEX + "," +
+ " " + PendingMessages.DUE_TIME + ") " +
+ " VALUES " +
+ " (" + MmsSms.MMS_PROTO + "," +
+ " new." + BaseColumns._ID + "," +
+ " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" +
+ "END;");
+
+ // When a message is moved out of Outbox, delete its pending status.
+ db.execSQL("CREATE TRIGGER delete_mms_pending_on_update " +
+ "AFTER UPDATE ON " + MmsProvider.TABLE_PDU + " " +
+ "WHEN old." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX +
+ " AND new." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " +
+ "BEGIN " +
+ " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG +
+ " WHERE " + PendingMessages.MSG_ID + "=new._id; " +
+ "END;");
+
+ // Delete pending status for a message when it is deleted.
+ db.execSQL("CREATE TRIGGER delete_mms_pending_on_delete " +
+ "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " +
+ "BEGIN " +
+ " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG +
+ " WHERE " + PendingMessages.MSG_ID + "=old._id; " +
+ "END;");
+
+ // TODO Add triggers for SMS retry-status management.
+
+ // Update the error flag of threads when the error type of
+ // a pending MM is updated.
+ db.execSQL("CREATE TRIGGER update_threads_error_on_update_mms " +
+ " AFTER UPDATE OF err_type ON pending_msgs " +
+ " WHEN (OLD.err_type < 10 AND NEW.err_type >= 10)" +
+ " OR (OLD.err_type >= 10 AND NEW.err_type < 10) " +
+ "BEGIN" +
+ " UPDATE threads SET error = " +
+ " CASE" +
+ " WHEN NEW.err_type >= 10 THEN error + 1" +
+ " ELSE error - 1" +
+ " END " +
+ " WHERE _id =" +
+ " (SELECT DISTINCT thread_id" +
+ " FROM pdu" +
+ " WHERE _id = NEW.msg_id); " +
+ "END;");
+
+ // Update the error flag of threads when delete pending message.
+ db.execSQL("CREATE TRIGGER update_threads_error_on_delete_mms " +
+ " BEFORE DELETE ON pdu" +
+ " WHEN OLD._id IN (SELECT DISTINCT msg_id" +
+ " FROM pending_msgs" +
+ " WHERE err_type >= 10) " +
+ "BEGIN " +
+ " UPDATE threads SET error = error - 1" +
+ " WHERE _id = OLD.thread_id; " +
+ "END;");
+
+ // Update the error flag of threads while moving an MM out of Outbox,
+ // which was failed to be sent permanently.
+ db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " +
+ " BEFORE UPDATE OF msg_box ON pdu " +
+ " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " +
+ " AND (OLD._id IN (SELECT DISTINCT msg_id" +
+ " FROM pending_msgs" +
+ " WHERE err_type >= 10)) " +
+ "BEGIN " +
+ " UPDATE threads SET error = error - 1" +
+ " WHERE _id = OLD.thread_id; " +
+ "END;");
+
+ // Update the error flag of threads after a text message was
+ // failed to send/receive.
+ db.execSQL("CREATE TRIGGER update_threads_error_on_update_sms " +
+ " AFTER UPDATE OF type ON sms" +
+ " WHEN (OLD.type != 5 AND NEW.type = 5)" +
+ " OR (OLD.type = 5 AND NEW.type != 5) " +
+ "BEGIN " +
+ " UPDATE threads SET error = " +
+ " CASE" +
+ " WHEN NEW.type = 5 THEN error + 1" +
+ " ELSE error - 1" +
+ " END " +
+ " WHERE _id = NEW.thread_id; " +
+ "END;");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+ Log.w(TAG, "Upgrading database from version " + oldVersion
+ + " to " + currentVersion + ".");
+
+ switch (oldVersion) {
+ case 24:
+ if (currentVersion <= 24) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion25(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 25:
+ if (currentVersion <= 25) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion26(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 26:
+ if (currentVersion <= 26) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion27(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 27:
+ if (currentVersion <= 27) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion28(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 28:
+ if (currentVersion <= 28) {
+ return;
+ }
+
+ // Test whether this database file is from TC2 branch.
+ Cursor c = db.rawQuery("SELECT * FROM threads", null);
+ if (c != null) {
+ try {
+ c.getColumnIndexOrThrow("snippet_cs");
+ } catch (IllegalArgumentException e) {
+ // Column 'snippet_cs' doesn't exist, which means
+ // this database file was maintained by TC2 branch
+ // and its version is inconsistent.
+ Log.w(TAG, "Upgrade database file from TC2!!!");
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion28(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion29(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 29:
+ if (currentVersion <= 29) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion30(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 30:
+ if (currentVersion <= 30) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion31(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 31:
+ if (currentVersion <= 31) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion32(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 32:
+ if (currentVersion <= 32) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion33(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 33:
+ if (currentVersion <= 33) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion34(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 34:
+ if (currentVersion <= 34) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion35(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 35:
+ if (currentVersion <= 35) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion36(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 36:
+ if (currentVersion <= 36) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion37(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 37:
+ if (currentVersion <= 37) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion38(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 38:
+ if (currentVersion <= 38) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion39(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 39:
+ if (currentVersion <= 39) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion40(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 40:
+ if (currentVersion <= 40) {
+ return;
+ }
+
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion41(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break; // force to destroy all old data;
+ } finally {
+ db.endTransaction();
+ }
+ // fall-through
+ case 41:
+ if (currentVersion <= 41) {
+ return;
+ }
+ db.beginTransaction();
+ try {
+ upgradeDatabaseToVersion42(db);
+ db.setTransactionSuccessful();
+ } catch (Throwable ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ break;
+ } finally {
+ db.endTransaction();
+ }
+ return;
+ }
+
+ Log.w(TAG, "Destroying all old data.");
+ dropCommonTriggers(db);
+ dropMmsTriggers(db);
+ dropCommonTables(db);
+ dropMmsTables(db);
+ dropSmsTables(db);
+ onCreate(db);
+ }
+
+ private void dropCommonTables(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS canonical_addresses");
+ db.execSQL("DROP TABLE IF EXISTS threads");
+ db.execSQL("DROP TABLE IF EXISTS " + MmsSmsProvider.TABLE_PENDING_MSG);
+ }
+
+ private void dropCommonTriggers(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_pdu");
+ db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_when_update_pdu");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_date_subject_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_read_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_update_mms");
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_mms");
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms");
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_update_sms");
+ }
+
+ private void dropSmsTables(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS sms");
+ db.execSQL("DROP TABLE IF EXISTS newSmsIndicator");
+ db.execSQL("DROP TABLE IF EXISTS raw");
+ db.execSQL("DROP TABLE IF EXISTS attachments");
+ db.execSQL("DROP TABLE IF EXISTS thread_ids");
+ db.execSQL("DROP TABLE IF EXISTS sr_pending");
+ }
+
+ private void dropMmsTables(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PDU + ";");
+ db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_ADDR + ";");
+ db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PART + ";");
+ db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_RATE + ";");
+ db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_DRM + ";");
+ }
+
+ private void dropMmsTriggers(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS part_cleanup;");
+ db.execSQL("DROP TRIGGER IF EXISTS addr_cleanup;");
+ db.execSQL("DROP TRIGGER IF EXISTS cleanup_delivery_and_read_report;");
+ }
+
+ private void upgradeDatabaseToVersion25(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE threads " +
+ "ADD COLUMN type INTEGER NOT NULL DEFAULT 0;");
+ }
+
+ private void upgradeDatabaseToVersion26(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE threads " +
+ "ADD COLUMN error INTEGER DEFAULT 0;");
+
+ // Do NOT use defined symbols when upgrading database
+ // because they may be changed and cannot be applied
+ // to old database.
+ db.execSQL("UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM pdu LEFT JOIN pending_msgs" +
+ " ON pdu.thread_id = pending_msgs.msg_id" +
+ " WHERE proto_type = 1 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM sms LEFT JOIN pending_msgs" +
+ " ON sms.thread_id = pending_msgs.msg_id" +
+ " WHERE proto_type = 0 AND err_type >= 10" +
+ " GROUP BY thread_id); ");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_update " +
+ " AFTER UPDATE OF err_type ON pending_msgs " +
+ "BEGIN " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM pdu LEFT JOIN pending_msgs" +
+ " ON pdu.thread_id = pending_msgs.msg_id" +
+ " WHERE proto_type = 1 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM sms LEFT JOIN pending_msgs" +
+ " ON sms.thread_id = pending_msgs.msg_id" +
+ " WHERE proto_type = 0 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_delete " +
+ " AFTER DELETE ON pending_msgs " +
+ "BEGIN " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM pdu LEFT JOIN pending_msgs" +
+ " ON pdu.thread_id = pending_msgs.msg_id" +
+ " WHERE proto_type = 1 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM sms LEFT JOIN pending_msgs" +
+ " ON sms.thread_id = pending_msgs.msg_id" +
+ " WHERE proto_type = 0 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion27(SQLiteDatabase db) {
+ db.execSQL("UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM pdu LEFT JOIN pending_msgs" +
+ " ON pdu._id = pending_msgs.msg_id" +
+ " WHERE proto_type = 1 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM sms LEFT JOIN pending_msgs" +
+ " ON sms._id = pending_msgs.msg_id" +
+ " WHERE proto_type = 0 AND err_type >= 10" +
+ " GROUP BY thread_id); ");
+
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_update " +
+ " AFTER UPDATE OF err_type ON pending_msgs " +
+ "BEGIN " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM pdu LEFT JOIN pending_msgs" +
+ " ON pdu._id = pending_msgs.msg_id" +
+ " WHERE proto_type = 1 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM sms LEFT JOIN pending_msgs" +
+ " ON sms._id = pending_msgs.msg_id" +
+ " WHERE proto_type = 0 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_delete " +
+ " AFTER DELETE ON pending_msgs " +
+ "BEGIN " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM pdu LEFT JOIN pending_msgs" +
+ " ON pdu._id = pending_msgs.msg_id" +
+ " WHERE proto_type = 1 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "UPDATE threads SET error = 1 WHERE _id IN" +
+ " (SELECT thread_id FROM sms LEFT JOIN pending_msgs" +
+ " ON sms._id = pending_msgs.msg_id" +
+ " WHERE proto_type = 0 AND err_type >= 10" +
+ " GROUP BY thread_id); " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion28(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE threads " +
+ "ADD COLUMN snippet_cs INTEGER NOT NULL DEFAULT 0;");
+
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON pdu " +
+ " WHEN new.msg_box!=5 AND new.msg_box!=3" +
+ " AND (new.m_type=132 OR new.m_type=130 OR new.m_type=128) " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, sub, msg_box ON pdu " +
+ " WHEN new.msg_box!=5 AND new.msg_box!=3" +
+ " AND (new.m_type=132 OR new.m_type=130 OR new.m_type=128) " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" +
+ " UPDATE OF read ON pdu " +
+ " WHEN new.msg_box!=5 AND new.msg_box!=3" +
+ " AND (new.m_type=132 OR new.m_type=130 OR new.m_type=128) " +
+ "BEGIN " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_delete " +
+ "AFTER DELETE ON sms " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = (SELECT body FROM SMS ORDER BY date DESC LIMIT 1)" +
+ " WHERE threads._id = old.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion29(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, sub, msg_box ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" +
+ " UPDATE OF read ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion30(SQLiteDatabase db) {
+ // Since SQLite doesn't support altering constraints
+ // of an existing table, I have to create a new table
+ // with updated constraints, copy old data into this
+ // table, drop old table and then rename the new table
+ // to 'threads'.
+ db.execSQL("CREATE TABLE temp_threads (" +
+ "_id INTEGER PRIMARY KEY," +
+ "date INTEGER DEFAULT 0," +
+ "subject TEXT," +
+ "recipient_ids TEXT," +
+ "snippet TEXT," +
+ "snippet_cs INTEGER DEFAULT 0," +
+ "read INTEGER DEFAULT 1," +
+ "type INTEGER DEFAULT 0," +
+ "error INTEGER DEFAULT 0);");
+ db.execSQL("INSERT INTO temp_threads SELECT * FROM threads;");
+ db.execSQL("DROP TABLE IF EXISTS threads;");
+ db.execSQL("ALTER TABLE temp_threads RENAME TO threads;");
+ }
+
+ private void upgradeDatabaseToVersion31(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete");
+
+ // Update threads table whenever a message in sms is deleted
+ // (Usually an abandoned draft.)
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_delete " +
+ "AFTER DELETE ON sms " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET" +
+ " snippet = (SELECT snippet FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET" +
+ " snippet_cs = (SELECT snippet_cs FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ "END;");
+
+ // Update threads table whenever a message in pdu is deleted
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " +
+ "AFTER DELETE ON pdu " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000)" +
+ " WHERE threads._id = old.thread_id;" +
+ " UPDATE threads SET" +
+ " snippet = (SELECT snippet FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET" +
+ " snippet_cs = (SELECT snippet_cs FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion32(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE IF NOT EXISTS rate (sent_time INTEGER);");
+ }
+
+ private void upgradeDatabaseToVersion33(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_update_mms " +
+ " AFTER UPDATE OF err_type ON pending_msgs " +
+ " WHEN (OLD.err_type < 10 AND NEW.err_type >= 10)" +
+ " OR (OLD.err_type >= 10 AND NEW.err_type < 10) " +
+ "BEGIN" +
+ " UPDATE threads SET error = " +
+ " CASE" +
+ " WHEN NEW.err_type >= 10 THEN error + 1" +
+ " ELSE error - 1" +
+ " END " +
+ " WHERE _id =" +
+ " (SELECT DISTINCT thread_id" +
+ " FROM pdu" +
+ " WHERE _id = NEW.msg_id); " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_delete_mms " +
+ " BEFORE DELETE ON pdu" +
+ " WHEN OLD._id IN (SELECT DISTINCT msg_id" +
+ " FROM pending_msgs" +
+ " WHERE err_type >= 10) " +
+ "BEGIN " +
+ " UPDATE threads SET error = error - 1" +
+ " WHERE _id = OLD.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_update_sms " +
+ " AFTER UPDATE OF type ON sms" +
+ " WHEN (OLD.type != 5 AND NEW.type = 5)" +
+ " OR (OLD.type = 5 AND NEW.type != 5) " +
+ "BEGIN " +
+ " UPDATE threads SET error = " +
+ " CASE" +
+ " WHEN NEW.type = 5 THEN error + 1" +
+ " ELSE error - 1" +
+ " END " +
+ " WHERE _id = NEW.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER update_threads_error_on_delete_sms " +
+ " AFTER DELETE ON sms" +
+ " WHEN (OLD.type = 5) " +
+ "BEGIN " +
+ " UPDATE threads SET error = error - 1" +
+ " WHERE _id = OLD.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion34(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_date_subject_on_update");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, body, msg_box" +
+ " ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion35(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE temp_threads (" +
+ "_id INTEGER PRIMARY KEY," +
+ "date INTEGER DEFAULT 0," +
+ "message_count INTEGER DEFAULT 0," +
+ "recipient_ids TEXT," +
+ "snippet TEXT," +
+ "snippet_cs INTEGER DEFAULT 0," +
+ "read INTEGER DEFAULT 1," +
+ "type INTEGER DEFAULT 0," +
+ "error INTEGER DEFAULT 0);");
+ db.execSQL("INSERT INTO temp_threads " +
+ "SELECT _id, date, 0 AS message_count, recipient_ids," +
+ " snippet, snippet_cs, read, type, error " +
+ "FROM threads;");
+ db.execSQL("DROP TABLE IF EXISTS threads;");
+ db.execSQL("ALTER TABLE temp_threads RENAME TO threads;");
+
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_date_subject_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_delete " +
+ "AFTER DELETE ON sms " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET" +
+ " snippet = (SELECT snippet FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET" +
+ " snippet_cs = (SELECT snippet_cs FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " +
+ "AFTER DELETE ON pdu " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000)" +
+ " WHERE threads._id = old.thread_id;" +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET" +
+ " snippet = (SELECT snippet FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET" +
+ " snippet_cs = (SELECT snippet_cs FROM" +
+ " (SELECT date * 1000 AS date, sub AS snippet," +
+ " sub_cs AS snippet_cs FROM pdu" +
+ " UNION SELECT date, body AS snippet, NULL AS snippet_cs" +
+ " FROM sms) ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = old.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, body, type" +
+ " ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, sub, msg_box ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion36(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE IF NOT EXISTS drm (_id INTEGER PRIMARY KEY, _data TEXT);");
+ db.execSQL("CREATE TRIGGER IF NOT EXISTS drm_file_cleanup DELETE ON drm " +
+ "BEGIN SELECT _DELETE_FILE(old._data); END;");
+ }
+
+ private void upgradeDatabaseToVersion37(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_delete " +
+ "AFTER DELETE ON sms " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET 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 = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = OLD.thread_id; " +
+ " UPDATE threads SET 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 = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = OLD.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " +
+ "AFTER DELETE ON pdu " +
+ "BEGIN " +
+ " UPDATE threads SET " +
+ " date = (strftime('%s','now') * 1000)" +
+ " WHERE threads._id = old.thread_id;" +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = old.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = old.thread_id; " +
+ " UPDATE threads SET 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 = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = OLD.thread_id; " +
+ " UPDATE threads SET 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 = OLD.thread_id ORDER BY date DESC LIMIT 1) " +
+ " WHERE threads._id = OLD.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TABLE temp_part (" +
+ "_id INTEGER PRIMARY KEY," +
+ "mid INTEGER," +
+ "seq INTEGER DEFAULT 0," +
+ "ct TEXT," +
+ "name TEXT," +
+ "chset INTEGER," +
+ "cd TEXT," +
+ "fn TEXT," +
+ "cid TEXT," +
+ "cl TEXT," +
+ "ctt_s INTEGER," +
+ "ctt_t TEXT," +
+ "_data TEXT);");
+ db.execSQL("INSERT INTO temp_part SELECT * FROM part;");
+ db.execSQL("UPDATE temp_part SET seq='0';");
+ db.execSQL("UPDATE temp_part SET seq='-1' WHERE ct='application/smil';");
+ db.execSQL("DROP TABLE IF EXISTS part;");
+ db.execSQL("ALTER TABLE temp_part RENAME TO part;");
+ }
+
+ private void upgradeDatabaseToVersion38(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS part_file_cleanup;");
+ db.execSQL("DROP TRIGGER IF EXISTS drm_file_cleanup;");
+ }
+
+ private void upgradeDatabaseToVersion39(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_date_subject_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is NULL) + " +
+ " (SELECT COUNT(DISTINCT pdu.m_id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is not NULL) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, body, type" +
+ " ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is NULL) + " +
+ " (SELECT COUNT(DISTINCT pdu.m_id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is not NULL) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is NULL) + " +
+ " (SELECT COUNT(DISTINCT pdu.m_id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is not NULL) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, sub, msg_box ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is NULL) + " +
+ " (SELECT COUNT(DISTINCT pdu.m_id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3 " +
+ " AND pdu.m_id is not NULL) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion40(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_date_subject_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, body, type" +
+ " ON sms " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.body," +
+ " snippet_cs = 0" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM sms" +
+ " WHERE read = 0" +
+ " AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1" +
+ " ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+
+ db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" +
+ " UPDATE OF date, sub, msg_box ON pdu " +
+ " WHEN new.m_type=132 OR new.m_type=130 OR new.m_type=128 " +
+ "BEGIN" +
+ " UPDATE threads SET" +
+ " date = (strftime('%s','now') * 1000), " +
+ " snippet = new.sub, " +
+ " snippet_cs = new.sub_cs" +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET message_count = " +
+ " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND sms.type != 3) + " +
+ " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " +
+ " ON threads._id = thread_id" +
+ " WHERE thread_id = new.thread_id" +
+ " AND (m_type=132 OR m_type=130 OR m_type=128)" +
+ " AND msg_box != 3) " +
+ " WHERE threads._id = new.thread_id; " +
+ " UPDATE threads SET read = " +
+ " CASE (SELECT COUNT(*)" +
+ " FROM pdu" +
+ " WHERE read = 0 AND thread_id = threads._id)" +
+ " WHEN 0 THEN 1 ELSE 0" +
+ " END" +
+ " WHERE threads._id = new.thread_id; " +
+ "END;");
+ }
+
+ private void upgradeDatabaseToVersion41(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms");
+ db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " +
+ " BEFORE UPDATE OF msg_box ON pdu " +
+ " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " +
+ " AND (OLD._id IN (SELECT DISTINCT msg_id" +
+ " FROM pending_msgs" +
+ " WHERE err_type >= 10)) " +
+ "BEGIN " +
+ " UPDATE threads SET error = error - 1" +
+ " 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");
+ }
+}
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
new file mode 100644
index 0000000..b27e332
--- /dev/null
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -0,0 +1,1019 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import com.google.android.mms.pdu.PduHeaders;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.BaseColumns;
+import android.provider.Telephony.CanonicalAddressesColumns;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.Threads;
+import android.provider.Telephony.ThreadsColumns;
+import android.provider.Telephony.MmsSms.PendingMessages;
+import android.provider.Telephony.Sms.Conversations;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class provides the ability to query the MMS and SMS databases
+ * at the same time, mixing messages from both in a single thread
+ * (A.K.A. conversation).
+ *
+ * A virtual column, MmsSms.TYPE_DISCRIMINATOR_COLUMN, may be
+ * requested in the projection for a query. Its value is either "mms"
+ * or "sms", depending on whether the message represented by the row
+ * is an MMS message or an SMS message, respectively.
+ *
+ * This class also provides the ability to find out what addresses
+ * participated in a particular thread. It doesn't support updates
+ * for either of these.
+ *
+ * This class provides a way to allocate and retrieve thread IDs.
+ * This is done atomically through a query. There is no insert URI
+ * for this.
+ *
+ * Finally, this class provides a way to delete or update all messages
+ * in a thread.
+ */
+public class MmsSmsProvider extends ContentProvider {
+ private static final UriMatcher URI_MATCHER =
+ new UriMatcher(UriMatcher.NO_MATCH);
+ private static final String LOG_TAG = "MmsSmsProvider";
+
+ private static final String NO_DELETES_INSERTS_OR_UPDATES =
+ "MmsSmsProvider does not support deletes, inserts, or updates for this URI.";
+ private static final int URI_CONVERSATIONS = 0;
+ private static final int URI_CONVERSATIONS_MESSAGES = 1;
+ private static final int URI_CONVERSATIONS_RECIPIENTS = 2;
+ private static final int URI_MESSAGES_BY_PHONE = 3;
+ private static final int URI_THREAD_ID = 4;
+ private static final int URI_CANONICAL_ADDRESS = 5;
+ private static final int URI_PENDING_MSG = 6;
+ private static final int URI_COMPLETE_CONVERSATIONS = 7;
+ private static final int URI_UNDELIVERED_MSG = 8;
+ private static final int URI_CONVERSATIONS_SUBJECT = 9;
+ private static final int URI_NOTIFICATIONS = 10;
+ private static final int URI_OBSOLETE_THREADS = 11;
+ private static final int URI_DRAFT = 12;
+
+ /**
+ * the name of the table that is used to store the queue of
+ * messages(both MMS and SMS) to be sent/downloaded.
+ */
+ public static final String TABLE_PENDING_MSG = "pending_msgs";
+
+ // These constants are used to construct union queries across the
+ // MMS and SMS base tables.
+
+ // 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 };
+
+ // These are the columns that appear only in the MMS message
+ // table.
+ private static final String[] MMS_ONLY_COLUMNS = {
+ Mms.CONTENT_CLASS, Mms.CONTENT_LOCATION, Mms.CONTENT_TYPE,
+ Mms.DELIVERY_REPORT, Mms.EXPIRY, Mms.MESSAGE_CLASS, Mms.MESSAGE_ID,
+ Mms.MESSAGE_SIZE, Mms.MESSAGE_TYPE, Mms.MESSAGE_BOX, Mms.PRIORITY,
+ Mms.READ_STATUS, Mms.RESPONSE_STATUS, Mms.RESPONSE_TEXT,
+ Mms.RETRIEVE_STATUS, Mms.RETRIEVE_TEXT_CHARSET, Mms.REPORT_ALLOWED,
+ Mms.READ_REPORT, Mms.STATUS, Mms.SUBJECT, Mms.SUBJECT_CHARSET,
+ Mms.TRANSACTION_ID, Mms.MMS_VERSION };
+
+ // These are the columns that appear only in the SMS message
+ // table.
+ private static final String[] SMS_ONLY_COLUMNS =
+ { "address", "body", "person", "reply_path_present",
+ "service_center", "status", "subject", "type" };
+
+ // These are all the columns that appear in the "threads" table.
+ private static final String[] THREADS_COLUMNS = {
+ BaseColumns._ID,
+ ThreadsColumns.DATE,
+ ThreadsColumns.RECIPIENT_IDS,
+ ThreadsColumns.MESSAGE_COUNT
+ };
+
+ // These are all the columns that appear in the MMS and SMS
+ // message tables.
+ private static final String[] UNION_COLUMNS =
+ new String[MMS_SMS_COLUMNS.length
+ + MMS_ONLY_COLUMNS.length
+ + SMS_ONLY_COLUMNS.length];
+
+ // These are all the columns that appear in the MMS table.
+ private static final Set<String> MMS_COLUMNS = new HashSet<String>();
+
+ // These are all the columns that appear in the SMS table.
+ private static final Set<String> SMS_COLUMNS = new HashSet<String>();
+
+ private static final String VND_ANDROID_DIR_MMS_SMS =
+ "vnd.android-dir/mms-sms";
+
+ private static final String[] ID_PROJECTION = { BaseColumns._ID };
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+ private static final String SMS_CONVERSATION_CONSTRAINT = "(" +
+ Sms.TYPE + " != " + Sms.MESSAGE_TYPE_DRAFT + ")";
+
+ private static final String MMS_CONVERSATION_CONSTRAINT = "(" +
+ Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS + " AND (" +
+ Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_SEND_REQ + " OR " +
+ Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + " OR " +
+ Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + "))";
+
+ private static final String AUTHORITY = "mms-sms";
+
+ static {
+ URI_MATCHER.addURI(AUTHORITY, "conversations", URI_CONVERSATIONS);
+ URI_MATCHER.addURI(AUTHORITY, "complete-conversations", URI_COMPLETE_CONVERSATIONS);
+
+ // In these patterns, "#" is the thread ID.
+ URI_MATCHER.addURI(
+ AUTHORITY, "conversations/#", URI_CONVERSATIONS_MESSAGES);
+ URI_MATCHER.addURI(
+ AUTHORITY, "conversations/#/recipients",
+ URI_CONVERSATIONS_RECIPIENTS);
+
+ URI_MATCHER.addURI(
+ AUTHORITY, "conversations/#/subject",
+ URI_CONVERSATIONS_SUBJECT);
+
+ // URI for deleting obsolete threads.
+ URI_MATCHER.addURI(AUTHORITY, "conversations/obsolete", URI_OBSOLETE_THREADS);
+
+ URI_MATCHER.addURI(
+ AUTHORITY, "messages/byphone/*",
+ URI_MESSAGES_BY_PHONE);
+
+ // In this pattern, two query parameter names are expected:
+ // "subject" and "recipient." Multiple "recipient" parameters
+ // may be present.
+ URI_MATCHER.addURI(AUTHORITY, "threadID", URI_THREAD_ID);
+
+ // Use this pattern to query the canonical address by given ID.
+ URI_MATCHER.addURI(AUTHORITY, "canonical-address/#", URI_CANONICAL_ADDRESS);
+
+ // In this pattern, two query parameters may be supplied:
+ // "protocol" and "message." For example:
+ // content://mms-sms/pending?
+ // -> Return all pending messages;
+ // content://mms-sms/pending?protocol=sms
+ // -> Only return pending SMs;
+ // content://mms-sms/pending?protocol=mms&message=1
+ // -> Return the the pending MM which ID equals '1'.
+ //
+ URI_MATCHER.addURI(AUTHORITY, "pending", URI_PENDING_MSG);
+
+ // Use this pattern to get a list of undelivered messages.
+ URI_MATCHER.addURI(AUTHORITY, "undelivered", URI_UNDELIVERED_MSG);
+
+ // Use this pattern to see what delivery status reports (for
+ // both MMS and SMS) have not been delivered to the user.
+ URI_MATCHER.addURI(AUTHORITY, "notifications", URI_NOTIFICATIONS);
+
+ URI_MATCHER.addURI(AUTHORITY, "draft", URI_DRAFT);
+ initializeColumnSets();
+ }
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor cursor = null;
+
+ switch(URI_MATCHER.match(uri)) {
+ case URI_COMPLETE_CONVERSATIONS:
+ cursor = getCompleteConversations(
+ projection, selection, selectionArgs, sortOrder);
+ break;
+ case URI_CONVERSATIONS:
+ String simple = uri.getQueryParameter("simple");
+ if ((simple != null) && simple.equals("true")) {
+ String threadType = uri.getQueryParameter("thread_type");
+ if (!TextUtils.isEmpty(threadType)) {
+ selection = concatSelections(
+ selection, Threads.TYPE + "=" + threadType);
+ }
+ cursor = getSimpleConversations(
+ projection, selection, selectionArgs, sortOrder);
+ } else {
+ cursor = getConversations(
+ projection, selection, selectionArgs, sortOrder);
+ }
+ break;
+ case URI_CONVERSATIONS_MESSAGES:
+ cursor = getConversationMessages(
+ uri.getPathSegments().get(1), projection, selection,
+ selectionArgs, sortOrder);
+ break;
+ case URI_CONVERSATIONS_RECIPIENTS:
+ cursor = getConversationById(
+ uri.getPathSegments().get(1), projection, selection,
+ selectionArgs, sortOrder);
+ break;
+ case URI_CONVERSATIONS_SUBJECT:
+ cursor = getConversationById(
+ uri.getPathSegments().get(1), projection, selection,
+ selectionArgs, sortOrder);
+ break;
+ case URI_MESSAGES_BY_PHONE:
+ cursor = getMessagesByPhoneNumber(
+ uri.getPathSegments().get(2), projection, selection,
+ selectionArgs, sortOrder);
+ break;
+ case URI_THREAD_ID:
+ List<String> recipients = uri.getQueryParameters("recipient");
+
+ cursor = getThreadId(recipients);
+ break;
+ case URI_CANONICAL_ADDRESS: {
+ String extraSelection = "_id=" + uri.getPathSegments().get(1);
+ String finalSelection = TextUtils.isEmpty(selection)
+ ? extraSelection : extraSelection + " AND " + selection;
+ cursor = db.query("canonical_addresses",
+ new String[] {"address"}, finalSelection, selectionArgs,
+ null, null, sortOrder);
+ break;
+ }
+ case URI_PENDING_MSG: {
+ String protoName = uri.getQueryParameter("protocol");
+ String msgId = uri.getQueryParameter("message");
+ int proto = TextUtils.isEmpty(protoName) ? -1
+ : (protoName.equals("sms") ? MmsSms.SMS_PROTO : MmsSms.MMS_PROTO);
+
+ String extraSelection = (proto != -1) ?
+ (PendingMessages.PROTO_TYPE + "=" + proto) : " 0=0 ";
+ if (!TextUtils.isEmpty(msgId)) {
+ extraSelection += " AND " + PendingMessages.MSG_ID + "=" + msgId;
+ }
+
+ String finalSelection = TextUtils.isEmpty(selection)
+ ? extraSelection : ("(" + extraSelection + ") AND " + selection);
+ String finalOrder = TextUtils.isEmpty(sortOrder)
+ ? PendingMessages.DUE_TIME : sortOrder;
+ cursor = db.query(TABLE_PENDING_MSG, null,
+ finalSelection, selectionArgs, null, null, finalOrder);
+ break;
+ }
+ case URI_UNDELIVERED_MSG: {
+ cursor = getUndeliveredMessages(projection, selection,
+ selectionArgs, sortOrder);
+ break;
+ }
+ case URI_DRAFT: {
+ cursor = getDraftThread(projection, selection, selectionArgs, sortOrder);
+ break;
+ }
+ default:
+ throw new IllegalStateException("Unrecognized URI:" + uri);
+ }
+
+ cursor.setNotificationUri(getContext().getContentResolver(), MmsSms.CONTENT_URI);
+ return cursor;
+ }
+
+ /**
+ * Return the canonical address ID for this address.
+ */
+ private long getSingleAddressId(String address) {
+ boolean isEmail = Mms.isEmailAddress(address);
+ String refinedAddress = isEmail ? address.toLowerCase() : address;
+ String selection =
+ isEmail
+ ? "address = ?"
+ : "PHONE_NUMBERS_EQUAL(address, ?)";
+ String[] selectionArgs = new String[] { refinedAddress };
+ Cursor cursor = null;
+
+ try {
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ cursor = db.query(
+ "canonical_addresses", ID_PROJECTION,
+ selection, selectionArgs, null, null, null);
+
+ if (cursor.getCount() == 0) {
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(CanonicalAddressesColumns.ADDRESS, refinedAddress);
+
+ db = mOpenHelper.getWritableDatabase();
+ return db.insert("canonical_addresses",
+ CanonicalAddressesColumns.ADDRESS, contentValues);
+ }
+
+ if (cursor.moveToFirst()) {
+ return cursor.getLong(cursor.getColumnIndexOrThrow(BaseColumns._ID));
+ }
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+
+ return -1L;
+ }
+
+ /**
+ * Return the canonical address IDs for these addresses.
+ */
+ private Set<Long> getAddressIds(List<String> addresses) {
+ Set<Long> result = new HashSet<Long>(addresses.size());
+
+ for (String address : addresses) {
+ if (!address.equals(PduHeaders.FROM_INSERT_ADDRESS_TOKEN_STR)) {
+ long id = getSingleAddressId(address);
+ if (id != -1L) {
+ result.add(id);
+ } else {
+ Log.e(LOG_TAG, "Address ID not found for: " + address);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Return a sorted array of the given Set of Longs.
+ */
+ private long[] getSortedSet(Set<Long> numbers) {
+ int size = numbers.size();
+ long[] result = new long[size];
+ int i = 0;
+
+ for (Long number : numbers) {
+ result[i++] = number;
+ }
+ Arrays.sort(result);
+ return result;
+ }
+
+ /**
+ * Return a String of the numbers in the given array, in order,
+ * separated by spaces.
+ */
+ private String getSpaceSeparatedNumbers(long[] numbers) {
+ int size = numbers.length;
+ StringBuilder buffer = new StringBuilder();
+
+ for (int i = 0; i < size; i++) {
+ if (i != 0) {
+ buffer.append(' ');
+ }
+ buffer.append(numbers[i]);
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Insert a record for a new thread.
+ */
+ private void insertThread(String recipientIds, int numberOfRecipients) {
+ ContentValues values = new ContentValues(4);
+
+ long date = System.currentTimeMillis();
+ values.put(ThreadsColumns.DATE, date - date % 1000);
+ values.put(ThreadsColumns.RECIPIENT_IDS, recipientIds);
+ if (numberOfRecipients > 1) {
+ values.put(Threads.TYPE, Threads.BROADCAST_THREAD);
+ }
+ values.put(ThreadsColumns.MESSAGE_COUNT, 0);
+
+ mOpenHelper.getWritableDatabase().insert("threads", null, values);
+ getContext().getContentResolver().notifyChange(MmsSms.CONTENT_URI, null);
+ }
+
+ /**
+ * Return the thread ID for this list of
+ * recipients IDs. If no thread exists with this ID, create
+ * one and return it. Callers should always use
+ * Threads.getThreadId to access this information.
+ */
+ private synchronized Cursor getThreadId(List<String> recipients) {
+ String recipientIds =
+ getSpaceSeparatedNumbers(
+ getSortedSet(getAddressIds(recipients)));
+ String THREAD_QUERY = "SELECT _id FROM threads " +
+ "WHERE recipient_ids = ?";
+
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor cursor = db.rawQuery(THREAD_QUERY, new String[] { recipientIds });
+
+ if (cursor.getCount() == 0) {
+ cursor.close();
+ insertThread(recipientIds, recipients.size());
+ db = mOpenHelper.getReadableDatabase(); // In case insertThread closed it
+ cursor = db.rawQuery(THREAD_QUERY, new String[] { recipientIds });
+ }
+
+ return cursor;
+ }
+
+ private static String concatSelections(String selection1, String selection2) {
+ if (TextUtils.isEmpty(selection1)) {
+ return selection2;
+ } else if (TextUtils.isEmpty(selection2)) {
+ return selection1;
+ } else {
+ return selection1 + " AND " + selection2;
+ }
+ }
+
+ /**
+ * If a null projection is given, return the union of all columns
+ * in both the MMS and SMS messages tables. Otherwise, return the
+ * given projection.
+ */
+ private static String[] handleNullMessageProjection(
+ String[] projection) {
+ return projection == null ? UNION_COLUMNS : projection;
+ }
+
+ /**
+ * If a null projection is given, return the set of all columns in
+ * the threads table. Otherwise, return the given projection.
+ */
+ private static String[] handleNullThreadsProjection(
+ String[] projection) {
+ return projection == null ? THREADS_COLUMNS : projection;
+ }
+
+ /**
+ * If a null sort order is given, return "normalized_date ASC".
+ * Otherwise, return the given sort order.
+ */
+ private static String handleNullSortOrder (String sortOrder) {
+ return sortOrder == null ? "normalized_date ASC" : sortOrder;
+ }
+
+ /**
+ * Return existing threads in the database.
+ */
+ private Cursor getSimpleConversations(String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ return mOpenHelper.getReadableDatabase().query("threads", projection,
+ selection, selectionArgs, null, null, " date DESC");
+ }
+
+ /**
+ * Return the thread which has draft in both MMS and SMS.
+ *
+ * Use this query:
+ *
+ * SELECT ...
+ * FROM (SELECT _id, thread_id, ...
+ * FROM pdu
+ * WHERE msg_box = 3 AND ...
+ * UNION
+ * SELECT _id, thread_id, ...
+ * FROM sms
+ * WHERE type = 3 AND ...
+ * )
+ * ;
+ */
+ private Cursor getDraftThread(String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ String[] innerProjection = new String[] {BaseColumns._ID, Conversations.THREAD_ID};
+ SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
+ SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
+
+ mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU);
+ smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+
+ String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection,
+ MMS_COLUMNS, 1, "mms",
+ concatSelections(selection, Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_DRAFTS),
+ selectionArgs, null, null);
+ String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerProjection,
+ SMS_COLUMNS, 1, "sms",
+ concatSelections(selection, Sms.TYPE + "=" + Sms.MESSAGE_TYPE_DRAFT),
+ selectionArgs, null, null);
+ SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
+
+ unionQueryBuilder.setDistinct(true);
+
+ String unionQuery = unionQueryBuilder.buildUnionQuery(
+ new String[] { mmsSubQuery, smsSubQuery }, null, null);
+
+ SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
+
+ outerQueryBuilder.setTables("(" + unionQuery + ")");
+
+ String outerQuery = outerQueryBuilder.buildQuery(
+ projection, null, null, null, null, sortOrder, null);
+
+ return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Return the most recent message in each conversation in both MMS
+ * and SMS.
+ *
+ * Use this query:
+ *
+ * SELECT ...
+ * FROM (SELECT thread_id AS tid, date * 1000 AS normalized_date, ...
+ * FROM pdu
+ * WHERE msg_box != 3 AND ...
+ * GROUP BY thread_id
+ * HAVING date = MAX(date)
+ * UNION
+ * SELECT thread_id AS tid, date AS normalized_date, ...
+ * FROM sms
+ * WHERE ...
+ * GROUP BY thread_id
+ * HAVING date = MAX(date))
+ * GROUP BY tid
+ * HAVING normalized_date = MAX(normalized_date);
+ *
+ * The msg_box != 3 comparisons ensure that we don't include draft
+ * messages.
+ */
+ private Cursor getConversations(String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
+ SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
+
+ mmsQueryBuilder.setTables(MmsProvider.TABLE_PDU);
+ smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+
+ String[] columns = handleNullMessageProjection(projection);
+ String[] innerMmsProjection = makeProjectionWithDateAndThreadId(
+ UNION_COLUMNS, 1000);
+ String[] innerSmsProjection = makeProjectionWithDateAndThreadId(
+ UNION_COLUMNS, 1);
+ String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
+ MMS_COLUMNS, 1, "mms",
+ concatSelections(selection, MMS_CONVERSATION_CONSTRAINT), selectionArgs,
+ "thread_id", "date = MAX(date)");
+ String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection,
+ SMS_COLUMNS, 1, "sms",
+ concatSelections(selection, SMS_CONVERSATION_CONSTRAINT), selectionArgs,
+ "thread_id", "date = MAX(date)");
+ SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
+
+ unionQueryBuilder.setDistinct(true);
+
+ String unionQuery = unionQueryBuilder.buildUnionQuery(
+ new String[] { mmsSubQuery, smsSubQuery }, null, null);
+
+ SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
+
+ outerQueryBuilder.setTables("(" + unionQuery + ")");
+
+ String outerQuery = outerQueryBuilder.buildQuery(
+ columns, null, null, "tid",
+ "normalized_date = MAX(normalized_date)", sortOrder, null);
+
+ return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Return every message in each conversation in both MMS
+ * and SMS.
+ */
+ private Cursor getCompleteConversations(String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ String unionQuery = buildConversationQuery(
+ projection, selection, selectionArgs, sortOrder);
+
+ return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Add normalized date and thread_id to the list of columns for an
+ * inner projection. This is necessary so that the outer query
+ * can have access to these columns even if the caller hasn't
+ * requested them in the result.
+ */
+ private String[] makeProjectionWithDateAndThreadId(
+ String[] projection, int dateMultiple) {
+ int projectionSize = projection.length;
+ String[] result = new String[projectionSize + 2];
+
+ result[0] = "thread_id AS tid";
+ result[1] = "date * " + dateMultiple + " AS normalized_date";
+ for (int i = 0; i < projectionSize; i++) {
+ result[i + 2] = projection[i];
+ }
+ return result;
+ }
+
+ /**
+ * Return the union of MMS and SMS messages for this thread ID.
+ */
+ private Cursor getConversationMessages(
+ String threadIdString, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ try {
+ Long.parseLong(threadIdString);
+ } catch (NumberFormatException exception) {
+ Log.e(LOG_TAG, "Thread ID must be a Long.");
+ return null;
+ }
+
+ String finalSelection = concatSelections(
+ selection, "thread_id = " + threadIdString);
+ String unionQuery = buildConversationQuery(
+ projection, finalSelection, selectionArgs, sortOrder);
+
+ return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Return the union of MMS and SMS messages whose recipients
+ * included this phone number.
+ *
+ * Use this query:
+ *
+ * SELECT ...
+ * FROM pdu, (SELECT _id AS address_id
+ * FROM addr
+ * WHERE PHONE_NUMBERS_EQUAL(addr.address, '<phoneNumber>'))
+ * AS matching_addresses
+ * WHERE pdu._id = matching_addresses.address_id
+ * UNION
+ * SELECT ...
+ * FROM sms
+ * WHERE PHONE_NUMBERS_EQUAL(sms.address, '<phoneNumber>');
+ */
+ private Cursor getMessagesByPhoneNumber(
+ String phoneNumber, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ String escapedPhoneNumber = DatabaseUtils.sqlEscapeString(phoneNumber);
+ String finalMmsSelection =
+ concatSelections(
+ selection,
+ "pdu._id = matching_addresses.address_id");
+ String finalSmsSelection =
+ concatSelections(
+ selection,
+ "PHONE_NUMBERS_EQUAL(address, " +
+ escapedPhoneNumber + ")");
+ SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
+ SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
+
+ mmsQueryBuilder.setDistinct(true);
+ smsQueryBuilder.setDistinct(true);
+ mmsQueryBuilder.setTables(
+ MmsProvider.TABLE_PDU +
+ ", (SELECT _id AS address_id " +
+ "FROM addr WHERE PHONE_NUMBERS_EQUAL(addr.address, " +
+ escapedPhoneNumber + ")) " +
+ "AS matching_addresses");
+ smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+
+ String[] columns = handleNullMessageProjection(projection);
+ String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, MMS_COLUMNS,
+ 0, "mms", finalMmsSelection, selectionArgs, null, null);
+ String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, columns, SMS_COLUMNS,
+ 0, "sms", finalSmsSelection, selectionArgs, null, null);
+ SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
+
+ unionQueryBuilder.setDistinct(true);
+
+ String unionQuery = unionQueryBuilder.buildUnionQuery(
+ new String[] { mmsSubQuery, smsSubQuery }, sortOrder, null);
+
+ return mOpenHelper.getReadableDatabase().rawQuery(unionQuery, EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Return the conversation of certain thread ID.
+ */
+ private Cursor getConversationById(
+ String threadIdString, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ try {
+ Long.parseLong(threadIdString);
+ } catch (NumberFormatException exception) {
+ Log.e(LOG_TAG, "Thread ID must be a Long.");
+ return null;
+ }
+
+ String extraSelection = "_id=" + threadIdString;
+ String finalSelection = concatSelections(selection, extraSelection);
+ SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
+ String[] columns = handleNullThreadsProjection(projection);
+
+ queryBuilder.setDistinct(true);
+ queryBuilder.setTables("threads");
+ return queryBuilder.query(
+ mOpenHelper.getReadableDatabase(), columns, finalSelection,
+ selectionArgs, sortOrder, null, null);
+ }
+
+ private static String joinPduAndPendingMsgTables() {
+ return MmsProvider.TABLE_PDU + " LEFT JOIN " + TABLE_PENDING_MSG
+ + " ON pdu._id = pending_msgs.msg_id";
+ }
+
+ private static String[] createMmsProjection(String[] old) {
+ String[] newProjection = new String[old.length];
+ for (int i = 0; i < old.length; i++) {
+ if (old[i].equals(BaseColumns._ID)) {
+ newProjection[i] = "pdu._id";
+ } else {
+ newProjection[i] = old[i];
+ }
+ }
+ return newProjection;
+ }
+
+ private Cursor getUndeliveredMessages(
+ String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ String[] mmsProjection = createMmsProjection(projection);
+
+ SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
+ SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
+
+ mmsQueryBuilder.setTables(joinPduAndPendingMsgTables());
+ smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+
+ String finalMmsSelection = concatSelections(
+ selection, Mms.MESSAGE_BOX + " = " + Mms.MESSAGE_BOX_OUTBOX);
+ String finalSmsSelection = concatSelections(
+ selection, "(" + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_OUTBOX
+ + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_FAILED
+ + " OR " + Sms.TYPE + " = " + Sms.MESSAGE_TYPE_QUEUED + ")");
+
+ String[] smsColumns = handleNullMessageProjection(projection);
+ String[] mmsColumns = handleNullMessageProjection(mmsProjection);
+ String[] innerMmsProjection = makeProjectionWithDateAndThreadId(
+ mmsColumns, 1000);
+ String[] innerSmsProjection = makeProjectionWithDateAndThreadId(
+ smsColumns, 1);
+
+ Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);
+ columnsPresentInTable.add("pdu._id");
+ columnsPresentInTable.add(PendingMessages.ERROR_TYPE);
+ String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
+ columnsPresentInTable, 1, "mms", finalMmsSelection, selectionArgs,
+ null, null);
+ String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection,
+ SMS_COLUMNS, 1, "sms", finalSmsSelection, selectionArgs,
+ null, null);
+ SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
+
+ unionQueryBuilder.setDistinct(true);
+
+ String unionQuery = unionQueryBuilder.buildUnionQuery(
+ new String[] { smsSubQuery, mmsSubQuery }, null, null);
+
+ SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
+
+ outerQueryBuilder.setTables("(" + unionQuery + ")");
+
+ String outerQuery = outerQueryBuilder.buildQuery(
+ smsColumns, null, null, null, null, sortOrder, null);
+
+ return mOpenHelper.getReadableDatabase().rawQuery(outerQuery, EMPTY_STRING_ARRAY);
+ }
+
+ /**
+ * Add normalized date to the list of columns for an inner
+ * projection.
+ */
+ private static String[] makeProjectionWithNormalizedDate(
+ String[] projection, int dateMultiple) {
+ int projectionSize = projection.length;
+ String[] result = new String[projectionSize + 1];
+
+ result[0] = "date * " + dateMultiple + " AS normalized_date";
+ System.arraycopy(projection, 0, result, 1, projectionSize);
+ return result;
+ }
+
+ private static String buildConversationQuery(String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ String[] mmsProjection = createMmsProjection(projection);
+
+ SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
+ SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
+
+ mmsQueryBuilder.setDistinct(true);
+ smsQueryBuilder.setDistinct(true);
+ mmsQueryBuilder.setTables(joinPduAndPendingMsgTables());
+ smsQueryBuilder.setTables(SmsProvider.TABLE_SMS);
+
+ String[] smsColumns = handleNullMessageProjection(projection);
+ String[] mmsColumns = handleNullMessageProjection(mmsProjection);
+ String[] innerMmsProjection = makeProjectionWithNormalizedDate(mmsColumns, 1000);
+ String[] innerSmsProjection = makeProjectionWithNormalizedDate(smsColumns, 1);
+
+ Set<String> columnsPresentInTable = new HashSet<String>(MMS_COLUMNS);
+ columnsPresentInTable.add("pdu._id");
+ columnsPresentInTable.add(PendingMessages.ERROR_TYPE);
+
+ String mmsSelection = concatSelections(selection,
+ Mms.MESSAGE_BOX + " != " + Mms.MESSAGE_BOX_DRAFTS);
+ String mmsSubQuery = mmsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerMmsProjection,
+ columnsPresentInTable, 0, "mms",
+ concatSelections(mmsSelection, MMS_CONVERSATION_CONSTRAINT),
+ selectionArgs, null, null);
+ String smsSubQuery = smsQueryBuilder.buildUnionSubQuery(
+ MmsSms.TYPE_DISCRIMINATOR_COLUMN, innerSmsProjection, SMS_COLUMNS,
+ 0, "sms", concatSelections(selection, SMS_CONVERSATION_CONSTRAINT),
+ selectionArgs, null, null);
+ SQLiteQueryBuilder unionQueryBuilder = new SQLiteQueryBuilder();
+
+ unionQueryBuilder.setDistinct(true);
+
+ String unionQuery = unionQueryBuilder.buildUnionQuery(
+ new String[] { smsSubQuery, mmsSubQuery },
+ handleNullSortOrder(sortOrder), null);
+
+ SQLiteQueryBuilder outerQueryBuilder = new SQLiteQueryBuilder();
+
+ outerQueryBuilder.setTables("(" + unionQuery + ")");
+
+ return outerQueryBuilder.buildQuery(
+ smsColumns, null, null, null, null, sortOrder, null);
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return VND_ANDROID_DIR_MMS_SMS;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection,
+ String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Context context = getContext();
+ int affectedRows = 0;
+
+ switch(URI_MATCHER.match(uri)) {
+ case URI_CONVERSATIONS_MESSAGES:
+ long threadId;
+ try {
+ threadId = Long.parseLong(uri.getLastPathSegment());
+ } catch (NumberFormatException e) {
+ Log.e(LOG_TAG, "Thread ID must be a long.");
+ break;
+ }
+ affectedRows = deleteConversation(uri, selection, selectionArgs);
+ MmsSmsDatabaseHelper.updateThread(db, threadId);
+ break;
+ case URI_CONVERSATIONS:
+ affectedRows = MmsProvider.deleteMessages(context, db,
+ selection, selectionArgs, uri)
+ + db.delete("sms", selection, selectionArgs);
+ MmsSmsDatabaseHelper.updateAllThreads(db, selection, selectionArgs);
+ break;
+ case URI_OBSOLETE_THREADS:
+ affectedRows = db.delete("threads",
+ "_id NOT IN (SELECT DISTINCT thread_id FROM sms " +
+ "UNION SELECT DISTINCT thread_id FROM pdu)", null);
+ break;
+ default:
+ throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES);
+ }
+
+ if (affectedRows > 0) {
+ context.getContentResolver().notifyChange(MmsSms.CONTENT_URI, null);
+ }
+ return affectedRows;
+ }
+
+ /**
+ * Delete the conversation with the given thread ID.
+ */
+ private int deleteConversation(Uri uri, String selection, String[] selectionArgs) {
+ String threadId = uri.getLastPathSegment();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ String finalSelection = concatSelections(selection, "thread_id = " + threadId);
+ return MmsProvider.deleteMessages(getContext(), db, finalSelection,
+ selectionArgs, uri)
+ + db.delete("sms", finalSelection, selectionArgs);
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES);
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values,
+ String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int affectedRows = 0;
+ switch(URI_MATCHER.match(uri)) {
+ case URI_CONVERSATIONS_MESSAGES:
+ String threadIdString = uri.getPathSegments().get(1);
+ affectedRows = updateConversation(threadIdString, values,
+ selection, selectionArgs);
+ break;
+ case URI_PENDING_MSG:
+ affectedRows = db.update(TABLE_PENDING_MSG, values, selection, null);
+ break;
+ default:
+ throw new UnsupportedOperationException(
+ NO_DELETES_INSERTS_OR_UPDATES);
+ }
+
+ if (affectedRows > 0) {
+ getContext().getContentResolver().notifyChange(
+ MmsSms.CONTENT_URI, null);
+ }
+ return affectedRows;
+ }
+
+ private int updateConversation(
+ String threadIdString, ContentValues values, String selection,
+ String[] selectionArgs) {
+ try {
+ Long.parseLong(threadIdString);
+ } catch (NumberFormatException exception) {
+ Log.e(LOG_TAG, "Thread ID must be a Long.");
+ return 0;
+ }
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ String finalSelection = concatSelections(selection, "thread_id=" + threadIdString);
+ return db.update(MmsProvider.TABLE_PDU, values, finalSelection, selectionArgs)
+ + db.update("sms", values, finalSelection, selectionArgs);
+ }
+
+ /**
+ * Construct Sets of Strings containing exactly the columns
+ * present in each table. We will use this when constructing
+ * UNION queries across the MMS and SMS tables.
+ */
+ private static void initializeColumnSets() {
+ int commonColumnCount = MMS_SMS_COLUMNS.length;
+ int mmsOnlyColumnCount = MMS_ONLY_COLUMNS.length;
+ int smsOnlyColumnCount = SMS_ONLY_COLUMNS.length;
+ Set<String> unionColumns = new HashSet<String>();
+
+ for (int i = 0; i < commonColumnCount; i++) {
+ MMS_COLUMNS.add(MMS_SMS_COLUMNS[i]);
+ SMS_COLUMNS.add(MMS_SMS_COLUMNS[i]);
+ unionColumns.add(MMS_SMS_COLUMNS[i]);
+ }
+ for (int i = 0; i < mmsOnlyColumnCount; i++) {
+ MMS_COLUMNS.add(MMS_ONLY_COLUMNS[i]);
+ unionColumns.add(MMS_ONLY_COLUMNS[i]);
+ }
+ for (int i = 0; i < smsOnlyColumnCount; i++) {
+ SMS_COLUMNS.add(SMS_ONLY_COLUMNS[i]);
+ unionColumns.add(SMS_ONLY_COLUMNS[i]);
+ }
+
+ int i = 0;
+ for (String columnName : unionColumns) {
+ UNION_COLUMNS[i++] = columnName;
+ }
+ }
+}
diff --git a/src/com/android/providers/telephony/SmsProvider.java b/src/com/android/providers/telephony/SmsProvider.java
new file mode 100644
index 0000000..ee60142
--- /dev/null
+++ b/src/com/android/providers/telephony/SmsProvider.java
@@ -0,0 +1,712 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.telephony;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import com.android.internal.database.ArrayListCursor;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.provider.Contacts;
+import android.provider.Telephony.Mms;
+import android.provider.Telephony.MmsSms;
+import android.provider.Telephony.Sms;
+import android.provider.Telephony.TextBasedSmsColumns;
+import android.provider.Telephony.Threads;
+import android.telephony.gsm.SmsManager;
+import android.telephony.gsm.SmsMessage;
+import android.text.TextUtils;
+import android.util.Config;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class SmsProvider extends ContentProvider {
+ private static final Uri NOTIFICATION_URI = Uri.parse("content://sms");
+ private static final Uri SIM_URI = Uri.parse("content://sms/sim");
+ static final String TABLE_SMS = "sms";
+ private static final String TABLE_RAW = "raw";
+ private static final String TABLE_SR_PENDING = "sr_pending";
+
+ private static final Integer ONE = Integer.valueOf(1);
+
+ /**
+ * These are the columns that are available when reading SMS
+ * messages from the SIM. Columns whose names begin with "is_"
+ * have either "true" or "false" as their values.
+ */
+ private final static String[] SIM_COLUMNS = new String[] {
+ // N.B.: These columns must appear in the same order as the
+ // calls to add appear in convertSimToSms.
+ "service_center_address", // getServiceCenterAddress
+ "address", // getDisplayOriginatingAddress
+ "message_class", // getMessageClass
+ "body", // getDisplayMessageBody
+ "date", // getTimestampMillis
+ "status", // getStatusOnSim
+ "index_on_sim", // getIndexOnSim
+ "is_status_report", // isStatusReportMessage
+ "transport_type", // Always "sms".
+ "type" // Always MESSAGE_TYPE_ALL.
+ };
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = MmsSmsDatabaseHelper.getInstance(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn, String selection,
+ String[] selectionArgs, String sort) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+
+ // Generate the body of the query.
+ int match = sURLMatcher.match(url);
+ switch (match) {
+ case SMS_ALL:
+ constructQueryForBox(qb, Sms.MESSAGE_TYPE_ALL);
+ break;
+
+ case SMS_UNDELIVERED:
+ constructQueryForUndelivered(qb);
+ break;
+
+ case SMS_FAILED:
+ constructQueryForBox(qb, Sms.MESSAGE_TYPE_FAILED);
+ break;
+
+ case SMS_QUEUED:
+ constructQueryForBox(qb, Sms.MESSAGE_TYPE_QUEUED);
+ break;
+
+ case SMS_INBOX:
+ constructQueryForBox(qb, Sms.MESSAGE_TYPE_INBOX);
+ break;
+
+ case SMS_SENT:
+ constructQueryForBox(qb, Sms.MESSAGE_TYPE_SENT);
+ break;
+
+ case SMS_DRAFT:
+ constructQueryForBox(qb, Sms.MESSAGE_TYPE_DRAFT);
+ break;
+
+ case SMS_OUTBOX:
+ constructQueryForBox(qb, Sms.MESSAGE_TYPE_OUTBOX);
+ break;
+
+ case SMS_ALL_ID:
+ qb.setTables(TABLE_SMS);
+ qb.appendWhere("(_id = " + url.getPathSegments().get(0) + ")");
+ break;
+
+ case SMS_INBOX_ID:
+ case SMS_FAILED_ID:
+ case SMS_SENT_ID:
+ case SMS_DRAFT_ID:
+ case SMS_OUTBOX_ID:
+ qb.setTables(TABLE_SMS);
+ qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
+ break;
+
+ case SMS_CONVERSATIONS_ID:
+ int threadID;
+
+ try {
+ threadID = Integer.parseInt(url.getPathSegments().get(1));
+ if (Config.LOGD) {
+ Log.d(TAG, "query conversations: threadID=" + threadID);
+ }
+ }
+ catch (Exception ex) {
+ Log.e(TAG,
+ "Bad conversation thread id: "
+ + url.getPathSegments().get(1));
+ return null;
+ }
+
+ qb.setTables(TABLE_SMS);
+ qb.appendWhere("thread_id = " + threadID);
+ break;
+
+ case SMS_CONVERSATIONS:
+ qb.setTables("sms, (SELECT thread_id AS group_thread_id, MAX(date) AS group_date, COUNT(*) AS msg_count FROM sms GROUP BY thread_id) AS groups");
+ qb.appendWhere("sms.thread_id = groups.group_thread_id AND sms.date = groups.group_date");
+ qb.setProjectionMap(sConversationProjectionMap);
+ break;
+
+ case SMS_RAW_MESSAGE:
+ qb.setTables("raw");
+ break;
+
+ case SMS_STATUS_PENDING:
+ qb.setTables("sr_pending");
+ break;
+
+ case SMS_ATTACHMENT:
+ qb.setTables("attachments");
+ break;
+
+ case SMS_ATTACHMENT_ID:
+ qb.setTables("attachments");
+ qb.appendWhere(
+ "(sms_id = " + url.getPathSegments().get(1) + ")");
+ break;
+
+ case SMS_QUERY_THREAD_ID:
+ qb.setTables("canonical_addresses");
+ if (projectionIn == null) {
+ projectionIn = sIDProjection;
+ }
+ break;
+
+ case SMS_STATUS_ID:
+ qb.setTables(TABLE_SMS);
+ qb.appendWhere("(_id = " + url.getPathSegments().get(1) + ")");
+ break;
+
+ case SMS_ALL_SIM:
+ return getAllMessagesFromSim();
+
+ case SMS_SIM:
+ String messageIndexString = url.getPathSegments().get(1);
+
+ return getSingleMessageFromSim(messageIndexString);
+
+ default:
+ Log.e(TAG, "Invalid request: " + url);
+ return null;
+ }
+
+ String orderBy = null;
+
+ if (!TextUtils.isEmpty(sort)) {
+ orderBy = sort;
+ } else if (qb.getTables().equals(TABLE_SMS)) {
+ orderBy = Sms.DEFAULT_SORT_ORDER;
+ }
+
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor ret = qb.query(db, projectionIn, selection, selectionArgs,
+ null, null, orderBy);
+
+ // TODO: Since the URLs are a mess, always use content://sms
+ ret.setNotificationUri(getContext().getContentResolver(),
+ NOTIFICATION_URI);
+ return ret;
+ }
+
+ private ArrayList<String> convertSimToSms(SmsMessage message) {
+ ArrayList result = new ArrayList();
+
+ // N.B.: These calls must appear in the same order as the
+ // columns appear in SIM_COLUMNS.
+ result.add(message.getServiceCenterAddress());
+ result.add(message.getDisplayOriginatingAddress());
+ result.add(message.getMessageClass().toString());
+ result.add(message.getDisplayMessageBody());
+ result.add(message.getTimestampMillis());
+ result.add(Sms.STATUS_NONE);
+ result.add(message.getIndexOnSim());
+ result.add(message.isStatusReportMessage());
+ result.add("sms");
+ result.add(TextBasedSmsColumns.MESSAGE_TYPE_ALL);
+ return result;
+ }
+
+ /**
+ * Return a Cursor containing just one message from the SIM.
+ */
+ private Cursor getSingleMessageFromSim(String messageIndexString) {
+ try {
+ int messageIndex = Integer.parseInt(messageIndexString);
+ SmsManager smsManager = SmsManager.getDefault();
+ ArrayList<SmsMessage> messages = smsManager.getAllMessagesFromSim();
+ ArrayList<ArrayList> singleRow = new ArrayList<ArrayList>();
+
+ SmsMessage message = messages.get(messageIndex);
+ if (message == null) {
+ throw new IllegalArgumentException(
+ "Message not retrieved. ID: " + messageIndexString);
+ }
+ singleRow.add(convertSimToSms(message));
+ return withSimNotificationUri(
+ new ArrayListCursor(SIM_COLUMNS, singleRow));
+ } catch (NumberFormatException exception) {
+ throw new IllegalArgumentException(
+ "Bad SMS SIM ID: " + messageIndexString);
+ }
+ }
+
+ /**
+ * Return a Cursor listing all the messages stored on the SIM.
+ */
+ private Cursor getAllMessagesFromSim() {
+ SmsManager smsManager = SmsManager.getDefault();
+ ArrayList<SmsMessage> messages = smsManager.getAllMessagesFromSim();
+ ArrayList<ArrayList> rows = new ArrayList<ArrayList>();
+
+ for (int count = messages.size(), i = 0; i < count; i++) {
+ SmsMessage message = messages.get(i);
+ if (message != null) {
+ rows.add(convertSimToSms(message));
+ }
+ }
+ return withSimNotificationUri(new ArrayListCursor(SIM_COLUMNS, rows));
+ }
+
+ private Cursor withSimNotificationUri(Cursor cursor) {
+ cursor.setNotificationUri(getContext().getContentResolver(),
+ SIM_URI);
+ return cursor;
+ }
+
+ private void constructQueryForBox(SQLiteQueryBuilder qb, int type) {
+ qb.setTables(TABLE_SMS);
+
+ if (type != Sms.MESSAGE_TYPE_ALL) {
+ qb.appendWhere("type=" + type);
+ }
+ }
+
+ private void constructQueryForUndelivered(SQLiteQueryBuilder qb) {
+ qb.setTables(TABLE_SMS);
+
+ qb.appendWhere("(type=" + Sms.MESSAGE_TYPE_OUTBOX +
+ " OR type=" + Sms.MESSAGE_TYPE_FAILED +
+ " OR type=" + Sms.MESSAGE_TYPE_QUEUED + ")");
+ }
+
+ @Override
+ public String getType(Uri url) {
+ switch (url.getPathSegments().size()) {
+ case 0:
+ return VND_ANDROID_DIR_SMS;
+ case 1:
+ try {
+ Integer.parseInt(url.getPathSegments().get(0));
+ return VND_ANDROID_SMS;
+ } catch (NumberFormatException ex) {
+ return VND_ANDROID_DIR_SMS;
+ }
+ case 2:
+ // TODO: What about "threadID"?
+ if (url.getPathSegments().get(0).equals("conversations")) {
+ return VND_ANDROID_SMSCHAT;
+ } else {
+ return VND_ANDROID_SMS;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues) {
+ ContentValues values;
+ long rowID;
+ int type = Sms.MESSAGE_TYPE_ALL;
+
+ int match = sURLMatcher.match(url);
+ if (Config.LOGD) {
+ Log.d(TAG, "insert url=" + url + ", match=" + match);
+ }
+
+ 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;
+ }
+
+ 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)) && (address != null)) {
+ values.put(Sms.THREAD_ID, Threads.getOrCreateThreadId(
+ getContext(), address));
+ }
+
+ 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 = getContext().getContentResolver().query(
+ Uri.withAppendedPath(
+ Contacts.Phones.CONTENT_FILTER_URL, address),
+ new String[] { Contacts.Phones.PERSON_ID },
+ null, null, null);
+ if (cursor != null) {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ Long id = Long.valueOf(cursor.getLong(0));
+ values.put(Sms.PERSON, id);
+ }
+ cursor.deactivate();
+ }
+ }
+ } else {
+ // Mark all non-inbox messages read.
+ values.put(Sms.READ, ONE);
+ }
+ } else {
+ if (initialValues == null) {
+ values = new ContentValues(1);
+ } else {
+ values = initialValues;
+ }
+ }
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ rowID = db.insert(table, "body", values);
+ if (rowID > 0) {
+ Uri uri = Uri.parse("content://" + table + "/" + rowID);
+ notifyChange(uri);
+ return uri;
+ } else {
+ Log.e(TAG,
+ "SmsProvider.insert: failed! " + values.toString());
+ }
+
+ return null;
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs) {
+ int count;
+ int match = sURLMatcher.match(url);
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ switch (match) {
+ case SMS_ALL:
+ count = db.delete(TABLE_SMS, where, whereArgs);
+ if (count != 0) {
+ // Don't update threads unless something changed.
+ MmsSmsDatabaseHelper.updateAllThreads(db, where, whereArgs);
+ }
+ break;
+
+ case SMS_ALL_ID:
+ try {
+ int message_id = Integer.parseInt(url.getPathSegments().get(0));
+ count = MmsSmsDatabaseHelper.deleteOneSms(db, message_id);
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ "Bad message id: " + url.getPathSegments().get(0));
+ }
+ break;
+
+ case SMS_CONVERSATIONS_ID:
+ int threadID;
+
+ try {
+ threadID = Integer.parseInt(url.getPathSegments().get(1));
+ } catch (Exception ex) {
+ throw new IllegalArgumentException(
+ "Bad conversation thread id: "
+ + url.getPathSegments().get(1));
+ }
+
+ // delete the messages from the sms table
+ where = DatabaseUtils.concatenateWhere("thread_id=" + threadID, where);
+ count = db.delete(TABLE_SMS, where, whereArgs);
+ MmsSmsDatabaseHelper.updateThread(db, threadID);
+ break;
+
+ case SMS_RAW_MESSAGE:
+ count = db.delete("raw", where, whereArgs);
+ break;
+
+ case SMS_STATUS_PENDING:
+ count = db.delete("sr_pending", where, whereArgs);
+ break;
+
+ case SMS_SIM:
+ String messageIndexString = url.getPathSegments().get(1);
+
+ return deleteMessageFromSim(messageIndexString);
+
+ default:
+ throw new IllegalArgumentException("Unknown URL");
+ }
+
+ if (count > 0) {
+ notifyChange(url);
+ }
+ return count;
+ }
+
+ /**
+ * Delete the message at index from SIM. Return true iff
+ * successful.
+ */
+ private int deleteMessageFromSim(String messageIndexString) {
+ SmsManager smsManager = SmsManager.getDefault();
+
+ try {
+ return smsManager.deleteMessageFromSim(
+ Integer.parseInt(messageIndexString))
+ ? 1 : 0;
+ } catch (NumberFormatException exception) {
+ throw new IllegalArgumentException(
+ "Bad SMS SIM ID: " + messageIndexString);
+ } finally {
+ ContentResolver cr = getContext().getContentResolver();
+
+ cr.notifyChange(SIM_URI, null);
+ }
+ }
+
+ @Override
+ public int update(
+ Uri url, ContentValues values, String where, String[] whereArgs) {
+ int count = 0;
+ String table = TABLE_SMS;
+ String extraWhere = null;
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ switch (sURLMatcher.match(url)) {
+ case SMS_RAW_MESSAGE:
+ table = TABLE_RAW;
+ break;
+
+ case SMS_STATUS_PENDING:
+ table = TABLE_SR_PENDING;
+ break;
+
+ case SMS_ALL:
+ case SMS_FAILED:
+ case SMS_QUEUED:
+ case SMS_INBOX:
+ case SMS_SENT:
+ case SMS_DRAFT:
+ case SMS_OUTBOX:
+ case SMS_CONVERSATIONS:
+ break;
+
+ case SMS_ALL_ID:
+ extraWhere = "_id=" + url.getPathSegments().get(0);
+ break;
+
+ case SMS_INBOX_ID:
+ case SMS_FAILED_ID:
+ case SMS_SENT_ID:
+ case SMS_DRAFT_ID:
+ case SMS_OUTBOX_ID:
+ extraWhere = "_id=" + url.getPathSegments().get(1);
+ break;
+
+ case SMS_CONVERSATIONS_ID: {
+ String threadId = url.getPathSegments().get(1);
+
+ try {
+ Integer.parseInt(threadId);
+ } catch (Exception ex) {
+ Log.e(TAG, "Bad conversation thread id: " + threadId);
+ break;
+ }
+
+ extraWhere = "thread_id=" + threadId;
+ break;
+ }
+
+ case SMS_STATUS_ID:
+ extraWhere = "_id=" + url.getPathSegments().get(1);
+ break;
+
+ default:
+ throw new UnsupportedOperationException(
+ "URI " + url + " not supported");
+ }
+
+ where = DatabaseUtils.concatenateWhere(where, extraWhere);
+ count = db.update(table, values, where, whereArgs);
+
+ if (count > 0) {
+ notifyChange(url);
+ }
+ return count;
+ }
+
+ private void notifyChange(Uri uri) {
+ ContentResolver cr = getContext().getContentResolver();
+ cr.notifyChange(uri, null);
+ cr.notifyChange(MmsSms.CONTENT_URI, null);
+ cr.notifyChange(Uri.parse("content://mms-sms/conversations/"), null);
+ }
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ private final static String TAG = "SmsProvider";
+ private final static String VND_ANDROID_SMS = "vnd.android.cursor.item/sms";
+ private final static String VND_ANDROID_SMSCHAT =
+ "vnd.android.cursor.item/sms-chat";
+ private final static String VND_ANDROID_DIR_SMS =
+ "vnd.android.cursor.dir/sms";
+
+ private static final HashMap<String, String> sConversationProjectionMap =
+ new HashMap<String, String>();
+ private static final String[] sIDProjection = new String[] { "_id" };
+
+ private static final int SMS_ALL = 0;
+ private static final int SMS_ALL_ID = 1;
+ private static final int SMS_INBOX = 2;
+ private static final int SMS_INBOX_ID = 3;
+ private static final int SMS_SENT = 4;
+ private static final int SMS_SENT_ID = 5;
+ private static final int SMS_DRAFT = 6;
+ private static final int SMS_DRAFT_ID = 7;
+ private static final int SMS_OUTBOX = 8;
+ private static final int SMS_OUTBOX_ID = 9;
+ private static final int SMS_CONVERSATIONS = 10;
+ private static final int SMS_CONVERSATIONS_ID = 11;
+ private static final int SMS_RAW_MESSAGE = 15;
+ private static final int SMS_ATTACHMENT = 16;
+ private static final int SMS_ATTACHMENT_ID = 17;
+ private static final int SMS_NEW_THREAD_ID = 18;
+ private static final int SMS_QUERY_THREAD_ID = 19;
+ private static final int SMS_STATUS_ID = 20;
+ private static final int SMS_STATUS_PENDING = 21;
+ private static final int SMS_ALL_SIM = 22;
+ private static final int SMS_SIM = 23;
+ private static final int SMS_FAILED = 24;
+ private static final int SMS_FAILED_ID = 25;
+ private static final int SMS_QUEUED = 26;
+ private static final int SMS_UNDELIVERED = 27;
+
+ private static final UriMatcher sURLMatcher =
+ new UriMatcher(UriMatcher.NO_MATCH);
+
+ static {
+ sURLMatcher.addURI("sms", null, SMS_ALL);
+ sURLMatcher.addURI("sms", "#", SMS_ALL_ID);
+ sURLMatcher.addURI("sms", "inbox", SMS_INBOX);
+ sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID);
+ sURLMatcher.addURI("sms", "sent", SMS_SENT);
+ sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID);
+ sURLMatcher.addURI("sms", "draft", SMS_DRAFT);
+ sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID);
+ sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX);
+ sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID);
+ sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED);
+ sURLMatcher.addURI("sms", "failed", SMS_FAILED);
+ sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID);
+ sURLMatcher.addURI("sms", "queued", SMS_QUEUED);
+ sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS);
+ sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID);
+ sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE);
+ sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT);
+ sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID);
+ sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID);
+ sURLMatcher.addURI("sms", "threadID/*", SMS_QUERY_THREAD_ID);
+ sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID);
+ sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING);
+ sURLMatcher.addURI("sms", "sim", SMS_ALL_SIM);
+ sURLMatcher.addURI("sms", "sim/#", SMS_SIM);
+
+ sConversationProjectionMap.put(Sms.Conversations.SNIPPET,
+ "body AS snippet");
+ sConversationProjectionMap.put("delta", null);
+ }
+}
diff --git a/src/com/android/providers/telephony/TelephonyProvider.java b/src/com/android/providers/telephony/TelephonyProvider.java
new file mode 100644
index 0000000..324d007
--- /dev/null
+++ b/src/com/android/providers/telephony/TelephonyProvider.java
@@ -0,0 +1,516 @@
+/* //device/content/providers/telephony/TelephonyProvider.java
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package com.android.providers.telephony;
+
+import android.content.*;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.Telephony;
+import android.util.Config;
+import android.util.Log;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class TelephonyProvider extends ContentProvider
+{
+ private static final String DATABASE_NAME = "telephony.db";
+ // DATABASE_VERSION needs to be in-sync with version in apns.xml.
+ private static final int DATABASE_VERSION = 4 << 16;
+ private static final int URL_TELEPHONY = 1;
+ private static final int URL_CURRENT = 2;
+ private static final int URL_ID = 3;
+ private static final int URL_RESTOREAPN = 4;
+
+ private static final String TAG = "TelephonyProvider";
+ private static final String CARRIERS_TABLE = "carriers";
+ private static final String PARTNER_APNS_PATH = "etc/apns-conf.xml";
+
+ private static final UriMatcher s_urlMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final ContentValues s_currentNullMap;
+ private static final ContentValues s_currentSetMap;
+
+ static {
+ s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
+ s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);
+ s_urlMatcher.addURI("telephony", "carriers/#", URL_ID);
+ s_urlMatcher.addURI("telephony", "carriers/restore", URL_RESTOREAPN);
+
+ s_currentNullMap = new ContentValues(1);
+ s_currentNullMap.put("current", (Long) null);
+
+ s_currentSetMap = new ContentValues(1);
+ s_currentSetMap.put("current", "1");
+ }
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+ // Context to access resources with
+ private Context mContext;
+
+ /**
+ * DatabaseHelper helper class for loading apns into a database.
+ *
+ * @param parser the system-default parser for apns.xml
+ * @param confidential an optional parser for confidential APNS (stored separately)
+ */
+ public DatabaseHelper(Context context) {
+ super(context, DATABASE_NAME, null, getVersion(context));
+ mContext = context;
+ }
+
+ private static int getVersion(Context context) {
+ // Get the database version, combining a static schema version and the XML version
+ Resources r = context.getResources();
+ XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
+ try {
+ XmlUtils.beginDocument(parser, "apns");
+ int publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
+ return DATABASE_VERSION | publicversion;
+ } catch (Exception e) {
+ Log.e(TAG, "Can't get version of APN database", e);
+ return DATABASE_VERSION;
+ } finally {
+ parser.close();
+ }
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // Set up the database schema
+ db.execSQL("CREATE TABLE " + CARRIERS_TABLE +
+ "(_id INTEGER PRIMARY KEY," +
+ "name TEXT," +
+ "numeric TEXT," +
+ "mcc TEXT," +
+ "mnc TEXT," +
+ "apn TEXT," +
+ "user TEXT," +
+ "server TEXT," +
+ "password TEXT," +
+ "proxy TEXT," +
+ "port TEXT," +
+ "mmsproxy TEXT," +
+ "mmsport TEXT," +
+ "mmsc TEXT," +
+ "type TEXT," +
+ "current INTEGER);");
+
+ initDatabase(db);
+ }
+
+ private void initDatabase(SQLiteDatabase db) {
+ // Read internal APNS data
+ Resources r = mContext.getResources();
+ XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
+ int publicversion = -1;
+ try {
+ XmlUtils.beginDocument(parser, "apns");
+ publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
+ loadApns(db, parser);
+ } catch (Exception e) {
+ Log.e(TAG, "Got execption while loading APN database.", e);
+ } finally {
+ parser.close();
+ }
+
+ // Read external APNS data (partner-provided)
+ XmlPullParser confparser = null;
+ // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
+ File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
+ FileReader confreader = null;
+ try {
+ confreader = new FileReader(confFile);
+ confparser = Xml.newPullParser();
+ confparser.setInput(confreader);
+ XmlUtils.beginDocument(confparser, "apns");
+
+ // Sanity check. Force internal version and confidential versions to agree
+ int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
+ if (publicversion != confversion) {
+ throw new IllegalStateException("Internal APNS file version doesn't match "
+ + confFile.getAbsolutePath());
+ }
+
+ loadApns(db, confparser);
+ } catch (FileNotFoundException e) {
+ // It's ok if the file isn't found. It means there isn't a confidential file
+ // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e);
+ } finally {
+ try { if (confreader != null) confreader.close(); } catch (IOException e) { }
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL("DROP TABLE IF EXISTS " + CARRIERS_TABLE + ";");
+ onCreate(db);
+ }
+
+ /**
+ * Gets the next row of apn values.
+ *
+ * @param parser the parser
+ * @return the row or null if it's not an apn
+ */
+ private ContentValues getRow(XmlPullParser parser) {
+ if (!"apn".equals(parser.getName())) {
+ return null;
+ }
+
+ ContentValues map = new ContentValues();
+
+ String mcc = parser.getAttributeValue(null, "mcc");
+ String mnc = parser.getAttributeValue(null, "mnc");
+ String numeric = mcc + mnc;
+
+ map.put(Telephony.Carriers.NUMERIC,numeric);
+ map.put(Telephony.Carriers.MCC, mcc);
+ map.put(Telephony.Carriers.MNC, mnc);
+ map.put(Telephony.Carriers.NAME, parser.getAttributeValue(null, "carrier"));
+ map.put(Telephony.Carriers.APN, parser.getAttributeValue(null, "apn"));
+ map.put(Telephony.Carriers.USER, parser.getAttributeValue(null, "user"));
+ map.put(Telephony.Carriers.SERVER, parser.getAttributeValue(null, "server"));
+ map.put(Telephony.Carriers.PASSWORD, parser.getAttributeValue(null, "password"));
+
+ // do not add NULL to the map so that insert() will set the default value
+ String proxy = parser.getAttributeValue(null, "proxy");
+ if (proxy != null) {
+ map.put(Telephony.Carriers.PROXY, proxy);
+ }
+ String port = parser.getAttributeValue(null, "port");
+ if (port != null) {
+ map.put(Telephony.Carriers.PORT, port);
+ }
+ String mmsproxy = parser.getAttributeValue(null, "mmsproxy");
+ if (mmsproxy != null) {
+ map.put(Telephony.Carriers.MMSPROXY, mmsproxy);
+ }
+ String mmsport = parser.getAttributeValue(null, "mmsport");
+ if (mmsport != null) {
+ map.put(Telephony.Carriers.MMSPORT, mmsport);
+ }
+ map.put(Telephony.Carriers.MMSC, parser.getAttributeValue(null, "mmsc"));
+ String type = parser.getAttributeValue(null, "type");
+ if (type != null) {
+ map.put(Telephony.Carriers.TYPE, type);
+ }
+
+ return map;
+ }
+
+ /*
+ * Loads apns from xml file into the database
+ *
+ * @param db the sqlite database to write to
+ * @param parser the xml parser
+ *
+ */
+ private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
+ if (parser != null) {
+ try {
+ while (true) {
+ XmlUtils.nextElement(parser);
+ ContentValues row = getRow(parser);
+ if (row != null) {
+ db.insert(CARRIERS_TABLE, null, row);
+ } else {
+ break; // do we really want to skip the rest of the file?
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "Got execption while getting perferred time zone.", e);
+ } catch (IOException e) {
+ Log.e(TAG, "Got execption while getting perferred time zone.", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ mOpenHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri url, String[] projectionIn, String selection,
+ String[] selectionArgs, String sort) {
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables("carriers");
+
+ int match = s_urlMatcher.match(url);
+ switch (match) {
+ // do nothing
+ case URL_TELEPHONY: {
+ break;
+ }
+
+
+ case URL_CURRENT: {
+ qb.appendWhere("current IS NOT NULL");
+ break;
+ }
+
+ case URL_ID: {
+ qb.appendWhere("_id = " + url.getPathSegments().get(1));
+ break;
+ }
+
+ default: {
+ return null;
+ }
+ }
+
+ SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+ Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort);
+ ret.setNotificationUri(getContext().getContentResolver(), url);
+ return ret;
+ }
+
+ @Override
+ public String getType(Uri url)
+ {
+ switch (s_urlMatcher.match(url)) {
+ case URL_TELEPHONY:
+ return "vnd.android.cursor.dir/telephony-carrier";
+
+ case URL_ID:
+ return "vnd.android.cursor.item/telephony-carrier";
+
+ default:
+ throw new IllegalArgumentException("Unknown URL " + url);
+ }
+ }
+
+ @Override
+ public Uri insert(Uri url, ContentValues initialValues)
+ {
+ Uri result = null;
+
+ checkPermission();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int match = s_urlMatcher.match(url);
+ boolean notify = false;
+ switch (match)
+ {
+ case URL_TELEPHONY:
+ {
+ ContentValues values;
+ if (initialValues != null) {
+ values = new ContentValues(initialValues);
+ } else {
+ values = new ContentValues();
+ }
+
+ // TODO Review this. This code should probably not bet here.
+ // It is valid for the database to return a null string.
+ if (values.containsKey(Telephony.Carriers.NAME) == false) {
+ values.put(Telephony.Carriers.NAME, "");
+ }
+ if (values.containsKey(Telephony.Carriers.APN) == false) {
+ values.put(Telephony.Carriers.APN, "");
+ }
+ if (values.containsKey(Telephony.Carriers.PORT) == false) {
+ values.put(Telephony.Carriers.PORT, "");
+ }
+ if (values.containsKey(Telephony.Carriers.PROXY) == false) {
+ values.put(Telephony.Carriers.PROXY, "");
+ }
+ if (values.containsKey(Telephony.Carriers.USER) == false) {
+ values.put(Telephony.Carriers.USER, "");
+ }
+ if (values.containsKey(Telephony.Carriers.SERVER) == false) {
+ values.put(Telephony.Carriers.SERVER, "");
+ }
+ if (values.containsKey(Telephony.Carriers.PASSWORD) == false) {
+ values.put(Telephony.Carriers.PASSWORD, "");
+ }
+ if (values.containsKey(Telephony.Carriers.MMSPORT) == false) {
+ values.put(Telephony.Carriers.MMSPORT, "");
+ }
+ if (values.containsKey(Telephony.Carriers.MMSPROXY) == false) {
+ values.put(Telephony.Carriers.MMSPROXY, "");
+ }
+
+ long rowID = db.insert(CARRIERS_TABLE, null, values);
+ if (rowID > 0)
+ {
+ result = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, rowID);
+ notify = true;
+ }
+
+ if (Config.LOGD) Log.d(TAG, "inserted " + values.toString() + " rowID = " + rowID);
+ break;
+ }
+
+ case URL_CURRENT:
+ {
+ // null out the previous operator
+ db.update("carriers", s_currentNullMap, "current IS NOT NULL", null);
+
+ String numeric = initialValues.getAsString("numeric");
+ int updated = db.update("carriers", s_currentSetMap,
+ "numeric = '" + numeric + "'", null);
+
+ if (updated > 0)
+ {
+ if (Config.LOGD) {
+ Log.d(TAG, "Setting numeric '" + numeric + "' to be the current operator");
+ }
+ }
+ else
+ {
+ Log.e(TAG, "Failed setting numeric '" + numeric + "' to the current operator");
+ }
+ break;
+ }
+ }
+
+ if (notify) {
+ getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
+ }
+
+ return result;
+ }
+
+ @Override
+ public int delete(Uri url, String where, String[] whereArgs)
+ {
+ int count;
+
+ checkPermission();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int match = s_urlMatcher.match(url);
+ switch (match)
+ {
+ case URL_TELEPHONY:
+ {
+ count = db.delete(CARRIERS_TABLE, where, whereArgs);
+ break;
+ }
+
+ case URL_CURRENT:
+ {
+ count = db.delete(CARRIERS_TABLE, where, whereArgs);
+ break;
+ }
+
+ case URL_ID:
+ {
+ count = db.delete(CARRIERS_TABLE, Telephony.Carriers._ID + "=?",
+ new String[] { url.getLastPathSegment() });
+ break;
+ }
+
+ case URL_RESTOREAPN: {
+ count = 1;
+ restoreDefaultAPN();
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Cannot delete that URL: " + url);
+ }
+ }
+
+ if (count > 0) {
+ getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
+ }
+
+ return count;
+ }
+
+ @Override
+ public int update(Uri url, ContentValues values, String where, String[] whereArgs)
+ {
+ int count;
+
+ checkPermission();
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ int match = s_urlMatcher.match(url);
+ switch (match)
+ {
+ case URL_TELEPHONY:
+ {
+ count = db.update(CARRIERS_TABLE, values, where, whereArgs);
+ break;
+ }
+
+ case URL_CURRENT:
+ {
+ count = db.update(CARRIERS_TABLE, values, where, whereArgs);
+ break;
+ }
+
+ case URL_ID:
+ {
+ if (where != null || whereArgs != null) {
+ throw new UnsupportedOperationException(
+ "Cannot update URL " + url + " with a where clause");
+ }
+ count = db.update(CARRIERS_TABLE, values, Telephony.Carriers._ID + "=?",
+ new String[] { url.getLastPathSegment() });
+ break;
+ }
+
+ default: {
+ throw new UnsupportedOperationException("Cannot update that URL: " + url);
+ }
+ }
+
+ if (count > 0) {
+ getContext().getContentResolver().notifyChange(Telephony.Carriers.CONTENT_URI, null);
+ }
+
+ return count;
+ }
+
+ private void checkPermission() {
+ // Check the permissions
+ getContext().enforceCallingOrSelfPermission("android.permission.WRITE_APN_SETTINGS",
+ "No permission to write APN settings");
+ }
+
+ private SQLiteOpenHelper mOpenHelper;
+
+ private void restoreDefaultAPN() {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+
+ db.delete(CARRIERS_TABLE, null, null);
+ ((DatabaseHelper) mOpenHelper).initDatabase(db);
+ }
+}