• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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.email;
18 
19 import com.android.email.provider.EmailContent;
20 import com.android.email.provider.EmailContent.Account;
21 import com.android.email.provider.EmailContent.AccountColumns;
22 import com.android.email.provider.EmailContent.HostAuth;
23 import com.android.email.provider.EmailContent.HostAuthColumns;
24 import com.android.email.provider.EmailContent.Mailbox;
25 import com.android.email.provider.EmailContent.MailboxColumns;
26 import com.android.email.provider.EmailContent.Message;
27 import com.android.email.provider.EmailContent.MessageColumns;
28 
29 import android.content.ContentResolver;
30 import android.database.Cursor;
31 
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.UnsupportedEncodingException;
36 import java.util.Date;
37 
38 import com.android.email.codec.binary.Base64;
39 
40 import android.content.Context;
41 import android.content.res.TypedArray;
42 import android.graphics.drawable.Drawable;
43 import android.text.Editable;
44 import android.widget.TextView;
45 
46 public class Utility {
readInputStream(InputStream in, String encoding)47     public final static String readInputStream(InputStream in, String encoding) throws IOException {
48         InputStreamReader reader = new InputStreamReader(in, encoding);
49         StringBuffer sb = new StringBuffer();
50         int count;
51         char[] buf = new char[512];
52         while ((count = reader.read(buf)) != -1) {
53             sb.append(buf, 0, count);
54         }
55         return sb.toString();
56     }
57 
arrayContains(Object[] a, Object o)58     public final static boolean arrayContains(Object[] a, Object o) {
59         for (int i = 0, count = a.length; i < count; i++) {
60             if (a[i].equals(o)) {
61                 return true;
62             }
63         }
64         return false;
65     }
66 
67     /**
68      * Combines the given array of Objects into a single string using the
69      * seperator character and each Object's toString() method. between each
70      * part.
71      *
72      * @param parts
73      * @param seperator
74      * @return
75      */
combine(Object[] parts, char seperator)76     public static String combine(Object[] parts, char seperator) {
77         if (parts == null) {
78             return null;
79         }
80         StringBuffer sb = new StringBuffer();
81         for (int i = 0; i < parts.length; i++) {
82             sb.append(parts[i].toString());
83             if (i < parts.length - 1) {
84                 sb.append(seperator);
85             }
86         }
87         return sb.toString();
88     }
89 
base64Decode(String encoded)90     public static String base64Decode(String encoded) {
91         if (encoded == null) {
92             return null;
93         }
94         byte[] decoded = new Base64().decode(encoded.getBytes());
95         return new String(decoded);
96     }
97 
base64Encode(String s)98     public static String base64Encode(String s) {
99         if (s == null) {
100             return s;
101         }
102         byte[] encoded = new Base64().encode(s.getBytes());
103         return new String(encoded);
104     }
105 
requiredFieldValid(TextView view)106     public static boolean requiredFieldValid(TextView view) {
107         return view.getText() != null && view.getText().length() > 0;
108     }
109 
requiredFieldValid(Editable s)110     public static boolean requiredFieldValid(Editable s) {
111         return s != null && s.length() > 0;
112     }
113 
114     /**
115      * Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
116      * double quote character to start and end if it's not already there.
117      *
118      * TODO: Rename this, because "quoteString()" can mean so many different things.
119      *
120      * sample -> "sample"
121      * "sample" -> "sample"
122      * ""sample"" -> "sample"
123      * "sample"" -> "sample"
124      * sa"mp"le -> "sa"mp"le"
125      * "sa"mp"le" -> "sa"mp"le"
126      * (empty string) -> ""
127      * " -> ""
128      * @param s
129      * @return
130      */
quoteString(String s)131     public static String quoteString(String s) {
132         if (s == null) {
133             return null;
134         }
135         if (!s.matches("^\".*\"$")) {
136             return "\"" + s + "\"";
137         }
138         else {
139             return s;
140         }
141     }
142 
143     /**
144      * Apply quoting rules per IMAP RFC,
145      * quoted          = DQUOTE *QUOTED-CHAR DQUOTE
146      * QUOTED-CHAR     = <any TEXT-CHAR except quoted-specials> / "\" quoted-specials
147      * quoted-specials = DQUOTE / "\"
148      *
149      * This is used primarily for IMAP login, but might be useful elsewhere.
150      *
151      * NOTE:  Not very efficient - you may wish to preflight this, or perhaps it should check
152      * for trouble chars before calling the replace functions.
153      *
154      * @param s The string to be quoted.
155      * @return A copy of the string, having undergone quoting as described above
156      */
imapQuoted(String s)157     public static String imapQuoted(String s) {
158 
159         // First, quote any backslashes by replacing \ with \\
160         // regex Pattern:  \\    (Java string const = \\\\)
161         // Substitute:     \\\\  (Java string const = \\\\\\\\)
162         String result = s.replaceAll("\\\\", "\\\\\\\\");
163 
164         // Then, quote any double-quotes by replacing " with \"
165         // regex Pattern:  "    (Java string const = \")
166         // Substitute:     \\"  (Java string const = \\\\\")
167         result = result.replaceAll("\"", "\\\\\"");
168 
169         // return string with quotes around it
170         return "\"" + result + "\"";
171     }
172 
173     /**
174      * A fast version of  URLDecoder.decode() that works only with UTF-8 and does only two
175      * allocations. This version is around 3x as fast as the standard one and I'm using it
176      * hundreds of times in places that slow down the UI, so it helps.
177      */
fastUrlDecode(String s)178     public static String fastUrlDecode(String s) {
179         try {
180             byte[] bytes = s.getBytes("UTF-8");
181             byte ch;
182             int length = 0;
183             for (int i = 0, count = bytes.length; i < count; i++) {
184                 ch = bytes[i];
185                 if (ch == '%') {
186                     int h = (bytes[i + 1] - '0');
187                     int l = (bytes[i + 2] - '0');
188                     if (h > 9) {
189                         h -= 7;
190                     }
191                     if (l > 9) {
192                         l -= 7;
193                     }
194                     bytes[length] = (byte) ((h << 4) | l);
195                     i += 2;
196                 }
197                 else if (ch == '+') {
198                     bytes[length] = ' ';
199                 }
200                 else {
201                     bytes[length] = bytes[i];
202                 }
203                 length++;
204             }
205             return new String(bytes, 0, length, "UTF-8");
206         }
207         catch (UnsupportedEncodingException uee) {
208             return null;
209         }
210     }
211 
212     /**
213      * Returns true if the specified date is within today. Returns false otherwise.
214      * @param date
215      * @return
216      */
isDateToday(Date date)217     public static boolean isDateToday(Date date) {
218         // TODO But Calendar is so slowwwwwww....
219         Date today = new Date();
220         if (date.getYear() == today.getYear() &&
221                 date.getMonth() == today.getMonth() &&
222                 date.getDate() == today.getDate()) {
223             return true;
224         }
225         return false;
226     }
227 
228     /*
229      * TODO disabled this method globally. It is used in all the settings screens but I just
230      * noticed that an unrelated icon was dimmed. Android must share drawables internally.
231      */
setCompoundDrawablesAlpha(TextView view, int alpha)232     public static void setCompoundDrawablesAlpha(TextView view, int alpha) {
233 //        Drawable[] drawables = view.getCompoundDrawables();
234 //        for (Drawable drawable : drawables) {
235 //            if (drawable != null) {
236 //                drawable.setAlpha(alpha);
237 //            }
238 //        }
239     }
240 
241     // TODO: unit test this
buildMailboxIdSelection(ContentResolver resolver, long mailboxId)242     public static String buildMailboxIdSelection(ContentResolver resolver, long mailboxId) {
243         // Setup default selection & args, then add to it as necessary
244         StringBuilder selection = new StringBuilder(
245                 MessageColumns.FLAG_LOADED + " IN ("
246                 + Message.FLAG_LOADED_PARTIAL + "," + Message.FLAG_LOADED_COMPLETE
247                 + ") AND ");
248         if (mailboxId == Mailbox.QUERY_ALL_INBOXES
249             || mailboxId == Mailbox.QUERY_ALL_DRAFTS
250             || mailboxId == Mailbox.QUERY_ALL_OUTBOX) {
251             // query for all mailboxes of type INBOX, DRAFTS, or OUTBOX
252             int type;
253             if (mailboxId == Mailbox.QUERY_ALL_INBOXES) {
254                 type = Mailbox.TYPE_INBOX;
255             } else if (mailboxId == Mailbox.QUERY_ALL_DRAFTS) {
256                 type = Mailbox.TYPE_DRAFTS;
257             } else {
258                 type = Mailbox.TYPE_OUTBOX;
259             }
260             StringBuilder inboxes = new StringBuilder();
261             Cursor c = resolver.query(Mailbox.CONTENT_URI,
262                         EmailContent.ID_PROJECTION,
263                         MailboxColumns.TYPE + "=? AND " + MailboxColumns.FLAG_VISIBLE + "=1",
264                         new String[] { Integer.toString(type) }, null);
265             // build an IN (mailboxId, ...) list
266             // TODO do this directly in the provider
267             while (c.moveToNext()) {
268                 if (inboxes.length() != 0) {
269                     inboxes.append(",");
270                 }
271                 inboxes.append(c.getLong(EmailContent.ID_PROJECTION_COLUMN));
272             }
273             c.close();
274             selection.append(MessageColumns.MAILBOX_KEY + " IN ");
275             selection.append("(").append(inboxes).append(")");
276         } else  if (mailboxId == Mailbox.QUERY_ALL_UNREAD) {
277             selection.append(Message.FLAG_READ + "=0");
278         } else if (mailboxId == Mailbox.QUERY_ALL_FAVORITES) {
279             selection.append(Message.FLAG_FAVORITE + "=1");
280         } else {
281             selection.append(MessageColumns.MAILBOX_KEY + "=" + mailboxId);
282         }
283         return selection.toString();
284     }
285 
286     public static class FolderProperties {
287 
288         private static FolderProperties sInstance;
289 
290         // Caches for frequently accessed resources.
291         private String[] mSpecialMailbox = new String[] {};
292         private TypedArray mSpecialMailboxDrawable;
293         private Drawable mDefaultMailboxDrawable;
294         private Drawable mSummaryStarredMailboxDrawable;
295         private Drawable mSummaryCombinedInboxDrawable;
296 
FolderProperties(Context context)297         private FolderProperties(Context context) {
298             mSpecialMailbox = context.getResources().getStringArray(R.array.mailbox_display_names);
299             for (int i = 0; i < mSpecialMailbox.length; ++i) {
300                 if ("".equals(mSpecialMailbox[i])) {
301                     // there is no localized name, so use the display name from the server
302                     mSpecialMailbox[i] = null;
303                 }
304             }
305             mSpecialMailboxDrawable =
306                 context.getResources().obtainTypedArray(R.array.mailbox_display_icons);
307             mDefaultMailboxDrawable =
308                 context.getResources().getDrawable(R.drawable.ic_list_folder);
309             mSummaryStarredMailboxDrawable =
310                 context.getResources().getDrawable(R.drawable.ic_list_starred);
311             mSummaryCombinedInboxDrawable =
312                 context.getResources().getDrawable(R.drawable.ic_list_combined_inbox);
313         }
314 
getInstance(Context context)315         public static FolderProperties getInstance(Context context) {
316             if (sInstance == null) {
317                 synchronized (FolderProperties.class) {
318                     if (sInstance == null) {
319                         sInstance = new FolderProperties(context);
320                     }
321                 }
322             }
323             return sInstance;
324         }
325 
326         /**
327          * Lookup names of localized special mailboxes
328          * @param type
329          * @return Localized strings
330          */
getDisplayName(int type)331         public String getDisplayName(int type) {
332             if (type < mSpecialMailbox.length) {
333                 return mSpecialMailbox[type];
334             }
335             return null;
336         }
337 
338         /**
339          * Lookup icons of special mailboxes
340          * @param type
341          * @return icon's drawable
342          */
getIconIds(int type)343         public Drawable getIconIds(int type) {
344             if (type < mSpecialMailboxDrawable.length()) {
345                 return mSpecialMailboxDrawable.getDrawable(type);
346             }
347             return mDefaultMailboxDrawable;
348         }
349 
getSummaryMailboxIconIds(long mailboxKey)350         public Drawable getSummaryMailboxIconIds(long mailboxKey) {
351             if (mailboxKey == Mailbox.QUERY_ALL_INBOXES) {
352                 return mSummaryCombinedInboxDrawable;
353             } else if (mailboxKey == Mailbox.QUERY_ALL_FAVORITES) {
354                 return mSummaryStarredMailboxDrawable;
355             } else if (mailboxKey == Mailbox.QUERY_ALL_DRAFTS) {
356                 return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_DRAFTS);
357             } else if (mailboxKey == Mailbox.QUERY_ALL_OUTBOX) {
358                 return mSpecialMailboxDrawable.getDrawable(Mailbox.TYPE_OUTBOX);
359             }
360             return mDefaultMailboxDrawable;
361         }
362     }
363 
364     private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?"
365             + " and " + HostAuthColumns.LOGIN + " like ?"
366             + " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\"";
367     private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?";
368 
369     /**
370      * Look for an existing account with the same username & server
371      *
372      * @param context a system context
373      * @param allowAccountId this account Id will not trigger (when editing an existing account)
374      * @param hostName the server
375      * @param userLogin the user login string
376      * @result null = no dupes found.  non-null = dupe account's display name
377      */
findDuplicateAccount(Context context, long allowAccountId, String hostName, String userLogin)378     public static String findDuplicateAccount(Context context, long allowAccountId, String hostName,
379             String userLogin) {
380         ContentResolver resolver = context.getContentResolver();
381         Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION,
382                 HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userLogin }, null);
383         try {
384             while (c.moveToNext()) {
385                 long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN);
386                 // Find account with matching hostauthrecv key, and return its display name
387                 Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION,
388                         ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null);
389                 try {
390                     while (c2.moveToNext()) {
391                         long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN);
392                         if (accountId != allowAccountId) {
393                             Account account = Account.restoreAccountWithId(context, accountId);
394                             if (account != null) {
395                                 return account.mDisplayName;
396                             }
397                         }
398                     }
399                 } finally {
400                     c2.close();
401                 }
402             }
403         } finally {
404             c.close();
405         }
406 
407         return null;
408     }
409 
410     /**
411      * Generate a random message-id header for locally-generated messages.
412      */
generateMessageId()413     public static String generateMessageId() {
414         StringBuffer sb = new StringBuffer();
415         sb.append("<");
416         for (int i = 0; i < 24; i++) {
417             sb.append(Integer.toString((int)(Math.random() * 35), 36));
418         }
419         sb.append(".");
420         sb.append(Long.toString(System.currentTimeMillis()));
421         sb.append("@email.android.com>");
422         return sb.toString();
423     }
424 
425 }
426