Home | History | Annotate | Download | only in provider
      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.emailcommon.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.text.TextUtils;
     25 
     26 import com.android.emailcommon.Logging;
     27 import com.android.emailcommon.provider.EmailContent.AccountColumns;
     28 import com.android.emailcommon.provider.EmailContent.MailboxColumns;
     29 import com.android.mail.utils.LogUtils;
     30 
     31 import java.util.HashMap;
     32 
     33 public 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