Advertisement
arnab

SmsPopupUtils.java

Jun 3rd, 2012
2,669
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 5 44.99 KB | None | 0 0
  1. package net.everythingandroid.smspopup.util;
  2.  
  3. import java.io.InputStream;
  4. import java.util.ArrayList;
  5. import java.util.Collections;
  6. import java.util.Comparator;
  7. import java.util.Iterator;
  8. import java.util.List;
  9. import java.util.Map;
  10. import java.util.regex.Matcher;
  11. import java.util.regex.Pattern;
  12.  
  13. import net.everythingandroid.smspopup.BuildConfig;
  14. import net.everythingandroid.smspopup.R;
  15. import net.everythingandroid.smspopup.provider.SmsMmsMessage;
  16. import net.everythingandroid.smspopup.provider.SmsPopupContract.ContactNotifications;
  17. import net.everythingandroid.smspopup.receiver.SmsReceiver;
  18. import net.everythingandroid.smspopup.util.ManagePreferences.Defaults;
  19. import android.annotation.SuppressLint;
  20. import android.app.ActivityManager;
  21. import android.app.ActivityManager.RunningTaskInfo;
  22. import android.content.ComponentName;
  23. import android.content.ContentResolver;
  24. import android.content.ContentUris;
  25. import android.content.ContentValues;
  26. import android.content.Context;
  27. import android.content.Intent;
  28. import android.content.SharedPreferences;
  29. import android.content.SharedPreferences.Editor;
  30. import android.content.pm.PackageManager;
  31. import android.content.res.Resources;
  32. import android.database.Cursor;
  33. import android.database.DatabaseUtils;
  34. import android.graphics.Bitmap;
  35. import android.graphics.BitmapFactory;
  36. import android.net.Uri;
  37. import android.os.Build;
  38. import android.preference.PreferenceManager;
  39. import android.provider.ContactsContract.CommonDataKinds.Email;
  40. import android.provider.ContactsContract.Contacts;
  41. import android.provider.ContactsContract.PhoneLookup;
  42. import android.telephony.SmsMessage;
  43. import android.text.TextUtils;
  44.  
  45. public class SmsPopupUtils {
  46.     // Content URIs for SMS app, these may change in future SDK
  47.     public static final Uri MMS_SMS_CONTENT_URI = Uri.parse("content://mms-sms/");
  48.     public static final Uri THREAD_ID_CONTENT_URI =
  49.             Uri.withAppendedPath(MMS_SMS_CONTENT_URI, "threadID");
  50.     public static final Uri CONVERSATION_CONTENT_URI =
  51.             Uri.withAppendedPath(MMS_SMS_CONTENT_URI, "conversations");
  52.     public static final String SMSTO_URI = "smsto:";
  53.     private static final String UNREAD_CONDITION = "read=0";
  54.  
  55.     public static final Uri SMS_CONTENT_URI = Uri.parse("content://sms");
  56.     public static final Uri SMS_INBOX_CONTENT_URI = Uri.withAppendedPath(SMS_CONTENT_URI, "inbox");
  57.  
  58.     public static final Uri MMS_CONTENT_URI = Uri.parse("content://mms");
  59.     public static final Uri MMS_INBOX_CONTENT_URI = Uri.withAppendedPath(MMS_CONTENT_URI, "inbox");
  60.  
  61.     public static final String SMSMMS_ID = "_id";
  62.     public static final String SMS_MIME_TYPE = "vnd.android-dir/mms-sms";
  63.     public static final int READ_THREAD = 1;
  64.     public static final int MESSAGE_TYPE_SMS = 1;
  65.     public static final int MESSAGE_TYPE_MMS = 2;
  66.  
  67.     // The max size of either the width or height of the contact photo
  68.     public static final int CONTACT_PHOTO_MAXSIZE = 1024;
  69.  
  70.     // Bitmap cache
  71. //    private static final int bitmapCacheSize = 5;
  72. //    private static LruCache<Uri, Bitmap> bitmapCache = null;
  73.  
  74.     private static final String[] AUTHOR_CONTACT_INFO =
  75.             { "Adam K <smspopup@everythingandroid.net>" };
  76.     private static final String[] AUTHOR_CONTACT_INFO_DONATE =
  77.             { "Adam K <smspopup+donate@everythingandroid.net>" };
  78.  
  79.     public static final Uri DONATE_PAYPAL_URI =
  80.             Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8246419");
  81.     public static final Uri DONATE_MARKET_URI =
  82.             Uri.parse("market://details?id=net.everythingandroid.smspopupdonate");
  83.  
  84.     public static final String SAMSUNG_OEM_NAME = "samsung";
  85.  
  86.     public static boolean isHoneycomb() {
  87.         // Can use static final constants like HONEYCOMB, declared in later versions
  88.         // of the OS since they are inlined at compile time. This is guaranteed behavior.
  89.         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
  90.     }
  91.  
  92.     public static boolean isICS() {
  93.         // Can use static final constants like ICS, declared in later versions
  94.         // of the OS since they are inlined at compile time. This is guaranteed behavior.
  95.         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
  96.     }
  97.  
  98.     /**
  99.      * Looks up a contacts display name by contact lookup key - if not found,
  100.      * the address (phone number) will be formatted and returned instead.
  101.      * @param context Context.
  102.      * @param lookupKey Contact lookup key.
  103.      * @param address Address (phone number) that will be returned if the contact cannot be
  104.      *          found. The address will be formatted before it is returned.
  105.      * @return Contact name or null if not found.
  106.      */
  107.     public static ContactIdentification getPersonNameByLookup(Context context, String lookupKey,
  108.             String contactId) {
  109.  
  110.         // Check for id, if null return the formatting phone number as the name
  111.         if (lookupKey == null) {
  112.             return null;
  113.         }
  114.  
  115.         Uri.Builder builder = Contacts.CONTENT_LOOKUP_URI.buildUpon();
  116.         builder.appendPath(lookupKey);
  117.         if (contactId != null) {
  118.             builder.appendPath(contactId);
  119.         }
  120.         Uri uri = builder.build();
  121.  
  122.         Cursor cursor = context.getContentResolver().query(
  123.                 uri,
  124.                 new String[] { Contacts._ID, Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME },
  125.                 null, null, null);
  126.  
  127.         if (cursor != null) {
  128.             try {
  129.                 if (cursor.moveToFirst()) {
  130.                     final String newId = cursor.getString(0);
  131.                     final String newLookup = cursor.getString(1);
  132.                     final String contactName = cursor.getString(2);
  133.                     if (BuildConfig.DEBUG)Log.v("Contact Display Name: " + contactName);
  134.                     return new ContactIdentification(newId, newLookup, contactName);
  135.                 }
  136.             } finally {
  137.                 if (cursor != null) {
  138.                     cursor.close();
  139.                 }
  140.             }
  141.         }
  142.  
  143.         return null;
  144.     }
  145.  
  146.     /*
  147.      * Class to hold contact lookup info (as of Android 2.0+ we need the id and lookup key)
  148.      */
  149.     public static class ContactIdentification {
  150.         public String contactId = null;
  151.         public String contactLookup = null;
  152.         public String contactName = null;
  153.  
  154.         public ContactIdentification(String _contactId, String _contactLookup, String _contactName) {
  155.             contactId = _contactId;
  156.             contactLookup = _contactLookup;
  157.             contactName = _contactName;
  158.         }
  159.     }
  160.  
  161.     /**
  162.      * Looks up a contacts id, given their address (phone number in this case). Returns null if not
  163.      * found
  164.      */
  165.     public static ContactIdentification getPersonIdFromPhoneNumber(
  166.             Context context, String address) {
  167.  
  168.         if (address == null) {
  169.             return null;
  170.         }
  171.  
  172.         Cursor cursor = null;
  173.         try {
  174.             cursor = context.getContentResolver().query(
  175.                     Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address)),
  176.                     new String[] { PhoneLookup._ID, PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY },
  177.                     null, null, null);
  178.         } catch (IllegalArgumentException e) {
  179.             Log.e("getPersonIdFromPhoneNumber(): " + e.toString());
  180.             return null;
  181.         } catch (Exception e) {
  182.             Log.e("getPersonIdFromPhoneNumber(): " + e.toString());
  183.             return null;
  184.         }
  185.  
  186.         if (cursor != null) {
  187.             try {
  188.                 if (cursor.getCount() > 0) {
  189.                     cursor.moveToFirst();
  190.                     String contactId = String.valueOf(cursor.getLong(0));
  191.                     String contactName = cursor.getString(1);
  192.                     String contactLookup = cursor.getString(2);
  193.  
  194.                     if (BuildConfig.DEBUG)
  195.                         Log.v("Found person: " + contactId + ", " + contactName + ", "
  196.                                 + contactLookup);
  197.                     return new ContactIdentification(contactId, contactLookup, contactName);
  198.                 }
  199.             } finally {
  200.                 if (cursor != null) {
  201.                     cursor.close();
  202.                 }
  203.             }
  204.         }
  205.  
  206.         return null;
  207.     }
  208.  
  209.     /**
  210.      * Looks up a contacts id, given their email address. Returns null if not found
  211.      */
  212.     public static ContactIdentification getPersonIdFromEmail(Context context, String email) {
  213.         if (email == null)
  214.             return null;
  215.  
  216.         Cursor cursor = null;
  217.         try {
  218.             cursor = context.getContentResolver().query(
  219.                     Uri.withAppendedPath(
  220.                             Email.CONTENT_LOOKUP_URI,
  221.                             Uri.encode(extractAddrSpec(email))),
  222.                     new String[] { Email.CONTACT_ID, Email.DISPLAY_NAME_PRIMARY, Email.LOOKUP_KEY },
  223.                     null, null, null);
  224.         } catch (IllegalArgumentException e) {
  225.             Log.e("getPersonIdFromEmail(): " + e.toString());
  226.             return null;
  227.         } catch (Exception e) {
  228.             Log.e("getPersonIdFromEmail(): " + e.toString());
  229.             return null;
  230.         }
  231.  
  232.         if (cursor != null) {
  233.             try {
  234.                 if (cursor.moveToFirst()) {
  235.  
  236.                     String contactId = String.valueOf(cursor.getLong(0));
  237.                     String contactName = cursor.getString(1);
  238.                     String contactLookup = cursor.getString(2);
  239.  
  240.                     if (BuildConfig.DEBUG)
  241.                         Log.v("Found person: " + contactId + ", " + contactName + ", "
  242.                                 + contactLookup);
  243.                     return new ContactIdentification(contactId, contactLookup, contactName);
  244.                 }
  245.             } finally {
  246.                 if (cursor != null) {
  247.                     cursor.close();
  248.                 }
  249.             }
  250.         }
  251.         return null;
  252.     }
  253.  
  254.     /**
  255.      *
  256.      * Looks up a contact photo by contact id, returns a Bitmap array that represents their photo
  257.      * (or null if not found or there was an error.
  258.      *
  259.      * I do my own scaling and validation of sizes - Android supports any size for contact photos
  260.      * and some apps are adding huge photos to contacts. Doing the scaling myself allows me more
  261.      * control over how things play out in those cases.
  262.      *
  263.      * @param context
  264.      *            the context
  265.      * @param id
  266.      *            contact id
  267.      * @param maxThumbSize
  268.      *            the max size the thumbnail can be
  269.      * @return Bitmap of the contacts photo (null if none or an error)
  270.      */
  271.     public static Bitmap getPersonPhoto(Context context, final Uri contactUri,
  272.                 final int thumbSize) {
  273.  
  274.         if (contactUri == null) {
  275.             return null;
  276.         }
  277.  
  278.         // First let's just check the dimensions of the contact photo
  279.         BitmapFactory.Options options = new BitmapFactory.Options();
  280.         options.inJustDecodeBounds = true;
  281.  
  282.         // The height and width are stored in 'options' but the photo itself is not loaded
  283.         loadContactPhoto(context, contactUri, options);
  284.  
  285.         // Raw height and width of contact photo
  286.         final int height = options.outHeight;
  287.         final int width = options.outWidth;
  288.  
  289.         if (BuildConfig.DEBUG)
  290.             Log.v("Contact photo size = " + height + "x" + width);
  291.  
  292.         // If photo is too large or not found get out
  293.         if (height > CONTACT_PHOTO_MAXSIZE || width > CONTACT_PHOTO_MAXSIZE ||
  294.                 width == 0 || height == 0) {
  295.             return null;
  296.         }
  297.  
  298.         // This time we're going to do it for real
  299.         options.inJustDecodeBounds = false;
  300.  
  301.         int newHeight = thumbSize;
  302.         int newWidth = thumbSize;
  303.  
  304.         // If we have an abnormal photo size that's larger than thumbsize then sample it down
  305.         boolean sampleDown = false;
  306.  
  307.         if (height > thumbSize || width > thumbSize) {
  308.             sampleDown = true;
  309.         }
  310.  
  311.         // If the dimensions are not the same then calculate new scaled dimenions
  312.         if (height < width) {
  313.             if (sampleDown) {
  314.                 options.inSampleSize = Math.round(height / thumbSize);
  315.             }
  316.             newHeight = Math.round(thumbSize * height / width);
  317.         } else {
  318.             if (sampleDown) {
  319.                 options.inSampleSize = Math.round(width / thumbSize);
  320.             }
  321.             newWidth = Math.round(thumbSize * width / height);
  322.         }
  323.  
  324.         // Fetch the real contact photo (sampled down if needed)
  325.         Bitmap contactBitmap = null;
  326.         try {
  327.             contactBitmap = loadContactPhoto(context, contactUri, options);
  328.         } catch (OutOfMemoryError e) {
  329.             Log.e("Out of memory when loading contact photo");
  330.         }
  331.  
  332.         // Not found or error, get out
  333.         if (contactBitmap == null)
  334.             return null;
  335.  
  336.         // Bitmap scaled to new height and width
  337.         return Bitmap.createScaledBitmap(contactBitmap, newWidth, newHeight, true);
  338.     }
  339.  
  340.     public static Bitmap getPersonPhoto(Context context, Uri contactUri) {
  341.         if (context == null) {
  342.             return null;
  343.         }
  344.         final Resources res = context.getResources();
  345.         final int thumbSize = (int) res.getDimension(R.dimen.contact_thumbnail_size);
  346.         final int thumbBorder = (int) res.getDimension(R.dimen.contact_thumbnail_border);
  347.         return getPersonPhoto(context, contactUri, thumbSize - thumbBorder);
  348.     }
  349.  
  350.     /**
  351.      * Opens an InputStream for the person's photo and returns the photo as a Bitmap. If the
  352.      * person's photo isn't present returns the placeholderImageResource instead.
  353.      *
  354.      * @param context
  355.      *            the Context
  356.      * @param id
  357.      *            the id of the person
  358.      * @param options
  359.      *            the decoding options, can be set to null
  360.      */
  361.     @SuppressLint("NewApi")
  362.     public static Bitmap loadContactPhoto(Context context, Uri contactUri,
  363.                 BitmapFactory.Options options) {
  364.  
  365.         if (contactUri == null) {
  366.             return null;
  367.         }
  368.  
  369.         final InputStream stream;
  370.         if (SmsPopupUtils.isICS()) {
  371.             stream = Contacts.openContactPhotoInputStream(context.getContentResolver(),
  372.                     contactUri, true);
  373.         } else {
  374.             stream = Contacts.openContactPhotoInputStream(context.getContentResolver(),
  375.                     contactUri);
  376.         }
  377.  
  378.         return stream != null ? BitmapFactory.decodeStream(stream, null, options) : null;
  379.     }
  380.  
  381.     /**
  382.      *
  383.      * Tries to locate the message thread id given the address (phone or email) of the message
  384.      * sender.
  385.      *
  386.      * @param context
  387.      *            a context to use
  388.      * @param address
  389.      *            phone number or email address of sender
  390.      * @return the thread id (or 0 if there was a problem)
  391.      */
  392.     public static long findThreadIdFromAddress(Context context, String address) {
  393.         if (address == null)
  394.             return 0;
  395.  
  396.         String THREAD_RECIPIENT_QUERY = "recipient";
  397.  
  398.         Uri.Builder uriBuilder = THREAD_ID_CONTENT_URI.buildUpon();
  399.         uriBuilder.appendQueryParameter(THREAD_RECIPIENT_QUERY, address);
  400.  
  401.         long threadId = 0;
  402.  
  403.         Cursor cursor = null;
  404.         try {
  405.  
  406.             cursor = context.getContentResolver().query(
  407.                     uriBuilder.build(),
  408.                     new String[] { Contacts._ID },
  409.                     null, null, null);
  410.  
  411.             if (cursor != null && cursor.moveToFirst()) {
  412.                 threadId = cursor.getLong(0);
  413.             }
  414.         } finally {
  415.             if (cursor != null) {
  416.                 cursor.close();
  417.             }
  418.         }
  419.         return threadId;
  420.     }
  421.  
  422.     /**
  423.      * Marks a specific message as read
  424.      */
  425.     public static void setMessageRead(
  426.             Context context, long messageId, int messageType) {
  427.  
  428.         SharedPreferences myPrefs = PreferenceManager.getDefaultSharedPreferences(context);
  429.         boolean markRead = myPrefs.getBoolean(
  430.                 context.getString(R.string.pref_markread_key),
  431.                 Defaults.PREFS_MARK_READ);
  432.         if (!markRead) {
  433.             return;
  434.         }
  435.  
  436.         if (messageId > 0) {
  437.             ContentValues values = new ContentValues(1);
  438.             values.put("read", READ_THREAD);
  439.  
  440.             Uri messageUri;
  441.  
  442.             if (SmsMmsMessage.MESSAGE_TYPE_MMS == messageType) {
  443.                 // Used to use URI of MMS_CONTENT_URI and it wasn't working, not sure why
  444.                 // this is diff to SMS
  445.                 messageUri = Uri.withAppendedPath(MMS_INBOX_CONTENT_URI, String.valueOf(messageId));
  446.             } else if (SmsMmsMessage.MESSAGE_TYPE_SMS == messageType) {
  447.                 messageUri = Uri.withAppendedPath(SMS_CONTENT_URI, String.valueOf(messageId));
  448.             } else {
  449.                 return;
  450.             }
  451.  
  452.             // Log.v("messageUri for marking message read: " + messageUri.toString());
  453.  
  454.             ContentResolver cr = context.getContentResolver();
  455.             int result;
  456.             try {
  457.                 result = cr.update(messageUri, values, null, null);
  458.             } catch (Exception e) {
  459.                 result = 0;
  460.             }
  461.             if (BuildConfig.DEBUG)
  462.                 Log.v(String.format("message id = %s marked as read, result = %s", messageId,
  463.                         result));
  464.         }
  465.     }
  466.  
  467.     /**
  468.      * Marks a specific message thread as read - all messages in the thread will be marked read
  469.      */
  470.     public static void setThreadRead(Context context, long threadId) {
  471.         SharedPreferences myPrefs = PreferenceManager.getDefaultSharedPreferences(context);
  472.         boolean markRead = myPrefs.getBoolean(
  473.                 context.getString(R.string.pref_markread_key),
  474.                 Defaults.PREFS_MARK_READ);
  475.  
  476.         if (!markRead)
  477.             return;
  478.  
  479.         if (threadId > 0) {
  480.             ContentValues values = new ContentValues(1);
  481.             values.put("read", READ_THREAD);
  482.  
  483.             ContentResolver cr = context.getContentResolver();
  484.             int result = 0;
  485.             try {
  486.                 result = cr.update(
  487.                         ContentUris.withAppendedId(CONVERSATION_CONTENT_URI, threadId),
  488.                         values, null, null);
  489.             } catch (Exception e) {
  490.                 if (BuildConfig.DEBUG)
  491.                     Log.v("error marking thread read");
  492.             }
  493.             if (BuildConfig.DEBUG)
  494.                 Log.v("thread id " + threadId + " marked as read, result = " + result);
  495.         }
  496.     }
  497.  
  498.     /**
  499.      * Tries to locate the message id (from the system database), given the message thread id, the
  500.      * timestamp of the message and the type of message (sms/mms)
  501.      */
  502.     public static long findMessageId(Context context, long threadId, long timestamp,
  503.             String body, int messageType) {
  504.  
  505.         long id = 0;
  506.         String selection = "body = " + DatabaseUtils.sqlEscapeString(body != null ? body : "");
  507.         selection += " and " + UNREAD_CONDITION;
  508.         final String sortOrder = "date DESC";
  509.         final String[] projection = new String[] { "_id", "date", "thread_id", "body" };
  510.  
  511.         if (threadId > 0) {
  512.             if (BuildConfig.DEBUG)
  513.                 Log.v("Trying to find message ID");
  514.             if (SmsMmsMessage.MESSAGE_TYPE_MMS == messageType) {
  515.                 // It seems MMS timestamps are stored in a seconds, whereas SMS timestamps are in
  516.                 // millis
  517.                 selection += " and date = " + (timestamp / 1000);
  518.             }
  519.  
  520.             Cursor cursor = context.getContentResolver().query(
  521.                     ContentUris.withAppendedId(CONVERSATION_CONTENT_URI, threadId),
  522.                     projection,
  523.                     selection,
  524.                     null,
  525.                     sortOrder);
  526.  
  527.             try {
  528.                 if (cursor != null && cursor.moveToFirst()) {
  529.                     id = cursor.getLong(0);
  530.                     if (BuildConfig.DEBUG)
  531.                         Log.v("Message id found = " + id);
  532.                     // Log.v("Timestamp = " + cursor.getLong(1));
  533.                 }
  534.             } finally {
  535.                 if (cursor != null) {
  536.                     cursor.close();
  537.                 }
  538.             }
  539.         }
  540.  
  541.         if (BuildConfig.DEBUG && id == 0) {
  542.             Log.v("Message id could not be found");
  543.         }
  544.  
  545.         return id;
  546.     }
  547.  
  548.     /**
  549.      * Tries to delete a message from the system database, given the thread id, the timestamp of the
  550.      * message and the message type (sms/mms).
  551.      */
  552.     public static void deleteMessage(Context context, long messageId,
  553.             long threadId, int messageType) {
  554.  
  555.         if (messageId > 0) {
  556.             if (BuildConfig.DEBUG)
  557.                 Log.v("id of message to delete is " + messageId);
  558.  
  559.             // We need to mark this message read first to ensure the entire thread is marked as read
  560.             setMessageRead(context, messageId, messageType);
  561.  
  562.             // Construct delete message uri
  563.             Uri deleteUri;
  564.  
  565.             if (SmsMmsMessage.MESSAGE_TYPE_MMS == messageType) {
  566.                 deleteUri = Uri.withAppendedPath(MMS_CONTENT_URI, String.valueOf(messageId));
  567.             } else if (SmsMmsMessage.MESSAGE_TYPE_SMS == messageType) {
  568.                 deleteUri = Uri.withAppendedPath(SMS_CONTENT_URI, String.valueOf(messageId));
  569.             } else {
  570.                 return;
  571.             }
  572.  
  573.             int count = 0;
  574.             try {
  575.                 count = context.getContentResolver().delete(deleteUri, null, null);
  576.             } catch (Exception e) {
  577.                 if (BuildConfig.DEBUG)
  578.                     Log.v("deleteMessage(): Problem deleting message - " + e.toString());
  579.             }
  580.  
  581.             if (BuildConfig.DEBUG)
  582.                 Log.v("Messages deleted: " + count);
  583.             if (count == 1) {
  584.                 // TODO: should only set the thread read if there are no more unread messages
  585.                 // setThreadRead(context, threadId);
  586.             }
  587.         }
  588.     }
  589.  
  590.     public static ArrayList<SmsMmsMessage> getUnreadMessages(Context context) {
  591.         final ArrayList<SmsMmsMessage> messages = getUnreadSms(context);
  592.         final ArrayList<SmsMmsMessage> mmsMessages = getUnreadMms(context);
  593.  
  594.         if (messages == null && mmsMessages == null) {
  595.             return null;
  596.         } else if (messages != null && mmsMessages == null) {
  597.             return messages;
  598.         } else if (messages == null && mmsMessages != null) {
  599.             return mmsMessages;
  600.         }
  601.         messages.addAll(mmsMessages);
  602.         Collections.sort(messages, new Comparator<SmsMmsMessage>(){
  603.             @Override
  604.             public int compare(SmsMmsMessage lhs, SmsMmsMessage rhs) {
  605.                 if (lhs.getTimestamp() < rhs.getTimestamp()) {
  606.                     return -1;
  607.                 }
  608.                 return 1;
  609.             }
  610.         });
  611.         messages.trimToSize();
  612.         final String a = "1";
  613.         return messages;
  614.     }
  615.  
  616.     /**
  617.      * Fetches a list of unread messages from the system database
  618.      *
  619.      * @param context
  620.      *            app context
  621.      * @param ignoreMessageId
  622.      *            message id to ignore (the one being displayed), setting this to 0 will return all
  623.      *            unread messages
  624.      *
  625.      * @return ArrayList of SmsMmsMessage
  626.      */
  627.     public static ArrayList<SmsMmsMessage> getUnreadSms(Context context) {
  628.         ArrayList<SmsMmsMessage> messages = null;
  629.  
  630.         final String[] projection =
  631.                 new String[] { "_id", "thread_id", "address", "date", "body" };
  632.         String selection = UNREAD_CONDITION + " and date>0 and body is not null and body != ''";
  633.         String[] selectionArgs = null;
  634.         final String sortOrder = "date ASC";
  635.  
  636.         // Create cursor
  637.         Cursor cursor = context.getContentResolver().query(
  638.                 SMS_INBOX_CONTENT_URI,
  639.                 projection,
  640.                 selection,
  641.                 selectionArgs,
  642.                 sortOrder);
  643.  
  644.         long messageId;
  645.         long threadId;
  646.         String address;
  647.         long timestamp;
  648.         String body;
  649.         SmsMmsMessage message;
  650.  
  651.         if (cursor != null) {
  652.             try {
  653.                 int count = cursor.getCount();
  654.                 if (count > 0) {
  655.                     messages = new ArrayList<SmsMmsMessage>(count);
  656.                     while (cursor.moveToNext()) {
  657.                         messageId = cursor.getLong(0);
  658.                         threadId = cursor.getLong(1);
  659.                         address = cursor.getString(2);
  660.                         timestamp = cursor.getLong(3);
  661.                         body = cursor.getString(4);
  662.  
  663.                         if (!TextUtils.isEmpty(address) && !TextUtils.isEmpty(body)
  664.                                 && timestamp > 0) {
  665.                             message = new SmsMmsMessage(
  666.                                     context, address, body, timestamp, threadId,
  667.                                     count, messageId, SmsMmsMessage.MESSAGE_TYPE_SMS);
  668.                             message.setNotify(false);
  669.                             messages.add(message);
  670.                         }
  671.                     }
  672.                 }
  673.             } finally {
  674.                 if (cursor != null) {
  675.                     cursor.close();
  676.                 }
  677.             }
  678.         }
  679.         return messages;
  680.     }
  681.  
  682.     public static ArrayList<SmsMmsMessage> getUnreadMms(Context context) {
  683.         ArrayList<SmsMmsMessage> messages = null;
  684.  
  685.         final String[] projection = new String[] { "_id", "thread_id", "date", "sub", "sub_cs" };
  686.         String selection = UNREAD_CONDITION;
  687.         String[] selectionArgs = null;
  688.         final String sortOrder = "date ASC";
  689.         int count = 0;
  690.  
  691. //        if (ignoreThreadId > 0) {
  692. //            selection += " and thread_id != ?";
  693. //            selectionArgs = new String[] { String.valueOf(ignoreThreadId) };
  694. //        }
  695.  
  696.         Cursor cursor = context.getContentResolver().query(
  697.                 MMS_INBOX_CONTENT_URI,
  698.                 projection,
  699.                 selection,
  700.                 selectionArgs,
  701.                 sortOrder);
  702.  
  703.         SmsMmsMessage message;
  704.         if (cursor != null) {
  705.             try {
  706.                 count = cursor.getCount();
  707.                 if (count > 0) {
  708.                     messages = new ArrayList<SmsMmsMessage>(count);
  709.                     while (cursor.moveToNext()) {
  710.                         long messageId = cursor.getLong(0);
  711.                         long threadId = cursor.getLong(1);
  712.                         long timestamp = cursor.getLong(2) * 1000;
  713.                         String subject = cursor.getString(3);
  714.  
  715.                         message = new SmsMmsMessage(context, messageId, threadId, timestamp,
  716.                                 subject, count, SmsMmsMessage.MESSAGE_TYPE_MMS);
  717.                         message.setNotify(false);
  718.                         messages.add(message);
  719.                     }
  720.                 }
  721.             } finally {
  722.                 if (cursor != null) {
  723.                     cursor.close();
  724.                 }
  725.             }
  726.         }
  727.         return messages;
  728.     }
  729.  
  730.     /**
  731.      *
  732.      * @return
  733.      */
  734.     public static Intent getSmsInboxIntent() {
  735.         Intent conversations = new Intent(Intent.ACTION_MAIN);
  736.         // conversations.addCategory(Intent.CATEGORY_DEFAULT);
  737.         conversations.setType(SMS_MIME_TYPE);
  738.         // should I be using FLAG_ACTIVITY_RESET_TASK_IF_NEEDED??
  739.         int flags =
  740.                 Intent.FLAG_ACTIVITY_NEW_TASK |
  741.                         Intent.FLAG_ACTIVITY_SINGLE_TOP |
  742.                         Intent.FLAG_ACTIVITY_CLEAR_TOP;
  743.         conversations.setFlags(flags);
  744.  
  745.         return conversations;
  746.     }
  747.  
  748.     /**
  749.      * Get system view sms thread Intent
  750.      *
  751.      * @param context
  752.      *            context
  753.      * @param threadId
  754.      *            the message thread id to view
  755.      * @return the intent that can be started with startActivity()
  756.      */
  757.     public static Intent getSmsToIntent(Context context, long threadId) {
  758.         Intent popup = new Intent(Intent.ACTION_VIEW);
  759.  
  760.         // Should *NOT* be using FLAG_ACTIVITY_MULTIPLE_TASK however something is broken on
  761.         // a few popular devices that received recent Froyo upgrades that means this is required
  762.         // in order to refresh the system compose message UI
  763.         int flags =
  764.                 Intent.FLAG_ACTIVITY_NEW_TASK |
  765.                         // Intent.FLAG_ACTIVITY_SINGLE_TOP |
  766.                         Intent.FLAG_ACTIVITY_CLEAR_TOP;
  767.         // Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
  768.  
  769.         popup.setFlags(flags);
  770.  
  771.         if (threadId > 0) {
  772.             // Log.v("^^Found threadId (" + threadId + "), sending to Sms intent");
  773.             popup.setData(Uri.withAppendedPath(THREAD_ID_CONTENT_URI, String.valueOf(threadId)));
  774.         } else {
  775.             return getSmsInboxIntent();
  776.         }
  777.         return popup;
  778.     }
  779.  
  780.     /**
  781.      * Get system sms-to Intent (normally "compose message" activity)
  782.      *
  783.      * @param context
  784.      *            context
  785.      * @param phoneNumber
  786.      *            the phone number to compose the message to
  787.      * @return the intent that can be started with startActivity()
  788.      */
  789.     public static Intent getSmsToIntent(Context context, String phoneNumber) {
  790.  
  791.         Intent popup = new Intent(Intent.ACTION_SENDTO);
  792.  
  793.         // Should *NOT* be using FLAG_ACTIVITY_MULTIPLE_TASK however something is broken on
  794.         // a few popular devices that received recent Froyo upgrades that means this is required
  795.         // in order to refresh the system compose message UI
  796.         int flags =
  797.                 Intent.FLAG_ACTIVITY_NEW_TASK |
  798.                         // Intent.FLAG_ACTIVITY_SINGLE_TOP |
  799.                         Intent.FLAG_ACTIVITY_CLEAR_TOP;
  800.         // Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
  801.  
  802.         popup.setFlags(flags);
  803.  
  804.         if (!"".equals(phoneNumber)) {
  805.             // Log.v("^^Found threadId (" + threadId + "), sending to Sms intent");
  806.             popup.setData(Uri.parse(SMSTO_URI + Uri.encode(phoneNumber)));
  807.         } else {
  808.             return getSmsInboxIntent();
  809.         }
  810.         return popup;
  811.     }
  812.  
  813.     /**
  814.    *
  815.    */
  816.     public static void launchEmailToIntent(Context context, String subject, boolean includeDebug) {
  817.         Intent msg = new Intent(Intent.ACTION_SEND);
  818.  
  819.         SharedPreferences myPrefs = PreferenceManager.getDefaultSharedPreferences(context);
  820.         boolean donated = myPrefs.getBoolean(context.getString(R.string.pref_donated_key), false);
  821.  
  822.         StringBuilder body = new StringBuilder();
  823.  
  824.         if (includeDebug) {
  825.             body.append(String.format("\n\n----------\nSysinfo - %s\nModel: %s\n\n",
  826.                     Build.FINGERPRINT, Build.MODEL));
  827.  
  828.             // body.append(String.format("\n\nBrand: %s\n\n", Build.BRAND));
  829.  
  830.             // Array of preference keys to include in email
  831.             final String[] pref_keys = {
  832.                     context.getString(R.string.pref_enabled_key),
  833.                     context.getString(R.string.pref_timeout_key),
  834.                     context.getString(R.string.pref_privacy_key),
  835.                     context.getString(R.string.pref_privacy_sender_key),
  836.                     context.getString(R.string.pref_privacy_always_key),
  837.                     context.getString(R.string.pref_dimscreen_key),
  838.                     context.getString(R.string.pref_markread_key),
  839.                     context.getString(R.string.pref_onlyShowOnKeyguard_key),
  840.                     context.getString(R.string.pref_show_buttons_key),
  841.                     context.getString(R.string.pref_button1_key),
  842.                     context.getString(R.string.pref_button2_key),
  843.                     context.getString(R.string.pref_button3_key),
  844.                     context.getString(R.string.pref_popup_enabled_key),
  845.                     context.getString(R.string.pref_notif_enabled_key),
  846.                     context.getString(R.string.pref_notif_sound_key),
  847.                     context.getString(R.string.pref_notif_icon_key),
  848.                     context.getString(R.string.pref_vibrate_key),
  849.                     context.getString(R.string.pref_vibrate_pattern_key),
  850.                     context.getString(R.string.pref_vibrate_pattern_custom_key),
  851.                     context.getString(R.string.pref_flashled_key),
  852.                     context.getString(R.string.pref_flashled_color_key),
  853.                     context.getString(R.string.pref_notif_repeat_key),
  854.                     context.getString(R.string.pref_notif_repeat_times_key),
  855.                     context.getString(R.string.pref_notif_repeat_interval_key),
  856.             };
  857.  
  858.             Map<String, ?> m = myPrefs.getAll();
  859.  
  860.             body.append(String.format("%s config -\n", subject));
  861.             for (int i = 0; i < pref_keys.length; i++) {
  862.                 try {
  863.                     body.append(String.format("%s: %s\n", pref_keys[i], m.get(pref_keys[i])));
  864.                 } catch (NullPointerException e) {
  865.                     // Nothing to do here
  866.                 }
  867.             }
  868.  
  869.             Cursor c = context.getContentResolver().query(
  870.                     ContactNotifications.CONTENT_URI, null, null, null, null);
  871.             int dbRowCount = 0;
  872.             if (c != null) {
  873.                 dbRowCount = c.getCount();
  874.             }
  875.             body.append("Db Rows: " + dbRowCount + "\n");
  876.  
  877.             // Add locale info
  878.             body.append(String.format("locale: %s\n",
  879.                     context.getResources().getConfiguration().locale.getDisplayName()));
  880.  
  881.             // TODO: fix this up so users can attach system logs to the email
  882.             // this almost works but for some reason the attachment never sends (while it still
  883.             // appears in the draft email that is created) :(
  884.             // Attach the log file if it exists
  885.             // Uri log = collectLogs(context);
  886.             // if (log != null) {
  887.             // msg.putExtra(Intent.EXTRA_STREAM, log);
  888.             // }
  889.         }
  890.  
  891.         msg.putExtra(Intent.EXTRA_EMAIL, donated ? AUTHOR_CONTACT_INFO_DONATE : AUTHOR_CONTACT_INFO);
  892.         msg.putExtra(Intent.EXTRA_SUBJECT, subject);
  893.         msg.putExtra(Intent.EXTRA_TEXT, body.toString());
  894.  
  895.         msg.setType("message/rfc822");
  896.         context.startActivity(Intent.createChooser(
  897.                 msg, context.getString(R.string.pref_sendemail_title)));
  898.     }
  899.  
  900.     /**
  901.      * Return current unread message count from system db (sms and mms)
  902.      *
  903.      * @param context
  904.      * @return unread sms+mms message count
  905.      */
  906.     public static int getUnreadMessagesCount(Context context) {
  907.         final ArrayList<SmsMmsMessage> messages = getUnreadMessages(context);
  908.         if (messages != null) {
  909.             return messages.size();
  910.         }
  911.         return 0;
  912.     }
  913.  
  914.     /**
  915.      *
  916.      * @param context
  917.      * @param ignoreThreadId
  918.      * @return
  919.      */
  920.     public static SmsMmsMessage getMmsDetails(Context context, long ignoreThreadId) {
  921.  
  922.         final String[] projection = new String[] { "_id", "thread_id", "date", "sub", "sub_cs" };
  923.         String selection = UNREAD_CONDITION;
  924.         String[] selectionArgs = null;
  925.         final String sortOrder = "date DESC";
  926.         int count = 0;
  927.  
  928.         if (ignoreThreadId > 0) {
  929.             selection += " and thread_id != ?";
  930.             selectionArgs = new String[] { String.valueOf(ignoreThreadId) };
  931.         }
  932.  
  933.         Cursor cursor = context.getContentResolver().query(
  934.                 MMS_INBOX_CONTENT_URI,
  935.                 projection,
  936.                 selection,
  937.                 selectionArgs,
  938.                 sortOrder);
  939.  
  940.         if (cursor != null) {
  941.             try {
  942.                 count = cursor.getCount();
  943.                 if (count > 0) {
  944.                     cursor.moveToFirst();
  945.                     long messageId = cursor.getLong(0);
  946.                     long threadId = cursor.getLong(1);
  947.                     long timestamp = cursor.getLong(2) * 1000;
  948.                     String subject = cursor.getString(3);
  949.  
  950.                     return new SmsMmsMessage(context, messageId, threadId, timestamp,
  951.                             subject, count, SmsMmsMessage.MESSAGE_TYPE_MMS);
  952.                 }
  953.  
  954.             } finally {
  955.                 if (cursor != null) {
  956.                     cursor.close();
  957.                 }
  958.             }
  959.         }
  960.         return null;
  961.     }
  962.  
  963.     public static SmsMmsMessage getMmsDetails(Context context) {
  964.         return getMmsDetails(context, 0);
  965.     }
  966.  
  967.     public static String getMmsAddress(Context context, long messageId) {
  968.         final String[] projection = new String[] { "address", "contact_id", "charset", "type" };
  969. //        final String selection = "type=137"; // "type="+ PduHeaders.FROM,
  970.         final String selection = null;
  971.  
  972.         Uri.Builder builder = MMS_CONTENT_URI.buildUpon();
  973.         builder.appendPath(String.valueOf(messageId)).appendPath("addr");
  974.  
  975.         Cursor cursor = context.getContentResolver().query(
  976.                 builder.build(),
  977.                 projection,
  978.                 selection,
  979.                 null, null);
  980.  
  981.         if (cursor != null) {
  982.             try {
  983.                 if (cursor.moveToFirst()) {
  984.                     // Apparently contact_id is always empty in this table so we can't get it from
  985.                     // here
  986.  
  987.                     // Just return the address
  988.                     return cursor.getString(0);
  989.                 }
  990.             } finally {
  991.                 if (cursor != null) {
  992.                     cursor.close();
  993.                 }
  994.             }
  995.         }
  996.  
  997.         return context.getString(android.R.string.unknownName);
  998.     }
  999.  
  1000.     public static final Pattern NAME_ADDR_EMAIL_PATTERN =
  1001.             Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
  1002.  
  1003.     public static final Pattern QUOTED_STRING_PATTERN =
  1004.             Pattern.compile("\\s*\"([^\"]*)\"\\s*");
  1005.  
  1006.     public static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile(
  1007.             "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
  1008.             "\\@" +
  1009.             "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
  1010.             "(" +
  1011.             "\\." +
  1012.             "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
  1013.             ")+"
  1014.         );
  1015.  
  1016.     public static boolean isEmailAddress(String email) {
  1017.         if (email == null) {
  1018.             return false;
  1019.         }
  1020.         return EMAIL_ADDRESS_PATTERN.matcher(email).matches();
  1021.     }
  1022.  
  1023.     private static String extractAddrSpec(String address) {
  1024.         Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
  1025.  
  1026.         if (match.matches()) {
  1027.             return match.group(2);
  1028.         }
  1029.         return address;
  1030.     }
  1031.  
  1032.     private static String getEmailDisplayName(String displayString) {
  1033.         Matcher match = QUOTED_STRING_PATTERN.matcher(displayString);
  1034.         if (match.matches()) {
  1035.             return match.group(1);
  1036.         }
  1037.         return displayString;
  1038.     }
  1039.  
  1040.     /**
  1041.      * Read the PDUs out of an {@link #SMS_RECEIVED_ACTION} or a {@link #DATA_SMS_RECEIVED_ACTION}
  1042.      * intent.
  1043.      *
  1044.      * @param intent
  1045.      *            the intent to read from
  1046.      * @return an array of SmsMessages for the PDUs
  1047.      */
  1048.     public static final SmsMessage[] getMessagesFromIntent(Intent intent) {
  1049.         Object[] messages = (Object[]) intent.getSerializableExtra("pdus");
  1050.         if (messages == null) {
  1051.             return null;
  1052.         }
  1053.         if (messages.length == 0) {
  1054.             return null;
  1055.         }
  1056.  
  1057.         byte[][] pduObjs = new byte[messages.length][];
  1058.  
  1059.         for (int i = 0; i < messages.length; i++) {
  1060.             pduObjs[i] = (byte[]) messages[i];
  1061.         }
  1062.         byte[][] pdus = new byte[pduObjs.length][];
  1063.         int pduCount = pdus.length;
  1064.         SmsMessage[] msgs = new SmsMessage[pduCount];
  1065.         for (int i = 0; i < pduCount; i++) {
  1066.             pdus[i] = pduObjs[i];
  1067.             msgs[i] = SmsMessage.createFromPdu(pdus[i]);
  1068.         }
  1069.         return msgs;
  1070.     }
  1071.  
  1072.     /**
  1073.      * Yet another hack to work with the undocumented SMS APIs. Some devices (and it seems to be
  1074.      * those running on CDMA networks) store a timestamp that is off by several hours - it seems
  1075.      * to be related to how the carrier sets up the SMS service center. This method checks for
  1076.      * substantial drift (30mins+) and stores a preference to indicate this which can be used later
  1077.      * to display the correct timestamp to the user.
  1078.      * @param context Context for fetching prefs
  1079.      * @param timestamp The system timestamp of when this message was received
  1080.      * @param smscTimestamp The service center timestamp
  1081.      * @return The calculated timestamp drift in millis
  1082.      */
  1083.     public static long updateSmscTimestampDrift(
  1084.             Context context, long timestamp, long smscTimestamp) {
  1085.         final int thirtyMins = 30 * 60 * 1000;
  1086.         long timeDrift = 0;
  1087.         final long timeDiff30Mins =
  1088.                 Math.round((smscTimestamp - timestamp) / thirtyMins);
  1089.  
  1090.         if (Math.abs(timeDiff30Mins) > 0) {
  1091.             timeDrift = timeDiff30Mins * thirtyMins;
  1092.             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
  1093.             Editor editor = prefs.edit();
  1094.             editor.putLong(ManagePreferences.SMSC_TIME_DRIFT, timeDrift);
  1095.             editor.commit();
  1096.         }
  1097.  
  1098.         return timeDrift;
  1099.     }
  1100.  
  1101.     /**
  1102.      * This function will see if the most recent activity was the system messaging app so we can
  1103.      * suppress the popup as the user is likely already viewing messages or composing a new message
  1104.      */
  1105.     public static final boolean inMessagingApp(Context context) {
  1106.  
  1107.         /*
  1108.          * These appear to be the 2 main intents that mean the user is using the messaging app
  1109.          *
  1110.          * action "android.intent.action.MAIN" data null class "com.android.mms.ui.ConversationList"
  1111.          * package "com.android.mms"
  1112.          *
  1113.          * action "android.intent.action.VIEW" data "content://mms-sms/threadID/3" class
  1114.          * "com.android.mms.ui.ComposeMessageActivity" package "com.android.mms"
  1115.          */
  1116.  
  1117.         ActivityManager mAM = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
  1118.  
  1119.         final List<RunningTaskInfo> mRunningTaskList = mAM.getRunningTasks(1);
  1120.         final Iterator<RunningTaskInfo> mIterator = mRunningTaskList.iterator();
  1121.         RunningTaskInfo mRunningTask;
  1122.         if (mIterator.hasNext()) {
  1123.             mRunningTask = mIterator.next();
  1124.             if (mRunningTask != null) {
  1125.                 final ComponentName runningTaskComponent = mRunningTask.baseActivity;
  1126.                 if (SmsMessageSender.MESSAGING_PACKAGE_NAME.equals(
  1127.                         runningTaskComponent.getPackageName())) {
  1128.                     final String runClassName = runningTaskComponent.getClassName();
  1129.                     for (int i=0; i<SmsMessageSender.MESSAGING_APP_ACTIVITIES.length; i++) {
  1130.                         if (SmsMessageSender.MESSAGING_APP_ACTIVITIES[i].equals(runClassName)) {
  1131.                             if (BuildConfig.DEBUG)
  1132.                                 Log.v("User in messaging app - from running task");
  1133.                             return true;
  1134.                         }
  1135.                     }
  1136.                 }
  1137.             }
  1138.         }
  1139.  
  1140.         return false;
  1141.     }
  1142.  
  1143.     /**
  1144.      * Enables or disables the main SMS receiver
  1145.      */
  1146.     public static void enableSmsPopup(Context context, boolean enable) {
  1147.         PackageManager pm = context.getPackageManager();
  1148.         ComponentName cn = new ComponentName(context, SmsReceiver.class);
  1149.  
  1150.         // Update preference so it reflects in the preference activity
  1151.         SharedPreferences myPrefs = PreferenceManager.getDefaultSharedPreferences(context);
  1152.         SharedPreferences.Editor settings = myPrefs.edit();
  1153.         settings.putBoolean(context.getString(R.string.pref_enabled_key), enable);
  1154.         settings.commit();
  1155.  
  1156.         if (enable) {
  1157.             if (BuildConfig.DEBUG)
  1158.                 Log.v("SMSPopup receiver is enabled");
  1159.             pm.setComponentEnabledSetting(cn,
  1160.                     PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
  1161.                     PackageManager.DONT_KILL_APP);
  1162.  
  1163.         } else {
  1164.             if (BuildConfig.DEBUG)
  1165.                 Log.v("SMSPopup receiver is disabled");
  1166.             pm.setComponentEnabledSetting(cn,
  1167.                     PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
  1168.                     PackageManager.DONT_KILL_APP);
  1169.         }
  1170.     }
  1171.  
  1172.     /**
  1173.      * Convert from pixels to density independent pixels.
  1174.      *
  1175.      * @param res
  1176.      *            Resources to fetch display metrics from.
  1177.      * @param pixels
  1178.      *            Pixel dimension to convert.
  1179.      * @return Density independent pixels.
  1180.      */
  1181.     public static int pixelsToDip(Resources res, int pixels) {
  1182.         final float scale = res.getDisplayMetrics().density;
  1183.         return (int) (pixels * scale + 0.5f);
  1184.     }
  1185. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement