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 17 package com.android.exchange.provider; 18 19 import android.content.ContentResolver; 20 import android.content.ContentUris; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.util.Log; 25 26 import com.android.emailcommon.Logging; 27 import com.android.emailcommon.provider.Account; 28 import com.android.emailcommon.provider.EmailContent.MailboxColumns; 29 import com.android.emailcommon.provider.Mailbox; 30 31 public 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; 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 parentCursor = resolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 134 noParentKeySelection, null, null); 135 if (parentCursor == null) return; 136 try { 137 while (parentCursor.moveToNext()) { 138 setFlagsAndChildrensParentKey(context, parentCursor, accountSelector); 139 } 140 } finally { 141 parentCursor.close(); 142 } 143 144 // Any mailboxes without a parent key should have parentKey set to -1 (no parent) 145 ContentValues values = new ContentValues(); 146 values.clear(); 147 values.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX); 148 resolver.update(Mailbox.CONTENT_URI, values, noParentKeySelection, null); 149 } 150 151 private static void setAccountSyncAdapterFlag(Context context, long accountId, boolean start) { 152 Account account = Account.restoreAccountWithId(context, accountId); 153 if (account == null) return; 154 // Set temporary flag indicating state of update of mailbox list 155 ContentValues cv = new ContentValues(); 156 cv.put(Account.FLAGS, start ? (account.mFlags | ACCOUNT_MAILBOX_CHANGE_FLAG) : 157 account.mFlags & ~ACCOUNT_MAILBOX_CHANGE_FLAG); 158 context.getContentResolver().update( 159 ContentUris.withAppendedId(Account.CONTENT_URI, account.mId), cv, null, null); 160 } 161 162 /** 163 * Indicate that the specified account is starting the process of changing its mailbox list 164 * @param context the caller's context 165 * @param accountId the account that is starting to change its mailbox list 166 */ 167 public static void startMailboxChanges(Context context, long accountId) { 168 setAccountSyncAdapterFlag(context, accountId, true); 169 } 170 171 /** 172 * Indicate that the specified account is ending the process of changing its mailbox list 173 * @param context the caller's context 174 * @param accountId the account that is finished with changes to its mailbox list 175 */ 176 public static void endMailboxChanges(Context context, long accountId) { 177 setAccountSyncAdapterFlag(context, accountId, false); 178 } 179 180 /** 181 * Check that we didn't leave the account's mailboxes in a (possibly) inconsistent state 182 * If we did, make them consistent again 183 * @param context the caller's context 184 * @param accountId the account whose mailboxes are to be checked 185 */ 186 public static void checkMailboxConsistency(Context context, long accountId) { 187 // If our temporary flag is set, we were interrupted during an update 188 // First, make sure we're current (really fast w/ caching) 189 Account account = Account.restoreAccountWithId(context, accountId); 190 if (account == null) return; 191 if ((account.mFlags & ACCOUNT_MAILBOX_CHANGE_FLAG) != 0) { 192 Log.w(Logging.LOG_TAG, "Account " + account.mDisplayName + 193 " has inconsistent mailbox data; fixing up..."); 194 // Set all account mailboxes to uninitialized parent key 195 ContentValues values = new ContentValues(); 196 values.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED); 197 String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId; 198 ContentResolver resolver = context.getContentResolver(); 199 resolver.update(Mailbox.CONTENT_URI, values, accountSelector, null); 200 // Fix up keys and flags 201 MailboxUtilities.fixupUninitializedParentKeys(context, accountSelector); 202 // Clear the temporary flag 203 endMailboxChanges(context, accountId); 204 } 205 } 206 } 207