By

New look for the site!

I have updated the site with a new responsive theme and dropped the separate mobile theme (that I was never particularly happy with).

Everything should now scale to the device size.

If you experience any problems or notice anything out-of-place, let me know!

By

Easytracker – Google Analytics integration for Android

Easytracker is a library that makes analytics integration easier. It handles session management and can automatically track activity views.

It is pretty simple to integrate, just download the zip and add the EasyTracker.jar into your project. If you are using fragments (I will cover that later) or would like to have to source on hand, you can download the source distribution and include it as a project on the build path.

Account Setup

If you don’t already have an account for Google Analytics you should do that now, then setup a profile for your Android application. In the dropdown next to ‘Website’s URL:” you can select “Not a website”. You will need your tracking code, so note that down somewhere for later.

Configuration

Configuration is pretty simple, just need to add a few lines to your strings.xml file, and add permissions in ApplicationManifest.xml (if they already don’t exist). The following is where you set your tracking code:-

<string name="ga_api_key">UA-XXXXXXXX-X</string>

The next few are optional.

If you wish to check what is being logged you can enable debug. As results don’t appear for 24 to 48 hours, this can be handy for initial setup.

<bool name="ga_debug">true</bool>

If you want your activities to be tracked automatically, you can set the following:-

<bool name="ga_auto_activity_tracking">true</bool>

You can configure how frequently (in seconds) information is sent to google, with the following:-

<item format="integer" name="ga_dispatchPeriod" type="integer">120</item>

In ApplicationManifest.xml you should ensure you have the following permissions

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Setting Up Tracked Activities

All activities you wish to track should extend TrackedActivity rather than Activity, TrackedListActivity rather than ListActivity.

You can track activities automatically by extending from these classes and setting the config option ‘ga_auto_activity_tracking’ to true. If you want to manually track, e.g. if you are using fragments, you should do this in, or after onStart(). Ensure you have invoked the  super.onStart() else you will get NullPointerExceptions.

@Override
public void onStart(){
    super.onStart();
    ...
    EasyTracker.getTracker().trackPageView("/Settings");
    ...
}

Fragment Support

I am using fragments in my app and currently you can only extend from TrackedActivity which extends Activity. You can get around this easily enough by making a copy the TrackedActivity class (I renamed it to TrackedFragmentActivity so it doesn’t clash) and changing its extends clause to the relevant FragmentActivity implementation (don’t forget to use the Support class if you are using the compatibility library). You will likely have to comment out an overridden method: onRetainNonConfigurationInstance() as it is marked as final in FragmentActivity and cannot be overridden.

Sending Events

You can also send events such as button clicks

        EasyTracker.getTracker().trackEvent("Button Click", "Clicked Hello", "", 0);

The parameters being category, action, label and value.

UPDATE: I noticed that things weren’t being logged to analytics, and the clue was the following:-

07-24 16:19:11.558: V/GoogleAnalyticsTracker(13431): dispatching hits in dry run mode

I needed to add another config option:-

<bool name="ga_dryRun">false</pre>

now I see the expected:

07-24 16:29:01.711: V/GoogleAnalyticsTracker(13697): HTTP Response Code: 200

Also, here is a good write-up on troubleshooting Google Analytics and EasyTracker

By

Flex 4 – cut/Copy/Paste context menus not appearing

The right-click context menu for s:TextArea s:RichTextEditor, s:TextEdit, etc. is an expected feature for any sort of form functionality however a bug in s:Panel breaks this functionality whenever the control is inside an s:Panel item.

<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
...
    <!-- right-clicking this will show a context with cut/copy/paste -->
    <s:TextArea text="text in app" />
    <!-- right-clicking this will NOT show a context with cut/copy/paste -->
    <s:Panel>
        <s:TextArea text="text in panel" />
    </s:Panel>
...
</application>

Note that this doesn’t affect ctrl+v/p/x keybindings – they work regardless.

There is a hack to get around this –  mark both the s:panel as well as it’s skin as mouseEnabled=”true”. What that means is that EVERY spark panel needs to have a custom skin with that override. This includes cases where there are two enclosing panels.

<!-- this won't work -->
<s:Panel>
    <s:Panel mouseEnabled="true" skinClass="CustomPanel">
        <s:TextArea text="text in panel" />
    </s:Panel>
</s:Panel>

<!-- this won't work -->
<s:Panel skinClass="CustomPanel" mouseEnabled="true">
    <s:Panel>
        <s:TextArea text="text in panel" />
    </s:Panel>
</s:Panel>

<!-- this WILL work -->
<s:Panel skinClass="CustomPanel" mouseEnabled="true">
    <s:Panel skinClass="CustomPanel" mouseEnabled="true">
        <s:TextArea text="text in panel" />
    </s:Panel>
</s:Panel>

It’s a major pain, but it is a workable solution where needed. To create the skin, in eclipse:-

  1. Click ‘file’ > ‘new’ > ‘MXML Skin’
  2. Enter the name for the skin
  3. Set the HostComponent as ‘spark.components.Panel’
  4. Check ‘create as copy of’
  5. Set to copy ‘spark.skins.spark.PanelSkin’
  6. Click ‘finish’
  7. In the ‘<s:SparkSkin ..>’ tag set mouseEnabled=”true”
  8. Set  mouseEnabled=”true” on your ’<s:Panel ..>’ tag
  9. On your ‘<s:Panel ..>’ tag set skinClass=”" to your new skin class

Now cut/copy/paste should appear in your context menu.

By

Android Developer Labs Melbourne 2012 – Lessons Learnt

I recently attended the Android Developer Labs in Melbourne, which featured talks on how to improve your apps appearance and quality, as well as to show the changes in Android 4.0 – Ice Cream Sandwich.

Android Beam looks to be interesting and I’ve been racking my brains trying to figure out a use for it now!

It was also good to see Google talking on piracy and methods to combat it. The talk on this was certainly interesting.

As a result, there is a big release of Fetch That Number on the horizon with call logs, search and support for tablets (including search services for devices without telephony) and extra large screen sizes such as the Galaxy Nexus. I have also worked to provide some other Ice Cream Sandwich/Honeycomb features such as the Action Bar, utilising the Fragments framework and the new Holo theme.

By

Formatting Phone Numbers in Java and Android

I needed to format numbers nicely while storing them as a raw string in my database, so I need to turn
0433333333 into 0433 333 333 and 0262195555 into (02) 6219 5555

I couldn’t find something that did this as nicely as I hoped, but this does the job if you know what format your numbers will be as I do (They are all Australian)

My searching didn’t turn up a string mask formatter apart from one in Swing (and I sure don’t want to be importing THAT into an Android project!) so splitting and adding spaces, brackets etc. using  .substring(…) was the nicest way I could think of without writing a whole library.

Make sure the number is a raw numeric string before converting:-

public final class PhoneNumberUtils {

    private PhoneNumberUtils() {

    }

    public static String stripAustralianNumber(String number) {
        if (StringUtils.isEmpty(number)) {
            return null;
        }
        number = stripCountryCode(number);
        // now strip non-numeric
        return number.replaceAll("[^\\d]", "");
    }

    private static String stripCountryCode(String string) {
        if (string.startsWith("+61")) {
            return string.replace("+61", "0");
        } else if (string.startsWith("+")) {
           //handle invalid numbers
        }
        return string;
    }

now the method to format your phone numbers – as noted, this is for Australian numbers, but you can change it easy enough.

 public static String prettyPhoneNumber(String phoneNumber) {

        phoneNumber = stripAustralianNumber(phoneNumber);
        StringBuilder sb = new StringBuilder();

        if (phoneNumber.startsWith("04") || phoneNumber.startsWith("1800")
                || phoneNumber.startsWith("1300")) {
            sb.append(phoneNumber.substring(0, 4)).append(" ")
                    .append(phoneNumber.substring(4, 7)).append(" ")
                    .append(phoneNumber.substring(7, phoneNumber.length()));
        } else if (phoneNumber.length() == 6 && phoneNumber.startsWith("13")) {
            sb.append(phoneNumber.substring(0, 2)).append(" ")
                    .append(phoneNumber.substring(2, 4)).append(" ")
                    .append(phoneNumber.substring(4, phoneNumber.length()));
        } else if (phoneNumber.startsWith("0")) {
            sb.append("(").append(phoneNumber.substring(0, 2)).append(") ")
                    .append(phoneNumber.substring(2, 6)).append(" ")
                    .append(phoneNumber.substring(6, phoneNumber.length()));
        } else {
            sb.append(phoneNumber.substring(0, 4)).append(" ")
                    .append(phoneNumber.substring(4, phoneNumber.length()));
        }

        return sb.toString();
    }
}

I may look at creating a library later on that will do things in a nicer way.

By

Fetch That Number Version 1.2.0 Released – Major Update!

The newest version of Fetch That Number for Australia includes new functionality and a brand new style.

Current users should update via the Android Market

New users should check out the product page which also contains links to the app at the Android Market.

New Features:-

  • new styling
  • A Reverse Lookup search activity so you can search for Australian phone numbers without ringing them (of course letting you add their details to your contacts on success!).
  • The Settings screen is now opened via standard Android menu options.
  • Extended info is now an optional search. You can request additional info from the Retrieved Details screen.
  • Added an ‘About’ screen with some handy links

Enjoy!

Also, feedback, suggestions are always welcome either in the comments or via the contact page!

By

Inserting and Updating a Contact in Android – Numbers, Addresses and Names – Part 1

I have finally got a working example of inserting and updating contacts using the new ContactsContract API, and I must say, it’s certainly not the prettiest API I’ve used. Sometimes it is downright malignant! I’m sure it made good sense when first created but I have found even the simplest actions like inserting an address somewhat of a chore.

The goal was to populate some values and display to the user the android edit screen. This could be done using the built-in USER_EDIT USER_INSERT or USER_INSERT_OR_UPDATE Activities like this:-

Intent i = new Intent(Intent.ACTION_INSERT);
i.setType(Contacts.CONTENT_TYPE);
i.putExtra(Insert.NAME, "Some Contact Name");
i.putExtra(Insert.EMAIL, "address@email.com");
i.putExtra(Insert.PHONE, "123-456-7890");
context.startActivity(i);

The problem here is that I CAN use Insert.POSTAL to insert an address, but it will insert the whole address on the ‘street’ entry, not break them into suburb etc. which looks rubbish. From memory this is the same with display name, with everything being in the firstname field.

Trawling through the API. there looks to be a new field called ‘data’ which may allow more advanced interaction with contacts however this is only available in honeycomb so at least for the next few years (assuming that it works how I expect it) it isn’t feasable.

So this left me looking for another solution, which was to insert the contact before showing the edit screen. Again, this isn’t optimal, but I figured it was easier for the user to delete an unwanted contact than it was to fix addresses to appear in the correct fields. This on the whole worked well using the ContactsContract API:-

array of contentProviderOperations we wish to run

        ArrayList ops = new ArrayList();

Add a new contact to a selected account (google in this case)

      
      ops.add(ContentProviderOperation
                    .newInsert(ContactsContract.RawContacts.CONTENT_URI)
                    .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE,
                            "com.google")
                    .withValue(ContactsContract.RawContacts.ACCOUNT_NAME,
                            mAccountName).build());

Add the display name. withValueBackReference is used as the user hasn’t been created yet. It will use the RAW_CONTACT_ID that will be created in the previous operation

        
ops.add(ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(
                        ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
                .withValue(
                        ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
                        mDisplayName).build());

Again using valueBackReference, we are inserting the phone number to the raw contact under a type of work.

        ops.add(ContentProviderOperation
                .newInsert(ContactsContract.Data.CONTENT_URI)
                .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
                .withValue(
                        ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
                .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER,
                        rci.getNumber())
                .withValue(ContactsContract.CommonDataKinds.Phone.TYPE,
                        ContactsContract.CommonDataKinds.Phone.TYPE_WORK)
                .build());

Now to create the address insert operation. I have marked it as a work address, and we specify each item in the address.

                Log.d(Constants.TAG, address.toString());
                ContentProviderOperation.Builder operationBuilder = ContentProviderOperation
                        .newInsert(ContactsContract.Data.CONTENT_URI);
                    operationBuilder.withValueBackReference(
                            ContactsContract.Data.RAW_CONTACT_ID, 0);

                operationBuilder
                        .withValue(
                                ContactsContract.Data.MIMETYPE,
                                ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE)
                        .withValue(
                                ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY,
                                mCountry)
                        .withValue(
                                ContactsContract.CommonDataKinds.StructuredPostal.REGION,
                                mState)
                        .withValue(
                                ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE,
                                mPostCode)
                        .withValue(
                                ContactsContract.CommonDataKinds.StructuredPostal.CITY,
                                mSuburb)
                        .withValue(
                                ContactsContract.CommonDataKinds.StructuredPostal.STREET,
                                mStreet)
                        .withValue(
                                ContactsContract.CommonDataKinds.StructuredPostal.TYPE,
                                ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK);

                ops.add(operationBuilder.build());
            }
        }

Last thing to do is to run the batch operation

Uri contactUri = _context.getContentResolver().applyBatch(
ContactsContract.AUTHORITY, ops);

The URI returned from the batch operation can be used to bring up the Edit User Activity:-

        Intent i = new Intent(Intent.ACTION_EDIT);
        i.setData(contactUri);
        context.startActivity(i);

I also do this within an AsyncTask as the UI will become unresponsive for a few seconds.

I will cover updates in part 2

Any questions, comments or suggestions, feel free to post a comment below.

By

Fetch That Number version 1.0.2 released

This is a bugfix release for Fetch That Number Australia.

There were several small bugs fixed around adding a contact after a reverse lookup. There is also some improvements with bug report information.

Any issues, please contact us or check out the product page FAQs.

By

Android Market – Authenticating With LVL From a Server

I have been struggling to work out why validation with the Android Market was succeeding with test accounts but with valid market accounts it was failing using code based on android-market-license-verification.

The issue turned out to be that the response data is stripped after verification.
Signed data comes in looking like this if it’s a TEST EMAIL account:-

0|136040138|com.foo.bar|1|ANlOHQOShF3uJUwv3Ql+fbsgEG9FD35Hag==|123456789012

if it is a genuine MARKET account

0|136040138|com.foo.bar|1|ANlOHQOShF3uJUwv3Ql+fbsgEG9FD35Hag==|123456789012:GR=10&VT=0987654321&GT=1234567890

When the signed data is validated, the data after the ‘:’ is stripped, so trying to re-validate the data will fail in the case of the market account, and pass in the case of the test email account.

By

Fetch That Number launched

Finally, after many months of procrastinating and perfecting, I have launched Fetch That Number into the Android Market.

Cost is $2.99 and available in Australia only at this stage.

Find out more about the app here including links to the market.