Sunday, June 29, 2014

Introduction to Official Android Wear Development Tutorial

    In this tutorial, I will go over writing a program for Android Wear physical devices. This app will allow the user to select a time interval on the Wear, and then set off a vibration on the user's wrist when the timer goes off before repeating. The goal of this app is to hopefully assist drivers on long road trips to stay alert (an idea that I had while driving back along I80 from San Francisco to Denver) and has been tested on the Samsung Gear Live physical device (which is where all of the screen shots have come from). The source code for this app is available on GitHub.

    For those interested, the app is available on the Play Store for free and working now that Play Services 5.0 is available.

    Also, before I begin, I should also throw out a thank you to ReadWrite, as I ended up getting to I/O last minute with a registration code through a contest on their awesome site.


    The first step is to download and install the latest Android Studio with SDK packages, then follow this documentation for putting together the broiler plate code for the mobile and wear version of the app. For this tutorial I named the main activity for the Wear portion 'IterationActivity', as this will be the activity screen that is displayed when the user opens the app on their Wear device. In the broiler plate code as of the current release, this activity extends WatchActivity, which is not available in the SDK, so we can safely change this to a regular Activity class. Next we can create an IntentService called TimerService that handles all of the background operations. We'll get back to filling this out once the infrastructure is ready to go.

    In the Wear and Mobile (it's important to have it in both, or the wear portion will not unpackage onto the user's device. I learned this the hard way with a lot of frustration :) ) sections, open AndroidManifest.xml and add the vibrate permission:

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

    Then add the service tag for our TimerService.class in the Wear manifest:

<service android:name=".Services.TimerService" />

    Next, under the Mobile section in build.gradle, add the following line to dependencies in order to package the Wear portion of the app with the Mobile:

wearApp project(':wear')

    Now that this app should compile and run directly on a Wear device, it's time to make it actually do something. The layout xml file used by IterativeActivity is a simple list view with some text above it, and should look like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Select an interval"
    />

    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />

</LinearLayout>

    Under rect_activity_iteration and round_activity_iteration layout files, we can replace the text view with a GridViewPager for containing our view:

<GridViewPager
    android:id="@+id/view_pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

    Back to the IterationActivity file, we can start adding content to onCreate(). The following block is added when the activity is started. If the SharedPreference for a selected duration is set, then the timer is started with that value, otherwise we continue with the block.

if( ( getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK ) == Intent.FLAG_ACTIVITY_NEW_TASK ) {
    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences( this );
    long duration = pref.getLong( SAVED_STATE_SELECTED_DURATION, 0 );
    if( duration != 0 ) {
        setupTimer( duration );
        finish();
        return;
    }
}

    If the activity hasn't been started before and there is no preselected interval time, then we can set up the array of times that the user can select from:

private void setupIterationArray() {
    int[] minutes = getResources().getIntArray( R.array.interation_minutes );
    for( int i = 0; i < minutes.length; i++ ) {
        IterationListItem item = new IterationListItem( getResources().getQuantityString( R.plurals.label_minutes, minutes[i], minutes[i] ),
                minutes[i]  * 60 * 1000 );
        mIterationTimes.add( item );
    }
}

    Next we set the content view to activity_iteration.xml:

setContentView(R.layout.activity_iteration);

    After the view is set, we can create the list of labels and times that the user can select from:

private void setupIterationArray() {
    int[] minutes = getResources().getIntArray( R.array.interation_minutes );
    for( int i = 0; i < minutes.length; i++ ) {
        IterationListItem item = new IterationListItem( getResources().getQuantityString( R.plurals.label_minutes, minutes[i], minutes[i] ),
                minutes[i]  * 60 * 1000 );
        mIterationTimes.add( item );
    }
}

    Finally under onCreate() we can initialize the list view and GoogleAPIClient. This will provide the activity for selecting durations in Wear:

private void initList() {
    mListView = (ListView) findViewById( R.id.list_view );
    mListView.setAdapter( new ArrayAdapter<IterationListItem>( this,
            android.R.layout.simple_list_item_1, mIterationTimes ) );
    mListView.setOnItemClickListener( this );
}

private void initGoogleApiClient() {
    mGoogleApiClient = new GoogleApiClient.Builder(this)
            .addApi( Wearable.API )
            .addConnectionCallbacks( this )
            .addOnConnectionFailedListener( this )
            .build();
}


    When one of these list items is clicked, the value is saved in a shared preference and the duration is sent to the setupTimer method:

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences( this );
    SharedPreferences.Editor editor = pref.edit();
    editor.putLong( SAVED_STATE_SELECTED_DURATION, mIterationTimes.get( position ).getDuration() );
    editor.commit();
    setupTimer( mIterationTimes.get( position ).getDuration() );
}

    setupTimer accepts a duration in milliseconds and cancels any notifications that this app may have created before, then builds and launches a new timer notification and registers an alarm before closing the list activity:

private void setupTimer( long duration ) {
    NotificationManagerCompat notificationManager = NotificationManagerCompat.from( this );
    notificationManager.cancel( 1 );
    notificationManager.notify( 1, buildNotification( duration ) );
    registerAlarmManager( duration );
    finish();
}

    The buildNotification method is pretty standard and similar to the three other notification posts I wrote about when going over the developers preview. The important part to note here is the PendingIntent that goes to TimerService and closes down the timer:

private Notification buildNotification( long duration ) {
    Intent removeIntent = new Intent( ACTION_REMOVE_TIMER, null, this, TimerService.class );
    PendingIntent pendingRemoveIntent = PendingIntent.getService( this, 0, removeIntent, PendingIntent.FLAG_UPDATE_CURRENT );

    return new NotificationCompat.Builder( this )
            .setSmallIcon( R.drawable.ic_launcher )
            .setContentTitle( "Stay Awake" )
            .setContentText(TimeUtil.getTimeString( duration ) )
            .setUsesChronometer( true )
            .setLargeIcon( BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher ) )
            .setWhen( System.currentTimeMillis() + duration )
            .addAction( R.drawable.ic_launcher, "Remove Timer", pendingRemoveIntent )
            .setDeleteIntent( pendingRemoveIntent )
            .setLocalOnly( true )
            .build();
}


    The final thing to do in IterationActivity is register an alarm that fires off an intent to TimerService once the duration has finished:

private void registerAlarmManager( long duration ) {
    AlarmManager alarmManager = (AlarmManager) getSystemService( Context.ALARM_SERVICE );
    Intent intent = new Intent( ACTION_SHOW_ALARM, null, this, TimerService.class );
    PendingIntent pendingIntent = PendingIntent.getService( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT );

    long time = System.currentTimeMillis() + duration;
    alarmManager.setExact( AlarmManager.RTC_WAKEUP, time, pendingIntent );
}

    Now that IterationActivity is set, we can focus on TimerService.class. HandleIntent listens for two types of actions: starting an alarm and removing an alarm.

@Override
protected void onHandleIntent(Intent intent) {
    String action = intent.getAction();

    if( IterationActivity.ACTION_SHOW_ALARM.equals( action ) ) {
        showAlarm();
    } else if( IterationActivity.ACTION_REMOVE_TIMER.equals( action ) ) {
        removeAlarm();
    }
}

    showAlarm() is only called after the first timer has ended, so it simply vibrates the Wear on call and fires an intent back to IterationActivity with a flag stating that the next notification timer should be posted.  It should be noted that after a lot of time and frustration, I switched to using a pattern here rather than simply passing in the duration, as Wear won't stop the vibration on the watch unless the screen is active if a pattern isn't used.

private void showAlarm() {
    final Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    long[] pattern = { 0, getResources().getInteger( R.integer.vibration_duration ) };
    v.vibrate( pattern, -1 );
    Intent intent = new Intent( this, IterationActivity.class );
    intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
    startActivity( intent );
}


    removeAlarm() cancels any notification timers that may be visible, any running alarm manager for our specified PendingIntent and sets the selected duration shared preference to 0.

private void removeAlarm() {
    NotificationManagerCompat notificationManager = NotificationManagerCompat.from( this );
    notificationManager.cancel( 1 );

    AlarmManager alarmManager = (AlarmManager) getSystemService( Context.ALARM_SERVICE );

    Intent intent = new Intent( IterationActivity.ACTION_SHOW_ALARM, null, this, TimerService.class );
    PendingIntent pendingIntent = PendingIntent.getService( this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT );

    alarmManager.cancel( pendingIntent );

    SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
    SharedPreferences.Editor editor = pref.edit();
    editor.putLong( IterationActivity.SAVED_STATE_SELECTED_DURATION, 0 );
    editor.apply();
}

    And with that we should have a fully functioning basic Android Wear application. Devices go on sale on the 7th, and I can honestly say from my few days using one it has been useful and will only get more useful as developers make additional apps.

Saturday, June 21, 2014

Using Street View in Android with the StreetViewPanoramaFragment

    Google has released street view functionality in the most recent version of the Play Services, allowing Android developers another amazing feature in their maps skill set. I decided to dive right in because of my excitement for this feature, and luckily Google made it incredibly easy to implement street view into an app. The code for this sample can be found on my GitHub account. This app will take the user's current location and display their nearest street view panorama location.


    Before starting, we need to do the necessary project setup. The gradle file should import the latest version of Google Play Services, and the manifest should list necessary permissions and meta-data:

<uses-feature
    android:glEsVersion="0x00020000"
    android:required="true" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="com.ptrprograms.streetview.permission.MAPS_RECEIVE" />

<meta-data
    android:name="com.google.android.maps.v2.API_KEY"
    android:value="@string/maps_api_key" />
<meta-data
    android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version" />

compile 'com.google.android.gms:play-services:4.+'

    The easiest way to get street view into an app is by implementing a fragment. While the fragment can easily be extended, I chose to simply embed the pre-made StreetViewPanoramaFragment and use it for my main layout.

<fragment
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/street_view_panorama_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    class="com.google.android.gms.maps.StreetViewPanoramaFragment" />

    While I would usually implement initial code in onCreate, for this example I call a function from onRestore that starts the location client connection.

private void updateStreetViewPosition() {
    if( mLocationClient == null )
        mLocationClient = new LocationClient( this, this, this );
    if( !mLocationClient.isConnected() && !mLocationClient.isConnecting() ) {
        mLocationClient.connect();
    }
}

    Using the GooglePlayServicesClient.ConnectionCallbacks listeners, I save the user's current location and initialize the street view panorama view.

@Override
public void onConnected(Bundle bundle) {
    if( mCurrentLocation == null ) {
        mCurrentLocation = new LatLng( mLocationClient.getLastLocation().getLatitude(), mLocationClient.getLastLocation().getLongitude() );
    }
    initStreetView();
}

    In initStreetView(), I get a reference to the StreetViewPanormaFragment, and extract the StreetViewPanorama from it. StreetViewPanorama is the control structure for everything we will do in street view, and it can only be obtained from the fragment by using getStreetViewPanorama:

StreetViewPanoramaFragment fragment = ( (StreetViewPanoramaFragment) getFragmentManager().findFragmentById( R.id.street_view_panorama_fragment ) );
if( mPanorama == null ) {
    if( fragment != null ) {
        mPanorama = fragment.getStreetViewPanorama();    

    The next part of initStreetView() takes into account member variables that are used for keeping track of camera properties on rotation or other configuration changes. By using the StreetViewPanormaCamera.Builder object, I set the tilt, bearing and zoom levels to either the default values or the user's saved values, and then assign the camera to the panorama view.

if( mPanorama != null && mCurrentLocation != null ) {
    StreetViewPanoramaCamera.Builder builder = new StreetViewPanoramaCamera.Builder( mPanorama.getPanoramaCamera() );
    if( mBearing != builder.bearing )
        builder.bearing = mBearing;
    if( mTilt != builder.tilt )
        builder.tilt = mTilt;
    if( mZoom != builder.zoom )
        builder.zoom = mZoom;
    mPanorama.animateTo(builder.build(), 0);
    mPanorama.setPosition( mCurrentLocation, 300 );
    mPanorama.setStreetNamesEnabled( true );
}

    This is where other properties can be set to the street view, like setStreetNamesEnabled above, such restricting the user's ability to zoom, leave their current panorama or implement listeners for user interactions. Another important portion above is setPosition, as it must be set for the street view to work, and the accepted values are a LatLng or a LatLng with an integer. The second parameter tells the system to find the nearest street viewable point within a set number of meters from the provided LatLng.



    The final portion in this demo app is onSavedInstanceState, where I save the user location if they have explored a bit in street view, as well as the tilt, bearing and zoom of the camera, in order to not reset their view on rotate.

if( mPanorama != null && mPanorama.getLocation() != null && mPanorama.getLocation().position != null ) {
    outState.putDouble( EXTRA_LAT, mPanorama.getLocation().position.latitude );
    outState.putDouble( EXTRA_LONG, mPanorama.getLocation().position.longitude );
}
if( mPanorama != null && mPanorama.getPanoramaCamera() != null ) {
    outState.putFloat( EXTRA_TILT, mPanorama.getPanoramaCamera().tilt );
    outState.putFloat( EXTRA_BEARING, mPanorama.getPanoramaCamera().bearing );
    outState.putFloat( EXTRA_ZOOM, mPanorama.getPanoramaCamera().zoom );
}

    And with that, we have a basic street view placed into an app. This opens the door for a lot of developers to improve their apps with another great feature.