Author Archives: ShortFuse

Navigation Drawer: What would you like to see it include?

Google has made their [slide out drawer available to the public](http://developer.android.com/design/patterns/navigation-drawer.html). I’m wondering what you would like to see in this menu.

Since I’m working on Google Hangouts incorporation I’m thinking of a drop down list of services to pick status/availability/sign-in/sign-out.

Also, I think some quick options should be available like Auto-Open QuickReply.

From here you would be able to see and create scheduled messages.

Let me know what else you would like to see.

ViewTitleIndicator playing nice with Themes

Build 70 incorporates ViewTitleIndicator by default now. The problem with the title indicators is that they don’t natively play nice with themes. Here’s how I fixed it:

TypedValue tvBarTabStyle = new TypedValue();
int actionBarStyleResId;
int backgroundResId;
int actionBarTabTextStyleResId;
int actionBarTabStyleResId;

if (VERSION.SDK_INT >= 14) {
  backgroundResId = V14.getBackgroundStackedResId();
  actionBarStyleResId = V11.getActionBarStyleResId();
  actionBarTabTextStyleResId = V11
      .getActionBarTabTextStyleResId();
  actionBarTabStyleResId = V11.getActionBarTabStyleResId();
} else if (VERSION.SDK_INT >= 11) {
  actionBarStyleResId = V11.getActionBarStyleResId();
  backgroundResId = android.R.attr.background;
  actionBarTabTextStyleResId = V11
      .getActionBarTabTextStyleResId();
  actionBarTabStyleResId = V11.getActionBarTabStyleResId();
} else {
  backgroundResId = com.actionbarsherlock.R.attr.backgroundStacked;
  actionBarStyleResId = com.actionbarsherlock.R.attr.actionBarStyle;
  actionBarTabTextStyleResId = com.actionbarsherlock.R.attr.actionBarTabTextStyle;
  actionBarTabStyleResId = com.actionbarsherlock.R.attr.actionBarTabStyle;
}

getTheme().resolveAttribute(actionBarStyleResId, tvBarTabStyle,
    true);
TypedArray taBackground = this.obtainStyledAttributes(
    tvBarTabStyle.resourceId, new int[] { backgroundResId });
mIndicator.setBackgroundResource(taBackground.getResourceId(0, 0));
taBackground.recycle();

TypedValue tvBarTabTextStyle = new TypedValue();
getTheme().resolveAttribute(actionBarTabTextStyleResId,
    tvBarTabTextStyle, true);

TypedArray taTitleTextColor = this.obtainStyledAttributes(
    tvBarTabTextStyle.resourceId,
    new int[] { android.R.attr.textColor });
mIndicator.setSelectedColor(taTitleTextColor.getColor(0, 0));
mIndicator.setTextColor(taTitleTextColor.getColor(0, 0));
taTitleTextColor.recycle();

TypedValue tvBarTabBarStyle = new TypedValue();
getTheme().resolveAttribute(actionBarTabStyleResId,
    tvBarTabBarStyle, true);
TypedArray taIndicator = this.obtainStyledAttributes(
    tvBarTabBarStyle.resourceId,
    new int[] { android.R.attr.background });
TypedValue tvDivider = new TypedValue();
taIndicator.getValue(0, tvDivider);
int color = -1;
StateListDrawable sldIndicator = (StateListDrawable) getResources()
    .getDrawable(tvDivider.resourceId);
sldIndicator.setState(new int[] { android.R.attr.state_selected });
Drawable drawable = sldIndicator.getCurrent();
Bitmap bmp = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
    drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
int height = bmp.getHeight();
int width = bmp.getWidth();
color = bmp.getPixel(width / 2, height / 2);
mIndicator.setFooterColor(color);
taIndicator.recycle();

Dynamic Dates with Java

I made a new change for build 48 of Fusion to show the date portion based on time. The difficulty is making it all work with all the different time zones and locales. 24 hours isn’t always a day because of daylight savings time. So I have to use Calendar. Here’s the static code:

public static String stripFieldFromPattern(SimpleDateFormat sdf, Date d,
        DateFormat.Field field) {
    StringBuilder b = new StringBuilder();
    boolean isLastCharValid = true;
    boolean isNextCharValid = false;
    AttributedCharacterIterator i = sdf.formatToCharacterIterator(d);
    for (char c = i.first(); c != AttributedCharacterIterator.DONE; c = i
            .next()) {

        Map<Attribute, Object> attributes = i.getAttributes();

        if (attributes.containsKey(field)) {
            isLastCharValid = false;
            continue;
        }

        char nextChar = i.next();
        isNextCharValid = (nextChar != AttributedCharacterIterator.DONE && i
                .getAttribute(field) == null);
        i.previous();

        if (!attributes.isEmpty() || c == ' ') {
            b.append(c);
        } else {
            if (isLastCharValid && isNextCharValid)
                b.append(c);
        }
        isLastCharValid = true;
    }
    return b.toString();
}

This will strip any field from a date pattern. I’d strip the Year field if an event occurred in the last year. Here’s the implementation as written in Fusion:

Calendar eventDateTime = Calendar.getInstance();
Calendar yesterday = Calendar.getInstance();
Calendar lastYear = Calendar.getInstance();

eventDateTime.setTimeInMillis(msgItem.getCompletionDateTime());
yesterday.add(Calendar.DATE, -1);
lastYear.add(Calendar.YEAR, -1);

String timeString;
String providerName;
if (eventDateTime.after(yesterday)) {
    timeString = DateFormat.getTimeInstance(DateFormat.SHORT,
            Locale.getDefault()).format(eventDateTime.getTime());
    providerName = IMProvider.IMServiceNames[msgItem
            .getIMProviderType().ordinal()];
} else if (eventDateTime.after(lastYear)) {
    SimpleDateFormat sdfOriginal = (SimpleDateFormat) SimpleDateFormat
            .getDateTimeInstance(DateFormat.SHORT,
                    DateFormat.SHORT, Locale.getDefault());
    timeString = Utils.stripFieldFromPattern(sdfOriginal,
            eventDateTime.getTime(), DateFormat.Field.YEAR);
    providerName = IMProvider.IMServiceNames[msgItem
            .getIMProviderType().ordinal()];

} else {
    timeString = DateFormat.getDateTimeInstance(DateFormat.SHORT,
            DateFormat.SHORT, Locale.getDefault()).format(
            eventDateTime.getTime());
    providerName = IMProvider.IMServiceShortNames[msgItem
            .getIMProviderType().ordinal()];
}

String format = "%1$s";
if (!this.useColoredStatus && !this.useIMServiceIcon
        && !this.useIMServiceIndicator)
    format += " via %2$s";
subText = String.format(format, timeString, providerName);

I just realized the providerName is repeated unnecessarily. Oops! Regardless, it has nothing to with the topic here.

Build 36! SMS fully sync now! READ ME!

http://www.mediafire.com/?9fr9lc5po5blc6h[1]

Okay, so now SMS messages sync. If you have a bunch of old messages you don’t care about. Uninstall Fusion and reinstall it. You shouldn’t have to worry about reinstalling the GV app. I’ll submit a warning next build if Fusion couldn’t get the necessary push permissions.

So, of course if SMS is syncing this is when we need to worry about duplicates. I do a synchronize lock for outgoing messages you probably won’t ever see duplicates there. Let me know if you ever do.

Incoming is a whole other issue. So, Fusion and your stock app will both receive the message and both will write to the android stock database. The “workaround” is make Fusion wait to see if maybe another app will input the same message into the stock database. This is kinda silly. There’s no clean way to do this automatically without making the app slower. That’s NOT an option for me.

So, you can disable Fusion from receiving incoming messages. The second your stock app writes the incoming message, Fusion will INSTANTLY resync the database and the message will popup. Of course, if you ever choose to disable/uninstall your default messenger, then turn that option back.

The option is enabled by default.

I made some crash fixes so it looks like we’re in business now. It’s just MMS and notifications from here on out. After that we’re at feature-complete and I can start reworking code around for better stability and speed (yeah, I said it).

I need somebody to make sure you don’t auto crash and then I’ll publish it on the Play Store.

Google Voice can’t delete individual messages…

Seriously, this is crazy. Apparently Google only knows how to work with “conversations”. There is literally no way to delete an individual message.

Now I’m not so sure how to implement this. There are two ways.

  1. Delete the entire conversation from Google Voice’s servers and keep every other message on your Fusion database. The problem is, if you lose your database (like uninstalling the app), the entire conversation is lost.
  2. Leave the conversation on Google Voice’s servers, but flag the MessageID as deleted. This pretty much means that Fusion will ignore the message on every sync, but it’ll still be on Google’s server.

Really dumb implementation.

Build 31! Feedback needed!

http://www.mediafire.com/?d5i7bwx347rckq5

I haven’t uploaded it to the Play Store. I need somebody to run it and let me know if it works (aka doesn’t crash on boot).

I moved everything to E164 by using libphonenumber. It’s bloated and slow (200kb) but I’ll trim that down later.

The database is upgraded on boot (which is why it needs testing).

The search function is working as expected now. Clicking the search button will list all your contacts immediately.

Loading the first instance of contacts takes a little bit more time, but remember that it’s all cached after the first run though.

Dark Action Bar is properly fixed, so no more odd coloring in Android 2.x

Permission Breakdown

Permissions Breakdown:
android.permission.ACCESS_NETWORK_STATE: Google Voice Connectivity
android.permission.INTERNET: Google Voice Connectivity
android.permission.GET_ACCOUNTS: Get Google Account Names
android.permission.MANAGE_ACCOUNTS: Create Google Account Prompt
android.permission.READ_CONTACTS: Build Contacts List
android.permission.READ_PROFILE: Get your profile icon
android.permission.RECEIVE_BOOT_COMPLETED: Start on Boot
android.permission.RECEIVE_SMS: Receive Text Messages
android.permission.SEND_SMS: Send Text Messages
android.permission.USE_CREDENTIALS: Get Google Voice OAuth2 credentials (never your password)
android.permission.VIBRATE: Vibrate on New Message
com.android.vending.BILLING: Future Donations
com.google.android.apps.googlevoice.INBOX_NOTIFICATION.permission.C2D_MESSAGE: GV Push Notification
com.google.android.apps.googlevoice.permission.RECEIVE_SMS: GV Push Workaround
com.google.android.c2dm.permission.RECEIVE: Push Notifications
com.google.android.providers.gsf.permission.READ_GSERVICES: Register GV Push Notifications