Tuesday, February 28, 2012

Formatting phone numbers on Android in Java, C++ and Javascript

You might already have noticed that Ice Cream Sandwich does a really good job in auto formatting phone numbers while you type them in. But unfortunately, when you use the formatNumber method from the PhoneNumberUtils class, you will quickly notice that you don't achieve the same level of formatting. And this especially for non-US numbers.

This all because the dialer in ICS isn't using the same methods. The logic behind the number formatting in ICS is based on libphonenumber. libphonenumber is described as "Google's common Java, C++ and Javascript library for parsing, formatting, storing and validating international phone numbers. The Java version is optimized for running on smartphones, and is used by the Android framework since 4.0 (Ice Cream Sandwich)."

What you need to do to get this working is simply including the jar of libphonenumber on your classpath and your set to go. As an extra you can also create a basic version of the PhoneNumberUtils class which can be found in the ICS sources and gives you some extra validation.

/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.phonenumber;

import android.text.TextUtils;

import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;

public class PhoneNumberUtils {

  /*
   * Special characters
   * 
   * (See "What is a phone number?" doc)
   * 'p' --- GSM pause character, same as comma 
   * 'n' --- GSM wild character
   * 'w' --- GSM wait character
   */
  public static final char PAUSE = ',';
  public static final char WAIT = ';';
  public static final char WILD = 'N';

  /** True if c is ISO-LATIN characters 0-9, *, # , +, WILD */
  public final static boolean isDialable(char c) {
    return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == WILD;
  }

  /**
   * Format a phone number.
   * <p>
   * If the given number doesn't have the country code, the phone will be
   * formatted to the default country's convention.
   * 
   * @param phoneNumber
   *            the number to be formatted.
   * @param defaultCountryIso
   *            the ISO 3166-1 two letters country code whose convention will
   *            be used if the given number doesn't have the country code.
   * @return the formatted number, or null if the given number is not valid.
   * 
   * @hide
   */
  public static String formatNumber(String phoneNumber, String defaultCountryIso) {
    // Do not attempt to format numbers that start with a hash or star
    // symbol.
    if (phoneNumber.startsWith("#") || phoneNumber.startsWith("*")) {
      return phoneNumber;
    }

    PhoneNumberUtil util = PhoneNumberUtil.getInstance();
    String result = null;
    try {
      PhoneNumber pn = util.parseAndKeepRawInput(phoneNumber, defaultCountryIso);
      result = util.formatInOriginalFormat(pn, defaultCountryIso);
    } catch (NumberParseException e) {
    }
    return result;
  }

  /**
   * Format the phone number only if the given number hasn't been formatted.
   * <p>
   * The number which has only dailable character is treated as not being
   * formatted.
   * 
   * @param phoneNumber
   *            the number to be formatted.
   * @param phoneNumberE164
   *            the E164 format number whose country code is used if the given
   *            phoneNumber doesn't have the country code.
   * @param defaultCountryIso
   *            the ISO 3166-1 two letters country code whose convention will
   *            be used if the phoneNumberE164 is null or invalid.
   * @return the formatted number if the given number has been formatted,
   *         otherwise, return the given number.
   * 
   * @hide
   */
  public static String formatNumber(String phoneNumber, String phoneNumberE164, String defaultCountryIso) {
    int len = phoneNumber.length();
    for (int i = 0; i < len; i++) {
      if (!isDialable(phoneNumber.charAt(i))) {
        return phoneNumber;
      }
    }
    PhoneNumberUtil util = PhoneNumberUtil.getInstance();
    // Get the country code from phoneNumberE164
    if (phoneNumberE164 != null && phoneNumberE164.length() >= 2 && phoneNumberE164.charAt(0) == '+') {
      try {
        PhoneNumber pn = util.parse(phoneNumberE164, defaultCountryIso);
        String regionCode = util.getRegionCodeForNumber(pn);
        if (!TextUtils.isEmpty(regionCode)) {
          defaultCountryIso = regionCode;
        }
      } catch (NumberParseException e) {
      }
    }
    String result = formatNumber(phoneNumber, defaultCountryIso);
    return result != null ? result : phoneNumber;
  }

}

To see it in action you can testdrive the Java version or the Javascript version.

Tuesday, November 22, 2011

Devoxx 2011 Schedule app : Stats, stats, stats, ...

Devoxx 2011 is over! Time to have a look on the Google Analytics stats that have been collected by the Devoxx 2011 Schedule app. Just as a reminder and also to put all numbers in the right perspective, the number of attendees for Devoxx 2011 was 3260. Also, all stats are generated for the date range starting at October 20th (the day the app went live) and ending on November 20th.


Visits and visitors

  • The application has attracted a total of 1073 absolute unique visitors. So this means that you can roughly say that about a third of all attendees used an Android device to consult the Devoxx schedule information.
  • A total of 12450 visits were tracked. 78% of those visits were tracked during the conference itself.


Pageviews

  • 216506 pageviews have been tracked for 466 pages. 81% of those pageviews occurred during the conference.
  • The home screen which also holds the sponsor logo's has been viewed 19465 times.
  • If you take a closer look on the sessions pageviews, then the session details about Bleeding Edge HTML5 by Paul Kinlan was viewed most with 1524 pageviews. Closely followed by Joshua Bloch's session on Java: The Good, the Bad and the Ugly Parts with 1503 pageviews.


Events

Several events were tracked in the application: clicks on the home screen, clicks on a session in a list view, starring a session, ... If we take a closer look at the top-most starred session, we have the following top 5:


Android Platform Versions

Note that the application only supported platform versions higher than or equal to 7 (2.1). Looking at the tracked api levels, we then get the following pie chart.

If you compare this with the platform version figures that are released on a monthly basis by Google, then you can easily see that this was a developers conference. About 75% of all devices were already running Android 2.3+ as compared to about 45% when looking at the last data published by Google.

And clearly Google is already working on the next version of Android as one device was tracked with an api level of 15 (Android 4.x?).


Devices

Fragmentation. Android. These two words are often combined in one sentence. On the web you can read a lot of stories that Android has this "fragmentation" problem. Now, this application has been installed on 108 different devices in a time frame of about a month. In the Android Market I received 7 unique error reports. None of these error reports were related to a particular device.

If you simply follow the guidelines and play by the rules during your development, then there is no such thing as Android fragmentation. And this does not mean that you can't use features that are only available starting from a specific api level. An example of this is c2dm, which is only available from api level 8, although the minimum api level of the Devoxx schedule application was 7.

If we go back to the analytics data, the top 10 list of Android devices used by Devoxx attendees are:

Only devices manufactured by Samsung and HTC are present in this top 10 and if you look at the numbers, then there is a slight preference for Samsung under the Devoxx attendees.

Another characteristic related to devices that is tracked is the resolution in which the device is running. This is also something very specific to Android as devices come in a lot of screen sizes and densities. But again, just follow the guidelines on how to support these. In total there were 24 different screen resolutions with the following top 10:

Conclusion

Personally I'm very satisfied with the results and feedback I got on the app this year. I think that we also may conclude that Devoxx going Paperless was a success this year. Besides these nice stats on the Devoxx Schedule application for Android we also had:

  • multiple Kinect enabled Digital Signage screens
  • an HTML5 tweetwall
  • an iPad schedule application
  • a second Android schedule application that targets even older Android versions
  • a mobile HTML5 schedule app

Also, having the Google Analytics data available in the Devoxx Schedule application is useful in two ways:

  • Before and during the conference it helps us to have a good overview of which sessions will attract a lot of people in order to make adjustments if really necessary.
  • And after the conference it helps us to detect which parts of and how the schedule application was used mostly.

Saturday, November 19, 2011

Using the new GridLayout on pre Ice Cream Sandwich devices

To address several drawbacks of nested layouts on the Android platform, two new widgets were introduced with the release of Ice Cream Sandwich (ICS): Space and GridLayout. Detailed info about these two new widgets can be found in a blog post by Philip Milne on the Android Developers blog and in the API docs.

Dianne Hackborn of the Android framework team mentioned in a post that they hope to have a version of this GridLayout available in the Support Package so that you can statically link this into your applications for use on previous versions of Android.

Now, with the drop of the ICS sources, all the source code related to these two new widgets came immediately available. After a first look on the sources it turned out that there is not much ICS-specific code. So I created a sample project that includes the related sources from ICS and the two GridLayout samples from the Api Demos application. Both UIs of the screenshots below are created through an XML-instantiated GridLayout. The left one is running on an emulator running Cupcake (1.5). And the right one is running on an emulator running Ice Cream Sandwich (4.0).


So, at this is moment it is already possible to start porting your UIs to a GridLayout. The main difference with the ICS specification and implementation is that you need to:
  • use a different XML namespace to use as prefix when specifying the attributes (columnCount, layout_columnSpan, layout_column, ...) related to the GridLayout
  • specify the full class name of the widgets GridLayout and Space in your layouts
An example of the ported XML layout from the Api Demos can be found here.

Wednesday, October 26, 2011

Devoxx 2011 Schedule Client for Android

My Devoxx 2011 Schedule application is now available in the Android market. Again as last year, it is re-using the Google I/O app, but with the main difference that it is using ViewPagers and CursorLoaders from the compatibility library and swipey/scrollable tabs all over the place.

Besides this, you can also keep your starred sessions in sync between your phone and tablet using Android's cloud to device messaging framework. This part of code is heavily inspired by the Google I/O session of Xavier Ducrohet and Brad Abrams on Android + App Engine: A Developer's Dream Combination.

The action bar pattern was implemented using ActionBarSherlock by Jake Wharton.

UPDATE: The action bar pattern was implemented using the Action Bar Compatibility sample from the Android SDK. This should also give a better experience on the forthcoming Ice Cream Sandwich handsets/tablets.

Again, all sources can be found on Google code.

Tuesday, September 20, 2011

ViewPager meets Swipey Tabs

Revision 3 of the Android compatibility package brought us the awesome class ViewPager. The new Android Market app has these nice Swipey Tabs of which the bits and pieces are explained by Kirill Grouchnikov.

A sample swipey tabs application based on this ViewPager can be found at http://code.google.com/p/android-playground.


The main elements are the class SwipeyTabs and the interface SwipeyTabsAdapter.

Layout defintions

The first layout definition we need is the one that will host our SwipeyTabs along with the ViewPager.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:swipeytabs="http://schemas.android.com/apk/res/net.peterkuterna.android.apps.swipeytabs"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <net.peterkuterna.android.apps.swipeytabs.SwipeyTabs
        android:id="@+id/swipeytabs"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        swipeytabs:bottomBarColor="#ff96aa39"
        swipeytabs:bottomBarHeight="2dip"
        swipeytabs:tabIndicatorHeight="3dip"
        android:background="#ff3b3b3b"/>

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="fill_parent"
        android:layout_height="0px"
        android:layout_weight="1" />

</LinearLayout>

As you can see, you can customize the layout of the swipey tab with the attributes bottomBarColor, bottomBarHeight and tabIndicatorHeight.

Second we will need a TextView layout definition that we are going to inflate when SwipeyTabs asks us to create a tab for a given position.
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/swipey_tab"
    android:layout_width="wrap_content"
    android:layout_height="30dip"
    android:paddingLeft="15dip"
    android:paddingRight="15dip"
    android:focusable="true"
    android:singleLine="true"
    android:textSize="14sp"
    android:background="@drawable/swipey_tab_indicator" />

As last we have a layout definition for the fragment we are going to instantiate in the FragmentPagerAdapter.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <TextView 
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"/>
    
</FrameLayout>

Setting up the adapter

The adapter that we inject into the ViewPager and SwipeyTabs simply extends the FragmentPagerAdapter and implements the interface SwipeyTabsAdapter. As soon as you set the adapter on the swipey tabs object, it will ask your adapter to deliver a TextView for a given position. The implementation in the sample application is as follows:
public TextView getTab(final int position, SwipeyTabs root) {
    TextView view = (TextView) LayoutInflater.from(mContext).inflate(
        R.layout.swipey_tab_indicator, root, false);
    view.setText(TITLES[position]);
    view.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            mViewPager.setCurrentItem(position);
        }
    });
   
    return view;
}

Integration with the ViewPager

To integrate SwipeyTabs with the ViewPager you only need to inject the swipey tab object as an OnPageChangeListener in the ViewPager.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_swipeytab);

    mViewPager = (ViewPager) findViewById(R.id.viewpager);
    mTabs = (SwipeyTabs) findViewById(R.id.swipeytabs);

    SwipeyTabsPagerAdapter adapter = new SwipeyTabsPagerAdapter(this, getSupportFragmentManager());
    mViewPager.setAdapter(adapter);
    mTabs.setAdapter(adapter);
    mViewPager.setOnPageChangeListener(mTabs);
    mViewPager.setCurrentItem(0);
}

Wednesday, September 14, 2011

Simple crossfade on an ImageView

In a project I have this ListView where I initially display a list of items that each contain an ImageView with a drawable from the resources. In the background a thread is started to download the actual image and I wanted a crossfade as soon as I could set this downloaded image on the ImageView.

Basically what you need to do is create a new TransitionDrawable where the contents of layer 0 is the current drawable of the image view. And layer 1 will contain the new drawable to which you want to crossfade to. Last thing you need to do is to enable the crossfading, applying this newly created transition drawable on the image and starting the transition.

public static void setImageBitmapWithFade(final ImageView imageView, final Bitmap bitmap) {
 Resources resources = imageView.getResources();
 BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, bitmap);
 setImageDrawableWithFade(imageView, bitmapDrawable);
}
        
public static void setImageDrawableWithFade(final ImageView imageView, final Drawable drawable) {
 Drawable currentDrawable = imageView.getDrawable();
 if (currentDrawable != null) {
  Drawable [] arrayDrawable = new Drawable[2];
  arrayDrawable[0] = currentDrawable;
  arrayDrawable[1] = drawable;
  TransitionDrawable transitionDrawable = new TransitionDrawable(arrayDrawable);
  transitionDrawable.setCrossFadeEnabled(true);
  imageView.setImageDrawable(transitionDrawable);
  transitionDrawable.startTransition(250);
 } else {
  imageView.setImageDrawable(drawable);
 }
}

Tuesday, May 24, 2011

Pinned Header ListView as in the Contacts app

The standard Android Contacts application has this PinnedHeaderListView implementation. This renders a letter (A, B, C, ...) in a section header on your ListView for all the list items starting with that letter. What's nice is that this section header stays visible as long as there is a list item visible starting with that letter. And it's being pushed up when the last list item is becoming invisible while scrolling the list view.

A sample project with the pinned header list view can be found at http://code.google.com/p/android-playground. Another example project using this list view is Devoxx 2010 Schedule app.


Layout definitions

First we need to define the layout definitions. We are not using a default ListView, but the custom PinnedHeaderListView.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <view class="net.peterkuterna.android.apps.pinnedheader.PinnedHeaderListView"
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

The layout for the header is also specified in a separated layout. An instance of this view will be passed on to the PinnedHeaderListView when setting up the list view.

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/header_text"
    android:layout_width="match_parent"
    android:layout_height="25dip"
    android:textStyle="bold"
    android:background="@color/pinned_header_background"
    android:textColor="@color/pinned_header_text"
    android:textSize="14sp"
    android:paddingLeft="6dip"
    android:gravity="center_vertical" />

Next, we also need to include this header view in the layout that defines the list item.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_item"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <include layout="@layout/list_item_header" />
    <include layout="@android:layout/simple_list_item_1" />
    <View android:id="@+id/list_divider"
        android:layout_width="match_parent"
        android:layout_height="1px"
        android:background="@android:drawable/divider_horizontal_dark" />
</LinearLayout>

Setting up the PinnedHeaderListView

To actually use this PinnedHeaderListView you only need to provide it a suitable ListAdapter, the header View and an OnScrollListener.

private void setupListView() {
    PinnedHeaderListView listView = (PinnedHeaderListView) findViewById(android.R.id.list);
    listView.setPinnedHeaderView(LayoutInflater.from(this).inflate(R.layout.list_item_header, listView, false));
    listView.setOnScrollListener(mAdapter);
    listView.setDividerHeight(0);
}

Implementing the ListAdapter

The ListAdapter that you will pass on to the PinnedHeaderListView needs to have the following interfaces implemented:
  • PinnedHeaderAdapter
  • SectionIndexer
  • OnScrollListener

The PinnedHeaderAdapter interface defines two methods that need to be implemented. The method getPinnedHeaderState needs to return the state of the pinned header in your list view for a certain position. This allows the list view to identify if the header is gone, is visible or if it needs to be pushed up. The second method configurePinnedHeader is being called when your list view is being layout and allows you to adapt (text, colors, ...) the pinned header view.

A SectionIndexer is being used to easily identify a section for a given position and a position for a given section in the list view. These methods are being used when binding the pinned header, when detecting the pinned header state and when configuring the pinned header.

As last a OnScrollListener callback is implemented which will be called when the list has been scrolled and which will call on its turn the configureHeaderView method of the PinnedHeaderListView.