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
setFlagsAndChildrensParentKey(Context context, Cursor parentCursor, String accountSelector)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
setFlagsAndChildrensParentKey(Context context, String accountSelector, String serverId)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
fixupUninitializedParentKeys(Context context, String accountSelector)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 
setAccountSyncAdapterFlag(Context context, long accountId, boolean start)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      */
startMailboxChanges(Context context, long accountId)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      */
endMailboxChanges(Context context, long accountId)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
checkMailboxConsistency(Context context, long accountId)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 
getHierarchicalName(Context context, long id, HashMap<Long, String> map, String name, long parentId)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 
setupHierarchicalNames(Context context, long accountId)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