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.


9 comments:

  1. Hi Paul,

    Did you test this app in a real device? I'm creating a watch face, but when the screens turn off, the TextClock stops to update its value. Any idea?

    []'s
    Glauber

    ReplyDelete
    Replies
    1. Yeah all of the screen shots are off of a Samsung Gear Live. That's odd that your textclock stops updating, I haven't run into that issue and I've kept my custom face on since I wrote it. You have the wake_lock permission and everything in the manifest set up (just taking a guess here, not sure if this is the actual problem)?

      Delete
  2. Thanks for this !

    Quick question : did you get it to work on the emulator ?

    I can't see to have it display other faces than the simple one. I can pick other ones, including your custom one, but they won't display.

    Thanks for the info :)

    Steve

    ReplyDelete
    Replies
    1. Hey, no problem.

      I actually haven't set it up on an emulator yet since I tend to prefer working on real hardware, but if I get time this week (probably closer to the weekend. This week all of my free time has gone to tearing apart the code for AndroidTV to get a post out for that - plus it's just awesome to work with) I'll try setting one up and letting you know.

      Delete
    2. Thanks, would be much appreciated. I'm continuing to test on my side, will let you know if I find something !

      Delete
  3. Hi Paul, I am new to wear development so please help me I have created a project in Android studio and then inside Mobile part I have created a activity, below I have written the code inside the activity and when I am running this activity I am not getting any message on my wear emulator which is already connected .

    public class MessagetoWear extends Activity{
    int notificationId = 001;
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    Intent viewIntent = new Intent(this, MessagetoWear.class);
    //viewIntent.putExtra(EXTRA_EVENT_ID, eventId);
    PendingIntent viewPendingIntent =
    PendingIntent.getActivity(this, 0, viewIntent, 0);

    NotificationCompat.Builder notificationBuilder =
    new NotificationCompat.Builder(this)
    .setSmallIcon(R.drawable.ic_launcher)
    .setContentTitle("First Wear App")
    .setContentText("First Wearable notification.")
    .setContentIntent(viewPendingIntent);

    NotificationManagerCompat notificationManager =
    NotificationManagerCompat.from(this);
    notificationManager.notify(notificationId,notificationBuilder.build());
    }
    }

    Please help me on this I have to write on wear also to receive the notification.

    ReplyDelete
  4. Nice Information! I personally really appreciate your article. This is a great website. I will make sure that I stop back again!.
    android watch

    ReplyDelete
  5. Do you have an updated version of this watch face using the official watch face api because I have a few watch faces made but I want to add a configuration activity to the wearable itself but I want to make sure Im using the proper method for this.

    ReplyDelete
    Replies
    1. Check out this project that I worked on - https://github.com/PaulTR/AndroidDemoProjects/tree/master/WatchFaceWithOptions

      I haven't written anything about using the custom activity yet, but it's pretty straight forward if you look at the manifest and the activity in that source.

      Delete