Sunday, July 6, 2014

Android Wear - Creating a Custom Watch Face with Options

*** Deprecated. Official API has been released, so please use that :) Here's a sample project to replicate this blog's end product: https://github.com/PaulTR/AndroidDemoProjects/tree/master/WatchFaceWithOptions ***

    For this tutorial I will go over creating a custom watch face for the Android Wear. There are rumors that Google will be releasing a watch face builder class, but right now no one is sure when/if that will happen. The point of this tutorial is to be a central place for information on building custom watch faces with the current system, as the information is currently scattered across Reddit and Google+ posts.

    This demo app allows the user to select a school from a list and then displays the time in the corner with the school logo below it. The settings activity for selecting the school is not a requirement for the watch face, as you can choose to only display one type of face, but I included it to show an additional feature. I also removed the mobile activity launcher, as it's not a necessity for the app. The code for this app is available on GitHub.
Watch face for Fresno State
    The first thing that needs to be done is to open up the mobile and wear AndroidManifest.xml files and add the permissions that will be needed for selecting a watch face:

<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

    In the wear AndroidManifest.xml file, add the following properties to the application tag:

<application
    android:allowBackup="true"
    android:icon="@drawable/bulldog_wallpaper"
    android:label="@string/app_name"
    android:theme="@android:style/Theme.DeviceDefault" >

    and the following properties to the activity tag for the watch face activity. Note the intent-filter and meta-data tags in the activity section, as those are required for determining if the activity should be selectable as a watch face. I use a general icon for the preview here, but generally a screen shot of the actual watch face (like above) should be used:

<activity
    android:name=".WatchFaceActivity"
    android:label="@string/app_name"
    android:enabled="true"
    android:taskAffinity=""
    android:allowEmbedded="true"
    android:theme="@android:style/Theme.DeviceDefault.NoActionBar" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="com.google.android.clockwork.home.category.HOME_BACKGROUND" />
    </intent-filter>
    <meta-data
        android:name="com.google.android.clockwork.home.preview"
        android:resource="@drawable/bulldog_wallpaper"/>
</activity>

Preview icon for the watch face

    In order to have the list of schools in the application area, we need to have an intent-filter added to the SettingsActivity activity tag:

<intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

    Once the manifest is put together, we can go ahead and fill in SettingsActivity to display a list of schools and save the selected school in a shared preference. In this example I use an object called SchoolObj that simply loads a list of school names and codes for organizing data:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate( savedInstanceState );
    setContentView( R.layout.activity_settings );
    final SchoolObj schools = new SchoolObj();
    ListView listView = (ListView) findViewById( R.id.list );
    ArrayAdapter<String> adapter = new ArrayAdapter( this, android.R.layout.simple_list_item_1, schools.schoolList );
    listView.setAdapter( adapter );
    listView.setOnItemClickListener( new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            SharedPreferences pref = getSharedPreferences( WatchFaceActivity.SHARED_PREFERENCE, Context.MODE_PRIVATE );
            SharedPreferences.Editor editor = pref.edit();
            editor.putString( SHARED_PREFERENCE_SCHOOL, schools.schoolCodeList.get( position ) );
            editor.commit();
            finish();
        }
    });
}

List of schools for customizing the watch face
    The final part to this application is the actual watch face activity. In onCreate() we can take a reference to the containing view, the TextClock object and the ImageView where the school logo will be stored. When onResume() is called we to see if SharedPreferences contains a key for a selected school and then set the color of the TextClock, ImageView drawable and background color for the watch face. If the user has just installed the application and has not selected a watch face, we set a default face.

@Override
protected void onResume() {
    super.onResume();
    SharedPreferences pref = getSharedPreferences( SHARED_PREFERENCE, Context.MODE_PRIVATE );

    if( pref.contains( SettingsActivity.SHARED_PREFERENCE_SCHOOL ) ) {
        String schoolCode = pref.getString( SettingsActivity.SHARED_PREFERENCE_SCHOOL, "" );
        loadSchoolWatchFace( schoolCode );
    } else {
        mBackground.setImageResource( R.drawable.bulldog_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
        mContainer.setBackgroundColor(getResources().getColor( android.R.color.white ) );
    }
}

    It is important that we set these properties every time that the activity resumes, as we must set the watch face to a "blank state" when the watch is put into a power conserving off state and onPause() is called

@Override
protected void onPause() {
    super.onPause();
    mBackground.setImageDrawable( null );
    mClock.setTextColor( getResources().getColor( android.R.color.white ) );
    mContainer.setBackgroundColor( getResources().getColor( android.R.color.black ) );
}

Watch Blank State
    The loadSchoolWatchFace( schoolCode ) method simply checks the school code in a set of if/else statements to set the proper image and colors, just like the default else portion of onResume()

private void loadSchoolWatchFace( String schoolCode ) {
    if( "cuboulder".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.cuboulder_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_orange_light));
    } else if( "fsu".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.floridastate_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
    } else if( "ucsc".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.bananaslugs_logo );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_orange_light));
    } else if( "berkeley".equals( schoolCode ) ) {
        mBackground.setImageResource( R.drawable.berkeley_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_orange_light ) );
    } else {
        //Default to Fresno
        mBackground.setImageResource( R.drawable.bulldog_wallpaper );
        mClock.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
    }

    mContainer.setBackgroundColor(getResources().getColor(android.R.color.white));
}
Florida State University watch face
UC Santa Cruz Banana Slugs watch face

    And that's all there is to it. The important parts to add are the manifest intent-filter and metadata items, and onResume()/onPause() for the watch face, then the rest adds functionality to the app. This can also work with an analog watch face and other views, so there's a lot more possibilities for developers to add some great features.