summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/datamodel/action/SyncCursorPair.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/datamodel/action/SyncCursorPair.java')
-rw-r--r--src/com/android/messaging/datamodel/action/SyncCursorPair.java712
1 files changed, 0 insertions, 712 deletions
diff --git a/src/com/android/messaging/datamodel/action/SyncCursorPair.java b/src/com/android/messaging/datamodel/action/SyncCursorPair.java
deleted file mode 100644
index b3a2676..0000000
--- a/src/com/android/messaging/datamodel/action/SyncCursorPair.java
+++ /dev/null
@@ -1,712 +0,0 @@
-/*
- * Copyright (C) 2015 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.messaging.datamodel.action;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.provider.Telephony.Mms;
-import android.provider.Telephony.Sms;
-import android.support.v4.util.LongSparseArray;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.DatabaseHelper;
-import com.android.messaging.datamodel.DatabaseWrapper;
-import com.android.messaging.datamodel.SyncManager;
-import com.android.messaging.datamodel.DatabaseHelper.MessageColumns;
-import com.android.messaging.datamodel.SyncManager.ThreadInfoCache;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.mmslib.SqliteWrapper;
-import com.android.messaging.sms.DatabaseMessages;
-import com.android.messaging.sms.DatabaseMessages.DatabaseMessage;
-import com.android.messaging.sms.DatabaseMessages.LocalDatabaseMessage;
-import com.android.messaging.sms.DatabaseMessages.MmsMessage;
-import com.android.messaging.sms.DatabaseMessages.SmsMessage;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.google.common.collect.Sets;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Set;
-
-/**
- * Class holding a pair of cursors - one for local db and one for telephony provider - allowing
- * synchronous stepping through messages as part of sync.
- */
-class SyncCursorPair {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- static final long SYNC_COMPLETE = -1L;
- static final long SYNC_STARTING = Long.MAX_VALUE;
-
- private CursorIterator mLocalCursorIterator;
- private CursorIterator mRemoteCursorsIterator;
-
- private final String mLocalSelection;
- private final String mRemoteSmsSelection;
- private final String mRemoteMmsSelection;
-
- /**
- * Check if SMS has been synchronized. We compare the counts of messages on both
- * sides and return true if they are equal.
- *
- * Note that this may not be the most reliable way to tell if messages are in sync.
- * For example, the local misses one message and has one obsolete message.
- * However, we have background sms sync once a while, also some other events might
- * trigger a full sync. So we will eventually catch up. And this should be rare to
- * happen.
- *
- * @return If sms is in sync with telephony sms/mms providers
- */
- static boolean allSynchronized(final DatabaseWrapper db) {
- return isSynchronized(db, LOCAL_MESSAGES_SELECTION, null,
- getSmsTypeSelectionSql(), null, getMmsTypeSelectionSql(), null);
- }
-
- SyncCursorPair(final long lowerBound, final long upperBound) {
- mLocalSelection = getTimeConstrainedQuery(
- LOCAL_MESSAGES_SELECTION,
- MessageColumns.RECEIVED_TIMESTAMP,
- lowerBound,
- upperBound,
- null /* threadColumn */, null /* threadId */);
- mRemoteSmsSelection = getTimeConstrainedQuery(
- getSmsTypeSelectionSql(),
- "date",
- lowerBound,
- upperBound,
- null /* threadColumn */, null /* threadId */);
- mRemoteMmsSelection = getTimeConstrainedQuery(
- getMmsTypeSelectionSql(),
- "date",
- ((lowerBound < 0) ? lowerBound : (lowerBound + 999) / 1000), /*seconds*/
- ((upperBound < 0) ? upperBound : (upperBound + 999) / 1000), /*seconds*/
- null /* threadColumn */, null /* threadId */);
- }
-
- SyncCursorPair(final long threadId, final String conversationId) {
- mLocalSelection = getTimeConstrainedQuery(
- LOCAL_MESSAGES_SELECTION,
- MessageColumns.RECEIVED_TIMESTAMP,
- -1L,
- -1L,
- MessageColumns.CONVERSATION_ID, conversationId);
- // Find all SMS messages (excluding drafts) within the sync window
- mRemoteSmsSelection = getTimeConstrainedQuery(
- getSmsTypeSelectionSql(),
- "date",
- -1L,
- -1L,
- Sms.THREAD_ID, Long.toString(threadId));
- mRemoteMmsSelection = getTimeConstrainedQuery(
- getMmsTypeSelectionSql(),
- "date",
- -1L, /*seconds*/
- -1L, /*seconds*/
- Mms.THREAD_ID, Long.toString(threadId));
- }
-
- void query(final DatabaseWrapper db) {
- // Load local messages in the sync window
- mLocalCursorIterator = new LocalCursorIterator(db, mLocalSelection);
- // Load remote messages in the sync window
- mRemoteCursorsIterator = new RemoteCursorsIterator(mRemoteSmsSelection,
- mRemoteMmsSelection);
- }
-
- boolean isSynchronized(final DatabaseWrapper db) {
- return isSynchronized(db, mLocalSelection, null, mRemoteSmsSelection,
- null, mRemoteMmsSelection, null);
- }
-
- void close() {
- if (mLocalCursorIterator != null) {
- mLocalCursorIterator.close();
- }
- if (mRemoteCursorsIterator != null) {
- mRemoteCursorsIterator.close();
- }
- }
-
- long scan(final int maxMessagesToScan,
- final int maxMessagesToUpdate, final ArrayList<SmsMessage> smsToAdd,
- final LongSparseArray<MmsMessage> mmsToAdd,
- final ArrayList<LocalDatabaseMessage> messagesToDelete,
- final SyncManager.ThreadInfoCache threadInfoCache) {
- // Set of local messages matched with the timestamp of a remote message
- final Set<DatabaseMessage> matchedLocalMessages = Sets.newHashSet();
- // Set of remote messages matched with the timestamp of a local message
- final Set<DatabaseMessage> matchedRemoteMessages = Sets.newHashSet();
- long lastTimestampMillis = SYNC_STARTING;
- // Number of messages scanned local and remote
- int localCount = 0;
- int remoteCount = 0;
- // Seed the initial values of remote and local messages for comparison
- DatabaseMessage remoteMessage = mRemoteCursorsIterator.next();
- DatabaseMessage localMessage = mLocalCursorIterator.next();
- // Iterate through messages on both sides in reverse time order
- // Import messages in remote not in local, delete messages in local not in remote
- while (localCount + remoteCount < maxMessagesToScan && smsToAdd.size()
- + mmsToAdd.size() + messagesToDelete.size() < maxMessagesToUpdate) {
- if (remoteMessage == null && localMessage == null) {
- // No more message on both sides - scan complete
- lastTimestampMillis = SYNC_COMPLETE;
- break;
- } else if ((remoteMessage == null && localMessage != null) ||
- (localMessage != null && remoteMessage != null &&
- localMessage.getTimestampInMillis()
- > remoteMessage.getTimestampInMillis())) {
- // Found a local message that is not in remote db
- // Delete the local message
- messagesToDelete.add((LocalDatabaseMessage) localMessage);
- lastTimestampMillis = Math.min(lastTimestampMillis,
- localMessage.getTimestampInMillis());
- // Advance to next local message
- localMessage = mLocalCursorIterator.next();
- localCount += 1;
- } else if ((localMessage == null && remoteMessage != null) ||
- (localMessage != null && remoteMessage != null &&
- localMessage.getTimestampInMillis()
- < remoteMessage.getTimestampInMillis())) {
- // Found a remote message that is not in local db
- // Add the remote message
- saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache);
- lastTimestampMillis = Math.min(lastTimestampMillis,
- remoteMessage.getTimestampInMillis());
- // Advance to next remote message
- remoteMessage = mRemoteCursorsIterator.next();
- remoteCount += 1;
- } else {
- // Found remote and local messages at the same timestamp
- final long matchedTimestamp = localMessage.getTimestampInMillis();
- lastTimestampMillis = Math.min(lastTimestampMillis, matchedTimestamp);
- // Get the next local and remote messages
- final DatabaseMessage remoteMessagePeek = mRemoteCursorsIterator.next();
- final DatabaseMessage localMessagePeek = mLocalCursorIterator.next();
- // Check if only one message on each side matches the current timestamp
- // by looking at the next messages on both sides. If they are either null
- // (meaning no more messages) or having a different timestamp. We want
- // to optimize for this since this is the most common case when majority
- // of the messages are in sync (so they one-to-one pair up at each timestamp),
- // by not allocating the data structures required to compare a set of
- // messages from both sides.
- if ((remoteMessagePeek == null ||
- remoteMessagePeek.getTimestampInMillis() != matchedTimestamp) &&
- (localMessagePeek == null ||
- localMessagePeek.getTimestampInMillis() != matchedTimestamp)) {
- // Optimize the common case where only one message on each side
- // that matches the same timestamp
- if (!remoteMessage.equals(localMessage)) {
- // local != remote
- // Delete local message
- messagesToDelete.add((LocalDatabaseMessage) localMessage);
- // Add remote message
- saveMessageToAdd(smsToAdd, mmsToAdd, remoteMessage, threadInfoCache);
- }
- // Get next local and remote messages
- localMessage = localMessagePeek;
- remoteMessage = remoteMessagePeek;
- localCount += 1;
- remoteCount += 1;
- } else {
- // Rare case in which multiple messages are in the same timestamp
- // on either or both sides
- // Gather all the matched remote messages
- matchedRemoteMessages.clear();
- matchedRemoteMessages.add(remoteMessage);
- remoteCount += 1;
- remoteMessage = remoteMessagePeek;
- while (remoteMessage != null &&
- remoteMessage.getTimestampInMillis() == matchedTimestamp) {
- Assert.isTrue(!matchedRemoteMessages.contains(remoteMessage));
- matchedRemoteMessages.add(remoteMessage);
- remoteCount += 1;
- remoteMessage = mRemoteCursorsIterator.next();
- }
- // Gather all the matched local messages
- matchedLocalMessages.clear();
- matchedLocalMessages.add(localMessage);
- localCount += 1;
- localMessage = localMessagePeek;
- while (localMessage != null &&
- localMessage.getTimestampInMillis() == matchedTimestamp) {
- if (matchedLocalMessages.contains(localMessage)) {
- // Duplicate message is local database is deleted
- messagesToDelete.add((LocalDatabaseMessage) localMessage);
- } else {
- matchedLocalMessages.add(localMessage);
- }
- localCount += 1;
- localMessage = mLocalCursorIterator.next();
- }
- // Delete messages local only
- for (final DatabaseMessage msg : Sets.difference(
- matchedLocalMessages, matchedRemoteMessages)) {
- messagesToDelete.add((LocalDatabaseMessage) msg);
- }
- // Add messages remote only
- for (final DatabaseMessage msg : Sets.difference(
- matchedRemoteMessages, matchedLocalMessages)) {
- saveMessageToAdd(smsToAdd, mmsToAdd, msg, threadInfoCache);
- }
- }
- }
- }
- return lastTimestampMillis;
- }
-
- DatabaseMessage getLocalMessage() {
- return mLocalCursorIterator.next();
- }
-
- DatabaseMessage getRemoteMessage() {
- return mRemoteCursorsIterator.next();
- }
-
- int getLocalPosition() {
- return mLocalCursorIterator.getPosition();
- }
-
- int getRemotePosition() {
- return mRemoteCursorsIterator.getPosition();
- }
-
- int getLocalCount() {
- return mLocalCursorIterator.getCount();
- }
-
- int getRemoteCount() {
- return mRemoteCursorsIterator.getCount();
- }
-
- /**
- * An iterator for a database cursor
- */
- interface CursorIterator {
- /**
- * Move to next element in the cursor
- *
- * @return The next element (which becomes the current)
- */
- public DatabaseMessage next();
- /**
- * Close the cursor
- */
- public void close();
- /**
- * Get the position
- */
- public int getPosition();
- /**
- * Get the count
- */
- public int getCount();
- }
-
- private static final String ORDER_BY_DATE_DESC = "date DESC";
-
- // A subquery that selects SMS/MMS messages in Bugle which are also in telephony
- private static final String LOCAL_MESSAGES_SELECTION = String.format(
- Locale.US,
- "(%s NOTNULL)",
- MessageColumns.SMS_MESSAGE_URI);
-
- private static final String ORDER_BY_TIMESTAMP_DESC =
- MessageColumns.RECEIVED_TIMESTAMP + " DESC";
-
- // TODO : This should move into the provider
- private static class LocalMessageQuery {
- private static final String[] PROJECTION = new String[] {
- MessageColumns._ID,
- MessageColumns.RECEIVED_TIMESTAMP,
- MessageColumns.SMS_MESSAGE_URI,
- MessageColumns.PROTOCOL,
- MessageColumns.CONVERSATION_ID,
- };
- private static final int INDEX_MESSAGE_ID = 0;
- private static final int INDEX_MESSAGE_TIMESTAMP = 1;
- private static final int INDEX_SMS_MESSAGE_URI = 2;
- private static final int INDEX_MESSAGE_SMS_TYPE = 3;
- private static final int INDEX_CONVERSATION_ID = 4;
- }
-
- /**
- * This class provides the same DatabaseMessage interface over a local SMS db message
- */
- private static LocalDatabaseMessage getLocalDatabaseMessage(final Cursor cursor) {
- if (cursor == null) {
- return null;
- }
- return new LocalDatabaseMessage(
- cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_ID),
- cursor.getInt(LocalMessageQuery.INDEX_MESSAGE_SMS_TYPE),
- cursor.getString(LocalMessageQuery.INDEX_SMS_MESSAGE_URI),
- cursor.getLong(LocalMessageQuery.INDEX_MESSAGE_TIMESTAMP),
- cursor.getString(LocalMessageQuery.INDEX_CONVERSATION_ID));
- }
-
- /**
- * The buffered cursor iterator for local SMS
- */
- private static class LocalCursorIterator implements CursorIterator {
- private Cursor mCursor;
- private final DatabaseWrapper mDatabase;
-
- LocalCursorIterator(final DatabaseWrapper database, final String selection)
- throws SQLiteException {
- mDatabase = database;
- try {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncCursorPair: Querying for local messages; selection = "
- + selection);
- }
- mCursor = mDatabase.query(
- DatabaseHelper.MESSAGES_TABLE,
- LocalMessageQuery.PROJECTION,
- selection,
- null /*selectionArgs*/,
- null/*groupBy*/,
- null/*having*/,
- ORDER_BY_TIMESTAMP_DESC);
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "SyncCursorPair: failed to query local sms/mms", e);
- // Can't query local database. So let's throw up the exception and abort sync
- // because we may end up import duplicate messages.
- throw e;
- }
- }
-
- @Override
- public DatabaseMessage next() {
- if (mCursor != null && mCursor.moveToNext()) {
- return getLocalDatabaseMessage(mCursor);
- }
- return null;
- }
-
- @Override
- public int getCount() {
- return (mCursor == null ? 0 : mCursor.getCount());
- }
-
- @Override
- public int getPosition() {
- return (mCursor == null ? 0 : mCursor.getPosition());
- }
-
- @Override
- public void close() {
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- }
- }
-
- /**
- * The cursor iterator for remote sms.
- * Since SMS and MMS are stored in different tables in telephony provider,
- * this class merges the two cursors and provides a unified view of messages
- * from both cursors. Note that the order is DESC.
- */
- private static class RemoteCursorsIterator implements CursorIterator {
- private Cursor mSmsCursor;
- private Cursor mMmsCursor;
- private DatabaseMessage mNextSms;
- private DatabaseMessage mNextMms;
-
- RemoteCursorsIterator(final String smsSelection, final String mmsSelection)
- throws SQLiteException {
- mSmsCursor = null;
- mMmsCursor = null;
- try {
- final Context context = Factory.get().getApplicationContext();
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncCursorPair: Querying for remote SMS; selection = "
- + smsSelection);
- }
- mSmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Sms.CONTENT_URI,
- SmsMessage.getProjection(),
- smsSelection,
- null /* selectionArgs */,
- ORDER_BY_DATE_DESC);
- if (mSmsCursor == null) {
- LogUtil.w(TAG, "SyncCursorPair: Remote SMS query returned null cursor; "
- + "need to cancel sync");
- throw new RuntimeException("Null cursor from remote SMS query");
- }
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "SyncCursorPair: Querying for remote MMS; selection = "
- + mmsSelection);
- }
- mMmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Mms.CONTENT_URI,
- DatabaseMessages.MmsMessage.getProjection(),
- mmsSelection,
- null /* selectionArgs */,
- ORDER_BY_DATE_DESC);
- if (mMmsCursor == null) {
- LogUtil.w(TAG, "SyncCursorPair: Remote MMS query returned null cursor; "
- + "need to cancel sync");
- throw new RuntimeException("Null cursor from remote MMS query");
- }
- // Move to the first element in the combined stream from both cursors
- mNextSms = getSmsCursorNext();
- mNextMms = getMmsCursorNext();
- } catch (final SQLiteException e) {
- LogUtil.e(TAG, "SyncCursorPair: failed to query remote messages", e);
- // If we ignore this, the following code would think there is no remote message
- // and will delete all the local sms. We should be cautious here. So instead,
- // let's throw the exception to the caller and abort sms sync. We do the same
- // thing if either of the remote cursors is null.
- throw e;
- }
- }
-
- @Override
- public DatabaseMessage next() {
- DatabaseMessage result = null;
- if (mNextSms != null && mNextMms != null) {
- if (mNextSms.getTimestampInMillis() >= mNextMms.getTimestampInMillis()) {
- result = mNextSms;
- mNextSms = getSmsCursorNext();
- } else {
- result = mNextMms;
- mNextMms = getMmsCursorNext();
- }
- } else {
- if (mNextSms != null) {
- result = mNextSms;
- mNextSms = getSmsCursorNext();
- } else {
- result = mNextMms;
- mNextMms = getMmsCursorNext();
- }
- }
- return result;
- }
-
- private DatabaseMessage getSmsCursorNext() {
- if (mSmsCursor != null && mSmsCursor.moveToNext()) {
- return SmsMessage.get(mSmsCursor);
- }
- return null;
- }
-
- private DatabaseMessage getMmsCursorNext() {
- if (mMmsCursor != null && mMmsCursor.moveToNext()) {
- return MmsMessage.get(mMmsCursor);
- }
- return null;
- }
-
- @Override
- // Return approximate cursor position allowing for read ahead on two cursors (hence -1)
- public int getPosition() {
- return (mSmsCursor == null ? 0 : mSmsCursor.getPosition()) +
- (mMmsCursor == null ? 0 : mMmsCursor.getPosition()) - 1;
- }
-
- @Override
- public int getCount() {
- return (mSmsCursor == null ? 0 : mSmsCursor.getCount()) +
- (mMmsCursor == null ? 0 : mMmsCursor.getCount());
- }
-
- @Override
- public void close() {
- if (mSmsCursor != null) {
- mSmsCursor.close();
- mSmsCursor = null;
- }
- if (mMmsCursor != null) {
- mMmsCursor.close();
- mMmsCursor = null;
- }
- }
- }
-
- /**
- * Type selection for importing sms messages. Only SENT and INBOX messages are imported.
- *
- * @return The SQL selection for importing sms messages
- */
- public static String getSmsTypeSelectionSql() {
- return MmsUtils.getSmsTypeSelectionSql();
- }
-
- /**
- * Type selection for importing mms messages.
- *
- * Criteria:
- * MESSAGE_BOX is INBOX, SENT or OUTBOX
- * MESSAGE_TYPE is SEND_REQ (sent), RETRIEVE_CONF (received) or NOTIFICATION_IND (download)
- *
- * @return The SQL selection for importing mms messages. This selects the message type,
- * not including the selection on timestamp.
- */
- public static String getMmsTypeSelectionSql() {
- return MmsUtils.getMmsTypeSelectionSql();
- }
-
- /**
- * Get a SQL selection string using an existing selection and time window limits
- * The limits are not applied if the value is < 0
- *
- * @param typeSelection The existing selection
- * @param from The inclusive lower bound
- * @param to The exclusive upper bound
- * @return The created SQL selection
- */
- private static String getTimeConstrainedQuery(final String typeSelection,
- final String timeColumn, final long from, final long to,
- final String threadColumn, final String threadId) {
- final StringBuilder queryBuilder = new StringBuilder();
- queryBuilder.append(typeSelection);
- if (from > 0) {
- queryBuilder.append(" AND ").append(timeColumn).append(">=").append(from);
- }
- if (to > 0) {
- queryBuilder.append(" AND ").append(timeColumn).append("<").append(to);
- }
- if (!TextUtils.isEmpty(threadColumn) && !TextUtils.isEmpty(threadId)) {
- queryBuilder.append(" AND ").append(threadColumn).append("=").append(threadId);
- }
- return queryBuilder.toString();
- }
-
- private static final String[] COUNT_PROJECTION = new String[] { "count()" };
-
- private static int getCountFromCursor(final Cursor cursor) {
- if (cursor != null && cursor.moveToFirst()) {
- return cursor.getInt(0);
- }
- // We should only return a number if we were able to read it from the cursor.
- // Otherwise, we throw an exception to cancel the sync.
- String cursorDesc = "";
- if (cursor == null) {
- cursorDesc = "null";
- } else if (cursor.getCount() == 0) {
- cursorDesc = "empty";
- }
- throw new IllegalArgumentException("Cannot get count from " + cursorDesc + " cursor");
- }
-
- private void saveMessageToAdd(final List<SmsMessage> smsToAdd,
- final LongSparseArray<MmsMessage> mmsToAdd, final DatabaseMessage message,
- final ThreadInfoCache threadInfoCache) {
- long threadId;
- if (message.getProtocol() == MessageData.PROTOCOL_MMS) {
- final MmsMessage mms = (MmsMessage) message;
- mmsToAdd.append(mms.getId(), mms);
- threadId = mms.mThreadId;
- } else {
- final SmsMessage sms = (SmsMessage) message;
- smsToAdd.add(sms);
- threadId = sms.mThreadId;
- }
- // Cache the lookup and canonicalization of the phone number outside of the transaction...
- threadInfoCache.getThreadRecipients(threadId);
- }
-
- /**
- * Check if SMS has been synchronized. We compare the counts of messages on both
- * sides and return true if they are equal.
- *
- * Note that this may not be the most reliable way to tell if messages are in sync.
- * For example, the local misses one message and has one obsolete message.
- * However, we have background sms sync once a while, also some other events might
- * trigger a full sync. So we will eventually catch up. And this should be rare to
- * happen.
- *
- * @return If sms is in sync with telephony sms/mms providers
- */
- private static boolean isSynchronized(final DatabaseWrapper db, final String localSelection,
- final String[] localSelectionArgs, final String smsSelection,
- final String[] smsSelectionArgs, final String mmsSelection,
- final String[] mmsSelectionArgs) {
- final Context context = Factory.get().getApplicationContext();
- Cursor localCursor = null;
- Cursor remoteSmsCursor = null;
- Cursor remoteMmsCursor = null;
- try {
- localCursor = db.query(
- DatabaseHelper.MESSAGES_TABLE,
- COUNT_PROJECTION,
- localSelection,
- localSelectionArgs,
- null/*groupBy*/,
- null/*having*/,
- null/*orderBy*/);
- final int localCount = getCountFromCursor(localCursor);
- remoteSmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Sms.CONTENT_URI,
- COUNT_PROJECTION,
- smsSelection,
- smsSelectionArgs,
- null/*orderBy*/);
- final int smsCount = getCountFromCursor(remoteSmsCursor);
- remoteMmsCursor = SqliteWrapper.query(
- context,
- context.getContentResolver(),
- Mms.CONTENT_URI,
- COUNT_PROJECTION,
- mmsSelection,
- mmsSelectionArgs,
- null/*orderBy*/);
- final int mmsCount = getCountFromCursor(remoteMmsCursor);
- final int remoteCount = smsCount + mmsCount;
- final boolean isInSync = (localCount == remoteCount);
- if (isInSync) {
- if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
- LogUtil.d(TAG, "SyncCursorPair: Same # of local and remote messages = "
- + localCount);
- }
- } else {
- LogUtil.i(TAG, "SyncCursorPair: Not in sync; # local messages = " + localCount
- + ", # remote message = " + remoteCount);
- }
- return isInSync;
- } catch (final Exception e) {
- LogUtil.e(TAG, "SyncCursorPair: failed to query local or remote message counts", e);
- // If something is wrong in querying database, assume we are synced so
- // we don't retry indefinitely
- } finally {
- if (localCursor != null) {
- localCursor.close();
- }
- if (remoteSmsCursor != null) {
- remoteSmsCursor.close();
- }
- if (remoteMmsCursor != null) {
- remoteMmsCursor.close();
- }
- }
- return true;
- }
-}