Changelogs

v0.77: Download MMS

v0.76: Vibration, Ringtone and Text Entry options

v0.74: QuickReply revamped and customizable. Close-able conversations.

v0.71: QuickReply for Android 3.0+

v0.70: Multiview support + theme fixes

v0.69: Open and share MMS

v0.68: Shows MMS, Navigation Modes and Collapsable Contact Banner

v0.65: Crash fixes

v0.64: Quick Reply system is complete

v0.63: Google Voice stability

v0.61: Mark as read is finished. Bug fixes

v0.60: New Icon, Quick Reply, Speed fixes

v0.55: Rich notifications + CDMA fix for splitting messages.

v0.54: Outgoing status fix

v0.53: 160 character limit fixed, photos not showing fixed, crash fixes

v0.52: Message new contacts, faster loading, fixed crashes and notifications

v0.51: Speed improvements

v0.49: Number parsing is complete

v0.47: Fixed crash if you don’t have a profile icon.

v0.46: Hi-res contact icons

v0.45: All known bugs fixes. On to UI phase

v0.44: Shortcode messaging has been fixed. Only warns about Google Voice if you’re not going to get messages.

v0.43: Fixed issue where Fusion would grab the wrong account (usually Dropbox). Fixed crashes on clicking notification.

v0.42: Fixed crash on syncing database with blank messages

v0.41: Fixed crash on swipe

v0.40: Fixed contacts spinner not working properly

v0.39: Race conditions, notifications, UI duplication fixes. Warnings added.

v0.38: Massive lag fix

v0.37: Minor bugfix

v0.36: Incoming messages now sync. Lots of bug fixes.

v0.35: Fixed search on Android 2.x. Outgoing messages now write to the internal db.

v0.34: Search works in conversations. SQL should work multithreaded now. Crash fixes. Search bug fixes. International support improved

v0.33: Copy, Delete, & Info options when long pressing messages. Links now open on click. Fixed crash on notification or reopening app. Notifications now take you directly to the conversation. Profile photo is now shown. Search bugfix.

v0.31: Clicking search lists everyone. Better phone number parsing and int’l support

v0.30: Search auto hides after picking a contact or selecting someone. Fixed Android 2.x crashes and contacts not auto sorting on new message

v0.29: Contact search and bug fixes

v0.28: Fixed huge bug where messages wouldn’t send.

v0.27: Contact icons and small crash fix.

v0.26: SQLite database lock crashes. Nearly all reported crashes fixed

v0.25: Fixed possible wrong sender and few more crashes (reports actually do help!). Added disable SMS option.

v0.24: Removed AsyncTasks. More crash fixes. Added disable notifications option

v0.23: Fixed the Play Store’s automatic tablet blocking. Should support any and all devices now.

Public Beta code is finished.

The code is finished. I think it’s good where it is. Performance is good. Except for a very few subroutines, everything is done on a separate thread. Google Voice in and out works with and without the Google Voice app installed, though I do suggest uninstalling it before installing my app. After that, you can reinstall Google Voice. I added a few more theme options. There are some bugs, and maybe one rare crash, but I just want to get this on Google Play so I can start getting crash/freeze logs.

The one crash is opening a notification after the activity has been killed by the memory manager. I’m not sure why it crashes but once I get it on Google Play, I can track it better.

The minor bug is that a contact will be listed as “Unread” until you swipe to the conversation. This means, you might have to swipe away and swipe to clear it. Not major, but slightly annoying.

Some finishing touches and GVoice Syncing

Okay, so I’ve decided to make some finishing touches for the beta. I’ve incorporated Google Voice syncing on incoming and outgoing texts. I just need to play around with how long it takes because it’s slowing down my stuff. It’s working, just a bit slow. Also, I need to play around the “Mark as Read” to better support old messages GVoice messaging when they’re imported.

I need to finish Google Voice syncing for the workaround if you have the Google Voice app installed.

Edit: Mark as Read is done. Syncs properly with Google Voice. 

I honestly thought that Services run on a completely different thread than your UI. Apparently they don’t. Well, time to rewrite some code…

I think… yes… ready for public beta

Well, I wrote some a VERY BASIC notification system last night. It doesn’t have quick reply or even stack messages in your window, but it works. It’s good enough for me to disable the official SMS app. I’ve been running it today and it’s all working well. I added the notification app to blink the color of the IM service. It blinks for 100ms every 5.0 seconds. Color is pretty important in this app and it’ll help you distinguish what’s going on.

So what I’m saying is, and the point of this post, I can start polishing UI. We’re at feature complete for all text-only messaging. SMS and GVoice are complete with the exception of inbox syncing.

The only exception is I haven’t written in media support (MMS, imgur parser, voicemails, etc), but it shouldn’t be impossible.

So, public beta? I think so. I just need to fix one last thing which is handling contacts with multiple numbers.

Here’s what works

  • Listing phone contacts with merging of existing received messages
  • Sending SMS Text
  • Receiving SMS Text
  • Sending GVoice Text
  • Receiving Pushed GVoice Text
  • Simple Notifications
  • Read/Unread system

Pending for official release

  • Create new message (somebody not in contacts) ~30min
  • Search contacts ~10min
  • Google Voice Inbox Sync (aka, if push failed) ~20min
  • Receive MMS / Imgur Parsing ~1hr
  • Send MMS / Imgur Links ~1hr
  • Customization options ~1hr
  • Expanded Notifications (Quick Reply) ~1hr
  • Message Options (Copy, Paste, Delete) ~30min
  • Send SMS Intent (for other apps to be able to create a message) ~15min
  • Startup Helper/Wizard (explore customization¬†options, select Google Voice account) ~1hr
  • Google Play Billing for non-free beta version (more on that later) ~2hr
  • Reveal official name and website ~2hr

Next Release

  • Reddit
  • Steam
  • Google Talk
  • Merge/Compatibility with stock SMS database

Down the road

  • Multiple GVoice accounts (UI planning not code structure)
  • Facebook
  • Custom camera/microphone interface
  • Backup system (SD or Cloud)

Known issues

  • Split contact view reacts strangely when swiping with keyboard input open (why would you anyway?)
  • When Google Voice authentication token expires, a new one isn’t requested automatically. Close and open the app renew it.

Well that’s known issues for now. Other than that, I think I can whip up an APK for testing.

Split Contacts List (Optional)

A couple of people were asking me for something like SlidingMenu, but it think SlidingMenu can get a bit difficult for some people to use. What I’ve done is created something similar to Google Play’s partial fragment screen. I”m not entirely sure how other people are doing it, but I got it to work pretty reliably without a major hassle with ViewGroups and the like. Let me start off by reiterating that almost all display options on the application are optional.

By default, I believe I’ll set the margin between 48dp and 96dp (so… 72dp?). For this example, I’m showing the the Light theme, which is pretty much the same thing as the dark theme, with the opposite Holo colors (instead of holo_blue_light, holo_blue_dark). I’m not sure if it’s as pretty as dark. Maybe it’s the colors or just the fact I prefer black. Maybe it’s the fact, by default Light background doesn’t use a gradient for a background.

I’m setting the dp to 144dp to really show off the effect. I’ve written a custom SeekBarPreference and it looks like this:

Screenshot_2013-02-28-16-42-48

The minimum is 0dp (off), max is 192dp and increments are 8dp.

The XML for this preference is written like this:

<org.shortfuse.hermes.SeekBarPreference
  android:defaultValue="0"
  android:key="pfContactsListRightMargin"
  android:max="192"
  android:text="dp"
  android:title="@string/pfContactsListRightMargin_title"
  seekBar:increment="8"
  seekBar:min="0" />

So it’ll pretty easy to make a seekable preference from now on.

Here’s how it looks in portrait view. Just remember I’m exaggerating here for effect.

split contacts screen

Here’s with the option off and in dark:
contactsfullwidthdark

The color on the right hand side of the contact is the “Unread Indicator.” I figure having the number of unread messages seems kind of silly, since regardless, when you flick over, they will be marked red. The color is based on the IM service. Seems silly now for just SMS and GVoice, but when you start adding more services it won’t be as needlessly colorful. I’m not sure if I want to move this to the immediate right of the contacts icon instead all the way on the lefthand, since I don’t like the colors clashing on the right hand side.

I just realized I haven’t considered putting the last message time in the contacts list.

Oh well, I’ll think of something.

Mock ups and suggestions welcome.

Edit: Portrait is okay, but totally awesome on landscape. I also moved the color indicator to the other side with gray as no new messages. Check it out:

landscape split contacts

You can manipulate both lists simultaneously. Slide to the right and you’ll see the text entry box like in the other pictures.

This means forwarding messages is going to be a lot easier, since you can select/copy messages on the right and hold and “paste & send” to the contacts on the left.

You just slide to the right to get to your edit box.

landscape split motion

And finally you’ll reach your normal messaging window:

landscape split end

SQL Update

Just going to post the new changes I did because Sender/Recipient is redundant with IsIncoming. I changed it to use ExternalAddress and indexed it.

private static final String DATABASE_CREATE =
  "CREATE TABLE Messages "
  + "("
    + "MessageID INTEGER PRIMARY KEY AUTOINCREMENT, "
    + "ProviderID INTEGER NOT NULL, "
    + "MessageStatusID UNSIGNED INTEGER NOT NULL, "
    + "IsIncoming BIT NOT NULL, "
    + "InternalAddress NVARCHAR(128) NULL, "
    + "ExternalAddress VARCHAR(128) NOT NULL, "
    + "CreationDateTime DATETIME NOT NULL, "
    + "LastSendAttemptDateTime DATETIME NULL, "
    + "CompletionDateTime DATETIME NULL, "
    + "MessageText TEXT NOT NULL, "
    + "ExtraData BLOB NULL, "
    + "ExtraDataTypeID UNSIGNED INTEGER NULL, "
    + "ImportMessageID NVARCHAR(40) NULL, "
    + "ImportConversationID NVARCHAR(40) NULL, " 
    + "IsRead BIT NOT NULL" 
  + ")";

private static final String DATABASE_INDEXES_CREATE =
  "CREATE INDEX IX_ProviderID_ExternalAddress_CreationDateTime ON MESSAGES "
  + "("
    + "ProviderID ASC, "
    + "ExternalAddress ASC,"      
    + "CreationDateTime ASC" 
  + ")";
  
String rawQuery = ""
  + "SELECT "
  + "  M2.MessageID, "
  + "  M2.ProviderID, "
  + "  M2.MessageStatusID, "
  + "  M2.IsIncoming, "
  + "  M2.InternalAddress, "
  + "  M2.ExternalAddress, "
  + "  M2.CreationDateTime, "
  + "  M2.LastSendAttemptDateTime, "
  + "  M2.CompletionDateTime, "
  + "  M2.MessageText, "
  + "  M2.ExtraData, "
  + "  M2.ExtraDataTypeID, "
  + "  M2.ImportMessageID, "
  + "  M2.ImportConversationID, "
  + "  M2.IsRead, "
  + "  LastMessageData.ProviderID, "
  + "  LastMessageData.ExternalAddress "
  + "FROM "
  + "  ( "
  + "    SELECT "
  + "      M1.ProviderID, "
  + "      M1.ExternalAddress, "
  + "      MAX(CreationDateTime) [LastMessageTime] "
  + "    FROM "
  + "      Messages M1 "
  + "    GROUP BY "
  + "      M1.ProviderID, "
  + "      M1.ExternalAddress "
  + "  ) AS LastMessageData "
  + "  LEFT JOIN Messages M2 ON  "
  + "    M2.CreationDateTime = LastMessageData.LastMessageTime "
  + "    AND M2.ExternalAddress = LastMessageData.ExternalAddress "
  + "    AND M2.ProviderID = LastMessageData.ProviderID "
  + "ORDER BY                   "
  + "  LastMessageData.ProviderID, "
  + "  LastMessageData.ExternalAddress,                "
  + "  M2.MessageID";

I don’t think there’s any theoretical way to make this faster. You’re all welcome to try, and I’ll patch it in.

Raw SQL queries and why you should care

Almost every android application uses SQLite in the background. It’s pretty powerful if you know what you’re doing. I’m going to show you what well done SQL script does for performance.

This is the old getContactList function:

public List<ContactItem> getContactsList() {
  Cursor senderCursor = db.query(DATABASE_TABLE, new String[] {
      "ProviderID", "Sender", "MAX(MessageID)" },
      "Sender IS NOT NULL AND IsIncoming = 1", null,
      "ProviderID, Sender", null, null);
  List<ContactItem> contactList = new ArrayList<ContactItem>();
  if (senderCursor != null) {
    while (senderCursor.moveToNext()) {   
      ContactItem c = new ContactItem();
      int providerId = senderCursor.getInt(0);
      String sender = senderCursor.getString(1);
      long messageId = senderCursor.getLong(2);

      c.setDisplayName(sender);
      c.setDisplayContactAddress(sender);
      ContactAddress ca = new ContactAddress();
      ca.setDisplayAddress(sender);
      ca.setParsedAddress(sender);
      ca.setIMProviderType(IMProviderTypes.values()[providerId]);
      c.setContactAddresses(new ContactAddress[] { ca });
      c.setLastMessageItem(this.getMessageItem(messageId));

      contactList.add(c);
    }
  }
  senderCursor.close();

  Cursor recipientCursor = db.query(DATABASE_TABLE, new String[] {
      "ProviderID", "Recipient", "MAX(MessageID)" },
      "Recipient IS NOT NULL AND IsIncoming = 0", null,
      "ProviderID, Recipient", null, null);
  if (recipientCursor != null) {
    while (recipientCursor.moveToNext()) {
      ContactItem c = new ContactItem();
      int providerId = recipientCursor.getInt(0);
      String recipient = recipientCursor.getString(1);
      long messageId = recipientCursor.getLong(2);

      c.setDisplayName(recipient);
      c.setDisplayContactAddress(recipient);
      ContactAddress ca = new ContactAddress();
      ca.setDisplayAddress(recipient);
      ca.setParsedAddress(recipient);
      ca.setIMProviderType(IMProviderTypes.values()[providerId]);
      c.setContactAddresses(new ContactAddress[] { ca });

      MessageItem msgItem = null;

      msgItem = this.getMessageItem(messageId);
      c.setLastMessageItem(msgItem);

      int count = contactList.size();
      boolean found = false;
      for (int i = 0; i < count; i++) {
        ContactItem c2 = contactList.get(i);
        if (c2.getContactAddresses()[0].equals(ca)) {
          found = true;
          MessageItem m2 = c2.getLastMessageItem();
          if (m2 == null
              || c2.getLastMessageItem().getMessageId() < messageId) {
            c.setLastMessageItem(msgItem);
            contactList.set(i, c);
            break;
          }
          break;
        }
      }
      if (!found) {
        contactList.add(c);
      }
    }
  }
  recipientCursor.close();
  return contactList;

}

I’ll admit, it’s pretty inefficient and here’s why. This generates a contact list based on your database messages. It first builds a small contact list based on every received message with including the last logged message. It does another SQL search per message to get that message information. So, if you have 100 contacts, we’re at 101 searches (1 for entire recipient list, 100 for each message). This is done via the getMessageItem(messageId) function.

Then it’ll search based on sender, who you’ve sent messages to (because sometimes we have to treat somebody as a contact, even though they’ve never responded). It’ll do the same. 1 search for result, and cross reference the received message list to append or update last message info and contacts.

So, we’re looking at 1 + x + 1 + (something between 0 and x) number of SQL queries requests using this method. If you were use batch the message information requests with an “IN” clause then it’ll be 1 + x + 1. Here, ‘x’ in signifies how many contacts you have. This is all achieved using the basic android/sqlite .query() function.

There’s also the issue where just because the message was written last to the database doesn’t mean it’s the last message received. This is gets screwed up when you start syncing old messages. In other words, instead of using MessageID, we should be using CreationDateTime

So what about raw queries? Well, compare and see. Here’s the optimized implementation:

public List<ContactItem> getContactsList() {
    String rawQuery = ""
        + "SELECT "
        + "  M2.MessageID, "
        + "  M2.ProviderID, "
        + "  M2.MessageStatusID, "
        + "  M2.IsIncoming, "
        + "  M2.Sender, "
        + "  M2.Recipient, "
        + "  M2.CreationDateTime, "
        + "  M2.LastSendAttemptDateTime, "
        + "  M2.CompletionDateTime, "
        + "  M2.MessageText, "
        + "  M2.ExtraData, "
        + "  M2.ExtraDataTypeID, "
        + "  M2.ImportMessageID, "
        + "  M2.ImportConversationID, "
        + "  M2.IsRead, "
        + "  LastMessageData.ProviderID, "
        + "  LastMessageData.Address "
        + "FROM "
        + "  ( "
        + "    SELECT "
        + "      ContactAddresses.ProviderID, "
        + "      ContactAddresses.Address, "
        + "      MAX(CreationDateTime) [LastMessageTime] "
        + "    FROM "
        + "      ( "
        + "        SELECT DISTINCT "
        + "          ProviderID, "
        + "          CASE WHEN IsIncoming = 1 THEN Sender ELSE Recipient END [Address] "
        + "        FROM "
        + "          Messages "
        + "      ) as ContactAddresses "
        + "      LEFT JOIN Messages M1 ON "
        + "        M1.ProviderID = ContactAddresses.ProviderID "
        + "        AND (M1.Sender = ContactAddresses.Address "
        + "          OR M1.Recipient = ContactAddresses.Address) "
        + "    GROUP BY "
        + "      ContactAddresses.ProviderID, "
        + "      ContactAddresses.Address "
        + "  ) AS LastMessageData "
        + "  LEFT JOIN Messages M2 ON  "
        + "    M2.CreationDateTime = LastMessageData.[LastMessageTime] "
        + "    AND (M2.Sender = LastMessageData.Address "
        + "      OR M2.Recipient = LastMessageData.Address) "
        + "ORDER BY                   "
        + "  LastMessageData.ProviderID, "
        + "  LastMessageData.Address,                "
        + "  M2.MessageID";
    Cursor cursor = db.rawQuery(rawQuery, null);
    List<ContactItem> contactList = new ArrayList<ContactItem>();
    if (cursor != null) {
      while (cursor.moveToNext()) {
        ContactItem c = new ContactItem();
        MessageItem m = parseMessageItemCursor(cursor);
        int index = allFields.length;
        int providerId = cursor.getInt(index++);
        String address = cursor.getString(index++);
        c.setDisplayName(address);
        c.setDisplayContactAddress(address);
        ContactAddress ca = new ContactAddress();
        ca.setDisplayAddress(address);
        ca.setParsedAddress(address);
        ca.setIMProviderType(IMProviderTypes.values()[providerId]);
        c.setContactAddresses(new ContactAddress[] { ca });
        c.setLastMessageItem(m);

        //check for extremely rare chance of exact same time
        int count = contactList.size();
        boolean found = false;
        for (int i = 0; i < count; i++) {
          ContactItem c2 = contactList.get(i);
          if (c2.getContactAddresses()[0].equals(ca)) {
            found = true;
            break;
          }
        }
        if (!found) {
          contactList.add(c);
        }

      }
    }
    cursor.close();

    return contactList;
}

Only one SQL query. SQL will return everything I need in a neat little package. The only thing I have to worry about is messages with exactly duplicated SenderID, Address and CreationDateTime. It’s ridiculously rare for a message to have an identical timestamp, but just in case, the ORDER BY clause means when that happens, the most recently inserted message would be on the bottom. I then check if the contact already exists and update it accordingly.

With a proper index on ServiceID and Sender/Recipient, then we can generate and load contact info in less than a second even when there are THOUSANDS of messaging in the database.

Experimenting with landscape and UI changes

Don’t you hate how when typing in landscape, the entire screen turns into a message box?

Well I’ve been experimenting with different layout UIs, but take a look at what I’m trying to perfect:

landscape example

Now you can see what’s actually going on when you message. I’m considering adding some shortcut under the send button what do you guys think?

Also, you’ll see I’ve included some UI changes per suggestions and added my Settings icon. You have the following 4 options (so far) in Settings > UI > Messages

  • IM Service Indicator (ON)
  • IM Service Icon (OFF)
  • Contact Picture (ON)
  • Colored Status (ON)

Theme options all stack. I think I’m going to leave this as default except for Colored Status, but it shows how you can customize it to your liking. Also, that Dark Theme regular contact picture I had to whip up in Photoshop.

Leave your suggestions for landscape view!