1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.exchange.provider; 18 19import android.content.ContentResolver; 20import android.content.ContentUris; 21import android.content.ContentValues; 22import android.content.Context; 23import android.database.Cursor; 24import android.util.Log; 25 26import com.android.emailcommon.Logging; 27import com.android.emailcommon.provider.Account; 28import com.android.emailcommon.provider.EmailContent.MailboxColumns; 29import com.android.emailcommon.provider.Mailbox; 30 31public class MailboxUtilities { 32 public static final String WHERE_PARENT_KEY_UNINITIALIZED = 33 "(" + MailboxColumns.PARENT_KEY + " isnull OR " + MailboxColumns.PARENT_KEY + "=" + 34 Mailbox.PARENT_KEY_UNINITIALIZED + ")"; 35 // The flag we use in Account to indicate a mailbox change in progress 36 private static final int ACCOUNT_MAILBOX_CHANGE_FLAG = Account.FLAGS_SYNC_ADAPTER; 37 38 /** 39 * Recalculate a mailbox's flags and the parent key of any children 40 * @param context the caller's context 41 * @param parentCursor a cursor to a mailbox that requires fixup 42 */ 43 public static void setFlagsAndChildrensParentKey(Context context, Cursor parentCursor, 44 String accountSelector) { 45 ContentResolver resolver = context.getContentResolver(); 46 String[] selectionArgs = new String[1]; 47 ContentValues parentValues = new ContentValues(); 48 // Get the data we need first 49 long parentId = parentCursor.getLong(Mailbox.CONTENT_ID_COLUMN); 50 int parentFlags = 0; 51 int parentType = parentCursor.getInt(Mailbox.CONTENT_TYPE_COLUMN); 52 String parentServerId = parentCursor.getString(Mailbox.CONTENT_SERVER_ID_COLUMN); 53 // All email-type boxes hold mail 54 if (parentType <= Mailbox.TYPE_NOT_EMAIL) { 55 parentFlags |= Mailbox.FLAG_HOLDS_MAIL + Mailbox.FLAG_SUPPORTS_SETTINGS; 56 } 57 // Outbox, Drafts, and Sent don't allow mail to be moved to them 58 if (parentType == Mailbox.TYPE_MAIL || parentType == Mailbox.TYPE_TRASH || 59 parentType == Mailbox.TYPE_JUNK || parentType == Mailbox.TYPE_INBOX) { 60 parentFlags |= Mailbox.FLAG_ACCEPTS_MOVED_MAIL; 61 } 62 // There's no concept of "append" in EAS so FLAG_ACCEPTS_APPENDED_MAIL is never used 63 // Mark parent mailboxes as parents & add parent key to children 64 // An example of a mailbox with a null serverId would be an Outbox that we create locally 65 // for hotmail accounts (which don't have a server-based Outbox) 66 if (parentServerId != null) { 67 selectionArgs[0] = parentServerId; 68 Cursor childCursor = resolver.query(Mailbox.CONTENT_URI, 69 Mailbox.ID_PROJECTION, MailboxColumns.PARENT_SERVER_ID + "=? AND " + 70 accountSelector, selectionArgs, null); 71 if (childCursor == null) return; 72 try { 73 while (childCursor.moveToNext()) { 74 parentFlags |= Mailbox.FLAG_HAS_CHILDREN | Mailbox.FLAG_CHILDREN_VISIBLE; 75 ContentValues childValues = new ContentValues(); 76 childValues.put(Mailbox.PARENT_KEY, parentId); 77 long childId = childCursor.getLong(Mailbox.ID_PROJECTION_COLUMN); 78 resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, childId), 79 childValues, null, null); 80 } 81 } finally { 82 childCursor.close(); 83 } 84 } else { 85 // Mark this is having no parent, so that we don't examine this mailbox again 86 parentValues.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX); 87 Log.w(Logging.LOG_TAG, "Mailbox with null serverId: " + 88 parentCursor.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN) + ", type: " + 89 parentType); 90 } 91 // Save away updated flags and parent key (if any) 92 parentValues.put(Mailbox.FLAGS, parentFlags); 93 resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, parentId), 94 parentValues, null, null); 95 } 96 97 /** 98 * Recalculate a mailbox's flags and the parent key of any children 99 * @param context the caller's context 100 * @param accountSelector (see description below in fixupUninitializedParentKeys) 101 * @param serverId the server id of an individual mailbox 102 */ 103 public static void setFlagsAndChildrensParentKey(Context context, String accountSelector, 104 String serverId) { 105 Cursor cursor = context.getContentResolver().query(Mailbox.CONTENT_URI, 106 Mailbox.CONTENT_PROJECTION, MailboxColumns.SERVER_ID + "=? AND " + accountSelector, 107 new String[] {serverId}, null); 108 if (cursor == null) return; 109 try { 110 if (cursor.moveToFirst()) { 111 setFlagsAndChildrensParentKey(context, cursor, accountSelector); 112 } 113 } finally { 114 cursor.close(); 115 } 116 } 117 118 /** 119 * Given an account selector, specifying the account(s) on which to work, create the parentKey 120 * and flags for each mailbox in the account(s) that is uninitialized (parentKey = 0 or null) 121 * 122 * @param accountSelector a sqlite WHERE clause expression to be used in determining the 123 * mailboxes to be acted upon, e.g. accountKey IN (1, 2), accountKey = 12, etc. 124 */ 125 public static void fixupUninitializedParentKeys(Context context, String accountSelector) { 126 // Sanity check first on our arguments 127 if (accountSelector == null) throw new IllegalArgumentException(); 128 // The selection we'll use to find uninitialized parent key mailboxes 129 String noParentKeySelection = WHERE_PARENT_KEY_UNINITIALIZED + " AND " + accountSelector; 130 131 // We'll loop through mailboxes with an uninitialized parent key 132 ContentResolver resolver = context.getContentResolver(); 133 Cursor noParentKeyMailboxCursor = 134 resolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 135 noParentKeySelection, null, null); 136 if (noParentKeyMailboxCursor == null) return; 137 try { 138 while (noParentKeyMailboxCursor.moveToNext()) { 139 setFlagsAndChildrensParentKey(context, noParentKeyMailboxCursor, accountSelector); 140 String parentServerId = 141 noParentKeyMailboxCursor.getString(Mailbox.CONTENT_PARENT_SERVER_ID_COLUMN); 142 // Fixup the parent so that the children's parentKey is updated 143 if (parentServerId != null) { 144 setFlagsAndChildrensParentKey(context, accountSelector, parentServerId); 145 } 146 } 147 } finally { 148 noParentKeyMailboxCursor.close(); 149 } 150 151 // Any mailboxes without a parent key should have parentKey set to -1 (no parent) 152 ContentValues values = new ContentValues(); 153 values.clear(); 154 values.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX); 155 resolver.update(Mailbox.CONTENT_URI, values, noParentKeySelection, null); 156 } 157 158 private static void setAccountSyncAdapterFlag(Context context, long accountId, boolean start) { 159 Account account = Account.restoreAccountWithId(context, accountId); 160 if (account == null) return; 161 // Set temporary flag indicating state of update of mailbox list 162 ContentValues cv = new ContentValues(); 163 cv.put(Account.FLAGS, start ? (account.mFlags | ACCOUNT_MAILBOX_CHANGE_FLAG) : 164 account.mFlags & ~ACCOUNT_MAILBOX_CHANGE_FLAG); 165 context.getContentResolver().update( 166 ContentUris.withAppendedId(Account.CONTENT_URI, account.mId), cv, null, null); 167 } 168 169 /** 170 * Indicate that the specified account is starting the process of changing its mailbox list 171 * @param context the caller's context 172 * @param accountId the account that is starting to change its mailbox list 173 */ 174 public static void startMailboxChanges(Context context, long accountId) { 175 setAccountSyncAdapterFlag(context, accountId, true); 176 } 177 178 /** 179 * Indicate that the specified account is ending the process of changing its mailbox list 180 * @param context the caller's context 181 * @param accountId the account that is finished with changes to its mailbox list 182 */ 183 public static void endMailboxChanges(Context context, long accountId) { 184 setAccountSyncAdapterFlag(context, accountId, false); 185 } 186 187 /** 188 * Check that we didn't leave the account's mailboxes in a (possibly) inconsistent state 189 * If we did, make them consistent again 190 * @param context the caller's context 191 * @param accountId the account whose mailboxes are to be checked 192 */ 193 public static void checkMailboxConsistency(Context context, long accountId) { 194 // If our temporary flag is set, we were interrupted during an update 195 // First, make sure we're current (really fast w/ caching) 196 Account account = Account.restoreAccountWithId(context, accountId); 197 if (account == null) return; 198 if ((account.mFlags & ACCOUNT_MAILBOX_CHANGE_FLAG) != 0) { 199 Log.w(Logging.LOG_TAG, "Account " + account.mDisplayName + 200 " has inconsistent mailbox data; fixing up..."); 201 // Set all account mailboxes to uninitialized parent key 202 ContentValues values = new ContentValues(); 203 values.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED); 204 String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId; 205 ContentResolver resolver = context.getContentResolver(); 206 resolver.update(Mailbox.CONTENT_URI, values, accountSelector, null); 207 // Fix up keys and flags 208 MailboxUtilities.fixupUninitializedParentKeys(context, accountSelector); 209 // Clear the temporary flag 210 endMailboxChanges(context, accountId); 211 } 212 } 213} 214