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