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.emailcommon.provider; 18 19import android.content.ContentResolver; 20import android.content.ContentUris; 21import android.content.ContentValues; 22import android.content.Context; 23import android.database.Cursor; 24import android.text.TextUtils; 25 26import com.android.emailcommon.Logging; 27import com.android.emailcommon.provider.EmailContent.AccountColumns; 28import com.android.emailcommon.provider.EmailContent.MailboxColumns; 29import com.android.mail.utils.LogUtils; 30 31import java.util.HashMap; 32 33public class MailboxUtilities { 34 35 public static final String FIX_PARENT_KEYS_METHOD = "fix_parent_keys"; 36 37 public static final String WHERE_PARENT_KEY_UNINITIALIZED = 38 "(" + MailboxColumns.PARENT_KEY + " isnull OR " + MailboxColumns.PARENT_KEY + "=" + 39 Mailbox.PARENT_KEY_UNINITIALIZED + ")"; 40 // The flag we use in Account to indicate a mailbox change in progress 41 private static final int ACCOUNT_MAILBOX_CHANGE_FLAG = Account.FLAGS_SYNC_ADAPTER; 42 43 /** 44 * Recalculate a mailbox's flags and the parent key of any children 45 * @param context the caller's context 46 * @param parentCursor a cursor to a mailbox that requires fixup 47 */ 48 @Deprecated 49 public static void setFlagsAndChildrensParentKey(Context context, Cursor parentCursor, 50 String accountSelector) { 51 ContentResolver resolver = context.getContentResolver(); 52 String[] selectionArgs = new String[1]; 53 ContentValues parentValues = new ContentValues(); 54 // Get the data we need first 55 long parentId = parentCursor.getLong(Mailbox.CONTENT_ID_COLUMN); 56 int parentFlags = 0; 57 int parentType = parentCursor.getInt(Mailbox.CONTENT_TYPE_COLUMN); 58 String parentServerId = parentCursor.getString(Mailbox.CONTENT_SERVER_ID_COLUMN); 59 // All email-type boxes hold mail 60 if (parentType <= Mailbox.TYPE_NOT_EMAIL) { 61 parentFlags |= Mailbox.FLAG_HOLDS_MAIL + Mailbox.FLAG_SUPPORTS_SETTINGS; 62 } 63 // Outbox, Drafts, and Sent don't allow mail to be moved to them 64 if (parentType == Mailbox.TYPE_MAIL || parentType == Mailbox.TYPE_TRASH || 65 parentType == Mailbox.TYPE_JUNK || parentType == Mailbox.TYPE_INBOX) { 66 parentFlags |= Mailbox.FLAG_ACCEPTS_MOVED_MAIL; 67 } 68 // There's no concept of "append" in EAS so FLAG_ACCEPTS_APPENDED_MAIL is never used 69 // Mark parent mailboxes as parents & add parent key to children 70 // An example of a mailbox with a null serverId would be an Outbox that we create locally 71 // for hotmail accounts (which don't have a server-based Outbox) 72 if (parentServerId != null) { 73 selectionArgs[0] = parentServerId; 74 Cursor childCursor = resolver.query(Mailbox.CONTENT_URI, 75 Mailbox.ID_PROJECTION, MailboxColumns.PARENT_SERVER_ID + "=? AND " + 76 accountSelector, selectionArgs, null); 77 if (childCursor == null) return; 78 try { 79 while (childCursor.moveToNext()) { 80 parentFlags |= Mailbox.FLAG_HAS_CHILDREN | Mailbox.FLAG_CHILDREN_VISIBLE; 81 ContentValues childValues = new ContentValues(); 82 childValues.put(Mailbox.PARENT_KEY, parentId); 83 long childId = childCursor.getLong(Mailbox.ID_PROJECTION_COLUMN); 84 resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, childId), 85 childValues, null, null); 86 } 87 } finally { 88 childCursor.close(); 89 } 90 } else { 91 // Mark this is having no parent, so that we don't examine this mailbox again 92 parentValues.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX); 93 LogUtils.w(Logging.LOG_TAG, "Mailbox with null serverId: " + 94 parentCursor.getString(Mailbox.CONTENT_DISPLAY_NAME_COLUMN) + ", type: " + 95 parentType); 96 } 97 // Save away updated flags and parent key (if any) 98 parentValues.put(Mailbox.FLAGS, parentFlags); 99 resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, parentId), 100 parentValues, null, null); 101 } 102 103 /** 104 * Recalculate a mailbox's flags and the parent key of any children 105 * @param context the caller's context 106 * @param accountSelector (see description below in fixupUninitializedParentKeys) 107 * @param serverId the server id of an individual mailbox 108 */ 109 @Deprecated 110 public static void setFlagsAndChildrensParentKey(Context context, String accountSelector, 111 String serverId) { 112 Cursor cursor = context.getContentResolver().query(Mailbox.CONTENT_URI, 113 Mailbox.CONTENT_PROJECTION, MailboxColumns.SERVER_ID + "=? AND " + accountSelector, 114 new String[] {serverId}, null); 115 if (cursor == null) return; 116 try { 117 if (cursor.moveToFirst()) { 118 setFlagsAndChildrensParentKey(context, cursor, accountSelector); 119 } 120 } finally { 121 cursor.close(); 122 } 123 } 124 125 /** 126 * Given an account selector, specifying the account(s) on which to work, create the parentKey 127 * and flags for each mailbox in the account(s) that is uninitialized (parentKey = 0 or null) 128 * 129 * @param accountSelector a sqlite WHERE clause expression to be used in determining the 130 * mailboxes to be acted upon, e.g. accountKey IN (1, 2), accountKey = 12, etc. 131 */ 132 @Deprecated 133 public static void fixupUninitializedParentKeys(Context context, String accountSelector) { 134 // Sanity check first on our arguments 135 if (accountSelector == null) throw new IllegalArgumentException(); 136 // The selection we'll use to find uninitialized parent key mailboxes 137 String noParentKeySelection = WHERE_PARENT_KEY_UNINITIALIZED + " AND " + accountSelector; 138 139 // We'll loop through mailboxes with an uninitialized parent key 140 ContentResolver resolver = context.getContentResolver(); 141 Cursor noParentKeyMailboxCursor = 142 resolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION, 143 noParentKeySelection, null, null); 144 if (noParentKeyMailboxCursor == null) return; 145 try { 146 while (noParentKeyMailboxCursor.moveToNext()) { 147 setFlagsAndChildrensParentKey(context, noParentKeyMailboxCursor, accountSelector); 148 String parentServerId = 149 noParentKeyMailboxCursor.getString(Mailbox.CONTENT_PARENT_SERVER_ID_COLUMN); 150 // Fixup the parent so that the children's parentKey is updated 151 if (parentServerId != null) { 152 setFlagsAndChildrensParentKey(context, accountSelector, parentServerId); 153 } 154 } 155 } finally { 156 noParentKeyMailboxCursor.close(); 157 } 158 159 // Any mailboxes without a parent key should have parentKey set to -1 (no parent) 160 ContentValues values = new ContentValues(); 161 values.put(Mailbox.PARENT_KEY, Mailbox.NO_MAILBOX); 162 resolver.update(Mailbox.CONTENT_URI, values, noParentKeySelection, null); 163 } 164 165 private static void setAccountSyncAdapterFlag(Context context, long accountId, boolean start) { 166 Account account = Account.restoreAccountWithId(context, accountId); 167 if (account == null) return; 168 // Set temporary flag indicating state of update of mailbox list 169 ContentValues cv = new ContentValues(); 170 cv.put(AccountColumns.FLAGS, start ? (account.mFlags | ACCOUNT_MAILBOX_CHANGE_FLAG) : 171 account.mFlags & ~ACCOUNT_MAILBOX_CHANGE_FLAG); 172 context.getContentResolver().update( 173 ContentUris.withAppendedId(Account.CONTENT_URI, account.mId), cv, null, null); 174 } 175 176 /** 177 * Indicate that the specified account is starting the process of changing its mailbox list 178 * @param context the caller's context 179 * @param accountId the account that is starting to change its mailbox list 180 */ 181 public static void startMailboxChanges(Context context, long accountId) { 182 setAccountSyncAdapterFlag(context, accountId, true); 183 } 184 185 /** 186 * Indicate that the specified account is ending the process of changing its mailbox list 187 * @param context the caller's context 188 * @param accountId the account that is finished with changes to its mailbox list 189 */ 190 public static void endMailboxChanges(Context context, long accountId) { 191 setAccountSyncAdapterFlag(context, accountId, false); 192 } 193 194 /** 195 * Check that we didn't leave the account's mailboxes in a (possibly) inconsistent state 196 * If we did, make them consistent again 197 * @param context the caller's context 198 * @param accountId the account whose mailboxes are to be checked 199 */ 200 @Deprecated 201 public static void checkMailboxConsistency(Context context, long accountId) { 202 // If our temporary flag is set, we were interrupted during an update 203 // First, make sure we're current (really fast w/ caching) 204 Account account = Account.restoreAccountWithId(context, accountId); 205 if (account == null) return; 206 if ((account.mFlags & ACCOUNT_MAILBOX_CHANGE_FLAG) != 0) { 207 LogUtils.w(Logging.LOG_TAG, "Account " + account.mDisplayName + 208 " has inconsistent mailbox data; fixing up..."); 209 // Set all account mailboxes to uninitialized parent key 210 ContentValues values = new ContentValues(); 211 values.put(Mailbox.PARENT_KEY, Mailbox.PARENT_KEY_UNINITIALIZED); 212 String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId; 213 ContentResolver resolver = context.getContentResolver(); 214 resolver.update(Mailbox.CONTENT_URI, values, accountSelector, null); 215 // Fix up keys and flags 216 MailboxUtilities.fixupUninitializedParentKeys(context, accountSelector); 217 // Clear the temporary flag 218 endMailboxChanges(context, accountId); 219 } 220 } 221 222 private static final String[] HIERARCHY_PROJECTION = new String[] { 223 MailboxColumns._ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.PARENT_KEY, 224 MailboxColumns.HIERARCHICAL_NAME 225 }; 226 private static final int HIERARCHY_ID = 0; 227 private static final int HIERARCHY_NAME = 1; 228 private static final int HIERARCHY_PARENT_KEY = 2; 229 private static final int HIERARCHY_HIERARCHICAL_NAME = 3; 230 231 private static String getHierarchicalName(Context context, long id, HashMap<Long, String> map, 232 String name, long parentId) { 233 String hierarchicalName; 234 if (map.containsKey(id)) { 235 return map.get(id); 236 } else if (parentId == Mailbox.NO_MAILBOX) { 237 hierarchicalName = name; 238 } else { 239 Mailbox parent = Mailbox.restoreMailboxWithId(context, parentId); 240 if (parent == null) return name + "/" + "??"; 241 hierarchicalName = getHierarchicalName(context, parentId, map, parent.mDisplayName, 242 parent.mParentKey) + "/" + name; 243 } 244 map.put(id, hierarchicalName); 245 return hierarchicalName; 246 } 247 248 public static void setupHierarchicalNames(Context context, long accountId) { 249 Account account = Account.restoreAccountWithId(context, accountId); 250 if (account == null) return; 251 // Start by clearing all names 252 ContentValues values = new ContentValues(); 253 String accountSelector = Mailbox.ACCOUNT_KEY + "=" + account.mId; 254 ContentResolver resolver = context.getContentResolver(); 255 HashMap<Long, String> nameMap = new HashMap<Long, String>(); 256 Cursor c = resolver.query(Mailbox.CONTENT_URI, HIERARCHY_PROJECTION, accountSelector, 257 null, null); 258 try { 259 while(c.moveToNext()) { 260 long id = c.getLong(HIERARCHY_ID); 261 String displayName = c.getString(HIERARCHY_NAME); 262 String name = getHierarchicalName(context, id, nameMap, displayName, 263 c.getLong(HIERARCHY_PARENT_KEY)); 264 String oldHierarchicalName = c.getString(HIERARCHY_HIERARCHICAL_NAME); 265 // Don't write the name unless it has changed or we don't need one (it's top-level) 266 if (name.equals(oldHierarchicalName) || 267 ((name.equals(displayName)) && TextUtils.isEmpty(oldHierarchicalName))) { 268 continue; 269 } 270 // If the name has changed, update it 271 values.put(MailboxColumns.HIERARCHICAL_NAME, name); 272 resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), values, null, 273 null); 274 } 275 } finally { 276 c.close(); 277 } 278 } 279} 280