196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/* 296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project 396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * 496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * you may not use this file except in compliance with the License. 696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * You may obtain a copy of the License at 796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * 896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * 1096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 1196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 1296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * See the License for the specific language governing permissions and 1496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * limitations under the License. 1596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 1696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 1731d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankpackage com.android.emailcommon.utility; 1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 197e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.app.Activity; 20d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport android.app.Fragment; 21e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport android.content.ContentResolver; 2209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentUris; 2309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.content.ContentValues; 24f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.content.Context; 25e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadlerimport android.database.Cursor; 26adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onukiimport android.database.CursorWrapper; 27a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.graphics.Typeface; 28bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onukiimport android.net.Uri; 2959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onukiimport android.os.AsyncTask; 3009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blankimport android.os.Environment; 31082443978595bee3b7907563dc7665f908872e20Makoto Onukiimport android.os.Handler; 32f11295f3352f7c89fc92c73731e41b057e236969Makoto Onukiimport android.os.Looper; 3319b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onukiimport android.os.StrictMode; 34d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onukiimport android.provider.OpenableColumns; 35a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.Spannable; 36a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.SpannableString; 37a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.SpannableStringBuilder; 38128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport android.text.TextUtils; 39a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onukiimport android.text.style.StyleSpan; 406cec1104fe8863fce2ee86ff5145076e6c436a00Doug Zongkerimport android.util.Base64; 41d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport android.util.Log; 42082443978595bee3b7907563dc7665f908872e20Makoto Onukiimport android.widget.ListView; 43f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongkerimport android.widget.TextView; 447e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onukiimport android.widget.Toast; 45e1f0b0a9bcc78ade7f3b2ac540705701f19cd6cdAndrew Stadler 46190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.Logging; 47190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.Account; 48190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent; 49190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.AccountColumns; 50190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.Attachment; 51190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.AttachmentColumns; 52190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.HostAuthColumns; 53190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.MailboxColumns; 54190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.EmailContent.Message; 55bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrookimport com.android.emailcommon.provider.EmailContent.MessageColumns; 56190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.HostAuth; 57190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.Mailbox; 58190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blankimport com.android.emailcommon.provider.ProviderUnavailableException; 59190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank 607e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onukiimport java.io.ByteArrayInputStream; 6144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onukiimport java.io.File; 62d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.FileDescriptor; 63d755cdce13217d25ac33169b5e410709636255c4Makoto Onukiimport java.io.FileNotFoundException; 6496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException; 6596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream; 6696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStreamReader; 67d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.PrintWriter; 68d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onukiimport java.io.StringWriter; 6996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.UnsupportedEncodingException; 70ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onukiimport java.net.URI; 71ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onukiimport java.net.URISyntaxException; 7220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.ByteBuffer; 7320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.CharBuffer; 7420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onukiimport java.nio.charset.Charset; 75697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onukiimport java.security.MessageDigest; 76d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onukiimport java.security.NoSuchAlgorithmException; 77f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onukiimport java.util.ArrayList; 78767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onukiimport java.util.Collection; 79989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.GregorianCalendar; 804e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onukiimport java.util.HashSet; 814e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onukiimport java.util.Set; 82989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Predaimport java.util.TimeZone; 83128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onukiimport java.util.regex.Pattern; 8496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 8596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpublic class Utility { 8620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki public static final Charset UTF_8 = Charset.forName("UTF-8"); 877e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki public static final Charset ASCII = Charset.forName("US-ASCII"); 887e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki 897e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki public static final String[] EMPTY_STRINGS = new String[0]; 90f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki public static final Long[] EMPTY_LONGS = new Long[0]; 9120225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki 92128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki // "GMT" + "+" or "-" + 4 digits 93128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki private static final Pattern DATE_CLEANUP_PATTERN_WRONG_TIMEZONE = 94128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki Pattern.compile("GMT([-+]\\d{4})$"); 95128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki 96f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki private static Handler sMainThreadHandler; 97f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki 98f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki /** 99f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki * @return a {@link Handler} tied to the main thread. 100f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki */ 101f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki public static Handler getMainThreadHandler() { 102f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki if (sMainThreadHandler == null) { 103f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki // No need to synchronize -- it's okay to create an extra Handler, which will be used 104f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki // only once and then thrown away. 105f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki sMainThreadHandler = new Handler(Looper.getMainLooper()); 106f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki } 107f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki return sMainThreadHandler; 108f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki } 109f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki 11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public final static String readInputStream(InputStream in, String encoding) throws IOException { 11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project InputStreamReader reader = new InputStreamReader(in, encoding); 11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project StringBuffer sb = new StringBuffer(); 11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project int count; 11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project char[] buf = new char[512]; 11596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project while ((count = reader.read(buf)) != -1) { 11696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project sb.append(buf, 0, count); 11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return sb.toString(); 11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 12096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 12196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public final static boolean arrayContains(Object[] a, Object o) { 122190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank int index = arrayIndex(a, o); 123190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank return (index >= 0); 124190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank } 125190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank 126190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank public final static int arrayIndex(Object[] a, Object o) { 12796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project for (int i = 0, count = a.length; i < count; i++) { 12896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (a[i].equals(o)) { 129190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank return i; 13096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 132190b2fb8a1be1d72475e2a60d0f00422712ee094Marc Blank return -1; 13396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 13496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 136fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy * Returns a concatenated string containing the output of every Object's 137fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy * toString() method, each separated by the given separator character. 13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 139fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy public static String combine(Object[] parts, char separator) { 14096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (parts == null) { 14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return null; 14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project StringBuffer sb = new StringBuffer(); 14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project for (int i = 0; i < parts.length; i++) { 14596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project sb.append(parts[i].toString()); 14696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (i < parts.length - 1) { 147fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy sb.append(separator); 14896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 14996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 15096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return sb.toString(); 15196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 15296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public static String base64Decode(String encoded) { 15396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (encoded == null) { 15496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return null; 15596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 156f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker byte[] decoded = Base64.decode(encoded, Base64.DEFAULT); 15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return new String(decoded); 15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 15996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 16096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public static String base64Encode(String s) { 16196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (s == null) { 16296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return s; 16396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 164f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker return Base64.encodeToString(s.getBytes(), Base64.NO_WRAP); 16596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 16696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 167e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank public static boolean isTextViewNotEmpty(TextView view) { 168e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank return !TextUtils.isEmpty(view.getText()); 16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 17096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 171e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank public static boolean isPortFieldValid(TextView view) { 172e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank CharSequence chars = view.getText(); 173e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank if (TextUtils.isEmpty(chars)) return false; 174e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank Integer port; 175e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank // In theory, we can't get an illegal value here, since the field is monitored for valid 176e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank // numeric input. But this might be used elsewhere without such a check. 177e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank try { 178e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank port = Integer.parseInt(chars.toString()); 179e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank } catch (NumberFormatException e) { 180e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank return false; 181e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank } 182e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank return port > 0 && port < 65536; 183ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki } 184ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki 185ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki /** 186ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki * Validate a hostname name field. 187ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki * 188ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki * Because we just use the {@link URI} class for validation, it'll accept some invalid 189ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki * host names, but it works well enough... 190ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki */ 191ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki public static boolean isServerNameValid(TextView view) { 192ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki return isServerNameValid(view.getText().toString()); 193ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki } 194ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki 195ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki public static boolean isServerNameValid(String serverName) { 196ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki serverName = serverName.trim(); 197ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki if (TextUtils.isEmpty(serverName)) { 198ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki return false; 199ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki } 200ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki try { 201ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki URI uri = new URI( 202ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki "http", 203ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki null, 204ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki serverName, 205ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki -1, 206ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki null, // path 207ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki null, // query 208ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki null); 209ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki return true; 210ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki } catch (URISyntaxException e) { 211ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki return false; 212ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki } 213ce4cce05b2ee5ea2d9629c189a79f7f30778f534Makoto Onuki } 21496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 21596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 216e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank * Ensures that the given string starts and ends with the double quote character. The string is 217e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank * not modified in any way except to add the double quote character to start and end if it's not 218e6cc662abc0b5fffe223cda5e980b4f05a4e91ddMarc Blank * already there. 219f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker * 22096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * TODO: Rename this, because "quoteString()" can mean so many different things. 221f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker * 22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * sample -> "sample" 22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * "sample" -> "sample" 22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * ""sample"" -> "sample" 22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * "sample"" -> "sample" 22696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * sa"mp"le -> "sa"mp"le" 22796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * "sa"mp"le" -> "sa"mp"le" 22896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * (empty string) -> "" 22996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * " -> "" 23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 23196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public static String quoteString(String s) { 23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (s == null) { 23396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return null; 23496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 23596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (!s.matches("^\".*\"$")) { 23696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return "\"" + s + "\""; 23796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 23896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project else { 23996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return s; 24096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 242f44a40cda14a49d5b02636a3a8a8b2eb8c23fc00Doug Zongker 24396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * A fast version of URLDecoder.decode() that works only with UTF-8 and does only two 24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * allocations. This version is around 3x as fast as the standard one and I'm using it 24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * hundreds of times in places that slow down the UI, so it helps. 24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public static String fastUrlDecode(String s) { 24996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project try { 25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project byte[] bytes = s.getBytes("UTF-8"); 25196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project byte ch; 25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project int length = 0; 25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project for (int i = 0, count = bytes.length; i < count; i++) { 25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project ch = bytes[i]; 25596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (ch == '%') { 25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project int h = (bytes[i + 1] - '0'); 25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project int l = (bytes[i + 2] - '0'); 25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (h > 9) { 25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project h -= 7; 26096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (l > 9) { 26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project l -= 7; 26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project bytes[length] = (byte) ((h << 4) | l); 26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project i += 2; 26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project else if (ch == '+') { 26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project bytes[length] = ' '; 26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project else { 27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project bytes[length] = bytes[i]; 27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project length++; 27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 27596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return new String(bytes, 0, length, "UTF-8"); 27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project catch (UnsupportedEncodingException uee) { 27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return null; 27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 281da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler private final static String HOSTAUTH_WHERE_CREDENTIALS = HostAuthColumns.ADDRESS + " like ?" 2820ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank + " and " + HostAuthColumns.LOGIN + " like ? ESCAPE '\\'" 283da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler + " and " + HostAuthColumns.PROTOCOL + " not like \"smtp\""; 284da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler private final static String ACCOUNT_WHERE_HOSTAUTH = AccountColumns.HOST_AUTH_KEY_RECV + "=?"; 285da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler 286da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler /** 287da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler * Look for an existing account with the same username & server 288da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler * 289da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler * @param context a system context 290da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler * @param allowAccountId this account Id will not trigger (when editing an existing account) 2919d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank * @param hostName the server's address 2929d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank * @param userLogin the user's login string 2939d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank * @result null = no matching account found. Account = matching account 294da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler */ 2959d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank public static Account findExistingAccount(Context context, long allowAccountId, 2969d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank String hostName, String userLogin) { 297da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler ContentResolver resolver = context.getContentResolver(); 2980ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank String userName = userLogin.replace("_", "\\_"); 299da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler Cursor c = resolver.query(HostAuth.CONTENT_URI, HostAuth.ID_PROJECTION, 3000ff0e155c51250bedc35b5b585003c8cb87f2244Marc Blank HOSTAUTH_WHERE_CREDENTIALS, new String[] { hostName, userName }, null); 30174143e89d538ce74e8c95e04cee56dca9412a7bdMarc Blank if (c == null) throw new ProviderUnavailableException(); 302da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler try { 303da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler while (c.moveToNext()) { 304da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler long hostAuthId = c.getLong(HostAuth.ID_PROJECTION_COLUMN); 3059d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank // Find account with matching hostauthrecv key, and return it 306da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler Cursor c2 = resolver.query(Account.CONTENT_URI, Account.ID_PROJECTION, 307da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler ACCOUNT_WHERE_HOSTAUTH, new String[] { Long.toString(hostAuthId) }, null); 308da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler try { 309da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler while (c2.moveToNext()) { 310da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler long accountId = c2.getLong(Account.ID_PROJECTION_COLUMN); 311da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler if (accountId != allowAccountId) { 312da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler Account account = Account.restoreAccountWithId(context, accountId); 313da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler if (account != null) { 3149d387ff0bb88ad952b53c956b468dcbcec248752Marc Blank return account; 315da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } 316da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } 317da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } 318da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } finally { 319da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler c2.close(); 320da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } 321da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } 322da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } finally { 323da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler c.close(); 324da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } 325da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler 326da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler return null; 327da8836a76cd8a6eaa7e3693eeacc6393870b2066Andrew Stadler } 328eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler 329eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler /** 330eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler * Generate a random message-id header for locally-generated messages. 331eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler */ 332eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler public static String generateMessageId() { 333eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler StringBuffer sb = new StringBuffer(); 334eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler sb.append("<"); 335eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler for (int i = 0; i < 24; i++) { 336eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler sb.append(Integer.toString((int)(Math.random() * 35), 36)); 337eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler } 338eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler sb.append("."); 339eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler sb.append(Long.toString(System.currentTimeMillis())); 340eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler sb.append("@email.android.com>"); 341eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler return sb.toString(); 342eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler } 343eb7752bf695b2a93854e0bb89ddbbc2236bb9aeaAndrew Stadler 344989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda /** 345989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda * Generate a time in milliseconds from a date string that represents a date/time in GMT 346fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy * @param date string in format 20090211T180303Z (rfc2445, iCalendar). 347989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda * @return the time in milliseconds (since Jan 1, 1970) 348989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda */ 349989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda public static long parseDateTimeToMillis(String date) { 350989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda GregorianCalendar cal = parseDateTimeToCalendar(date); 351989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda return cal.getTimeInMillis(); 352989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda } 353989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda 354989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda /** 355989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda * Generate a GregorianCalendar from a date string that represents a date/time in GMT 356fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy * @param date string in format 20090211T180303Z (rfc2445, iCalendar). 357989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda * @return the GregorianCalendar 358989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda */ 359989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda public static GregorianCalendar parseDateTimeToCalendar(String date) { 360989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)), 361989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda Integer.parseInt(date.substring(4, 6)) - 1, Integer.parseInt(date.substring(6, 8)), 362989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda Integer.parseInt(date.substring(9, 11)), Integer.parseInt(date.substring(11, 13)), 363989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda Integer.parseInt(date.substring(13, 15))); 364989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda cal.setTimeZone(TimeZone.getTimeZone("GMT")); 365989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda return cal; 366989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda } 367989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda /** 368989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda * Generate a time in milliseconds from an email date string that represents a date/time in GMT 369fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy * @param date string in format 2010-02-23T16:00:00.000Z (ISO 8601, rfc3339) 370989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda * @return the time in milliseconds (since Jan 1, 1970) 371989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda */ 372989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda public static long parseEmailDateTimeToMillis(String date) { 373989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda GregorianCalendar cal = new GregorianCalendar(Integer.parseInt(date.substring(0, 4)), 374989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda Integer.parseInt(date.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)), 375989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date.substring(14, 16)), 376989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda Integer.parseInt(date.substring(17, 19))); 377989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda cal.setTimeZone(TimeZone.getTimeZone("GMT")); 378989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda return cal.getTimeInMillis(); 379989552c10744e2d7f8fca1bdb2baef5273a8a0b9Mihai Preda } 38020225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki 3817e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki private static byte[] encode(Charset charset, String s) { 38220225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki if (s == null) { 38320225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki return null; 38420225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki } 3857e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki final ByteBuffer buffer = charset.encode(CharBuffer.wrap(s)); 38620225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki final byte[] bytes = new byte[buffer.limit()]; 38720225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki buffer.get(bytes); 38820225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki return bytes; 38920225d57609d6a5e482c088fdad60c29212d31a0Makoto Onuki } 39088a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki 3917e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki private static String decode(Charset charset, byte[] b) { 392fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki if (b == null) { 393fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki return null; 394fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki } 3957e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki final CharBuffer cb = charset.decode(ByteBuffer.wrap(b)); 396fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki return new String(cb.array(), 0, cb.length()); 397fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki } 398fe61f358ab67cac2aa454a6dd3ea6bbf876e343cMakoto Onuki 3997e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki /** Converts a String to UTF-8 */ 4007e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki public static byte[] toUtf8(String s) { 4017e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki return encode(UTF_8, s); 4027e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki } 4037e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki 4047e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki /** Builds a String from UTF-8 bytes */ 4057e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki public static String fromUtf8(byte[] b) { 4067e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki return decode(UTF_8, b); 4077e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki } 4087e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki 4097e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki /** Converts a String to ASCII bytes */ 4107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki public static byte[] toAscii(String s) { 4117e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki return encode(ASCII, s); 4127e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki } 4137e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki 4147e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki /** Builds a String from ASCII bytes */ 4157e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki public static String fromAscii(byte[] b) { 4167e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki return decode(ASCII, b); 4177e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki } 4187e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki 41988a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki /** 42088a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki * @return true if the input is the first (or only) byte in a UTF-8 character 42188a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki */ 42288a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki public static boolean isFirstUtf8Byte(byte b) { 42388a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki // If the top 2 bits is '10', it's not a first byte. 42488a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki return (b & 0xc0) != 0x80; 42588a94bca1922615564e70a27bb6ae72bca487c75Makoto Onuki } 426dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki 427dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki public static String byteToHex(int b) { 428dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki return byteToHex(new StringBuilder(), b).toString(); 429dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki } 430dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki 431dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki public static StringBuilder byteToHex(StringBuilder sb, int b) { 432dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki b &= 0xFF; 433dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki sb.append("0123456789ABCDEF".charAt(b >> 4)); 434dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki sb.append("0123456789ABCDEF".charAt(b & 0xF)); 435dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki return sb; 436dfeb1184ebf6c59fc6e617149e03edb73b7e0df7Makoto Onuki } 437eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank 438eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank public static String replaceBareLfWithCrlf(String str) { 439eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank return str.replace("\r", "").replace("\n", "\r\n"); 440eba33f8b5a0cc4a28ae5a9d6632df475c4b0a794Marc Blank } 44159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki 44259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki /** 44359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki * Cancel an {@link AsyncTask}. If it's already running, it'll be interrupted. 44459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki */ 44559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki public static void cancelTaskInterrupt(AsyncTask<?, ?, ?> task) { 44659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki cancelTask(task, true); 44759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki } 44859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki 44959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki /** 4504a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki * Cancel an {@link EmailAsyncTask}. If it's already running, it'll be interrupted. 4514a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki */ 4524a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki public static void cancelTaskInterrupt(EmailAsyncTask<?, ?, ?> task) { 4534a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki if (task != null) { 4544a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki task.cancel(true); 4554a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki } 4564a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki } 4574a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki 4584a893e60b468e0b67db522c047fa7fab057cfd6fMakoto Onuki /** 45959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki * Cancel an {@link AsyncTask}. 46059cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki * 46159cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this 46259cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki * task should be interrupted; otherwise, in-progress tasks are allowed 46359cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki * to complete. 46459cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki */ 46559cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) { 46659cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { 46759cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki task.cancel(mayInterruptIfRunning); 46859cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki } 46959cf1d05c111e3b5fb18417db41ce47b623b5b1eMakoto Onuki } 470d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki 4713a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blank public static String getSmallHash(final String value) { 472d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki final MessageDigest sha; 473d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki try { 474d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki sha = MessageDigest.getInstance("SHA-1"); 475d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki } catch (NoSuchAlgorithmException impossible) { 476d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki return null; 477d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki } 478697f98aea5a8344a17ca5e9b0410d484fdce6555Makoto Onuki sha.update(Utility.toUtf8(value)); 479d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki final int hash = getSmallHashFromSha1(sha.digest()); 480d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki return Integer.toString(hash); 481d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki } 482d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki 483d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki /** 484d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki * @return a non-negative integer generated from 20 byte SHA-1 hash. 485d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki */ 486d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki /* package for testing */ static int getSmallHashFromSha1(byte[] sha1) { 487d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki final int offset = sha1[19] & 0xf; // SHA1 is 20 bytes. 488d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki return ((sha1[offset] & 0x7f) << 24) 489d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki | ((sha1[offset + 1] & 0xff) << 16) 490d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki | ((sha1[offset + 2] & 0xff) << 8) 491d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki | ((sha1[offset + 3] & 0xff)); 492d2a0d23380a2751d82f9d1f955a812f94a301e2aMakoto Onuki } 493128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki 494128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki /** 495128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki * Try to make a date MIME(RFC 2822/5322)-compliant. 496128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki * 497128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki * It fixes: 498128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki * - "Thu, 10 Dec 09 15:08:08 GMT-0700" to "Thu, 10 Dec 09 15:08:08 -0700" 499128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki * (4 digit zone value can't be preceded by "GMT") 500128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki * We got a report saying eBay sends a date in this format 501128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki */ 502128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki public static String cleanUpMimeDate(String date) { 503128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki if (TextUtils.isEmpty(date)) { 504128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki return date; 505128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki } 506128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki date = DATE_CLEANUP_PATTERN_WRONG_TIMEZONE.matcher(date).replaceFirst("$1"); 507128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki return date; 508128fb393e8ed613c2ce283c0bd51684af2ba444dMakoto Onuki } 5097e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki 5107e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki public static ByteArrayInputStream streamFromAsciiString(String ascii) { 5117e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki return new ByteArrayInputStream(toAscii(ascii)); 5127e5ba0e1eaee76ab6e6c7ea9362348f660796596Makoto Onuki } 5137e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki 5147e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki /** 515f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki * A thread safe way to show a Toast. Can be called from any thread. 5167e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki * 517f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki * @param context context 5187e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki * @param resId Resource ID of the message string. 5197e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki */ 520f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki public static void showToast(Context context, int resId) { 521f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki showToast(context, context.getResources().getString(resId)); 52291d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki } 52391d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki 52491d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki /** 525f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki * A thread safe way to show a Toast. Can be called from any thread. 52691d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki * 527f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki * @param context context 52891d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki * @param message Message to show. 52991d47ccb47e4d4e340854a091d39886fbbadbf15Makoto Onuki */ 530f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki public static void showToast(final Context context, final String message) { 531f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki getMainThreadHandler().post(new Runnable() { 5325701e0a555a5c263862156c1291aa13b06850425Todd Kennedy @Override 5337e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki public void run() { 534f11295f3352f7c89fc92c73731e41b057e236969Makoto Onuki Toast.makeText(context, message, Toast.LENGTH_LONG).show(); 5357e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki } 5367e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki }); 5377e24c6c6f9efcba98b7e26f9e387b6591acd1355Makoto Onuki } 5383f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki 5393f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki /** 540b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank * Run {@code r} on a worker thread, returning the AsyncTask 541b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank * @return the AsyncTask; this is primarily for use by unit tests, which require the 542b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank * result of the task 543d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki * 544d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki * @deprecated use {@link EmailAsyncTask#runAsyncParallel} or 545d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki * {@link EmailAsyncTask#runAsyncSerial} 5463f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki */ 547d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki @Deprecated 548b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank public static AsyncTask<Void, Void, Void> runAsync(final Runnable r) { 549b53b1501055cbf5040bfd7b88a9cda084574c398Marc Blank return new AsyncTask<Void, Void, Void>() { 5503f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki @Override protected Void doInBackground(Void... params) { 5513f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki r.run(); 5523f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki return null; 5533f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki } 554d72f7bdf114a21db6aac66a7e83d6b002c8e8ed5Makoto Onuki }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 5553f545a4060982b8a5d715905c7818d59056c1ee0Makoto Onuki } 55644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki 55744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki /** 55844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki * Interface used in {@link #createUniqueFile} instead of {@link File#createNewFile()} to make 55944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki * it testable. 56044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki */ 56144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki /* package */ interface NewFileCreator { 56244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki public static final NewFileCreator DEFAULT = new NewFileCreator() { 56344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki @Override public boolean createNewFile(File f) throws IOException { 56444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki return f.createNewFile(); 56544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 56644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki }; 56744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki public boolean createNewFile(File f) throws IOException ; 56844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 56944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki 57044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki /** 57144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki * Creates a new empty file with a unique name in the given directory by appending a hyphen and 57244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki * a number to the given filename. 57344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki * 57444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki * @return a new File object, or null if one could not be created 57544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki */ 57644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki public static File createUniqueFile(File directory, String filename) throws IOException { 57744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki return createUniqueFileInternal(NewFileCreator.DEFAULT, directory, filename); 57844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 57944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki 58044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki /* package */ static File createUniqueFileInternal(NewFileCreator nfc, 58144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki File directory, String filename) throws IOException { 58244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki File file = new File(directory, filename); 58344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki if (nfc.createNewFile(file)) { 58444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki return file; 58544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 58644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki // Get the extension of the file, if any. 58744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki int index = filename.lastIndexOf('.'); 58844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki String format; 58944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki if (index != -1) { 59044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki String name = filename.substring(0, index); 59144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki String extension = filename.substring(index); 59244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki format = name + "-%d" + extension; 59344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } else { 59444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki format = filename + "-%d"; 59544b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 59644b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki 59744b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki for (int i = 2; i < Integer.MAX_VALUE; i++) { 59844b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki file = new File(directory, String.format(format, i)); 59944b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki if (nfc.createNewFile(file)) { 60044b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki return file; 60144b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 60244b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 60344b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki return null; 60444b5242edd938450f5e7bc5569852fa5f793da41Makoto Onuki } 605bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki 60607597e547bc02cd2247caa866d25b94745dcd448Marc Blank public interface CursorGetter<T> { 60707597e547bc02cd2247caa866d25b94745dcd448Marc Blank T get(Cursor cursor, int column); 60807597e547bc02cd2247caa866d25b94745dcd448Marc Blank } 60907597e547bc02cd2247caa866d25b94745dcd448Marc Blank 61007597e547bc02cd2247caa866d25b94745dcd448Marc Blank private static final CursorGetter<Long> LONG_GETTER = new CursorGetter<Long>() { 6115701e0a555a5c263862156c1291aa13b06850425Todd Kennedy @Override 61207597e547bc02cd2247caa866d25b94745dcd448Marc Blank public Long get(Cursor cursor, int column) { 61307597e547bc02cd2247caa866d25b94745dcd448Marc Blank return cursor.getLong(column); 61407597e547bc02cd2247caa866d25b94745dcd448Marc Blank } 61507597e547bc02cd2247caa866d25b94745dcd448Marc Blank }; 61607597e547bc02cd2247caa866d25b94745dcd448Marc Blank 61707597e547bc02cd2247caa866d25b94745dcd448Marc Blank private static final CursorGetter<Integer> INT_GETTER = new CursorGetter<Integer>() { 6185701e0a555a5c263862156c1291aa13b06850425Todd Kennedy @Override 61907597e547bc02cd2247caa866d25b94745dcd448Marc Blank public Integer get(Cursor cursor, int column) { 62007597e547bc02cd2247caa866d25b94745dcd448Marc Blank return cursor.getInt(column); 62107597e547bc02cd2247caa866d25b94745dcd448Marc Blank } 62207597e547bc02cd2247caa866d25b94745dcd448Marc Blank }; 62307597e547bc02cd2247caa866d25b94745dcd448Marc Blank 624ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki private static final CursorGetter<String> STRING_GETTER = new CursorGetter<String>() { 6255701e0a555a5c263862156c1291aa13b06850425Todd Kennedy @Override 626ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki public String get(Cursor cursor, int column) { 627ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki return cursor.getString(column); 628ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki } 629ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki }; 630ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki 63107597e547bc02cd2247caa866d25b94745dcd448Marc Blank private static final CursorGetter<byte[]> BLOB_GETTER = new CursorGetter<byte[]>() { 6325701e0a555a5c263862156c1291aa13b06850425Todd Kennedy @Override 63307597e547bc02cd2247caa866d25b94745dcd448Marc Blank public byte[] get(Cursor cursor, int column) { 63407597e547bc02cd2247caa866d25b94745dcd448Marc Blank return cursor.getBlob(column); 63507597e547bc02cd2247caa866d25b94745dcd448Marc Blank } 63607597e547bc02cd2247caa866d25b94745dcd448Marc Blank }; 63707597e547bc02cd2247caa866d25b94745dcd448Marc Blank 638bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki /** 6397093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki * @return if {@code original} is to the EmailProvider, add "?limit=1". Otherwise just returns 6407093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki * {@code original}. 6417093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki * 6427093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki * Other providers don't support the limit param. Also, changing URI passed from other apps 6437093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki * can cause permission errors. 6447093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki */ 6457093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki /* package */ static Uri buildLimitOneUri(Uri original) { 6467093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki if ("content".equals(original.getScheme()) && 6477093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki EmailContent.AUTHORITY.equals(original.getAuthority())) { 6487093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki return EmailContent.uriWithLimit(original, 1); 6497093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki } 6507093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki return original; 6517093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki } 6527093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki 6537093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki /** 65407597e547bc02cd2247caa866d25b94745dcd448Marc Blank * @return a generic in column {@code column} of the first result row, if the query returns at 655bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki * least 1 row. Otherwise returns {@code defaultValue}. 656bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki */ 65707597e547bc02cd2247caa866d25b94745dcd448Marc Blank public static <T extends Object> T getFirstRowColumn(Context context, Uri uri, 65807597e547bc02cd2247caa866d25b94745dcd448Marc Blank String[] projection, String selection, String[] selectionArgs, String sortOrder, 65907597e547bc02cd2247caa866d25b94745dcd448Marc Blank int column, T defaultValue, CursorGetter<T> getter) { 66007597e547bc02cd2247caa866d25b94745dcd448Marc Blank // Use PARAMETER_LIMIT to restrict the query to the single row we need 6617093746dd5017b1dacb726eb7b083922de15612eMakoto Onuki uri = buildLimitOneUri(uri); 662bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki Cursor c = context.getContentResolver().query(uri, projection, selection, selectionArgs, 663bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki sortOrder); 664b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (c != null) { 665b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki try { 666b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki if (c.moveToFirst()) { 66707597e547bc02cd2247caa866d25b94745dcd448Marc Blank return getter.get(c, column); 668b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 669b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } finally { 670b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki c.close(); 671bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki } 672bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki } 673bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki return defaultValue; 674bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki } 675bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki 676bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki /** 67707597e547bc02cd2247caa866d25b94745dcd448Marc Blank * {@link #getFirstRowColumn} for a Long with null as a default value. 678bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki */ 679bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki public static Long getFirstRowLong(Context context, Uri uri, String[] projection, 680bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki String selection, String[] selectionArgs, String sortOrder, int column) { 68107597e547bc02cd2247caa866d25b94745dcd448Marc Blank return getFirstRowColumn(context, uri, projection, selection, selectionArgs, 68207597e547bc02cd2247caa866d25b94745dcd448Marc Blank sortOrder, column, null, LONG_GETTER); 683bcf32320e2600e96c8a9e997a8903bfc3893b35eMakoto Onuki } 684833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki 685833fe73b99e62ad9cf6e80c782717c7de1ff12e4Makoto Onuki /** 68607597e547bc02cd2247caa866d25b94745dcd448Marc Blank * {@link #getFirstRowColumn} for a Long with a provided default value. 68736bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki */ 68807597e547bc02cd2247caa866d25b94745dcd448Marc Blank public static Long getFirstRowLong(Context context, Uri uri, String[] projection, 68936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki String selection, String[] selectionArgs, String sortOrder, int column, 69007597e547bc02cd2247caa866d25b94745dcd448Marc Blank Long defaultValue) { 69107597e547bc02cd2247caa866d25b94745dcd448Marc Blank return getFirstRowColumn(context, uri, projection, selection, selectionArgs, 69207597e547bc02cd2247caa866d25b94745dcd448Marc Blank sortOrder, column, defaultValue, LONG_GETTER); 69336bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki } 69436bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki 69536bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki /** 69607597e547bc02cd2247caa866d25b94745dcd448Marc Blank * {@link #getFirstRowColumn} for an Integer with null as a default value. 69736bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki */ 69836bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki public static Integer getFirstRowInt(Context context, Uri uri, String[] projection, 69936bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki String selection, String[] selectionArgs, String sortOrder, int column) { 70007597e547bc02cd2247caa866d25b94745dcd448Marc Blank return getFirstRowColumn(context, uri, projection, selection, selectionArgs, 70107597e547bc02cd2247caa866d25b94745dcd448Marc Blank sortOrder, column, null, INT_GETTER); 70207597e547bc02cd2247caa866d25b94745dcd448Marc Blank } 70307597e547bc02cd2247caa866d25b94745dcd448Marc Blank 70407597e547bc02cd2247caa866d25b94745dcd448Marc Blank /** 70507597e547bc02cd2247caa866d25b94745dcd448Marc Blank * {@link #getFirstRowColumn} for an Integer with a provided default value. 70607597e547bc02cd2247caa866d25b94745dcd448Marc Blank */ 70707597e547bc02cd2247caa866d25b94745dcd448Marc Blank public static Integer getFirstRowInt(Context context, Uri uri, String[] projection, 70807597e547bc02cd2247caa866d25b94745dcd448Marc Blank String selection, String[] selectionArgs, String sortOrder, int column, 70907597e547bc02cd2247caa866d25b94745dcd448Marc Blank Integer defaultValue) { 71007597e547bc02cd2247caa866d25b94745dcd448Marc Blank return getFirstRowColumn(context, uri, projection, selection, selectionArgs, 71107597e547bc02cd2247caa866d25b94745dcd448Marc Blank sortOrder, column, defaultValue, INT_GETTER); 71236bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki } 71336bdeeb0e17cb6fbf08023eeb3e4f1db58b48aeaMakoto Onuki 71407597e547bc02cd2247caa866d25b94745dcd448Marc Blank /** 715ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki * {@link #getFirstRowColumn} for a String with null as a default value. 716ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki */ 717ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki public static String getFirstRowString(Context context, Uri uri, String[] projection, 718ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki String selection, String[] selectionArgs, String sortOrder, int column) { 7190e00160074abffe03285b355dce8b6ae2a6e3138Makoto Onuki return getFirstRowString(context, uri, projection, selection, selectionArgs, sortOrder, 7200e00160074abffe03285b355dce8b6ae2a6e3138Makoto Onuki column, null); 721ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki } 722ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki 723ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki /** 724ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki * {@link #getFirstRowColumn} for a String with a provided default value. 725ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki */ 726ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki public static String getFirstRowString(Context context, Uri uri, String[] projection, 727ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki String selection, String[] selectionArgs, String sortOrder, int column, 728ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki String defaultValue) { 729ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki return getFirstRowColumn(context, uri, projection, selection, selectionArgs, 730ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki sortOrder, column, defaultValue, STRING_GETTER); 731ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki } 732ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki 733ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki /** 734ee7205d100eff01a75c292b80f41cd24a2b19b84Makoto Onuki * {@link #getFirstRowColumn} for a byte array with a provided default value. 73507597e547bc02cd2247caa866d25b94745dcd448Marc Blank */ 736b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki public static byte[] getFirstRowBlob(Context context, Uri uri, String[] projection, 737b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki String selection, String[] selectionArgs, String sortOrder, int column, 738b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki byte[] defaultValue) { 73907597e547bc02cd2247caa866d25b94745dcd448Marc Blank return getFirstRowColumn(context, uri, projection, selection, selectionArgs, sortOrder, 74007597e547bc02cd2247caa866d25b94745dcd448Marc Blank column, defaultValue, BLOB_GETTER); 741b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki } 742b1ea9c3c12d8d9da5c1e49a8752076ce60861e9fMakoto Onuki 7434dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank public static boolean attachmentExists(Context context, Attachment attachment) { 7444dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank if (attachment == null) { 7454dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank return false; 7464dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank } else if (attachment.mContentBytes != null) { 7474dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank return true; 748bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } else if (TextUtils.isEmpty(attachment.mContentUri)) { 749bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return false; 750bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 751bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook try { 752bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Uri fileUri = Uri.parse(attachment.mContentUri); 753d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki try { 754bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook InputStream inStream = context.getContentResolver().openInputStream(fileUri); 755567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki try { 756bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook inStream.close(); 757bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } catch (IOException e) { 758bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Nothing to be done if can't close the stream 759567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki } 760bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return true; 761bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } catch (FileNotFoundException e) { 762567ed19f7b2ca4c97f022b299ff2f9b5eba8c937Makoto Onuki return false; 763d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki } 764bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } catch (RuntimeException re) { 765bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Log.w(Logging.LOG_TAG, "attachmentExists RuntimeException=" + re); 766bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return false; 767d755cdce13217d25ac33169b5e410709636255c4Makoto Onuki } 76809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 76909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 77009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 77109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Check whether the message with a given id has unloaded attachments. If the message is 77209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * a forwarded message, we look instead at the messages's source for the attachments. If the 77309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * message or forward source can't be found, we return false 77409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param context the caller's context 77509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @param messageId the id of the message 77609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * @return whether or not the message has unloaded attachments 77709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 77809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean hasUnloadedAttachments(Context context, long messageId) { 77909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Message msg = Message.restoreMessageWithId(context, messageId); 78009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (msg == null) return false; 78109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment[] atts = Attachment.restoreAttachmentsWithMessageId(context, messageId); 78209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank for (Attachment att: atts) { 7834dcb1c5fdaacc40309b77af2a32532bc60218523Marc Blank if (!attachmentExists(context, att)) { 78409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // If the attachment doesn't exist and isn't marked for download, we're in trouble 78509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // since the outbound message will be stuck indefinitely in the Outbox. Instead, 78609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // we'll just delete the attachment and continue; this is far better than the 78709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // alternative. In theory, this situation shouldn't be possible. 78809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if ((att.mFlags & (Attachment.FLAG_DOWNLOAD_FORWARD | 78909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.FLAG_DOWNLOAD_USER_REQUEST)) == 0) { 79031d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank Log.d(Logging.LOG_TAG, "Unloaded attachment isn't marked for download: " + 79109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank att.mFileName + ", #" + att.mId); 79209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.delete(context, Attachment.CONTENT_URI, att.mId); 793bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } else if (att.mContentUri != null) { 79409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // In this case, the attachment file is gone from the cache; let's clear the 79509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank // contentUri; this should be a very unusual case 79609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank ContentValues cv = new ContentValues(); 79709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank cv.putNull(AttachmentColumns.CONTENT_URI); 79809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Attachment.update(context, Attachment.CONTENT_URI, att.mId, cv); 79909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 80009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return true; 80109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 80209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 80309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return false; 80409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 80509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 80609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 80709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Convenience method wrapping calls to retrieve columns from a single row, via EmailProvider. 80809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * The arguments are exactly the same as to contentResolver.query(). Results are returned in 80909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * an array of Strings corresponding to the columns in the projection. If the cursor has no 81009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * rows, null is returned. 81109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 81209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static String[] getRowColumns(Context context, Uri contentUri, String[] projection, 81309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String selection, String[] selectionArgs) { 81409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String[] values = new String[projection.length]; 81509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank ContentResolver cr = context.getContentResolver(); 81609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank Cursor c = cr.query(contentUri, projection, selection, selectionArgs, null); 81709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank try { 81809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank if (c.moveToFirst()) { 81909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank for (int i = 0; i < projection.length; i++) { 82009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank values[i] = c.getString(i); 82109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 82209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } else { 82309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return null; 82409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 82509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } finally { 82609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank c.close(); 82709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 82809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return values; 82909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 83009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 83109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank /** 83209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Convenience method for retrieving columns from a particular row in EmailProvider. 83309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * Passed in here are a base uri (e.g. Message.CONTENT_URI), the unique id of a row, and 83409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank * a projection. This method calls the previous one with the appropriate URI. 83509fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank */ 83609fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static String[] getRowColumns(Context context, Uri baseUri, long id, 83709fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank String ... projection) { 83809fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return getRowColumns(context, ContentUris.withAppendedId(baseUri, id), projection, null, 83909fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank null); 84009fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 84109fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank 84209fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank public static boolean isExternalStorageMounted() { 84309fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); 84409fd4d0a181db511a07950f52ad56cc6e686356bMarc Blank } 845f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki 846f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki /** 847f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki * Class that supports running any operation for each account. 848f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki */ 849f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki public abstract static class ForEachAccount extends AsyncTask<Void, Void, Long[]> { 850f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki private final Context mContext; 851f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki 852f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki public ForEachAccount(Context context) { 853f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki mContext = context; 854f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 855f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki 856f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki @Override 857f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki protected final Long[] doInBackground(Void... params) { 858f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki ArrayList<Long> ids = new ArrayList<Long>(); 859f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank Cursor c = mContext.getContentResolver().query(Account.CONTENT_URI, 860f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank Account.ID_PROJECTION, null, null, null); 861f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki try { 862f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki while (c.moveToNext()) { 863f5418f1f93b02e7fab9f15eb201800b65510998eMarc Blank ids.add(c.getLong(Account.ID_PROJECTION_COLUMN)); 864f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 865f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } finally { 866f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki c.close(); 867f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 868f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki return ids.toArray(EMPTY_LONGS); 869f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 870f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki 871f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki @Override 872f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki protected final void onPostExecute(Long[] ids) { 873f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki if (ids != null && !isCancelled()) { 874f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki for (long id : ids) { 875f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki performAction(id); 876f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 877f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 878f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki onFinished(); 879f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 880f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki 881f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki /** 882f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki * This method will be called for each account. 883f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki */ 884f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki protected abstract void performAction(long accountId); 885f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki 886f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki /** 887f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki * Called when the iteration is finished. 888f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki */ 889f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki protected void onFinished() { 890f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 891f52afae9424fe41071cc34a8d6cbcb82b992a411Makoto Onuki } 892767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki 893a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy /** 894a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy * Updates the last seen message key in the mailbox data base for the INBOX of the currently 895a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy * shown account. If the account is {@link Account#ACCOUNT_ID_COMBINED_VIEW}, the INBOX for 896a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy * all accounts are updated. 897a17ee57c7ff7eb3391cc32f700aaa4fed8aa971fTodd Kennedy * @return an {@link EmailAsyncTask} for test only. 898a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy */ 899f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank public static EmailAsyncTask<Void, Void, Void> updateLastNotifiedMessageKey( 900f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank final Context context, final long mailboxId) { 901a17ee57c7ff7eb3391cc32f700aaa4fed8aa971fTodd Kennedy return EmailAsyncTask.runAsyncParallel(new Runnable() { 902f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank private void updateLastSeenMessageKeyForMailbox(long mailboxId) { 903a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy ContentResolver resolver = context.getContentResolver(); 904f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (mailboxId == Mailbox.QUERY_ALL_INBOXES) { 905a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy Cursor c = resolver.query( 906f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION, Mailbox.TYPE + "=?", 907f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank new String[] { Integer.toString(Mailbox.TYPE_INBOX) }, null); 908a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy if (c == null) throw new ProviderUnavailableException(); 909a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy try { 910a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy while (c.moveToNext()) { 911a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy final long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN); 912f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank updateLastSeenMessageKeyForMailbox(id); 913a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } 914a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } finally { 915a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy c.close(); 916a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } 917f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } else if (mailboxId > 0L) { 918f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId); 919f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank // mailbox has been removed 920a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy if (mailbox == null) { 921a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy return; 922a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } 923a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy // We use the highest _id for the account the mailbox table as the "last seen 924a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy // message key". We don't care if the message has been read or not. We only 925a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy // need a point at which we can compare against in the future. By setting this 926a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy // value, we are claiming that every message before this has potentially been 927a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy // seen by the user. 928f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank long mostRecentMessageId = Utility.getFirstRowLong(context, 929f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank ContentUris.withAppendedId( 930f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank EmailContent.MAILBOX_MOST_RECENT_MESSAGE_URI, mailboxId), 931f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Message.ID_COLUMN_PROJECTION, null, null, null, 932f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Message.ID_MAILBOX_COLUMN_ID, -1L); 933f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank long lastNotifiedMessageId = mailbox.mLastNotifiedMessageKey; 93483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy // Only update the db if the value has changed 935f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank if (mostRecentMessageId != lastNotifiedMessageId) { 936f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Log.d(Logging.LOG_TAG, "Most recent = " + mostRecentMessageId + 937f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank ", last notified: " + lastNotifiedMessageId + 938f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank "; updating last notified"); 93983693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy ContentValues values = mailbox.toContentValues(); 940f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank values.put(MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY, mostRecentMessageId); 94183693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy resolver.update( 94283693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy Mailbox.CONTENT_URI, 94383693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy values, 94483693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy EmailContent.ID_SELECTION, 94583693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy new String[] { Long.toString(mailbox.mId) }); 946f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank } else { 947f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank Log.d(Logging.LOG_TAG, "Most recent = last notified; no change"); 94883693a6acaffd219f65e04054bc04530e9cc132fTodd Kennedy } 949a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } 950a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } 951a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy 952a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy @Override 953a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy public void run() { 954f419287f22ae44f25e1ba1f757ec33c7941bbfa8Marc Blank updateLastSeenMessageKeyForMailbox(mailboxId); 955a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } 956a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy }); 957a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy } 958a9ac20b96f8d2e2e97fb2878afb9df4795024450Todd Kennedy 959767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki public static long[] toPrimitiveLongArray(Collection<Long> collection) { 9604e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki // Need to do this manually because we're converting to a primitive long array, not 9614e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki // a Long array. 962767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki final int size = collection.size(); 963767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki final long[] ret = new long[size]; 964767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki // Collection doesn't have get(i). (Iterable doesn't have size()) 965767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki int i = 0; 966767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki for (Long value : collection) { 967767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki ret[i++] = value; 968767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki } 969767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki return ret; 970767f9fe2ebcca7eee20f2a048f33a96ad4bf53daMakoto Onuki } 971082443978595bee3b7907563dc7665f908872e20Makoto Onuki 9724e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki public static Set<Long> toLongSet(long[] array) { 9734e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki // Need to do this manually because we're converting from a primitive long array, not 9744e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki // a Long array. 9754e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki final int size = array.length; 9764e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki HashSet<Long> ret = new HashSet<Long>(size); 9774e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki for (int i = 0; i < size; i++) { 9784e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki ret.add(array[i]); 9794e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki } 9804e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki return ret; 9814e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki } 9824e033e0ac79d85bc8df8e52bdfc8b1fc9ad29f3eMakoto Onuki 983082443978595bee3b7907563dc7665f908872e20Makoto Onuki /** 984082443978595bee3b7907563dc7665f908872e20Makoto Onuki * Workaround for the {@link ListView#smoothScrollToPosition} randomly scroll the view bug 985082443978595bee3b7907563dc7665f908872e20Makoto Onuki * if it's called right after {@link ListView#setAdapter}. 986082443978595bee3b7907563dc7665f908872e20Makoto Onuki */ 987082443978595bee3b7907563dc7665f908872e20Makoto Onuki public static void listViewSmoothScrollToPosition(final Activity activity, 988082443978595bee3b7907563dc7665f908872e20Makoto Onuki final ListView listView, final int position) { 989082443978595bee3b7907563dc7665f908872e20Makoto Onuki // Workarond: delay-call smoothScrollToPosition() 990082443978595bee3b7907563dc7665f908872e20Makoto Onuki new Handler().post(new Runnable() { 991082443978595bee3b7907563dc7665f908872e20Makoto Onuki @Override 992082443978595bee3b7907563dc7665f908872e20Makoto Onuki public void run() { 993082443978595bee3b7907563dc7665f908872e20Makoto Onuki if (activity.isFinishing()) { 994082443978595bee3b7907563dc7665f908872e20Makoto Onuki return; // Activity being destroyed 995082443978595bee3b7907563dc7665f908872e20Makoto Onuki } 996082443978595bee3b7907563dc7665f908872e20Makoto Onuki listView.smoothScrollToPosition(position); 997082443978595bee3b7907563dc7665f908872e20Makoto Onuki } 998082443978595bee3b7907563dc7665f908872e20Makoto Onuki }); 999082443978595bee3b7907563dc7665f908872e20Makoto Onuki } 1000d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki 1001d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki private static final String[] ATTACHMENT_META_NAME_PROJECTION = { 1002d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki OpenableColumns.DISPLAY_NAME 1003d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki }; 1004d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki private static final int ATTACHMENT_META_NAME_COLUMN_DISPLAY_NAME = 0; 1005d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki 1006d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki /** 1007d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki * @return Filename of a content of {@code contentUri}. If the provider doesn't provide the 1008d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki * filename, returns the last path segment of the URI. 1009d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki */ 1010d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki public static String getContentFileName(Context context, Uri contentUri) { 1011d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki String name = getFirstRowString(context, contentUri, ATTACHMENT_META_NAME_PROJECTION, null, 1012d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki null, null, ATTACHMENT_META_NAME_COLUMN_DISPLAY_NAME); 1013d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki if (name == null) { 1014d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki name = contentUri.getLastPathSegment(); 1015d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki } 1016d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki return name; 1017d1ee5b8fa5fe92df1ded5953a9e3f001b38a1ac7Makoto Onuki } 1018adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1019adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki /** 1020a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki * Append a bold span to a {@link SpannableStringBuilder}. 1021a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki */ 1022a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki public static SpannableStringBuilder appendBold(SpannableStringBuilder ssb, String text) { 1023a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki if (!TextUtils.isEmpty(text)) { 1024a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki SpannableString ss = new SpannableString(text); 1025a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki ss.setSpan(new StyleSpan(Typeface.BOLD), 0, ss.length(), 1026a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1027a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki ssb.append(ss); 1028a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki } 1029a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki 1030a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki return ssb; 1031a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki } 1032a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki 1033a826d3fb03f29a07ea12e44237b2c02ea1926c74Makoto Onuki /** 1034adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki * Stringify a cursor for logging purpose. 1035adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki */ 1036adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki public static String dumpCursor(Cursor c) { 1037adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki StringBuilder sb = new StringBuilder(); 1038adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki sb.append("["); 1039adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki while (c != null) { 1040adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki sb.append(c.getClass()); // Class name may not be available if toString() is overridden 1041adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki sb.append("/"); 1042adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki sb.append(c.toString()); 1043adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki if (c.isClosed()) { 1044adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki sb.append(" (closed)"); 1045adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1046adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki if (c instanceof CursorWrapper) { 1047adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki c = ((CursorWrapper) c).getWrappedCursor(); 1048adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki sb.append(", "); 1049adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } else { 1050adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki break; 1051adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1052adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1053adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki sb.append("]"); 1054adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki return sb.toString(); 1055adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1056adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1057adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki /** 1058adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki * Cursor wrapper that remembers where it was closed. 1059adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki * 1060adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki * Use {@link #get} to create a wrapped cursor. 1061adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki * USe {@link #getTraceIfAvailable} to get the stack trace. 1062adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki * Use {@link #log} to log if/where it was closed. 1063adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki */ 1064adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki public static class CloseTraceCursorWrapper extends CursorWrapper { 106580a2e9109daa5d13cd748305b4c5f83578f33728Makoto Onuki private static final boolean TRACE_ENABLED = false; 1066adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1067adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki private Exception mTrace; 1068adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1069adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki private CloseTraceCursorWrapper(Cursor cursor) { 1070adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki super(cursor); 1071adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1072adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1073adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki @Override 1074adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki public void close() { 1075adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki mTrace = new Exception("STACK TRACE"); 1076adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki super.close(); 1077adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1078adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1079adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki public static Exception getTraceIfAvailable(Cursor c) { 1080adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki if (c instanceof CloseTraceCursorWrapper) { 1081adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki return ((CloseTraceCursorWrapper) c).mTrace; 1082adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } else { 1083adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki return null; 1084adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1085adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1086adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1087adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki public static void log(Cursor c) { 1088adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki if (c == null) { 1089adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki return; 1090adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1091adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki if (c.isClosed()) { 109231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank Log.w(Logging.LOG_TAG, "Cursor was closed here: Cursor=" + c, 109331d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank getTraceIfAvailable(c)); 1094adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } else { 109531d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blank Log.w(Logging.LOG_TAG, "Cursor not closed. Cursor=" + c); 1096adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1097adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1098adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1099adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki public static Cursor get(Cursor original) { 1100adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki return TRACE_ENABLED ? new CloseTraceCursorWrapper(original) : original; 1101adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1102adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki 1103adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki /* package */ static CloseTraceCursorWrapper alwaysCreateForTest(Cursor original) { 1104adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki return new CloseTraceCursorWrapper(original); 1105adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 1106adbb6f8bc4df787a8e589d505b3f2e7d9c47cea4Makoto Onuki } 11070c75f83f03b3ef8e54163cba2d67ba086cf8af58Makoto Onuki 11080c75f83f03b3ef8e54163cba2d67ba086cf8af58Makoto Onuki /** 1109fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy * Test that the given strings are equal in a null-pointer safe fashion. 1110fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy */ 1111fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy public static boolean areStringsEqual(String s1, String s2) { 1112fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy return (s1 != null && s1.equals(s2)) || (s1 == null && s2 == null); 1113fe68c0e7c2672e09076038b36ad24f095633d313Todd Kennedy } 111419b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki 111519b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki public static void enableStrictMode(boolean enabled) { 111619b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki StrictMode.setThreadPolicy(enabled 111719b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki ? new StrictMode.ThreadPolicy.Builder().detectAll().build() 111819b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki : StrictMode.ThreadPolicy.LAX); 111919b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki StrictMode.setVmPolicy(enabled 112019b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki ? new StrictMode.VmPolicy.Builder().detectAll().build() 112119b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki : StrictMode.VmPolicy.LAX); 112219b2a7ebc9cc770baace1605ff5b44b3fcb46320Makoto Onuki } 1123d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki 1124d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki public static String dumpFragment(Fragment f) { 1125d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki StringWriter sw = new StringWriter(); 1126d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki PrintWriter w = new PrintWriter(sw); 1127d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki f.dump("", new FileDescriptor(), w, new String[0]); 1128d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki return sw.toString(); 1129d8a2e33998a2959d28441e609e915f7e5ad07b7fMakoto Onuki } 11308de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki 11318de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki /** 11328de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki * Builds an "in" expression for SQLite. 11338de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki * 11348de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki * e.g. "ID" + 1,2,3 -> "ID in (1,2,3)". If {@code values} is empty or null, it returns an 11358de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki * empty string. 11368de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki */ 11378de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki public static String buildInSelection(String columnName, Collection<? extends Number> values) { 11388de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki if ((values == null) || (values.size() == 0)) { 11398de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki return ""; 11408de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki } 11418de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki StringBuilder sb = new StringBuilder(); 11428de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki sb.append(columnName); 11438de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki sb.append(" in ("); 11448de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki String sep = ""; 11458de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki for (Number n : values) { 11468de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki sb.append(sep); 11478de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki sb.append(n.toString()); 11488de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki sep = ","; 11498de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki } 11508de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki sb.append(')'); 11518de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki return sb.toString(); 11528de5bda81594182757c1aa94a65cfb8c7b360b34Makoto Onuki } 1153bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 1154bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook /** 1155bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * Updates the last seen message key in the mailbox data base for the INBOX of the currently 1156bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * shown account. If the account is {@link Account#ACCOUNT_ID_COMBINED_VIEW}, the INBOX for 1157bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * all accounts are updated. 1158bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook * @return an {@link EmailAsyncTask} for test only. 1159bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook */ 1160bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public static EmailAsyncTask<Void, Void, Void> updateLastSeenMessageKey(final Context context, 1161bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final long accountId) { 1162bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return EmailAsyncTask.runAsyncParallel(new Runnable() { 1163bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook private void updateLastSeenMessageKeyForAccount(long accountId) { 1164bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook ContentResolver resolver = context.getContentResolver(); 1165bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (accountId == Account.ACCOUNT_ID_COMBINED_VIEW) { 1166bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Cursor c = resolver.query( 1167bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Account.CONTENT_URI, EmailContent.ID_PROJECTION, null, null, null); 1168bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (c == null) throw new ProviderUnavailableException(); 1169bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook try { 1170bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook while (c.moveToNext()) { 1171bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook final long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN); 1172bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook updateLastSeenMessageKeyForAccount(id); 1173bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 1174bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } finally { 1175bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook c.close(); 1176bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 1177bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } else if (accountId > 0L) { 1178bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Mailbox mailbox = 1179bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_INBOX); 1180bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 1181bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // mailbox has been removed 1182bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (mailbox == null) { 1183bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook return; 1184bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 1185bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // We use the highest _id for the account the mailbox table as the "last seen 1186bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // message key". We don't care if the message has been read or not. We only 1187bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // need a point at which we can compare against in the future. By setting this 1188bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // value, we are claiming that every message before this has potentially been 1189bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // seen by the user. 1190bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook long messageId = Utility.getFirstRowLong( 1191bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook context, 1192bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Message.CONTENT_URI, 1193bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook EmailContent.ID_PROJECTION, 1194bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook MessageColumns.MAILBOX_KEY + "=?", 1195bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook new String[] { Long.toString(mailbox.mId) }, 1196bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook MessageColumns.ID + " DESC", 1197bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook EmailContent.ID_PROJECTION_COLUMN, 0L); 1198bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook long oldLastSeenMessageId = Utility.getFirstRowLong( 1199bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook context, ContentUris.withAppendedId(Mailbox.CONTENT_URI, mailbox.mId), 1200bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook new String[] { MailboxColumns.LAST_SEEN_MESSAGE_KEY }, 1201bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook null, null, null, 0, 0L); 1202bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook // Only update the db if the value has changed 1203bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook if (messageId != oldLastSeenMessageId) { 1204bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook ContentValues values = mailbox.toContentValues(); 1205bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook values.put(MailboxColumns.LAST_SEEN_MESSAGE_KEY, messageId); 1206bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook resolver.update( 1207bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook Mailbox.CONTENT_URI, 1208bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook values, 1209bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook EmailContent.ID_SELECTION, 1210bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook new String[] { Long.toString(mailbox.mId) }); 1211bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 1212bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 1213bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 1214bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook 1215bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook @Override 1216bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook public void run() { 1217bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook updateLastSeenMessageKeyForAccount(accountId); 1218bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 1219bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook }); 1220bc47398187c6ffd132435e51d8d61e6ec79a79dbPaul Westbrook } 122196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project} 1222