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