Thursday, May 16, 2013

API for Fetching the Google Voice Configuration on Android

Recent version of the Google Voice app on Android include a service which returns a Bundle of configuration values.  Currently the only value provided in the bundle is the subscriber number (the Google Voice phone number).

We have a need to programatically determine the user's Google Voice phone number from our own app.  Here's how it can be done...

First, include the Google Voice FETCH_CONFIGURATION permission in your app's AndroidManifest.xml file:

<uses-permission android:name="com.google.android.apps.googlevoice.permission.FETCH_CONFIGURATION"/>

Next, include the following three files in your src directory:

src/com/google/android/apps/googlevoice/IGoogleVoiceConfiguration.aidl

package com.google.android.apps.googlevoice;

interface IGoogleVoiceConfiguration {
    Bundle getConfiguration();
} 

src/com/google/android/apps/googlevoice/GoogleVoiceConfigurationAPI.java

package com.google.android.apps.googlevoice;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;

import android.util.Log;

/**
 * API for fetching the Google Voice configuration. It currently only
 * provides one value, "subscriberNumber".
 */
public class GoogleVoiceConfigurationAPI implements ServiceConnection {

    private String logTag = "GoogleVoiceConfigAPI";

    public GoogleVoiceConfigurationAPI() {}

    public GoogleVoiceConfigurationAPI( String logTag) {
        this.logTag = logTag;
    }

    /**
     * Key to use for extracting the subscriber number from the configuration bundle.
     */
    public static final String KEY_SUBSCRIBER_NUMBER = "subscriberNumber";

    private static final String GOOGLE_VOICE_CONFIGURATION_SERVICE_ACTION =
        "com.google.android.apps.googlevoice.IGoogleVoiceConfiguration";

    private Bundle configuration = null;

    Context context = null;
    GoogleVoiceConfigurationListener listener = null;

    /**
     * Fetch the configuration and invoke the listener callback with the configuration bundle,
     * if available.
     * @param context context required to make service calls to Google Voice
     * @param listener listener to receive the configuration bundle - the listener's
     * onFetchGoogleVoiceConfiguration() will be called with the configuration info if this
     * method returns true.
     * @return true if the GoogleVoiceConfiguration service can be bound, otherwise false.
     * If true, you can expect the listener to be called, otherwise it won't be invoked.
     */
    public boolean fetchConfiguration(Context context, GoogleVoiceConfigurationListener listener) {
        if (context == null || listener == null)
            throw new IllegalArgumentException("null parameter values not allowed");

        if (configuration != null) {
            try {
                listener.onFetchGoogleVoiceConfiguration(configuration);
            } catch (Exception x) {
                Log.e(logTag, x.getMessage(), x);
            }
            return true;
        }

        this.context = context;
        this.listener = listener;

        Intent i = new Intent(GOOGLE_VOICE_CONFIGURATION_SERVICE_ACTION);
        return context.bindService(i, this, Context.BIND_AUTO_CREATE);
    }

    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        try {
            IGoogleVoiceConfiguration iGoogleVoiceConfiguration = IGoogleVoiceConfiguration.Stub.asInterface(iBinder);
            configuration = iGoogleVoiceConfiguration.getConfiguration();
            if (listener != null) listener.onFetchGoogleVoiceConfiguration(configuration);
        } catch (Exception x) {
            Log.e(logTag, x.getMessage(), x);
        } finally {
            context.unbindService(this);
        }
    }

    public void onServiceDisconnected(ComponentName componentName) {
    }
}

src/com/google/android/apps/googlevoic/GoogleVoiceConfigurationListener.java

package com.google.android.apps.googlevoice;

import android.os.Bundle;

/**
 * Listener interface for receiving the google voice configuration bundle.
 */
public interface GoogleVoiceConfigurationListener {

    public void onFetchGoogleVoiceConfiguration(Bundle configuration);
}


Finally, you can call the API from your own app:
package com.example.googlevoice.config;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.google.android.apps.googlevoice.GoogleVoiceConfigurationAPI;
import com.google.android.apps.googlevoice.GoogleVoiceConfigurationListener;

public class GoogleVoiceConfigActivity extends Activity
{
    static final String TAG = "GoogleVoiceConfig";

    GoogleVoiceConfigurationAPI googleVoiceConfigurationAPI = new GoogleVoiceConfigurationAPI(TAG);

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button fetchItButton = (Button)findViewById(R.id.fetchConfigButton);

        fetchItButton.setOnClickListener( new View.OnClickListener() {
            public void onClick(View view) {
                TextView numberTextView = (TextView)findViewById(R.id.numberTextView);
                try {
                    if (!googleVoiceConfigurationAPI.fetchConfiguration(GoogleVoiceConfigActivity.this, listener))
                    {
                        numberTextView.setText(
                            "Configuration unavailable: Google Voice is not installed or not configured");
                    }
                } catch (SecurityException x) {
                    // This app is missing the following permission in its AndroidManifest.xml file:
                    // <uses-permission android:name="com.google.android.apps.googlevoice.permission.FETCH_CONFIGURATION">
                    Log.e(TAG, x.getMessage(), x);
                    numberTextView.setText( "Permission denied: this app's AndroidManifest.xml is missing permission"+
                        " com.google.android.apps.googlevoice.permission.FETCH_CONFIGURATION");
                } catch (Exception x) {
                    Log.e(TAG, x.getMessage(), x);
                    numberTextView.setText(x.getMessage());
                }
            }
        });
    }

    GoogleVoiceConfigurationListener listener = new GoogleVoiceConfigurationListener() {
        public void onFetchGoogleVoiceConfiguration(Bundle configuration)
        {
            String subscriberNumber = configuration.getString( GoogleVoiceConfigurationAPI.KEY_SUBSCRIBER_NUMBER);
            TextView numberTextView = (TextView)findViewById(R.id.numberTextView);
            numberTextView.setText("Subscriber number: " + subscriberNumber);
        }
    };
}
Full source can be downloaded from: this link