Revert "Initial checkin of AOSP Messaging app."
This reverts commit 461a34b466.
Change-Id: Iac4ca77eeaa94989e91dead49a7959c905bd3078
This commit is contained in:
@@ -1,478 +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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.provider.Telephony;
|
||||
import android.support.v4.util.LongSparseArray;
|
||||
|
||||
import com.android.messaging.datamodel.action.SyncMessagesAction;
|
||||
import com.android.messaging.datamodel.data.ParticipantData;
|
||||
import com.android.messaging.sms.MmsUtils;
|
||||
import com.android.messaging.util.Assert;
|
||||
import com.android.messaging.util.BugleGservices;
|
||||
import com.android.messaging.util.BugleGservicesKeys;
|
||||
import com.android.messaging.util.BuglePrefs;
|
||||
import com.android.messaging.util.BuglePrefsKeys;
|
||||
import com.android.messaging.util.LogUtil;
|
||||
import com.android.messaging.util.OsUtil;
|
||||
import com.android.messaging.util.PhoneUtils;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class manages message sync with the Telephony SmsProvider/MmsProvider.
|
||||
*/
|
||||
public class SyncManager {
|
||||
private static final String TAG = LogUtil.BUGLE_TAG;
|
||||
|
||||
/**
|
||||
* Record of any user customization to conversation settings
|
||||
*/
|
||||
public static class ConversationCustomization {
|
||||
private final boolean mArchived;
|
||||
private final boolean mMuted;
|
||||
private final boolean mNoVibrate;
|
||||
private final String mNotificationSoundUri;
|
||||
|
||||
public ConversationCustomization(final boolean archived, final boolean muted,
|
||||
final boolean noVibrate, final String notificationSoundUri) {
|
||||
mArchived = archived;
|
||||
mMuted = muted;
|
||||
mNoVibrate = noVibrate;
|
||||
mNotificationSoundUri = notificationSoundUri;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return mArchived;
|
||||
}
|
||||
|
||||
public boolean isMuted() {
|
||||
return mMuted;
|
||||
}
|
||||
|
||||
public boolean noVibrate() {
|
||||
return mNoVibrate;
|
||||
}
|
||||
|
||||
public String getNotificationSoundUri() {
|
||||
return mNotificationSoundUri;
|
||||
}
|
||||
}
|
||||
|
||||
SyncManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of in progress sync - used to keep track of whether sync is running
|
||||
*/
|
||||
private long mSyncInProgressTimestamp = -1;
|
||||
|
||||
/**
|
||||
* Timestamp of current sync batch upper bound - used to determine if message makes batch dirty
|
||||
*/
|
||||
private long mCurrentUpperBoundTimestamp = -1;
|
||||
|
||||
/**
|
||||
* Timestamp of messages inserted since sync batch started - used to determine if batch dirty
|
||||
*/
|
||||
private long mMaxRecentChangeTimestamp = -1L;
|
||||
|
||||
private final ThreadInfoCache mThreadInfoCache = new ThreadInfoCache();
|
||||
|
||||
/**
|
||||
* User customization to conversations. If this is set, we need to recover them after
|
||||
* a full sync.
|
||||
*/
|
||||
private LongSparseArray<ConversationCustomization> mCustomization = null;
|
||||
|
||||
/**
|
||||
* Start an incremental sync (backed off a few seconds)
|
||||
*/
|
||||
public static void sync() {
|
||||
SyncMessagesAction.sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an incremental sync (with no backoff)
|
||||
*/
|
||||
public static void immediateSync() {
|
||||
SyncMessagesAction.immediateSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a full sync (for debugging)
|
||||
*/
|
||||
public static void forceSync() {
|
||||
SyncMessagesAction.fullSync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from data model thread when starting a sync batch
|
||||
* @param upperBoundTimestamp upper bound timestamp for sync batch
|
||||
*/
|
||||
public synchronized void startSyncBatch(final long upperBoundTimestamp) {
|
||||
Assert.isTrue(mCurrentUpperBoundTimestamp < 0);
|
||||
mCurrentUpperBoundTimestamp = upperBoundTimestamp;
|
||||
mMaxRecentChangeTimestamp = -1L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from data model thread at end of batch to determine if any messages added in window
|
||||
* @param lowerBoundTimestamp lower bound timestamp for sync batch
|
||||
* @return true if message added within window from lower to upper bound timestamp of batch
|
||||
*/
|
||||
public synchronized boolean isBatchDirty(final long lowerBoundTimestamp) {
|
||||
Assert.isTrue(mCurrentUpperBoundTimestamp >= 0);
|
||||
final long max = mMaxRecentChangeTimestamp;
|
||||
|
||||
final boolean dirty = (max >= 0 && max >= lowerBoundTimestamp);
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: Sync batch of messages from " + lowerBoundTimestamp
|
||||
+ " to " + mCurrentUpperBoundTimestamp + " is "
|
||||
+ (dirty ? "DIRTY" : "clean") + "; max change timestamp = "
|
||||
+ mMaxRecentChangeTimestamp);
|
||||
}
|
||||
|
||||
mCurrentUpperBoundTimestamp = -1L;
|
||||
mMaxRecentChangeTimestamp = -1L;
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from data model or background worker thread to indicate start of message add process
|
||||
* (add must complete on that thread before action transitions to new thread/stage)
|
||||
* @param timestamp timestamp of message being added
|
||||
*/
|
||||
public synchronized void onNewMessageInserted(final long timestamp) {
|
||||
if (mCurrentUpperBoundTimestamp >= 0 && timestamp <= mCurrentUpperBoundTimestamp) {
|
||||
// Message insert in current sync window
|
||||
mMaxRecentChangeTimestamp = Math.max(mCurrentUpperBoundTimestamp, timestamp);
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " before upper bound of "
|
||||
+ "current sync batch " + mCurrentUpperBoundTimestamp);
|
||||
}
|
||||
} else if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: New message @ " + timestamp + " after upper bound of "
|
||||
+ "current sync batch " + mCurrentUpperBoundTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously checks whether sync is allowed and starts sync if allowed
|
||||
* @param full - true indicates a full (not incremental) sync operation
|
||||
* @param startTimestamp - starttimestamp for this sync (if allowed)
|
||||
* @return - true if sync should start
|
||||
*/
|
||||
public synchronized boolean shouldSync(final boolean full, final long startTimestamp) {
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
|
||||
LogUtil.v(TAG, "SyncManager: Checking shouldSync " + (full ? "full " : "")
|
||||
+ "at " + startTimestamp);
|
||||
}
|
||||
|
||||
if (full) {
|
||||
final long delayUntilFullSync = delayUntilFullSync(startTimestamp);
|
||||
if (delayUntilFullSync > 0) {
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: Full sync requested for " + startTimestamp
|
||||
+ " delayed for " + delayUntilFullSync + " ms");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSyncing()) {
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: Not allowed to " + (full ? "full " : "")
|
||||
+ "sync yet; still running sync started at " + mSyncInProgressTimestamp);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: Starting " + (full ? "full " : "") + "sync at "
|
||||
+ startTimestamp);
|
||||
}
|
||||
|
||||
mSyncInProgressTimestamp = startTimestamp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return delay (in ms) until allowed to run a full sync (0 meaning can run immediately)
|
||||
* @param startTimestamp Timestamp used to start the sync
|
||||
* @return 0 if allowed to run now, else delay in ms
|
||||
*/
|
||||
public long delayUntilFullSync(final long startTimestamp) {
|
||||
final BugleGservices bugleGservices = BugleGservices.get();
|
||||
final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
|
||||
|
||||
final long lastFullSyncTime = prefs.getLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME, -1L);
|
||||
final long smsFullSyncBackoffTimeMillis = bugleGservices.getLong(
|
||||
BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS,
|
||||
BugleGservicesKeys.SMS_FULL_SYNC_BACKOFF_TIME_MILLIS_DEFAULT);
|
||||
final long noFullSyncBefore = (lastFullSyncTime < 0 ? startTimestamp :
|
||||
lastFullSyncTime + smsFullSyncBackoffTimeMillis);
|
||||
|
||||
final long delayUntilFullSync = noFullSyncBefore - startTimestamp;
|
||||
if (delayUntilFullSync > 0) {
|
||||
return delayUntilFullSync;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sync currently in progress (public for asserts/logging).
|
||||
*/
|
||||
public synchronized boolean isSyncing() {
|
||||
return (mSyncInProgressTimestamp >= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sync batch should be in progress - compares upperBound with in memory value
|
||||
* @param upperBoundTimestamp - upperbound timestamp for sync batch
|
||||
* @return - true if timestamps match (otherwise batch is orphan from older process)
|
||||
*/
|
||||
public synchronized boolean isSyncing(final long upperBoundTimestamp) {
|
||||
Assert.isTrue(upperBoundTimestamp >= 0);
|
||||
return (upperBoundTimestamp == mCurrentUpperBoundTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if sync has completed for the first time.
|
||||
*/
|
||||
public boolean getHasFirstSyncCompleted() {
|
||||
final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
|
||||
return prefs.getLong(BuglePrefsKeys.LAST_SYNC_TIME,
|
||||
BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT) !=
|
||||
BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once sync is complete
|
||||
*/
|
||||
public synchronized void complete() {
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: Sync started at " + mSyncInProgressTimestamp
|
||||
+ " marked as complete");
|
||||
}
|
||||
mSyncInProgressTimestamp = -1L;
|
||||
// Conversation customization only used once
|
||||
mCustomization = null;
|
||||
}
|
||||
|
||||
private final ContentObserver mMmsSmsObserver = new TelephonyMessagesObserver();
|
||||
private boolean mSyncOnChanges = false;
|
||||
private boolean mNotifyOnChanges = false;
|
||||
|
||||
/**
|
||||
* Register content observer when necessary and kick off a catch up sync
|
||||
*/
|
||||
public void updateSyncObserver(final Context context) {
|
||||
registerObserver(context);
|
||||
// Trigger an sms sync in case we missed and messages before registering this observer or
|
||||
// becoming the SMS provider.
|
||||
immediateSync();
|
||||
}
|
||||
|
||||
private void registerObserver(final Context context) {
|
||||
if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
|
||||
// Not default SMS app - need to actively monitor telephony but not notify
|
||||
mNotifyOnChanges = false;
|
||||
mSyncOnChanges = true;
|
||||
} else if (OsUtil.isSecondaryUser()){
|
||||
// Secondary users default SMS app - need to actively monitor telephony and notify
|
||||
mNotifyOnChanges = true;
|
||||
mSyncOnChanges = true;
|
||||
} else {
|
||||
// Primary users default SMS app - don't monitor telephony (most changes from this app)
|
||||
mNotifyOnChanges = false;
|
||||
mSyncOnChanges = false;
|
||||
}
|
||||
if (mNotifyOnChanges || mSyncOnChanges) {
|
||||
context.getContentResolver().registerContentObserver(Telephony.MmsSms.CONTENT_URI,
|
||||
true, mMmsSmsObserver);
|
||||
} else {
|
||||
context.getContentResolver().unregisterContentObserver(mMmsSmsObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setCustomization(
|
||||
final LongSparseArray<ConversationCustomization> customization) {
|
||||
this.mCustomization = customization;
|
||||
}
|
||||
|
||||
public synchronized ConversationCustomization getCustomizationForThread(final long threadId) {
|
||||
if (mCustomization != null) {
|
||||
return mCustomization.get(threadId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void resetLastSyncTimestamps() {
|
||||
final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
|
||||
prefs.putLong(BuglePrefsKeys.LAST_FULL_SYNC_TIME,
|
||||
BuglePrefsKeys.LAST_FULL_SYNC_TIME_DEFAULT);
|
||||
prefs.putLong(BuglePrefsKeys.LAST_SYNC_TIME, BuglePrefsKeys.LAST_SYNC_TIME_DEFAULT);
|
||||
}
|
||||
|
||||
private class TelephonyMessagesObserver extends ContentObserver {
|
||||
public TelephonyMessagesObserver() {
|
||||
// Just run on default thread
|
||||
super(null);
|
||||
}
|
||||
|
||||
// Implement the onChange(boolean) method to delegate the change notification to
|
||||
// the onChange(boolean, Uri) method to ensure correct operation on older versions
|
||||
// of the framework that did not have the onChange(boolean, Uri) method.
|
||||
@Override
|
||||
public void onChange(final boolean selfChange) {
|
||||
onChange(selfChange, null);
|
||||
}
|
||||
|
||||
// Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
|
||||
@Override
|
||||
public void onChange(final boolean selfChange, final Uri uri) {
|
||||
// Handle change.
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
|
||||
LogUtil.v(TAG, "SyncManager: Sms/Mms DB changed @" + System.currentTimeMillis()
|
||||
+ " for " + (uri == null ? "<unk>" : uri.toString()) + " "
|
||||
+ mSyncOnChanges + "/" + mNotifyOnChanges);
|
||||
}
|
||||
|
||||
if (mSyncOnChanges) {
|
||||
// If sync is already running this will do nothing - but at end of each sync
|
||||
// action there is a check for recent messages that should catch new changes.
|
||||
SyncManager.immediateSync();
|
||||
}
|
||||
if (mNotifyOnChanges) {
|
||||
// TODO: Secondary users are not going to get notifications
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ThreadInfoCache getThreadInfoCache() {
|
||||
return mThreadInfoCache;
|
||||
}
|
||||
|
||||
public static class ThreadInfoCache {
|
||||
// Cache of thread->conversationId map
|
||||
private final LongSparseArray<String> mThreadToConversationId =
|
||||
new LongSparseArray<String>();
|
||||
|
||||
// Cache of thread->recipients map
|
||||
private final LongSparseArray<List<String>> mThreadToRecipients =
|
||||
new LongSparseArray<List<String>>();
|
||||
|
||||
// Remember the conversation ids that need to be archived
|
||||
private final HashSet<String> mArchivedConversations = new HashSet<>();
|
||||
|
||||
public synchronized void clear() {
|
||||
if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
|
||||
LogUtil.d(TAG, "SyncManager: Cleared ThreadInfoCache");
|
||||
}
|
||||
mThreadToConversationId.clear();
|
||||
mThreadToRecipients.clear();
|
||||
mArchivedConversations.clear();
|
||||
}
|
||||
|
||||
public synchronized boolean isArchived(final String conversationId) {
|
||||
return mArchivedConversations.contains(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a conversation based on the message's thread id
|
||||
*
|
||||
* @param threadId The message's thread
|
||||
* @param refSubId The subId used for normalizing phone numbers in the thread
|
||||
* @param customization The user setting customization to the conversation if any
|
||||
* @return The existing conversation id or new conversation id
|
||||
*/
|
||||
public synchronized String getOrCreateConversation(final DatabaseWrapper db,
|
||||
final long threadId, int refSubId, final ConversationCustomization customization) {
|
||||
// This function has several components which need to be atomic.
|
||||
Assert.isTrue(db.getDatabase().inTransaction());
|
||||
|
||||
// If we already have this conversation ID in our local map, just return it
|
||||
String conversationId = mThreadToConversationId.get(threadId);
|
||||
if (conversationId != null) {
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
final List<String> recipients = getThreadRecipients(threadId);
|
||||
final ArrayList<ParticipantData> participants =
|
||||
BugleDatabaseOperations.getConversationParticipantsFromRecipients(recipients,
|
||||
refSubId);
|
||||
|
||||
if (customization != null) {
|
||||
// There is user customization we need to recover
|
||||
conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
|
||||
customization.isArchived(), participants, customization.isMuted(),
|
||||
customization.noVibrate(), customization.getNotificationSoundUri());
|
||||
if (customization.isArchived()) {
|
||||
mArchivedConversations.add(conversationId);
|
||||
}
|
||||
} else {
|
||||
conversationId = BugleDatabaseOperations.getOrCreateConversation(db, threadId,
|
||||
false/*archived*/, participants, false/*noNotification*/,
|
||||
false/*noVibrate*/, null/*soundUri*/);
|
||||
}
|
||||
|
||||
if (conversationId != null) {
|
||||
mThreadToConversationId.put(threadId, conversationId);
|
||||
return conversationId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Load the recipients of a thread from telephony provider. If we fail, use
|
||||
* a predefined unknown recipient. This should not return null.
|
||||
*
|
||||
* @param threadId
|
||||
*/
|
||||
public synchronized List<String> getThreadRecipients(final long threadId) {
|
||||
List<String> recipients = mThreadToRecipients.get(threadId);
|
||||
if (recipients == null) {
|
||||
recipients = MmsUtils.getRecipientsByThread(threadId);
|
||||
if (recipients != null && recipients.size() > 0) {
|
||||
mThreadToRecipients.put(threadId, recipients);
|
||||
}
|
||||
}
|
||||
|
||||
if (recipients == null || recipients.isEmpty()) {
|
||||
LogUtil.w(TAG, "SyncManager : using unknown sender since thread " + threadId +
|
||||
" couldn't find any recipients.");
|
||||
|
||||
// We want to try our best to load the messages,
|
||||
// so if recipient info is broken, try to fix it with unknown recipient
|
||||
recipients = Lists.newArrayList();
|
||||
recipients.add(ParticipantData.getUnknownSenderDestination());
|
||||
}
|
||||
|
||||
return recipients;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user