275 lines
13 KiB
Java
275 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2011 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.emailcommon.provider;
|
|
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentUris;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.database.Cursor;
|
|
import android.os.Debug;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
|
|
import com.android.emailcommon.Logging;
|
|
import com.android.emailcommon.provider.Account;
|
|
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|
import com.android.emailcommon.provider.Mailbox;
|
|
|
|
import java.util.HashMap;
|
|
|
|
public class MailboxUtilities {
|
|
public static final String WHERE_PARENT_KEY_UNINITIALIZED =
|
|
"(" + MailboxColumns.PARENT_KEY + " isnull OR " + MailboxColumns.PARENT_KEY + "=" +
|
|
Mailbox.PARENT_KEY_UNINITIALIZED + ")";
|
|
// The flag we use in Account to indicate a mailbox change in progress
|
|
private static final int ACCOUNT_MAILBOX_CHANGE_FLAG = Account.FLAGS_SYNC_ADAPTER;
|
|
|
|
/**
|
|
* Recalculate a mailbox's flags and the parent key of any children
|
|
* @param context the caller's context
|
|
* @param parentCursor a cursor to a mailbox that requires fixup
|
|
*/
|
|
public static void setFlagsAndChildrensParentKey(Context context, Cursor parentCursor,
|
|
String accountSelector) {
|
|
ContentResolver resolver = context.getContentResolver();
|
|
String[] selectionArgs = new String[1];
|
|
ContentValues parentValues = new ContentValues();
|
|
// Get the data we need first
|
|
long parentId = parentCursor.getLong(Mailbox.CONTENT_ID_COLUMN);
|
|
int parentFlags = 0;
|
|
int parentType = parentCursor.getInt(Mailbox.CONTENT_TYPE_COLUMN);
|
|
String parentServerId = parentCursor.getString(Mailbox.CONTENT_SERVER_ID_COLUMN);
|
|
// All email-type boxes hold mail
|
|
if (parentType <= Mailbox.TYPE_NOT_EMAIL) {
|
|
parentFlags |= Mailbox.FLAG_HOLDS_MAIL + Mailbox.FLAG_SUPPORTS_SETTINGS;
|
|
}
|
|
// Outbox, Drafts, and Sent don't allow mail to be moved to them
|
|
if (parentType == Mailbox.TYPE_MAIL || parentType == Mailbox.TYPE_TRASH ||
|
|
parentType == Mailbox.TYPE_JUNK || parentType == Mailbox.TYPE_INBOX) {
|
|
parentFlags |= Mailbox.FLAG_ACCEPTS_MOVED_MAIL;
|
|
}
|
|
// There's no concept of "append" in EAS so FLAG_ACCEPTS_APPENDED_MAIL is never used
|
|
// Mark parent mailboxes as parents & add parent key to children
|
|
// An example of a mailbox with a null serverId would be an Outbox that we create locally
|
|
// for hotmail accounts (which don't have a server-based Outbox)
|
|
if (parentServerId != null) {
|
|
selectionArgs[0] = parentServerId;
|
|
Cursor childCursor = resolver.query(Mailbox.CONTENT_URI,
|
|
Mailbox.ID_PROJECTION, MailboxColumns.PARENT_SERVER_ID + "=? AND " +
|
|
accountSelector, selectionArgs, null);
|
|
if (childCursor == null) return;
|
|
try {
|
|
while (childCursor.moveToNext()) {
|
|
parentFlags |= Mailbox.FLAG_HAS_CHILDREN | Mailbox.FLAG_CHILDREN_VISIBLE;
|
|
ContentValues childValues = new ContentValues();
|
|
childValues.put(Mailbox.PARENT_KEY, parentId);
|
|
long childId = childCursor.getLong(Mailbox.ID_PROJECTION_COLUMN);
|
|
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, childId),
|
|
childValues, null, null);
|
|
}
|
|
} finally {
|
|
childCursor.close();
|
|
}
|
|
} else {
|
|
// Mark this is having no parent, so that we don't examine this mailbox again
|
|
parentValues.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX);
|
|
Log.w(Logging.LOG_TAG, "Mailbox with null serverId: " +
|
|
parentCursor.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN) + ", type: " +
|
|
parentType);
|
|
}
|
|
// Save away updated flags and parent key (if any)
|
|
parentValues.put(Mailbox.FLAGS, parentFlags);
|
|
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, parentId),
|
|
parentValues, null, null);
|
|
}
|
|
|
|
/**
|
|
* Recalculate a mailbox's flags and the parent key of any children
|
|
* @param context the caller's context
|
|
* @param accountSelector (see description below in fixupUninitializedParentKeys)
|
|
* @param serverId the server id of an individual mailbox
|
|
*/
|
|
public static void setFlagsAndChildrensParentKey(Context context, String accountSelector,
|
|
String serverId) {
|
|
Cursor cursor = context.getContentResolver().query(Mailbox.CONTENT_URI,
|
|
Mailbox.CONTENT_PROJECTION, MailboxColumns.SERVER_ID + "=? AND " + accountSelector,
|
|
new String[] {serverId}, null);
|
|
if (cursor == null) return;
|
|
try {
|
|
if (cursor.moveToFirst()) {
|
|
setFlagsAndChildrensParentKey(context, cursor, accountSelector);
|
|
}
|
|
} finally {
|
|
cursor.close();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given an account selector, specifying the account(s) on which to work, create the parentKey
|
|
* and flags for each mailbox in the account(s) that is uninitialized (parentKey = 0 or null)
|
|
*
|
|
* @param accountSelector a sqlite WHERE clause expression to be used in determining the
|
|
* mailboxes to be acted upon, e.g. accountKey IN (1, 2), accountKey = 12, etc.
|
|
*/
|
|
public static void fixupUninitializedParentKeys(Context context, String accountSelector) {
|
|
// Sanity check first on our arguments
|
|
if (accountSelector == null) throw new IllegalArgumentException();
|
|
// The selection we'll use to find uninitialized parent key mailboxes
|
|
String noParentKeySelection = WHERE_PARENT_KEY_UNINITIALIZED + " AND " + accountSelector;
|
|
|
|
// We'll loop through mailboxes with an uninitialized parent key
|
|
ContentResolver resolver = context.getContentResolver();
|
|
Cursor noParentKeyMailboxCursor =
|
|
resolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
|
|
noParentKeySelection, null, null);
|
|
if (noParentKeyMailboxCursor == null) return;
|
|
try {
|
|
while (noParentKeyMailboxCursor.moveToNext()) {
|
|
setFlagsAndChildrensParentKey(context, noParentKeyMailboxCursor, accountSelector);
|
|
String parentServerId =
|
|
noParentKeyMailboxCursor.getString(Mailbox.CONTENT_PARENT_SERVER_ID_COLUMN);
|
|
// Fixup the parent so that the children's parentKey is updated
|
|
if (parentServerId != null) {
|
|
setFlagsAndChildrensParentKey(context, accountSelector, parentServerId);
|
|
}
|
|
}
|
|
} finally {
|
|
noParentKeyMailboxCursor.close();
|
|
}
|
|
|
|
// Any mailboxes without a parent key should have parentKey set to -1 (no parent)
|
|
ContentValues values = new ContentValues();
|
|
values.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX);
|
|
resolver.update(Mailbox.CONTENT_URI, values, noParentKeySelection, null);
|
|
}
|
|
|
|
private static void setAccountSyncAdapterFlag(Context context, long accountId, boolean start) {
|
|
Account account = Account.restoreAccountWithId(context, accountId);
|
|
if (account == null) return;
|
|
// Set temporary flag indicating state of update of mailbox list
|
|
ContentValues cv = new ContentValues();
|
|
cv.put(Account.FLAGS, start ? (account.mFlags | ACCOUNT_MAILBOX_CHANGE_FLAG) :
|
|
account.mFlags & ~ACCOUNT_MAILBOX_CHANGE_FLAG);
|
|
context.getContentResolver().update(
|
|
ContentUris.withAppendedId(Account.CONTENT_URI, account.mId), cv, null, null);
|
|
}
|
|
|
|
/**
|
|
* Indicate that the specified account is starting the process of changing its mailbox list
|
|
* @param context the caller's context
|
|
* @param accountId the account that is starting to change its mailbox list
|
|
*/
|
|
public static void startMailboxChanges(Context context, long accountId) {
|
|
setAccountSyncAdapterFlag(context, accountId, true);
|
|
}
|
|
|
|
/**
|
|
* Indicate that the specified account is ending the process of changing its mailbox list
|
|
* @param context the caller's context
|
|
* @param accountId the account that is finished with changes to its mailbox list
|
|
*/
|
|
public static void endMailboxChanges(Context context, long accountId) {
|
|
setAccountSyncAdapterFlag(context, accountId, false);
|
|
}
|
|
|
|
/**
|
|
* Check that we didn't leave the account's mailboxes in a (possibly) inconsistent state
|
|
* If we did, make them consistent again
|
|
* @param context the caller's context
|
|
* @param accountId the account whose mailboxes are to be checked
|
|
*/
|
|
public static void checkMailboxConsistency(Context context, long accountId) {
|
|
// If our temporary flag is set, we were interrupted during an update
|
|
// First, make sure we're current (really fast w/ caching)
|
|
Account account = Account.restoreAccountWithId(context, accountId);
|
|
if (account == null) return;
|
|
if ((account.mFlags & ACCOUNT_MAILBOX_CHANGE_FLAG) != 0) {
|
|
Log.w(Logging.LOG_TAG, "Account " + account.mDisplayName +
|
|
" has inconsistent mailbox data; fixing up...");
|
|
// Set all account mailboxes to uninitialized parent key
|
|
ContentValues values = new ContentValues();
|
|
values.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED);
|
|
String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId;
|
|
ContentResolver resolver = context.getContentResolver();
|
|
resolver.update(Mailbox.CONTENT_URI, values, accountSelector, null);
|
|
// Fix up keys and flags
|
|
MailboxUtilities.fixupUninitializedParentKeys(context, accountSelector);
|
|
// Clear the temporary flag
|
|
endMailboxChanges(context, accountId);
|
|
}
|
|
}
|
|
|
|
private static final String[] HIERARCHY_PROJECTION = new String[] {
|
|
MailboxColumns.ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.PARENT_KEY,
|
|
MailboxColumns.HIERARCHICAL_NAME
|
|
};
|
|
private static final int HIERARCHY_ID = 0;
|
|
private static final int HIERARCHY_NAME = 1;
|
|
private static final int HIERARCHY_PARENT_KEY = 2;
|
|
private static final int HIERARCHY_HIERARCHICAL_NAME = 3;
|
|
|
|
private static String getHierarchicalName(Context context, long id, HashMap<Long, String> map,
|
|
String name, long parentId) {
|
|
String hierarchicalName;
|
|
if (map.containsKey(id)) {
|
|
return map.get(id);
|
|
} else if (parentId == Mailbox.NO_MAILBOX) {
|
|
hierarchicalName = name;
|
|
} else {
|
|
Mailbox parent = Mailbox.restoreMailboxWithId(context, parentId);
|
|
if (parent == null) return name + "/" + "??";
|
|
hierarchicalName = getHierarchicalName(context, parentId, map, parent.mDisplayName,
|
|
parent.mParentKey) + "/" + name;
|
|
}
|
|
map.put(id, hierarchicalName);
|
|
return hierarchicalName;
|
|
}
|
|
|
|
public static void setupHierarchicalNames(Context context, long accountId) {
|
|
Account account = Account.restoreAccountWithId(context, accountId);
|
|
if (account == null) return;
|
|
// Start by clearing all names
|
|
ContentValues values = new ContentValues();
|
|
String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId;
|
|
ContentResolver resolver = context.getContentResolver();
|
|
HashMap<Long, String> nameMap = new HashMap<Long, String>();
|
|
Cursor c = resolver.query(Mailbox.CONTENT_URI, HIERARCHY_PROJECTION, accountSelector,
|
|
null, null);
|
|
try {
|
|
while(c.moveToNext()) {
|
|
long id = c.getLong(HIERARCHY_ID);
|
|
String displayName = c.getString(HIERARCHY_NAME);
|
|
String name = getHierarchicalName(context, id, nameMap, displayName,
|
|
c.getLong(HIERARCHY_PARENT_KEY));
|
|
String oldHierarchicalName = c.getString(HIERARCHY_HIERARCHICAL_NAME);
|
|
// Don't write the name unless it has changed or we don't need one (it's top-level)
|
|
if (name.equals(oldHierarchicalName) ||
|
|
((name.equals(displayName)) && TextUtils.isEmpty(oldHierarchicalName))) {
|
|
continue;
|
|
}
|
|
// If the name has changed, update it
|
|
values.put(MailboxColumns.HIERARCHICAL_NAME, name);
|
|
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), values, null,
|
|
null);
|
|
}
|
|
} finally {
|
|
c.close();
|
|
}
|
|
}
|
|
}
|