Merge "WatchFace sample" into lmp-docs
diff --git a/wearable/wear/WatchFace/Application/.gitignore b/wearable/wear/WatchFace/Application/.gitignore
new file mode 100644
index 0000000..8fd2e1c
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2014 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.
+src/template/
+src/common/
+build.gradle
diff --git a/wearable/wear/WatchFace/Application/proguard-project.txt b/wearable/wear/WatchFace/Application/proguard-project.txt
new file mode 100644
index 0000000..0d8f171
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/proguard-project.txt
@@ -0,0 +1,20 @@
+ To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml b/wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..732e306
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.example.android.wearable.watchface" >
+
+    <uses-sdk android:minSdkVersion="18"
+              android:targetSdkVersion="21" />
+
+    <!-- Permissions required by the wearable app -->
+    <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+    <!-- All intent-filters for config actions must include the categories
+        com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION and
+        android.intent.category.DEFAULT. -->
+    <application
+            android:allowBackup="true"
+            android:icon="@drawable/ic_launcher"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme" >
+
+        <activity
+                android:name=".AnalogAndCardBoundsWatchFaceConfigActivity"
+                android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="com.example.android.wearable.watchface.CONFIG_ANALOG" />
+                <action android:name="com.example.android.wearable.watchface.CONFIG_CARD_BOUNDS" />
+                <category android:name="com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name=".DigitalWatchFaceCompanionConfigActivity"
+                android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+                <category android:name="com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name=".TiltWatchFaceConfigActivity"
+                android:label="@string/app_name">
+            <intent-filter>
+                <action android:name="com.example.android.wearable.watchface.CONFIG_TILT" />
+                <category android:name="com.google.android.wearable.watchface.category.COMPANION_CONFIGURATION" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <meta-data
+                android:name="com.google.android.gms.version"
+                android:value="@integer/google_play_services_version" />
+
+    </application>
+
+</manifest>
diff --git a/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/AnalogAndCardBoundsWatchFaceConfigActivity.java b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/AnalogAndCardBoundsWatchFaceConfigActivity.java
new file mode 100644
index 0000000..5943e6b
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/AnalogAndCardBoundsWatchFaceConfigActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.support.wearable.companion.WatchFaceCompanion;
+import android.widget.TextView;
+
+public class AnalogAndCardBoundsWatchFaceConfigActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_analog_watch_face_config);
+
+        ComponentName name =
+                getIntent().getParcelableExtra(WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
+        TextView label = (TextView) findViewById(R.id.label);
+        label.setText(label.getText() + " (" + name.getClassName() + ")");
+    }
+}
diff --git a/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceCompanionConfigActivity.java b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceCompanionConfigActivity.java
new file mode 100644
index 0000000..b04f11e
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceCompanionConfigActivity.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.wearable.companion.WatchFaceCompanion;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataItem;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.Wearable;
+
+/**
+ * The phone-side config activity for {@code DigitalWatchFaceService}. Like the watch-side config
+ * activity ({@code DigitalWatchFaceWearableConfigActivity}), allows for setting the background
+ * color. Additionally, enables setting the color for hour, minute and second digits.
+ */
+public class DigitalWatchFaceCompanionConfigActivity extends Activity
+        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener,
+                ResultCallback<DataApi.DataItemResult> {
+    private static final String TAG = "DigitalWatchFaceConfig";
+
+    // TODO: use the shared constants (needs covering all the samples with Gradle build model)
+    private static final String KEY_BACKGROUND_COLOR = "BACKGROUND_COLOR";
+    private static final String KEY_HOURS_COLOR = "HOURS_COLOR";
+    private static final String KEY_MINUTES_COLOR = "MINUTES_COLOR";
+    private static final String KEY_SECONDS_COLOR = "SECONDS_COLOR";
+    private static final String PATH_WITH_FEATURE = "/watch_face_config/Digital";
+
+    private GoogleApiClient mGoogleApiClient;
+    private String mPeerId;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_digital_watch_face_config);
+
+        mPeerId = getIntent().getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID);
+        mGoogleApiClient = new GoogleApiClient.Builder(this)
+                .addConnectionCallbacks(this)
+                .addOnConnectionFailedListener(this)
+                .addApi(Wearable.API)
+                .build();
+
+        ComponentName name = getIntent().getParcelableExtra(
+                WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
+        TextView label = (TextView)findViewById(R.id.label);
+        label.setText(label.getText() + " (" + name.getClassName() + ")");
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mGoogleApiClient.connect();
+    }
+
+    @Override
+    protected void onStop() {
+        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+            mGoogleApiClient.disconnect();
+        }
+        super.onStop();
+    }
+
+    @Override // GoogleApiClient.ConnectionCallbacks
+    public void onConnected(Bundle connectionHint) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onConnected: " + connectionHint);
+        }
+
+        if (mPeerId != null) {
+            Uri.Builder builder = new Uri.Builder();
+            Uri uri = builder.scheme("wear").path(PATH_WITH_FEATURE).authority(mPeerId).build();
+            Wearable.DataApi.getDataItem(mGoogleApiClient, uri).setResultCallback(this);
+        } else {
+            displayNoConnectedDeviceDialog();
+        }
+    }
+
+    @Override // ResultCallback<DataApi.DataItemResult>
+    public void onResult(DataApi.DataItemResult dataItemResult) {
+        if (dataItemResult.getStatus().isSuccess() && dataItemResult.getDataItem() != null) {
+            DataItem configDataItem = dataItemResult.getDataItem();
+            DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
+            DataMap config = dataMapItem.getDataMap();
+            setUpAllPickers(config);
+        } else {
+            // If DataItem with the current config can't be retrieved, select the default items on
+            // each picker.
+            setUpAllPickers(null);
+        }
+    }
+
+    @Override // GoogleApiClient.ConnectionCallbacks
+    public void onConnectionSuspended(int cause) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onConnectionSuspended: " + cause);
+        }
+    }
+
+    @Override // GoogleApiClient.OnConnectionFailedListener
+    public void onConnectionFailed(ConnectionResult result) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onConnectionFailed: " + result);
+        }
+    }
+
+    private void displayNoConnectedDeviceDialog() {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        String messageText = getResources().getString(R.string.title_no_device_connected);
+        String okText = getResources().getString(R.string.ok_no_device_connected);
+        builder.setMessage(messageText)
+                .setCancelable(false)
+                .setPositiveButton(okText, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) { }
+                });
+        AlertDialog alert = builder.create();
+        alert.show();
+    }
+
+    /**
+     * Sets up selected items for all pickers according to given {@code config} and sets up their
+     * item selection listeners.
+     *
+     * @param config the {@code DigitalWatchFaceService} config {@link DataMap}. If null, the
+     *         default items are selected.
+     */
+    private void setUpAllPickers(DataMap config) {
+        setUpColorPickerSelection(R.id.background, KEY_BACKGROUND_COLOR, config,
+                R.string.color_black);
+        setUpColorPickerSelection(R.id.hours, KEY_HOURS_COLOR, config, R.string.color_white);
+        setUpColorPickerSelection(R.id.minutes, KEY_MINUTES_COLOR, config, R.string.color_white);
+        setUpColorPickerSelection(R.id.seconds, KEY_SECONDS_COLOR, config, R.string.color_gray);
+
+        setUpColorPickerListener(R.id.background, KEY_BACKGROUND_COLOR);
+        setUpColorPickerListener(R.id.hours, KEY_HOURS_COLOR);
+        setUpColorPickerListener(R.id.minutes, KEY_MINUTES_COLOR);
+        setUpColorPickerListener(R.id.seconds, KEY_SECONDS_COLOR);
+    }
+
+    private void setUpColorPickerSelection(int spinnerId, final String configKey, DataMap config,
+            int defaultColorNameResId) {
+        String defaultColorName = getString(defaultColorNameResId);
+        int defaultColor = Color.parseColor(defaultColorName);
+        int color;
+        if (config != null) {
+            color = config.getInt(configKey, defaultColor);
+        } else {
+            color = defaultColor;
+        }
+        Spinner spinner = (Spinner) findViewById(spinnerId);
+        String[] colorNames = getResources().getStringArray(R.array.color_array);
+        for (int i = 0; i < colorNames.length; i++) {
+            if (Color.parseColor(colorNames[i]) == color) {
+                spinner.setSelection(i);
+                break;
+            }
+        }
+    }
+
+    private void setUpColorPickerListener(int spinnerId, final String configKey) {
+        Spinner spinner = (Spinner) findViewById(spinnerId);
+        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long id) {
+                final String colorName = (String) adapterView.getItemAtPosition(pos);
+                sendConfigUpdateMessage(configKey, Color.parseColor(colorName));
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> adapterView) { }
+        });
+    }
+
+    private void sendConfigUpdateMessage(String configKey, int color) {
+        if (mPeerId != null) {
+            DataMap config = new DataMap();
+            config.putInt(configKey, color);
+            byte[] rawData = config.toByteArray();
+            Wearable.MessageApi.sendMessage(mGoogleApiClient, mPeerId, PATH_WITH_FEATURE, rawData);
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "Sent watch face config message: " + configKey + " -> "
+                        + Integer.toHexString(color));
+            }
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceConfigActivity.java b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceConfigActivity.java
new file mode 100644
index 0000000..303e72e
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceConfigActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.support.wearable.companion.WatchFaceCompanion;
+import android.widget.TextView;
+
+public class TiltWatchFaceConfigActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_tilt_watch_face_config);
+
+        ComponentName name =
+                getIntent().getParcelableExtra(WatchFaceCompanion.EXTRA_WATCH_FACE_COMPONENT);
+        TextView label = (TextView)findViewById(R.id.label);
+        label.setText(label.getText() + " (" + name.getClassName() + ")");
+    }
+}
diff --git a/wearable/wear/WatchFace/Application/src/main/res/drawable-hdpi/ic_launcher.png b/wearable/wear/WatchFace/Application/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..589f229
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Application/src/main/res/drawable-mdpi/ic_launcher.png b/wearable/wear/WatchFace/Application/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000..77dd571
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Application/src/main/res/drawable-xhdpi/ic_launcher.png b/wearable/wear/WatchFace/Application/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..fe34ebe
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Application/src/main/res/drawable-xxhdpi/ic_launcher.png b/wearable/wear/WatchFace/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..ab80bcd
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Application/src/main/res/layout/activity_analog_watch_face_config.xml b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_analog_watch_face_config.xml
new file mode 100644
index 0000000..ec816c2
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_analog_watch_face_config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/label"
+        android:text="@string/analog_config_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</FrameLayout>
diff --git a/wearable/wear/WatchFace/Application/src/main/res/layout/activity_digital_watch_face_config.xml b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_digital_watch_face_config.xml
new file mode 100644
index 0000000..204d523
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_digital_watch_face_config.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<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:id="@+id/label"
+        android:text="@string/digital_config_text"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <TextView
+            android:text="@string/digital_config_background"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+        <Spinner
+            android:id="@+id/background"
+            android:entries="@array/color_array"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="3" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <TextView
+            android:text="@string/digital_config_hours"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+        <Spinner
+            android:id="@+id/hours"
+            android:entries="@array/color_array"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="3" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <TextView
+            android:text="@string/digital_config_minutes"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+        <Spinner
+            android:id="@+id/minutes"
+            android:entries="@array/color_array"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="3" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <TextView
+            android:text="@string/digital_config_seconds"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" />
+        <Spinner
+            android:id="@+id/seconds"
+            android:entries="@array/color_array"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="3" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/wearable/wear/WatchFace/Application/src/main/res/layout/activity_tilt_watch_face_config.xml b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_tilt_watch_face_config.xml
new file mode 100644
index 0000000..bda2d68
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/layout/activity_tilt_watch_face_config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/label"
+        android:text="@string/tilt_config_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+</FrameLayout>
diff --git a/wearable/wear/WatchFace/Application/src/main/res/values/strings.xml b/wearable/wear/WatchFace/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..aacb108
--- /dev/null
+++ b/wearable/wear/WatchFace/Application/src/main/res/values/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+    <string name="analog_config_text">This is the config activity for the Analog and Card Bounds watch faces</string>
+    <string name="digital_config_text">Digital watch face configuration</string>
+    <string name="tilt_config_text">Tilt watch face configuration</string>
+    <string name="digital_config_background">Background</string>
+    <string name="digital_config_hours">Hours</string>
+    <string name="digital_config_minutes">Minutes</string>
+    <string name="digital_config_seconds">Seconds</string>
+
+    <string name="title_no_device_connected">No wearable device is currently connected.</string>
+    <string name="ok_no_device_connected">OK</string>
+
+    <string name="color_black">Black</string>
+    <string name="color_blue">Blue</string>
+    <string name="color_gray">Gray</string>
+    <string name="color_green">Green</string>
+    <string name="color_navy">Navy</string>
+    <string name="color_red">Red</string>
+    <string name="color_white">White</string>
+
+    <string-array name="color_array">
+        <item>@string/color_black</item>
+        <item>@string/color_blue</item>
+        <item>@string/color_gray</item>
+        <item>@string/color_green</item>
+        <item>@string/color_navy</item>
+        <item>@string/color_red</item>
+        <item>@string/color_white</item>
+    </string-array>
+</resources>
diff --git a/wearable/wear/WatchFace/CONTRIB.md b/wearable/wear/WatchFace/CONTRIB.md
new file mode 100644
index 0000000..14a4fcf
--- /dev/null
+++ b/wearable/wear/WatchFace/CONTRIB.md
@@ -0,0 +1,35 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement (CLA).
+
+  * If you are an individual writing original source code and you're sure you
+    own the intellectual property, then you'll need to sign an [individual CLA]
+    (https://developers.google.com/open-source/cla/individual).
+  * If you work for a company that wants to allow you to contribute your work,
+    then you'll need to sign a [corporate CLA]
+    (https://developers.google.com/open-source/cla/corporate).
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing A Patch
+
+1. Submit an issue describing your proposed change to the repo in question.
+1. The repo owner will respond to your issue promptly.
+1. If your proposed change is accepted, and you haven't already done so, sign a
+   Contributor License Agreement (see details above).
+1. Fork the desired repo, develop and test your code changes.
+1. Ensure that your code adheres to the existing style in the sample to which
+   you are contributing. Refer to the
+   [Android Code Style Guide]
+   (https://source.android.com/source/code-style.html) for the
+   recommended coding standards for this organization.
+1. Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
+
diff --git a/wearable/wear/WatchFace/LICENSE b/wearable/wear/WatchFace/LICENSE
new file mode 100644
index 0000000..1af981f
--- /dev/null
+++ b/wearable/wear/WatchFace/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2014 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.
diff --git a/wearable/wear/WatchFace/Wearable/.gitignore b/wearable/wear/WatchFace/Wearable/.gitignore
new file mode 100644
index 0000000..8fd2e1c
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2014 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.
+src/template/
+src/common/
+build.gradle
diff --git a/wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml b/wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6e0aac2
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/AndroidManifest.xml
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.example.android.wearable.watchface" >
+
+    <uses-sdk android:minSdkVersion="21"
+        android:targetSdkVersion="21" />
+
+    <uses-feature android:name="android.hardware.type.watch" />
+
+    <!-- Required to act as a custom watch face. -->
+    <uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <!-- Calendar permission used by CalendarWatchFaceService -->
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+    <application
+            android:allowBackup="true"
+            android:icon="@drawable/ic_launcher"
+            android:label="@string/app_name" >
+
+        <service
+                android:name=".AnalogWatchFaceService"
+                android:label="@string/analog_name"
+                android:allowEmbedded="true"
+                android:taskAffinity=""
+                android:permission="android.permission.BIND_WALLPAPER" >
+            <meta-data
+                    android:name="android.service.wallpaper"
+                    android:resource="@xml/watch_face" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.preview"
+                    android:resource="@drawable/preview_analog" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+                    android:value="com.example.android.wearable.watchface.CONFIG_ANALOG" />
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+            </intent-filter>
+        </service>
+
+        <service
+                android:name=".SweepWatchFaceService"
+                android:label="@string/sweep_name"
+                android:allowEmbedded="true"
+                android:taskAffinity=""
+                android:permission="android.permission.BIND_WALLPAPER" >
+            <meta-data
+                    android:name="android.service.wallpaper"
+                    android:resource="@xml/watch_face" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.preview"
+                    android:resource="@drawable/preview_analog" />
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+            </intent-filter>
+        </service>
+
+        <service
+                android:name=".TiltWatchFaceService"
+                android:label="@string/tilt_name"
+                android:allowEmbedded="true"
+                android:taskAffinity=""
+                android:permission="android.permission.BIND_WALLPAPER" >
+            <meta-data
+                    android:name="android.service.wallpaper"
+                    android:resource="@xml/watch_face" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.preview"
+                    android:resource="@drawable/preview_tilt" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+                    android:value="com.example.android.wearable.watchface.CONFIG_TILT" />
+
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+            </intent-filter>
+        </service>
+
+        <service
+                android:name=".CardBoundsWatchFaceService"
+                android:label="@string/card_bounds_name"
+                android:allowEmbedded="true"
+                android:taskAffinity=""
+                android:permission="android.permission.BIND_WALLPAPER" >
+            <meta-data
+                    android:name="android.service.wallpaper"
+                    android:resource="@xml/watch_face" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.preview"
+                    android:resource="@drawable/preview_card_bounds" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+                    android:value="com.example.android.wearable.watchface.CONFIG_CARD_BOUNDS" />
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+            </intent-filter>
+        </service>
+
+        <service
+                android:name=".DigitalWatchFaceService"
+                android:label="@string/digital_name"
+                android:allowEmbedded="true"
+                android:taskAffinity=""
+                android:permission="android.permission.BIND_WALLPAPER" >
+            <meta-data
+                    android:name="android.service.wallpaper"
+                    android:resource="@xml/watch_face" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.preview"
+                    android:resource="@drawable/preview_digital" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.companionConfigurationAction"
+                    android:value="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
+                    android:value="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+            </intent-filter>
+        </service>
+
+        <!-- All intent-filters for config actions must include the categories
+            com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION
+            and android.intent.category.DEFAULT. -->
+
+        <activity
+                android:name=".DigitalWatchFaceWearableConfigActivity"
+                android:label="@string/digital_config_name">
+            <intent-filter>
+                <action android:name="com.example.android.wearable.watchface.CONFIG_DIGITAL" />
+                <category android:name="com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <service
+                android:name=".CalendarWatchFaceService"
+                android:label="@string/calendar_name"
+                android:allowEmbedded="true"
+                android:taskAffinity=""
+                android:permission="android.permission.BIND_WALLPAPER" >
+            <meta-data
+                    android:name="android.service.wallpaper"
+                    android:resource="@xml/watch_face" />
+            <meta-data
+                    android:name="com.google.android.wearable.watchface.preview"
+                    android:resource="@drawable/preview_calendar" />
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService" />
+                <category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
+            </intent-filter>
+        </service>
+
+        <service android:name=".DigitalWatchFaceConfigListenerService">
+            <intent-filter>
+                <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
+            </intent-filter>
+        </service>
+
+        <meta-data
+                android:name="com.google.android.gms.version"
+                android:value="@integer/google_play_services_version" />
+
+    </application>
+
+</manifest>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/AnalogWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/AnalogWatchFaceService.java
new file mode 100644
index 0000000..19c8d2b
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/AnalogWatchFaceService.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sample analog watch face with a ticking second hand. In ambient mode, the second hand isn't
+ * shown. On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient
+ * mode. The watch face is drawn with less contrast in mute mode.
+ *
+ * {@link SweepWatchFaceService} is similar but has a sweep second hand.
+ */
+public class AnalogWatchFaceService extends CanvasWatchFaceService {
+    private static final String TAG = "AnalogWatchFaceService";
+
+    /**
+     * Update rate in milliseconds for interactive mode. We update once a second to advance the
+     * second hand.
+     */
+    private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
+
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+
+    private class Engine extends CanvasWatchFaceService.Engine {
+        static final int MSG_UPDATE_TIME = 0;
+
+        Paint mHourPaint;
+        Paint mMinutePaint;
+        Paint mSecondPaint;
+        Paint mTickPaint;
+        boolean mMute;
+        Time mTime;
+
+        /** Handler to update the time once a second in interactive mode. */
+        final Handler mUpdateTimeHandler = new Handler() {
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_UPDATE_TIME:
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "updating time");
+                        }
+                        invalidate();
+                        if (isVisible()) {
+                            long timeMs = System.currentTimeMillis();
+                            long delayMs = INTERACTIVE_UPDATE_RATE_MS
+                                    - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
+                            mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+                        }
+                        break;
+                }
+            }
+        };
+
+        final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mTime.clear(intent.getStringExtra("time-zone"));
+                mTime.setToNow();
+            }
+        };
+        boolean mRegisteredTimeZoneReceiver = false;
+
+        /**
+         * Whether the display supports fewer bits for each color in ambient mode. When true, we
+         * disable anti-aliasing in ambient mode.
+         */
+        boolean mLowBitAmbient;
+
+        Bitmap mBackgroundBitmap;
+        Bitmap mBackgroundScaledBitmap;
+
+        @Override
+        public void onCreate(SurfaceHolder holder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCreate");
+            }
+            super.onCreate(holder);
+
+            setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
+                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+                    .setShowSystemUiTime(false)
+                    .build());
+
+            Resources resources = AnalogWatchFaceService.this.getResources();
+            Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+            mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
+
+            mHourPaint = new Paint();
+            mHourPaint.setARGB(255, 200, 200, 200);
+            mHourPaint.setStrokeWidth(5.f);
+            mHourPaint.setAntiAlias(true);
+            mHourPaint.setStrokeCap(Paint.Cap.ROUND);
+
+            mMinutePaint = new Paint();
+            mMinutePaint.setARGB(255, 200, 200, 200);
+            mMinutePaint.setStrokeWidth(3.f);
+            mMinutePaint.setAntiAlias(true);
+            mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
+
+            mSecondPaint = new Paint();
+            mSecondPaint.setARGB(255, 255, 0, 0);
+            mSecondPaint.setStrokeWidth(2.f);
+            mSecondPaint.setAntiAlias(true);
+            mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
+
+            mTickPaint = new Paint();
+            mTickPaint.setARGB(100, 255, 255, 255);
+            mTickPaint.setStrokeWidth(2.f);
+            mTickPaint.setAntiAlias(true);
+
+            mTime = new Time();
+        }
+
+        @Override
+        public void onDestroy() {
+            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+            super.onDestroy();
+        }
+
+        @Override
+        public void onPropertiesChanged(Bundle properties) {
+            super.onPropertiesChanged(properties);
+            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
+            }
+        }
+
+        @Override
+        public void onTimeTick() {
+            super.onTimeTick();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+            }
+            invalidate();
+        }
+
+        @Override
+        public void onAmbientModeChanged(boolean inAmbientMode) {
+            super.onAmbientModeChanged(inAmbientMode);
+            if (mLowBitAmbient) {
+                boolean antiAlias = !inAmbientMode;
+                mHourPaint.setAntiAlias(antiAlias);
+                mMinutePaint.setAntiAlias(antiAlias);
+                mSecondPaint.setAntiAlias(antiAlias);
+                mTickPaint.setAntiAlias(antiAlias);
+            }
+            invalidate();
+        }
+
+        @Override
+        public void onInterruptionFilterChanged(int interruptionFilter) {
+            super.onInterruptionFilterChanged(interruptionFilter);
+            boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
+            if (mMute != inMuteMode) {
+                mMute = inMuteMode;
+                mHourPaint.setAlpha(inMuteMode ? 100 : 255);
+                mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
+                mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
+                invalidate();
+            }
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            mTime.setToNow();
+
+            int width = canvas.getWidth();
+            int height = canvas.getHeight();
+
+            // Draw the background, scaled to fit.
+            if (mBackgroundScaledBitmap == null
+                    || mBackgroundScaledBitmap.getWidth() != width
+                    || mBackgroundScaledBitmap.getHeight() != height) {
+                mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
+                        width, height, true /* filter */);
+            }
+            canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
+
+            // Find the center. Ignore the window insets so that, on round watches with a
+            // "chin", the watch face is centered on the entire screen, not just the usable
+            // portion.
+            float centerX = width / 2f;
+            float centerY = height / 2f;
+
+            // Draw the ticks.
+            float innerTickRadius = centerX - 10;
+            float outerTickRadius = centerX;
+            for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
+                float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
+                float innerX = (float) Math.sin(tickRot) * innerTickRadius;
+                float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
+                float outerX = (float) Math.sin(tickRot) * outerTickRadius;
+                float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
+                canvas.drawLine(centerX + innerX, centerY + innerY,
+                        centerX + outerX, centerY + outerY, mTickPaint);
+            }
+
+            float secRot = mTime.second / 30f * (float) Math.PI;
+            int minutes = mTime.minute;
+            float minRot = minutes / 30f * (float) Math.PI;
+            float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+
+            float secLength = centerX - 20;
+            float minLength = centerX - 40;
+            float hrLength = centerX - 80;
+
+            if (!isInAmbientMode()) {
+                float secX = (float) Math.sin(secRot) * secLength;
+                float secY = (float) -Math.cos(secRot) * secLength;
+                canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
+            }
+
+            float minX = (float) Math.sin(minRot) * minLength;
+            float minY = (float) -Math.cos(minRot) * minLength;
+            canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);
+
+            float hrX = (float) Math.sin(hrRot) * hrLength;
+            float hrY = (float) -Math.cos(hrRot) * hrLength;
+            canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
+        }
+
+        @Override
+        public void onVisibilityChanged(boolean visible) {
+            super.onVisibilityChanged(visible);
+
+            if (visible) {
+                registerReceiver();
+
+                // Update time zone in case it changed while we weren't visible.
+                mTime.clear(TimeZone.getDefault().getID());
+                mTime.setToNow();
+
+                mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
+            } else {
+                mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+                unregisterReceiver();
+            }
+        }
+
+        private void registerReceiver() {
+            if (mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = true;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+            AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+        }
+
+        private void unregisterReceiver() {
+            if (!mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = false;
+            AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java
new file mode 100644
index 0000000..929651d
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CalendarWatchFaceService.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.provider.WearableCalendarContract;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.DynamicLayout;
+import android.text.Editable;
+import android.text.Html;
+import android.text.Layout;
+import android.text.SpannableStringBuilder;
+import android.text.TextPaint;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Proof of concept sample watch face that demonstrates how a watch face can load calendar data.
+ */
+public class CalendarWatchFaceService extends CanvasWatchFaceService {
+    private static final String TAG = "CalendarWatchFace";
+
+    /**
+     * How long to wait after loading meetings before loading them again. Meetings are only loaded
+     * in interactive mode.
+     */
+    private static final long LOAD_MEETINGS_DELAY_MS = TimeUnit.MINUTES.toMillis(1);
+
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+
+    private class Engine extends CanvasWatchFaceService.Engine {
+
+        static final int BACKGROUND_COLOR = Color.BLACK;
+        static final int FOREGROUND_COLOR = Color.WHITE;
+        static final int TEXT_SIZE = 25;
+        static final int MSG_LOAD_MEETINGS = 0;
+
+        /** Editable string containing the text to draw with the number of meetings in bold. */
+        final Editable mEditable = new SpannableStringBuilder();
+
+        /** Width specified when {@link #mLayout} was created. */
+        int mLayoutWidth;
+
+        /** Layout to wrap {@link #mEditable} onto multiple lines. */
+        DynamicLayout mLayout;
+
+        /** Paint used to draw text. */
+        final TextPaint mTextPaint = new TextPaint();
+
+        int mNumMeetings;
+
+        private AsyncTask<Void, Void, Integer> mLoadMeetingsTask;
+
+        /** Handler to load the meetings once a minute in interactive mode. */
+        final Handler mLoadMeetingsHandler = new Handler() {
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_LOAD_MEETINGS:
+                        cancelLoadMeetingTask();
+                        mLoadMeetingsTask = new LoadMeetingsTask();
+                        mLoadMeetingsTask.execute();
+                        break;
+                }
+            }
+        };
+
+        @Override
+        public void onCreate(SurfaceHolder holder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCreate");
+            }
+            super.onCreate(holder);
+            setWatchFaceStyle(new WatchFaceStyle.Builder(CalendarWatchFaceService.this)
+                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+                    .setShowSystemUiTime(false)
+                    .build());
+
+            mTextPaint.setColor(FOREGROUND_COLOR);
+            mTextPaint.setTextSize(TEXT_SIZE);
+        }
+
+        @Override
+        public void onDestroy() {
+            mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
+            cancelLoadMeetingTask();
+            super.onDestroy();
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            // Create or update mLayout if necessary.
+            if (mLayout == null || mLayoutWidth != canvas.getWidth()) {
+                mLayoutWidth = canvas.getWidth();
+                mLayout = new DynamicLayout(mEditable, mTextPaint, mLayoutWidth,
+                        Layout.Alignment.ALIGN_NORMAL, 1 /* spacingMult */, 0 /* spacingAdd */,
+                        false /* includePad */);
+            }
+
+            // Update the contents of mEditable.
+            mEditable.clear();
+            mEditable.append(Html.fromHtml(getResources().getQuantityString(
+                    R.plurals.calendar_meetings, mNumMeetings, mNumMeetings)));
+
+            // Draw the text on a solid background.
+            canvas.drawColor(BACKGROUND_COLOR);
+            mLayout.draw(canvas);
+        }
+
+        @Override
+        public void onVisibilityChanged(boolean visible) {
+            super.onVisibilityChanged(visible);
+            if (visible) {
+                mLoadMeetingsHandler.sendEmptyMessage(MSG_LOAD_MEETINGS);
+            } else {
+                mLoadMeetingsHandler.removeMessages(MSG_LOAD_MEETINGS);
+                cancelLoadMeetingTask();
+            }
+        }
+
+        private void onMeetingsLoaded(Integer result) {
+            if (result != null) {
+                mNumMeetings = result;
+                invalidate();
+            }
+            if (isVisible()) {
+                mLoadMeetingsHandler.sendEmptyMessageDelayed(
+                        MSG_LOAD_MEETINGS, LOAD_MEETINGS_DELAY_MS);
+            }
+        }
+
+        private void cancelLoadMeetingTask() {
+            if (mLoadMeetingsTask != null) {
+                mLoadMeetingsTask.cancel(true);
+            }
+        }
+
+        /**
+         * Asynchronous task to load the meetings from the content provider and report the number of
+         * meetings back via {@link #onMeetingsLoaded}.
+         */
+        private class LoadMeetingsTask extends AsyncTask<Void, Void, Integer> {
+            @Override
+            protected Integer doInBackground(Void... voids) {
+                long begin = System.currentTimeMillis();
+                Uri.Builder builder =
+                        WearableCalendarContract.Instances.CONTENT_URI.buildUpon();
+                ContentUris.appendId(builder, begin);
+                ContentUris.appendId(builder, begin + DateUtils.DAY_IN_MILLIS);
+                final Cursor cursor = getContentResolver() .query(builder.build(),
+                        null, null, null, null);
+                int numMeetings = cursor.getCount();
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Num meetings: " + numMeetings);
+                }
+                return numMeetings;
+            }
+
+            @Override
+            protected void onPostExecute(Integer result) {
+                onMeetingsLoaded(result);
+            }
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CardBoundsWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CardBoundsWatchFaceService.java
new file mode 100644
index 0000000..c0d4437
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/CardBoundsWatchFaceService.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+/**
+ * Proof of concept sample watch face that demonstrates how a watch face can detect where the peek
+ * card is. This watch face draws a border around the area where the peeking card is.
+ */
+public class CardBoundsWatchFaceService extends CanvasWatchFaceService {
+
+    private static final String TAG = "CardBoundsWatchFace";
+
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+
+    private class Engine extends CanvasWatchFaceService.Engine {
+
+        static final int BORDER_WIDTH_PX = 5;
+
+        final Rect mCardBounds = new Rect();
+        final Paint mPaint = new Paint();
+
+        @Override
+        public void onCreate(SurfaceHolder holder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCreate");
+            }
+            super.onCreate(holder);
+            setWatchFaceStyle(new WatchFaceStyle.Builder(CardBoundsWatchFaceService.this)
+                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+                    .setShowSystemUiTime(true)
+                    .setPeekOpacityMode(WatchFaceStyle.PEEK_OPACITY_MODE_TRANSLUCENT)
+                    .build());
+        }
+
+        @Override
+        public void onAmbientModeChanged(boolean inAmbientMode) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+            }
+            super.onAmbientModeChanged(inAmbientMode);
+            invalidate();
+        }
+
+        @Override
+        public void onPeekCardPositionUpdate(Rect bounds) {
+            super.onPeekCardPositionUpdate(bounds);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onPeekCardPositionUpdate: " + bounds);
+            }
+            super.onPeekCardPositionUpdate(bounds);
+            if (!bounds.equals(mCardBounds)) {
+                mCardBounds.set(bounds);
+                invalidate();
+            }
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            // Clear screen.
+            canvas.drawColor(isInAmbientMode() ? Color.BLACK : Color.BLUE);
+
+            // Draw border around card in interactive mode.
+            if (!isInAmbientMode()) {
+                mPaint.setColor(Color.MAGENTA);
+                canvas.drawRect(mCardBounds.left - BORDER_WIDTH_PX,
+                        mCardBounds.top - BORDER_WIDTH_PX,
+                        mCardBounds.right + BORDER_WIDTH_PX,
+                        mCardBounds.bottom + BORDER_WIDTH_PX, mPaint);
+            }
+
+            // Fill area under card.
+            mPaint.setColor(isInAmbientMode() ? Color.RED : Color.GREEN);
+            canvas.drawRect(mCardBounds, mPaint);
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceConfigListenerService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceConfigListenerService.java
new file mode 100644
index 0000000..725c51a
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceConfigListenerService.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.MessageEvent;
+import com.google.android.gms.wearable.Wearable;
+import com.google.android.gms.wearable.WearableListenerService;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link WearableListenerService} listening for {@link DigitalWatchFaceService} config messages
+ * and updating the config {@link com.google.android.gms.wearable.DataItem} accordingly.
+ */
+public class DigitalWatchFaceConfigListenerService extends WearableListenerService
+        implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
+    private static final String TAG = "DigitalListenerService";
+
+    private GoogleApiClient mGoogleApiClient;
+
+    @Override // WearableListenerService
+    public void onMessageReceived(MessageEvent messageEvent) {
+        if (!messageEvent.getPath().equals(DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
+            return;
+        }
+        byte[] rawData = messageEvent.getData();
+        // It's allowed that the message carries only some of the keys used in the config DataItem
+        // and skips the ones that we don't want to change.
+        DataMap configKeysToOverwrite = DataMap.fromByteArray(rawData);
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Received watch face config message: " + configKeysToOverwrite);
+        }
+
+        if (mGoogleApiClient == null) {
+            mGoogleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this)
+                    .addOnConnectionFailedListener(this).addApi(Wearable.API).build();
+        }
+        if (!mGoogleApiClient.isConnected()) {
+            ConnectionResult connectionResult =
+                    mGoogleApiClient.blockingConnect(30, TimeUnit.SECONDS);
+
+            if (!connectionResult.isSuccess()) {
+                Log.e(TAG, "Failed to connect to GoogleApiClient.");
+                return;
+            }
+        }
+
+        DigitalWatchFaceUtil.overwriteKeysInConfigDataMap(mGoogleApiClient, configKeysToOverwrite);
+    }
+
+    @Override // GoogleApiClient.ConnectionCallbacks
+    public void onConnected(Bundle connectionHint) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onConnected: " + connectionHint);
+        }
+    }
+
+    @Override  // GoogleApiClient.ConnectionCallbacks
+    public void onConnectionSuspended(int cause) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onConnectionSuspended: " + cause);
+        }
+    }
+
+    @Override  // GoogleApiClient.OnConnectionFailedListener
+    public void onConnectionFailed(ConnectionResult result) {
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "onConnectionFailed: " + result);
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceService.java
new file mode 100644
index 0000000..7620d1a
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceService.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.WindowInsets;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataEvent;
+import com.google.android.gms.wearable.DataEventBuffer;
+import com.google.android.gms.wearable.DataItem;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.Wearable;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sample digital watch face with blinking colons and seconds. In ambient mode, the seconds are
+ * replaced with an AM/PM indicator and the colons don't blink. On devices with low-bit ambient
+ * mode, the text is drawn without anti-aliasing in ambient mode. On devices which require burn-in
+ * protection, the hours are drawn in normal rather than bold. The time is drawn with less contrast
+ * and without seconds in mute mode.
+ */
+public class DigitalWatchFaceService extends CanvasWatchFaceService {
+    private static final String TAG = "DigitalWatchFaceService";
+
+    private static final Typeface BOLD_TYPEFACE =
+            Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
+    private static final Typeface NORMAL_TYPEFACE =
+            Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
+
+    /**
+     * Update rate in milliseconds for normal (not ambient and not mute) mode. We update twice
+     * a second to blink the colons.
+     */
+    private static final long NORMAL_UPDATE_RATE_MS = 500;
+
+    /**
+     * Update rate in milliseconds for mute mode. We update every minute, like in ambient mode.
+     */
+    private static final long MUTE_UPDATE_RATE_MS = TimeUnit.MINUTES.toMillis(1);
+
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+
+    private class Engine extends CanvasWatchFaceService.Engine implements DataApi.DataListener,
+            GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
+        static final String COLON_STRING = ":";
+
+        /** Alpha value for drawing time when in mute mode. */
+        static final int MUTE_ALPHA = 100;
+
+        /** Alpha value for drawing time when not in mute mode. */
+        static final int NORMAL_ALPHA = 255;
+
+        static final int MSG_UPDATE_TIME = 0;
+
+        /** How often {@link #mUpdateTimeHandler} ticks in milliseconds. */
+        long mInteractiveUpdateRateMs = NORMAL_UPDATE_RATE_MS;
+
+        /** Handler to update the time periodically in interactive mode. */
+        final Handler mUpdateTimeHandler = new Handler() {
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_UPDATE_TIME:
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "updating time");
+                        }
+                        invalidate();
+                        if (isVisible()) {
+                            long timeMs = System.currentTimeMillis();
+                            long delayMs = mInteractiveUpdateRateMs
+                                    - (timeMs % mInteractiveUpdateRateMs);
+                            mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
+                        }
+                        break;
+                }
+            }
+        };
+
+        GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(DigitalWatchFaceService.this)
+                .addConnectionCallbacks(this)
+                .addOnConnectionFailedListener(this)
+                .addApi(Wearable.API)
+                .build();
+
+        final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mTime.clear(intent.getStringExtra("time-zone"));
+                mTime.setToNow();
+            }
+        };
+        boolean mRegisteredTimeZoneReceiver = false;
+
+        Paint mBackgroundPaint;
+        Paint mHourPaint;
+        Paint mMinutePaint;
+        Paint mSecondPaint;
+        Paint mAmPmPaint;
+        Paint mColonPaint;
+        float mColonWidth;
+        boolean mMute;
+        Time mTime;
+        boolean mShouldDrawColons;
+        float mXOffset;
+        float mYOffset;
+        String mAmString;
+        String mPmString;
+        int mInteractiveBackgroundColor =
+                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND;
+        int mInteractiveHourDigitsColor =
+                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS;
+        int mInteractiveMinuteDigitsColor =
+                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS;
+        int mInteractiveSecondDigitsColor =
+                DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS;
+
+        /**
+         * Whether the display supports fewer bits for each color in ambient mode. When true, we
+         * disable anti-aliasing in ambient mode.
+         */
+        boolean mLowBitAmbient;
+
+        @Override
+        public void onCreate(SurfaceHolder holder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCreate");
+            }
+            super.onCreate(holder);
+
+            setWatchFaceStyle(new WatchFaceStyle.Builder(DigitalWatchFaceService.this)
+                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
+                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+                    .setShowSystemUiTime(false)
+                    .build());
+            Resources resources = DigitalWatchFaceService.this.getResources();
+            mYOffset = resources.getDimension(R.dimen.digital_y_offset);
+            mAmString = resources.getString(R.string.digital_am);
+            mPmString = resources.getString(R.string.digital_pm);
+
+            mBackgroundPaint = new Paint();
+            mBackgroundPaint.setColor(mInteractiveBackgroundColor);
+            mHourPaint = createTextPaint(mInteractiveHourDigitsColor, BOLD_TYPEFACE);
+            mMinutePaint = createTextPaint(mInteractiveMinuteDigitsColor);
+            mSecondPaint = createTextPaint(mInteractiveSecondDigitsColor);
+            mAmPmPaint = createTextPaint(resources.getColor(R.color.digital_am_pm));
+            mColonPaint = createTextPaint(resources.getColor(R.color.digital_colons));
+
+            mTime = new Time();
+        }
+
+        @Override
+        public void onDestroy() {
+            mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+            super.onDestroy();
+        }
+
+        private Paint createTextPaint(int defaultInteractiveColor) {
+            return createTextPaint(defaultInteractiveColor, NORMAL_TYPEFACE);
+        }
+
+        private Paint createTextPaint(int defaultInteractiveColor, Typeface typeface) {
+            Paint paint = new Paint();
+            paint.setColor(defaultInteractiveColor);
+            paint.setTypeface(typeface);
+            paint.setAntiAlias(true);
+            return paint;
+        }
+
+        @Override
+        public void onVisibilityChanged(boolean visible) {
+            super.onVisibilityChanged(visible);
+
+            if (visible) {
+                mGoogleApiClient.connect();
+
+                registerReceiver();
+
+                // Update time zone in case it changed while we weren't visible.
+                mTime.clear(TimeZone.getDefault().getID());
+                mTime.setToNow();
+
+                mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
+            } else {
+                mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+
+                unregisterReceiver();
+
+                if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+                    Wearable.DataApi.removeListener(mGoogleApiClient, this);
+                    mGoogleApiClient.disconnect();
+                }
+            }
+        }
+
+        private void registerReceiver() {
+            if (mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = true;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+            DigitalWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+        }
+
+        private void unregisterReceiver() {
+            if (!mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = false;
+            DigitalWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+        }
+
+        @Override
+        public void onApplyWindowInsets(WindowInsets insets) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onApplyWindowInsets: " + (insets.isRound() ? "round" : "square"));
+            }
+            super.onApplyWindowInsets(insets);
+
+            // Load resources that have alternate values for round watches.
+            Resources resources = DigitalWatchFaceService.this.getResources();
+            boolean isRound = insets.isRound();
+            mXOffset = resources.getDimension(isRound
+                    ? R.dimen.digital_x_offset_round : R.dimen.digital_x_offset);
+            float textSize = resources.getDimension(isRound
+                    ? R.dimen.digital_text_size_round : R.dimen.digital_text_size);
+            float amPmSize = resources.getDimension(isRound
+                    ? R.dimen.digital_am_pm_size_round : R.dimen.digital_am_pm_size);
+
+            mHourPaint.setTextSize(textSize);
+            mMinutePaint.setTextSize(textSize);
+            mSecondPaint.setTextSize(textSize);
+            mAmPmPaint.setTextSize(amPmSize);
+            mColonPaint.setTextSize(textSize);
+
+            mColonWidth = mColonPaint.measureText(COLON_STRING);
+        }
+
+        @Override
+        public void onPropertiesChanged(Bundle properties) {
+            super.onPropertiesChanged(properties);
+
+            boolean burnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false);
+            mHourPaint.setTypeface(burnInProtection ? NORMAL_TYPEFACE : BOLD_TYPEFACE);
+
+            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onPropertiesChanged: burn-in protection = " + burnInProtection
+                        + ", low-bit ambient = " + mLowBitAmbient);
+            }
+        }
+
+        @Override
+        public void onTimeTick() {
+            super.onTimeTick();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+            }
+            invalidate();
+        }
+
+        @Override
+        public void onAmbientModeChanged(boolean inAmbientMode) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+            }
+            super.onAmbientModeChanged(inAmbientMode);
+            adjustPaintColorToCurrentMode(mBackgroundPaint, mInteractiveBackgroundColor,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND);
+            adjustPaintColorToCurrentMode(mHourPaint, mInteractiveHourDigitsColor,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
+            adjustPaintColorToCurrentMode(mMinutePaint, mInteractiveMinuteDigitsColor,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
+            // Actually, the seconds are not rendered in the ambient mode, so we could pass just
+            // any value as ambientColor here.
+            adjustPaintColorToCurrentMode(mSecondPaint, mInteractiveSecondDigitsColor,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
+
+            if (mLowBitAmbient) {
+                boolean antiAlias = !inAmbientMode;
+                mHourPaint.setAntiAlias(antiAlias);
+                mMinutePaint.setAntiAlias(antiAlias);
+                mSecondPaint.setAntiAlias(antiAlias);
+                mAmPmPaint.setAntiAlias(antiAlias);
+                mColonPaint.setAntiAlias(antiAlias);
+            }
+            invalidate();
+        }
+
+        private void adjustPaintColorToCurrentMode(Paint paint, int interactiveColor,
+                int ambientColor) {
+            paint.setColor(isInAmbientMode() ? ambientColor : interactiveColor);
+        }
+
+        @Override
+        public void onInterruptionFilterChanged(int interruptionFilter) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onInterruptionFilterChanged: " + interruptionFilter);
+            }
+            super.onInterruptionFilterChanged(interruptionFilter);
+
+            boolean inMuteMode = interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE;
+            // We only need to update once a minute in mute mode.
+            setInteractiveUpdateRateMs(inMuteMode ? MUTE_UPDATE_RATE_MS : NORMAL_UPDATE_RATE_MS);
+
+            if (mMute != inMuteMode) {
+                mMute = inMuteMode;
+                int alpha = inMuteMode ? MUTE_ALPHA : NORMAL_ALPHA;
+                mHourPaint.setAlpha(alpha);
+                mMinutePaint.setAlpha(alpha);
+                mColonPaint.setAlpha(alpha);
+                mAmPmPaint.setAlpha(alpha);
+                invalidate();
+            }
+        }
+
+        public void setInteractiveUpdateRateMs(long updateRateMs) {
+            if (updateRateMs == mInteractiveUpdateRateMs) {
+                return;
+            }
+            mInteractiveUpdateRateMs = updateRateMs;
+            if (isVisible()) {
+                mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
+                mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
+            }
+        }
+
+        private void updatePaintIfInteractive(Paint paint, int interactiveColor) {
+            if (!isInAmbientMode() && paint != null) {
+                paint.setColor(interactiveColor);
+            }
+        }
+
+        private void setInteractiveBackgroundColor(int color) {
+            mInteractiveBackgroundColor = color;
+            updatePaintIfInteractive(mBackgroundPaint, color);
+        }
+
+        private void setInteractiveHourDigitsColor(int color) {
+            mInteractiveHourDigitsColor = color;
+            updatePaintIfInteractive(mHourPaint, color);
+        }
+
+        private void setInteractiveMinuteDigitsColor(int color) {
+            mInteractiveMinuteDigitsColor = color;
+            updatePaintIfInteractive(mMinutePaint, color);
+        }
+
+        private void setInteractiveSecondDigitsColor(int color) {
+            mInteractiveSecondDigitsColor = color;
+            updatePaintIfInteractive(mSecondPaint, color);
+        }
+
+        private String formatTwoDigitNumber(int hour) {
+            return String.format("%02d", hour);
+        }
+
+        private int convertTo12Hour(int hour) {
+            int result = hour % 12;
+            return (result == 0) ? 12 : result;
+        }
+
+        private String getAmPmString(int hour) {
+            return (hour < 12) ? mAmString : mPmString;
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            mTime.setToNow();
+
+            // Show colons for the first half of each second so the colons blink on when the time
+            // updates.
+            mShouldDrawColons = (System.currentTimeMillis() % 1000) < 500;
+
+            // Draw the background.
+            canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), mBackgroundPaint);
+
+            // Draw the hours.
+            float x = mXOffset;
+            String hourString = String.valueOf(convertTo12Hour(mTime.hour));
+            canvas.drawText(hourString, x, mYOffset, mHourPaint);
+            x += mHourPaint.measureText(hourString);
+
+            // In ambient and mute modes, always draw the first colon. Otherwise, draw the
+            // first colon for the first half of each second.
+            if (isInAmbientMode() || mMute || mShouldDrawColons) {
+                canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+            }
+            x += mColonWidth;
+
+            // Draw the minutes.
+            String minuteString = formatTwoDigitNumber(mTime.minute);
+            canvas.drawText(minuteString, x, mYOffset, mMinutePaint);
+            x += mMinutePaint.measureText(minuteString);
+
+            // In ambient and mute modes, draw AM/PM. Otherwise, draw a second blinking
+            // colon followed by the seconds.
+            if (isInAmbientMode() || mMute) {
+                x += mColonWidth;
+                canvas.drawText(getAmPmString(mTime.hour), x, mYOffset, mAmPmPaint);
+            } else {
+                if (mShouldDrawColons) {
+                    canvas.drawText(COLON_STRING, x, mYOffset, mColonPaint);
+                }
+                x += mColonWidth;
+                canvas.drawText(formatTwoDigitNumber(mTime.second), x, mYOffset,
+                        mSecondPaint);
+            }
+        }
+
+        private void updateConfigDataItemAndUiOnStartup() {
+            DigitalWatchFaceUtil.fetchConfigDataMap(mGoogleApiClient,
+                    new DigitalWatchFaceUtil.FetchConfigDataMapCallback() {
+                        @Override
+                        public void onConfigDataMapFetched(DataMap startupConfig) {
+                            // If the DataItem hasn't been created yet or some keys are missing,
+                            // use the default values.
+                            setDefaultValuesForMissingConfigKeys(startupConfig);
+                            DigitalWatchFaceUtil.putConfigDataItem(mGoogleApiClient, startupConfig);
+
+                            updateUiForConfigDataMap(startupConfig);
+                        }
+                    }
+            );
+        }
+
+        private void setDefaultValuesForMissingConfigKeys(DataMap config) {
+            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND);
+            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_HOURS_COLOR,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
+            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_MINUTES_COLOR,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
+            addIntKeyIfMissing(config, DigitalWatchFaceUtil.KEY_SECONDS_COLOR,
+                    DigitalWatchFaceUtil.COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
+        }
+
+        private void addIntKeyIfMissing(DataMap config, String key, int color) {
+            if (!config.containsKey(key)) {
+                config.putInt(key, color);
+            }
+        }
+
+        @Override // DataApi.DataListener
+        public void onDataChanged(DataEventBuffer dataEvents) {
+            try {
+                for (DataEvent dataEvent : dataEvents) {
+                    if (dataEvent.getType() != DataEvent.TYPE_CHANGED) {
+                        continue;
+                    }
+
+                    DataItem dataItem = dataEvent.getDataItem();
+                    if (!dataItem.getUri().getPath().equals(
+                            DigitalWatchFaceUtil.PATH_WITH_FEATURE)) {
+                        continue;
+                    }
+
+                    DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItem);
+                    DataMap config = dataMapItem.getDataMap();
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Config DataItem updated:" + config);
+                    }
+                    updateUiForConfigDataMap(config);
+                }
+            } finally {
+                dataEvents.close();
+            }
+        }
+
+        private void updateUiForConfigDataMap(final DataMap config) {
+            boolean uiUpdated = false;
+            for (String configKey : config.keySet()) {
+                if (!config.containsKey(configKey)) {
+                    continue;
+                }
+                int color = config.getInt(configKey);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Found watch face config key: " + configKey + " -> "
+                            + Integer.toHexString(color));
+                }
+                if (updateUiForKey(configKey, color)) {
+                    uiUpdated = true;
+                }
+            }
+            if (uiUpdated) {
+                invalidate();
+            }
+        }
+
+        /**
+         * Updates the color of a UI item according to the given {@code configKey}. Does nothing if
+         * {@code configKey} isn't recognized.
+         *
+         * @return whether UI has been updated
+         */
+        private boolean updateUiForKey(String configKey, int color) {
+            if (configKey.equals(DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR)) {
+                setInteractiveBackgroundColor(color);
+            } else if (configKey.equals(DigitalWatchFaceUtil.KEY_HOURS_COLOR)) {
+                setInteractiveHourDigitsColor(color);
+            } else if (configKey.equals(DigitalWatchFaceUtil.KEY_MINUTES_COLOR)) {
+                setInteractiveMinuteDigitsColor(color);
+            } else if (configKey.equals(DigitalWatchFaceUtil.KEY_SECONDS_COLOR)) {
+                setInteractiveSecondDigitsColor(color);
+            } else {
+                Log.w(TAG, "Ignoring unknown config key: " + configKey);
+                return false;
+            }
+            return true;
+        }
+
+        @Override  // GoogleApiClient.ConnectionCallbacks
+        public void onConnected(Bundle connectionHint) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onConnected: " + connectionHint);
+            }
+            Wearable.DataApi.addListener(mGoogleApiClient, Engine.this);
+            updateConfigDataItemAndUiOnStartup();
+        }
+
+        @Override  // GoogleApiClient.ConnectionCallbacks
+        public void onConnectionSuspended(int cause) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onConnectionSuspended: " + cause);
+            }
+        }
+
+        @Override  // GoogleApiClient.OnConnectionFailedListener
+        public void onConnectionFailed(ConnectionResult result) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onConnectionFailed: " + result);
+            }
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceUtil.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceUtil.java
new file mode 100644
index 0000000..1c4af70
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceUtil.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.graphics.Color;
+import android.net.Uri;
+import android.util.Log;
+
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.ResultCallback;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataItem;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.NodeApi;
+import com.google.android.gms.wearable.PutDataMapRequest;
+import com.google.android.gms.wearable.Wearable;
+
+public final class DigitalWatchFaceUtil {
+    private static final String TAG = "DigitalWatchFaceUtil";
+
+    /**
+     * The {@link DataMap} key for {@link DigitalWatchFaceService} background color name.
+     * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+     */
+    public static final String KEY_BACKGROUND_COLOR = "BACKGROUND_COLOR";
+
+    /**
+     * The {@link DataMap} key for {@link DigitalWatchFaceService} hour digits color name.
+     * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+     */
+    public static final String KEY_HOURS_COLOR = "HOURS_COLOR";
+
+    /**
+     * The {@link DataMap} key for {@link DigitalWatchFaceService} minute digits color name.
+     * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+     */
+    public static final String KEY_MINUTES_COLOR = "MINUTES_COLOR";
+
+    /**
+     * The {@link DataMap} key for {@link DigitalWatchFaceService} second digits color name.
+     * The color name must be a {@link String} recognized by {@link Color#parseColor}.
+     */
+    public static final String KEY_SECONDS_COLOR = "SECONDS_COLOR";
+
+    /**
+     * The path for the {@link DataItem} containing {@link DigitalWatchFaceService} configuration.
+     */
+    public static final String PATH_WITH_FEATURE = "/watch_face_config/Digital";
+
+    /**
+     * Name of the default interactive mode background color and the ambient mode background color.
+     */
+    public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_BACKGROUND = "Black";
+    public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_BACKGROUND =
+            parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_BACKGROUND);
+
+    /**
+     * Name of the default interactive mode hour digits color and the ambient mode hour digits
+     * color.
+     */
+    public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_HOUR_DIGITS = "White";
+    public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_HOUR_DIGITS =
+            parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_HOUR_DIGITS);
+
+    /**
+     * Name of the default interactive mode minute digits color and the ambient mode minute digits
+     * color.
+     */
+    public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_MINUTE_DIGITS = "White";
+    public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_MINUTE_DIGITS =
+            parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_MINUTE_DIGITS);
+
+    /**
+     * Name of the default interactive mode second digits color and the ambient mode second digits
+     * color.
+     */
+    public static final String COLOR_NAME_DEFAULT_AND_AMBIENT_SECOND_DIGITS = "Gray";
+    public static final int COLOR_VALUE_DEFAULT_AND_AMBIENT_SECOND_DIGITS =
+            parseColor(COLOR_NAME_DEFAULT_AND_AMBIENT_SECOND_DIGITS);
+
+    /**
+     * Callback interface to perform an action with the current config {@link DataMap} for
+     * {@link DigitalWatchFaceService}.
+     */
+    public interface FetchConfigDataMapCallback {
+        /**
+         * Callback invoked with the current config {@link DataMap} for
+         * {@link DigitalWatchFaceService}.
+         */
+        void onConfigDataMapFetched(DataMap config);
+    }
+
+    private static int parseColor(String colorName) {
+        return Color.parseColor(colorName.toLowerCase());
+    }
+
+    /**
+     * Asynchronously fetches the current config {@link DataMap} for {@link DigitalWatchFaceService}
+     * and passes it to the given callback.
+     * <p>
+     * If the current config {@link DataItem} doesn't exist, it isn't created and the callback
+     * receives an empty DataMap.
+     */
+    public static void fetchConfigDataMap(final GoogleApiClient client,
+            final FetchConfigDataMapCallback callback) {
+        Wearable.NodeApi.getLocalNode(client).setResultCallback(
+                new ResultCallback<NodeApi.GetLocalNodeResult>() {
+                    @Override
+                    public void onResult(NodeApi.GetLocalNodeResult getLocalNodeResult) {
+                        String localNode = getLocalNodeResult.getNode().getId();
+                        Uri uri = new Uri.Builder()
+                                .scheme("wear")
+                                .path(DigitalWatchFaceUtil.PATH_WITH_FEATURE)
+                                .authority(localNode)
+                                .build();
+                        Wearable.DataApi.getDataItem(client, uri)
+                                .setResultCallback(new DataItemResultCallback(callback));
+                    }
+                }
+        );
+    }
+
+    /**
+     * Overwrites (or sets, if not present) the keys in the current config {@link DataItem} with
+     * the ones appearing in the given {@link DataMap}. If the config DataItem doesn't exist,
+     * it's created.
+     * <p>
+     * It is allowed that only some of the keys used in the config DataItem appear in
+     * {@code configKeysToOverwrite}. The rest of the keys remains unmodified in this case.
+     */
+    public static void overwriteKeysInConfigDataMap(final GoogleApiClient googleApiClient,
+            final DataMap configKeysToOverwrite) {
+
+        DigitalWatchFaceUtil.fetchConfigDataMap(googleApiClient,
+                new FetchConfigDataMapCallback() {
+                    @Override
+                    public void onConfigDataMapFetched(DataMap currentConfig) {
+                        DataMap overwrittenConfig = new DataMap();
+                        overwrittenConfig.putAll(currentConfig);
+                        overwrittenConfig.putAll(configKeysToOverwrite);
+                        DigitalWatchFaceUtil.putConfigDataItem(googleApiClient, overwrittenConfig);
+                    }
+                }
+        );
+    }
+
+    /**
+     * Overwrites the current config {@link DataItem}'s {@link DataMap} with {@code newConfig}.
+     * If the config DataItem doesn't exist, it's created.
+     */
+    public static void putConfigDataItem(GoogleApiClient googleApiClient, DataMap newConfig) {
+        PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_WITH_FEATURE);
+        DataMap configToPut = putDataMapRequest.getDataMap();
+        configToPut.putAll(newConfig);
+        Wearable.DataApi.putDataItem(googleApiClient, putDataMapRequest.asPutDataRequest())
+                .setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
+                    @Override
+                    public void onResult(DataApi.DataItemResult dataItemResult) {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "putDataItem result status: " + dataItemResult.getStatus());
+                        }
+                    }
+                });
+    }
+
+    private static class DataItemResultCallback implements ResultCallback<DataApi.DataItemResult> {
+
+        private final FetchConfigDataMapCallback mCallback;
+
+        public DataItemResultCallback(FetchConfigDataMapCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onResult(DataApi.DataItemResult dataItemResult) {
+            if (dataItemResult.getStatus().isSuccess()) {
+                if (dataItemResult.getDataItem() != null) {
+                    DataItem configDataItem = dataItemResult.getDataItem();
+                    DataMapItem dataMapItem = DataMapItem.fromDataItem(configDataItem);
+                    DataMap config = dataMapItem.getDataMap();
+                    mCallback.onConfigDataMapFetched(config);
+                } else {
+                    mCallback.onConfigDataMapFetched(new DataMap());
+                }
+            }
+        }
+    }
+
+    private DigitalWatchFaceUtil() { }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceWearableConfigActivity.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceWearableConfigActivity.java
new file mode 100644
index 0000000..a2e5251
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/DigitalWatchFaceWearableConfigActivity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.wearable.view.WearableListView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.Wearable;
+
+/**
+ * The watch-side config activity for {@link DigitalWatchFaceService}, which allows for setting the
+ * background color.
+ */
+public class DigitalWatchFaceWearableConfigActivity extends Activity
+        implements WearableListView.ClickListener {
+    private static final String TAG = "DigitalWatchFaceConfig";
+
+    private GoogleApiClient mGoogleApiClient;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_digital_config);
+
+        WearableListView listView = (WearableListView) findViewById(R.id.color_picker);
+
+        listView.setHasFixedSize(true);
+        listView.setClickListener(this);
+
+        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
+        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
+        listView.setLayoutManager(layoutManager);
+
+        String[] colors = getResources().getStringArray(R.array.color_array);
+        listView.setAdapter(new ColorListAdapter(colors));
+
+        mGoogleApiClient = new GoogleApiClient.Builder(this)
+                .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
+                    @Override
+                    public void onConnected(Bundle connectionHint) {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "onConnected: " + connectionHint);
+                        }
+                    }
+
+                    @Override
+                    public void onConnectionSuspended(int cause) {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "onConnectionSuspended: " + cause);
+                        }
+                    }
+                })
+                .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
+                    @Override
+                    public void onConnectionFailed(ConnectionResult result) {
+                        if (Log.isLoggable(TAG, Log.DEBUG)) {
+                            Log.d(TAG, "onConnectionFailed: " + result);
+                        }
+                    }
+                })
+                .addApi(Wearable.API)
+                .build();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mGoogleApiClient.connect();
+    }
+
+    @Override
+    protected void onStop() {
+        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
+            mGoogleApiClient.disconnect();
+        }
+        super.onStop();
+    }
+
+    @Override // WearableListView.ClickListener
+    public void onClick(WearableListView.ViewHolder viewHolder) {
+        ColorItemViewHolder colorItemViewHolder = (ColorItemViewHolder) viewHolder;
+        String colorName = colorItemViewHolder.mLabel.getText().toString();
+        updateConfigDataItem(Color.parseColor(colorName));
+        finish();
+    }
+
+    private void updateConfigDataItem(final int backgroundColor) {
+        DataMap configKeysToOverwrite = new DataMap();
+        configKeysToOverwrite.putInt(DigitalWatchFaceUtil.KEY_BACKGROUND_COLOR,
+                backgroundColor);
+        DigitalWatchFaceUtil.overwriteKeysInConfigDataMap(mGoogleApiClient, configKeysToOverwrite);
+    }
+
+    @Override // WearableListView.ClickListener
+    public void onTopEmptyRegionClick() { }
+
+    private class ColorListAdapter extends RecyclerView.Adapter<ColorItemViewHolder> {
+
+        private final String[] mColors;
+
+        public ColorListAdapter(String[] colors) {
+            mColors = colors;
+        }
+
+        @Override
+        public ColorItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            View root = LayoutInflater.from(parent.getContext())
+                    .inflate(R.layout.color_picker_item, parent, false /* attachToRoot */);
+            TextView label = (TextView) root.findViewById(R.id.label);
+            return new ColorItemViewHolder(root, label);
+        }
+
+        @Override
+        public void onBindViewHolder(ColorItemViewHolder holder, int position) {
+            String colorName = mColors[position];
+            holder.mLabel.setText(colorName);
+
+            RecyclerView.LayoutParams layoutParams =
+                    new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.WRAP_CONTENT);
+            int colorPickerItemMargin = (int) getResources()
+                    .getDimension(R.dimen.digital_config_color_picker_item_margin);
+            // Add margins to first and last item to make it possible for user to tap on them.
+            if (position == 0) {
+                layoutParams.setMargins(0, colorPickerItemMargin, 0, 0);
+            } else if (position == mColors.length - 1) {
+                layoutParams.setMargins(0, 0, 0, colorPickerItemMargin);
+            } else {
+                layoutParams.setMargins(0, 0, 0, 0);
+            }
+            holder.itemView.setLayoutParams(layoutParams);
+        }
+
+        @Override
+        public int getItemCount() {
+            return mColors.length;
+        }
+    }
+
+    private static class ColorItemViewHolder extends WearableListView.ViewHolder {
+        private final TextView mLabel;
+
+        public ColorItemViewHolder(View root, TextView label) {
+            super(root);
+            this.mLabel = label;
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/Gles2ColoredTriangleList.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/Gles2ColoredTriangleList.java
new file mode 100644
index 0000000..2441c65
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/Gles2ColoredTriangleList.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import android.opengl.GLES20;
+import android.opengl.GLU;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+/**
+ * A list of triangles drawn in a single solid color using OpenGL ES 2.0.
+ */
+public class Gles2ColoredTriangleList {
+    private static final String TAG = "GlColoredTriangleList";
+
+    /** Whether to check for GL errors. This is slow, so not appropriate for production builds. */
+    private static final boolean CHECK_GL_ERRORS = false;
+
+    /** Number of coordinates per vertex in this array: one for each of x, y, and z. */
+    private static final int COORDS_PER_VERTEX = 3;
+
+    /** Number of bytes to store a float in GL. */
+    public static final int BYTES_PER_FLOAT = 4;
+
+    /** Number of bytes per vertex. */
+    private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * BYTES_PER_FLOAT;
+
+    /** Triangles have three vertices. */
+    private static final int VERTICE_PER_TRIANGLE = 3;
+
+    /**
+     * Number of components in an OpenGL color. The components are:<ol>
+     * <li>red
+     * <li>green
+     * <li>blue
+     * <li>alpha
+     * </ol>
+     */
+    private static final int NUM_COLOR_COMPONENTS = 4;
+
+    /** Shaders to render this triangle list. */
+    private final Program mProgram;
+
+    /** The VBO containing the vertex coordinates. */
+    private final FloatBuffer mVertexBuffer;
+
+    /**
+     * Color of this triangle list represented as an array of floats in the range [0, 1] in RGBA
+     * order.
+     */
+    private final float mColor[];
+
+    /** Number of coordinates in this triangle list. */
+    private final int mNumCoords;
+
+    /**
+     * Creates a Gles2ColoredTriangleList to draw a triangle list with the given vertices and color.
+     *
+     * @param program program for drawing triangles
+     * @param triangleCoords flat array of 3D coordinates of triangle vertices in counterclockwise
+     *                       order
+     * @param color color in RGBA order, each in the range [0, 1]
+     */
+    public Gles2ColoredTriangleList(Program program, float[] triangleCoords, float[] color) {
+        if (triangleCoords.length % (VERTICE_PER_TRIANGLE * COORDS_PER_VERTEX) != 0) {
+            throw new IllegalArgumentException("must be multiple"
+                    + " of VERTICE_PER_TRIANGLE * COORDS_PER_VERTEX coordinates");
+        }
+        if (color.length != NUM_COLOR_COMPONENTS) {
+            throw new IllegalArgumentException("wrong number of color components");
+        }
+        mProgram = program;
+        mColor = color;
+
+        ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT);
+
+        // Use the device hardware's native byte order.
+        bb.order(ByteOrder.nativeOrder());
+
+        // Create a FloatBuffer that wraps the ByteBuffer.
+        mVertexBuffer = bb.asFloatBuffer();
+
+        // Add the coordinates to the FloatBuffer.
+        mVertexBuffer.put(triangleCoords);
+
+        // Go back to the start for reading.
+        mVertexBuffer.position(0);
+
+        mNumCoords = triangleCoords.length / COORDS_PER_VERTEX;
+    }
+
+    /**
+     * Draws this triangle list using OpenGL commands.
+     *
+     * @param mvpMatrix the Model View Project matrix to draw this triangle list
+     */
+    public void draw(float[] mvpMatrix) {
+        // Pass the MVP matrix, vertex data, and color to OpenGL.
+        mProgram.bind(mvpMatrix, mVertexBuffer, mColor);
+
+        // Draw the triangle list.
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mNumCoords);
+        if (CHECK_GL_ERRORS) checkGlError("glDrawArrays");
+    }
+
+    /**
+     * Checks if any of the GL calls since the last time this method was called set an error
+     * condition. Call this method immediately after calling a GL method. Pass the name of the GL
+     * operation. For example:
+     *
+     * <pre>
+     * mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor");
+     * MyGLRenderer.checkGlError("glGetUniformLocation");</pre>
+     *
+     * If the operation is not successful, the check throws an exception.
+     *
+     * <p><em>Note</em> This is quite slow so it's best to use it sparingly in production builds.
+     *
+     * @param glOperation name of the OpenGL call to check
+     */
+    private static void checkGlError(String glOperation) {
+        int error = GLES20.glGetError();
+        if (error != GLES20.GL_NO_ERROR) {
+            String errorString = GLU.gluErrorString(error);
+            if (errorString == null) {
+                errorString = GLUtils.getEGLErrorString(error);
+            }
+            String message = glOperation + " caused GL error 0x" + Integer.toHexString(error) +
+                    ": " + errorString;
+            Log.e(TAG, message);
+            throw new RuntimeException(message);
+        }
+    }
+
+    /**
+     * Compiles an OpenGL shader.
+     *
+     * @param type {@link GLES20#GL_VERTEX_SHADER} or {@link GLES20#GL_FRAGMENT_SHADER}
+     * @param shaderCode string containing the shader source code
+     * @return ID for the shader
+     */
+    private static int loadShader(int type, String shaderCode){
+        // Create a vertex or fragment shader.
+        int shader = GLES20.glCreateShader(type);
+        if (CHECK_GL_ERRORS) checkGlError("glCreateShader");
+        if (shader == 0) {
+            throw new IllegalStateException("glCreateShader failed");
+        }
+
+        // Add the source code to the shader and compile it.
+        GLES20.glShaderSource(shader, shaderCode);
+        if (CHECK_GL_ERRORS) checkGlError("glShaderSource");
+        GLES20.glCompileShader(shader);
+        if (CHECK_GL_ERRORS) checkGlError("glCompileShader");
+
+        return shader;
+    }
+
+    /** OpenGL shaders for drawing solid colored triangle lists. */
+    public static class Program {
+        /** Trivial vertex shader that transforms the input vertex by the MVP matrix. */
+        private static final String VERTEX_SHADER_CODE = "" +
+                "uniform mat4 uMvpMatrix;\n" +
+                "attribute vec4 aPosition;\n" +
+                "void main() {\n" +
+                "    gl_Position = uMvpMatrix * aPosition;\n" +
+                "}\n";
+
+        /** Trivial fragment shader that draws with a fixed color. */
+        private static final String FRAGMENT_SHADER_CODE = "" +
+                "precision mediump float;\n" +
+                "uniform vec4 uColor;\n" +
+                "void main() {\n" +
+                "    gl_FragColor = uColor;\n" +
+                "}\n";
+
+        /** ID OpenGL uses to identify this program. */
+        private final int mProgramId;
+
+        /** Handle for uMvpMatrix uniform in vertex shader. */
+        private final int mMvpMatrixHandle;
+
+        /** Handle for aPosition attribute in vertex shader. */
+        private final int mPositionHandle;
+
+        /** Handle for uColor uniform in fragment shader. */
+        private final int mColorHandle;
+
+        /**
+         * Creates a program to draw triangle lists. For optimal drawing efficiency, one program
+         * should be used for all triangle lists being drawn.
+         */
+        public Program() {
+            // Prepare shaders.
+            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);
+            int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);
+
+            // Create empty OpenGL Program.
+            mProgramId = GLES20.glCreateProgram();
+            if (CHECK_GL_ERRORS) checkGlError("glCreateProgram");
+            if (mProgramId == 0) {
+                throw new IllegalStateException("glCreateProgram failed");
+            }
+
+            // Add the shaders to the program.
+            GLES20.glAttachShader(mProgramId, vertexShader);
+            if (CHECK_GL_ERRORS) checkGlError("glAttachShader");
+            GLES20.glAttachShader(mProgramId, fragmentShader);
+            if (CHECK_GL_ERRORS) checkGlError("glAttachShader");
+
+            // Link the program so it can be executed.
+            GLES20.glLinkProgram(mProgramId);
+            if (CHECK_GL_ERRORS) checkGlError("glLinkProgram");
+
+            // Get a handle to the uMvpMatrix uniform in the vertex shader.
+            mMvpMatrixHandle = GLES20.glGetUniformLocation(mProgramId, "uMvpMatrix");
+            if (CHECK_GL_ERRORS) checkGlError("glGetUniformLocation");
+
+            // Get a handle to the vertex shader's aPosition attribute.
+            mPositionHandle = GLES20.glGetAttribLocation(mProgramId, "aPosition");
+            if (CHECK_GL_ERRORS) checkGlError("glGetAttribLocation");
+
+            // Enable vertex array (VBO).
+            GLES20.glEnableVertexAttribArray(mPositionHandle);
+            if (CHECK_GL_ERRORS) checkGlError("glEnableVertexAttribArray");
+
+            // Get a handle to fragment shader's uColor uniform.
+            mColorHandle = GLES20.glGetUniformLocation(mProgramId, "uColor");
+            if (CHECK_GL_ERRORS) checkGlError("glGetUniformLocation");
+        }
+
+        /**
+         * Tells OpenGL to use this program. Call this method before drawing a sequence of
+         * triangle lists.
+         */
+        public void use() {
+            GLES20.glUseProgram(mProgramId);
+            if (CHECK_GL_ERRORS) checkGlError("glUseProgram");
+        }
+
+        /** Sends the given MVP matrix, vertex data, and color to OpenGL. */
+        public void bind(float[] mvpMatrix, FloatBuffer vertexBuffer, float[] color) {
+            // Pass the MVP matrix to OpenGL.
+            GLES20.glUniformMatrix4fv(mMvpMatrixHandle, 1 /* count */, false /* transpose */,
+                    mvpMatrix, 0 /* offset */);
+            if (CHECK_GL_ERRORS) checkGlError("glUniformMatrix4fv");
+
+            // Pass the VBO with the triangle list's vertices to OpenGL.
+            GLES20.glEnableVertexAttribArray(mPositionHandle);
+            if (CHECK_GL_ERRORS) checkGlError("glEnableVertexAttribArray");
+            GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+                    false /* normalized */, VERTEX_STRIDE, vertexBuffer);
+            if (CHECK_GL_ERRORS) checkGlError("glVertexAttribPointer");
+
+            // Pass the triangle list's color to OpenGL.
+            GLES20.glUniform4fv(mColorHandle, 1 /* count */, color, 0 /* offset */);
+            if (CHECK_GL_ERRORS) checkGlError("glUniform4fv");
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/SweepWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/SweepWatchFaceService.java
new file mode 100644
index 0000000..5fc738b
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/SweepWatchFaceService.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+
+/**
+ * Sample analog watch face with a sweep second hand. In ambient mode, the second hand isn't shown.
+ * On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode.
+ * The watch face is drawn with less contrast in mute mode.
+ *
+ * {@link AnalogWatchFaceService} is similar but has a ticking second hand.
+ */
+public class SweepWatchFaceService extends CanvasWatchFaceService {
+    private static final String TAG = "SweepWatchFaceService";
+
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+
+    private class Engine extends CanvasWatchFaceService.Engine {
+        Paint mHourPaint;
+        Paint mMinutePaint;
+        Paint mSecondPaint;
+        Paint mTickPaint;
+        boolean mMute;
+        Time mTime;
+
+        final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mTime.clear(intent.getStringExtra("time-zone"));
+                mTime.setToNow();
+            }
+        };
+        boolean mRegisteredTimeZoneReceiver = false;
+
+        /**
+         * Whether the display supports fewer bits for each color in ambient mode. When true, we
+         * disable anti-aliasing in ambient mode.
+         */
+        boolean mLowBitAmbient;
+
+        Bitmap mBackgroundBitmap;
+        Bitmap mBackgroundScaledBitmap;
+
+        @Override
+        public void onCreate(SurfaceHolder holder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCreate");
+            }
+            super.onCreate(holder);
+
+            setWatchFaceStyle(new WatchFaceStyle.Builder(SweepWatchFaceService.this)
+                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+                    .setShowSystemUiTime(false)
+                    .build());
+
+            Resources resources = SweepWatchFaceService.this.getResources();
+            Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+            mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
+
+            mHourPaint = new Paint();
+            mHourPaint.setARGB(255, 200, 200, 200);
+            mHourPaint.setStrokeWidth(5.f);
+            mHourPaint.setAntiAlias(true);
+            mHourPaint.setStrokeCap(Paint.Cap.ROUND);
+
+            mMinutePaint = new Paint();
+            mMinutePaint.setARGB(255, 200, 200, 200);
+            mMinutePaint.setStrokeWidth(3.f);
+            mMinutePaint.setAntiAlias(true);
+            mMinutePaint.setStrokeCap(Paint.Cap.ROUND);
+
+            mSecondPaint = new Paint();
+            mSecondPaint.setARGB(255, 255, 0, 0);
+            mSecondPaint.setStrokeWidth(2.f);
+            mSecondPaint.setAntiAlias(true);
+            mSecondPaint.setStrokeCap(Paint.Cap.ROUND);
+
+            mTickPaint = new Paint();
+            mTickPaint.setARGB(100, 255, 255, 255);
+            mTickPaint.setStrokeWidth(2.f);
+            mTickPaint.setAntiAlias(true);
+
+            mTime = new Time();
+        }
+
+        @Override
+        public void onPropertiesChanged(Bundle properties) {
+            super.onPropertiesChanged(properties);
+            mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
+            }
+        }
+
+        @Override
+        public void onTimeTick() {
+            super.onTimeTick();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+            }
+            invalidate();
+        }
+
+        @Override
+        public void onAmbientModeChanged(boolean inAmbientMode) {
+            super.onAmbientModeChanged(inAmbientMode);
+            if (mLowBitAmbient) {
+                boolean antiAlias = !inAmbientMode;
+                mHourPaint.setAntiAlias(antiAlias);
+                mMinutePaint.setAntiAlias(antiAlias);
+                mSecondPaint.setAntiAlias(antiAlias);
+                mTickPaint.setAntiAlias(antiAlias);
+            }
+            invalidate();
+        }
+
+        @Override
+        public void onInterruptionFilterChanged(int interruptionFilter) {
+            super.onInterruptionFilterChanged(interruptionFilter);
+            boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
+            if (mMute != inMuteMode) {
+                mMute = inMuteMode;
+                mHourPaint.setAlpha(inMuteMode ? 100 : 255);
+                mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
+                mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
+                invalidate();
+            }
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "onDraw");
+            }
+            long now = System.currentTimeMillis();
+            mTime.set(now);
+            int milliseconds = (int) (now % 1000);
+
+            int width = canvas.getWidth();
+            int height = canvas.getHeight();
+
+            // Draw the background, scaled to fit.
+            if (mBackgroundScaledBitmap == null
+                    || mBackgroundScaledBitmap.getWidth() != width
+                    || mBackgroundScaledBitmap.getHeight() != height) {
+                mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
+                        width, height, true /* filter */);
+            }
+            canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);
+
+            // Find the center. Ignore the window insets so that, on round watches with a
+            // "chin", the watch face is centered on the entire screen, not just the usable
+            // portion.
+            float centerX = width / 2f;
+            float centerY = height / 2f;
+
+            // Draw the ticks.
+            float innerTickRadius = centerX - 10;
+            float outerTickRadius = centerX;
+            for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
+                float tickRot = (float) (tickIndex * Math.PI * 2 / 12);
+                float innerX = (float) Math.sin(tickRot) * innerTickRadius;
+                float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
+                float outerX = (float) Math.sin(tickRot) * outerTickRadius;
+                float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
+                canvas.drawLine(centerX + innerX, centerY + innerY,
+                        centerX + outerX, centerY + outerY, mTickPaint);
+            }
+
+            float seconds = mTime.second + milliseconds / 1000f;
+            float secRot = seconds / 30f * (float) Math.PI;
+            int minutes = mTime.minute;
+            float minRot = minutes / 30f * (float) Math.PI;
+            float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.PI;
+
+            float secLength = centerX - 20;
+            float minLength = centerX - 40;
+            float hrLength = centerX - 80;
+
+            if (!isInAmbientMode()) {
+                float secX = (float) Math.sin(secRot) * secLength;
+                float secY = (float) -Math.cos(secRot) * secLength;
+                canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
+            }
+
+            float minX = (float) Math.sin(minRot) * minLength;
+            float minY = (float) -Math.cos(minRot) * minLength;
+            canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);
+
+            float hrX = (float) Math.sin(hrRot) * hrLength;
+            float hrY = (float) -Math.cos(hrRot) * hrLength;
+            canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
+
+            // Draw every frame as long as we're visible.
+            if (isVisible()) {
+                invalidate();
+            }
+        }
+
+        @Override
+        public void onVisibilityChanged(boolean visible) {
+            super.onVisibilityChanged(visible);
+
+            if (visible) {
+                registerReceiver();
+
+                // Update time zone in case it changed while we weren't visible.
+                mTime.clear(TimeZone.getDefault().getID());
+                mTime.setToNow();
+
+                invalidate();
+            } else {
+                unregisterReceiver();
+            }
+        }
+
+        private void registerReceiver() {
+            if (mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = true;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+            SweepWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+        }
+
+        private void unregisterReceiver() {
+            if (!mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = false;
+            SweepWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceService.java b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceService.java
new file mode 100644
index 0000000..ce90d77
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/java/com/example/android/wearable/watchface/TiltWatchFaceService.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2014 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.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.support.wearable.watchface.Gles2WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sample watch face using OpenGL. The watch face is rendered using
+ * {@link Gles2ColoredTriangleList}s. The camera moves around in interactive mode and stops moving
+ * when the watch enters ambient mode.
+ */
+public class TiltWatchFaceService extends Gles2WatchFaceService {
+
+    private static final String TAG = "TiltWatchFaceService";
+
+    /** Expected frame rate in interactive mode. */
+    private static final long FPS = 60;
+
+    /** How long each frame is displayed at expected frame rate. */
+    private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS;
+
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+
+    private class Engine extends Gles2WatchFaceService.Engine {
+        /** Cycle time before the camera motion repeats. */
+        private static final long CYCLE_PERIOD_SECONDS = 5;
+
+        /** Number of camera angles to precompute. */
+        private final int mNumCameraAngles = (int) (CYCLE_PERIOD_SECONDS * FPS);
+
+        /** Projection transformation matrix. Converts from 3D to 2D. */
+        private final float[] mProjectionMatrix = new float[16];
+
+        /**
+         * View transformation matrices to use in interactive mode. Converts from world to camera-
+         * relative coordinates. One matrix per camera position.
+         */
+        private final float[][] mViewMatrices = new float[mNumCameraAngles][16];
+
+        /** The view transformation matrix to use in ambient mode */
+        private final float[] mAmbientViewMatrix = new float[16];
+
+        /**
+         * Model transformation matrices. Converts from model-relative coordinates to world
+         * coordinates. One matrix per degree of rotation.
+         */
+        private final float[][] mModelMatrices = new float[360][16];
+
+        /**
+         * Products of {@link #mViewMatrices} and {@link #mProjectionMatrix}. One matrix per camera
+         * position.
+         */
+        private final float[][] mVpMatrices = new float[mNumCameraAngles][16];
+
+        /** The product of {@link #mAmbientViewMatrix} and {@link #mProjectionMatrix} */
+        private final float[] mAmbientVpMatrix = new float[16];
+
+        /**
+         * Product of {@link #mModelMatrices}, {@link #mViewMatrices}, and
+         * {@link #mProjectionMatrix}.
+         */
+        private final float[] mMvpMatrix = new float[16];
+
+        /** Triangles for the 4 major ticks. These are grouped together to speed up rendering. */
+        private Gles2ColoredTriangleList mMajorTickTriangles;
+
+        /** Triangles for the 8 minor ticks. These are grouped together to speed up rendering. */
+        private Gles2ColoredTriangleList mMinorTickTriangles;
+
+        /** Triangle for the second hand. */
+        private Gles2ColoredTriangleList mSecondHandTriangle;
+
+        /** Triangle for the minute hand. */
+        private Gles2ColoredTriangleList mMinuteHandTriangle;
+
+        /** Triangle for the hour hand. */
+        private Gles2ColoredTriangleList mHourHandTriangle;
+
+        private Time mTime = new Time();
+
+        /** Whether we've registered {@link #mTimeZoneReceiver}. */
+        private boolean mRegisteredTimeZoneReceiver;
+
+        private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mTime.clear(intent.getStringExtra("time-zone"));
+                mTime.setToNow();
+            }
+        };
+
+        @Override
+        public void onCreate(SurfaceHolder surfaceHolder) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onCreate");
+            }
+            super.onCreate(surfaceHolder);
+            setWatchFaceStyle(new WatchFaceStyle.Builder(TiltWatchFaceService.this)
+                    .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+                    .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+                    .setStatusBarGravity(Gravity.RIGHT | Gravity.TOP)
+                    .setHotwordIndicatorGravity(Gravity.LEFT | Gravity.TOP)
+                    .setShowSystemUiTime(false)
+                    .build());
+        }
+
+        @Override
+        public void onGlContextCreated() {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onGlContextCreated");
+            }
+            super.onGlContextCreated();
+
+            // Create program for drawing triangles.
+            Gles2ColoredTriangleList.Program triangleProgram =
+                    new Gles2ColoredTriangleList.Program();
+
+            // We only draw triangles which all use the same program so we don't need to switch
+            // programs mid-frame. This means we can tell OpenGL to use this program only once
+            // rather than having to do so for each frame. This makes OpenGL draw faster.
+            triangleProgram.use();
+
+            // Create triangles for the ticks.
+            mMajorTickTriangles = createMajorTicks(triangleProgram);
+            mMinorTickTriangles = createMinorTicks(triangleProgram);
+
+            // Create triangles for the hands.
+            mSecondHandTriangle = createHand(
+                    triangleProgram,
+                    0.02f /* width */,
+                    1.0f /* height */,
+                    new float[]{
+                            1.0f /* red */,
+                            0.0f /* green */,
+                            0.0f /* blue */,
+                            1.0f /* alpha */
+                    }
+            );
+            mMinuteHandTriangle = createHand(
+                    triangleProgram,
+                    0.06f /* width */,
+                    0.8f /* height */,
+                    new float[]{
+                            0.7f /* red */,
+                            0.7f /* green */,
+                            0.7f /* blue */,
+                            1.0f /* alpha */
+                    }
+            );
+            mHourHandTriangle = createHand(
+                    triangleProgram,
+                    0.1f /* width */,
+                    0.5f /* height */,
+                    new float[]{
+                            0.9f /* red */,
+                            0.9f /* green */,
+                            0.9f /* blue */,
+                            1.0f /* alpha */
+                    }
+            );
+
+            // Precompute the clock angles.
+            for (int i = 0; i < mModelMatrices.length; ++i) {
+                Matrix.setRotateM(mModelMatrices[i], 0, i, 0, 0, 1);
+            }
+
+            // Precompute the camera angles.
+            for (int i = 0; i < mNumCameraAngles; ++i) {
+                // Set the camera position (View matrix). When active, move the eye around to show
+                // off that this is 3D.
+                final float cameraAngle = (float) (((float) i) / mNumCameraAngles * 2 * Math.PI);
+                final float eyeX = (float) Math.cos(cameraAngle);
+                final float eyeY = (float) Math.sin(cameraAngle);
+                Matrix.setLookAtM(mViewMatrices[i],
+                        0, // dest index
+                        eyeX, eyeY, -3, // eye
+                        0, 0, 0, // center
+                        0, 1, 0); // up vector
+            }
+
+            Matrix.setLookAtM(mAmbientViewMatrix,
+                    0, // dest index
+                    0, 0, -3, // eye
+                    0, 0, 0, // center
+                    0, 1, 0); // up vector
+        }
+
+        @Override
+        public void onGlSurfaceCreated(int width, int height) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onGlSurfaceCreated");
+            }
+            super.onGlSurfaceCreated(width, height);
+
+            // Adjust the viewport based on geometry changes such as screen rotation.
+            GLES20.glViewport(0, 0, width, height);
+
+            // Update the projection matrix based on the new aspect ratio.
+            final float aspectRatio = (float) width / height;
+            Matrix.frustumM(mProjectionMatrix,
+                    0 /* offset */,
+                    -aspectRatio /* left */,
+                    aspectRatio /* right */,
+                    -1 /* bottom */,
+                    1 /* top */,
+                    2 /* near */,
+                    7 /* far */);
+
+            // Precompute the products of Projection and View matrices for each camera angle.
+            for (int i = 0; i < mNumCameraAngles; ++i) {
+                Matrix.multiplyMM(mVpMatrices[i], 0, mProjectionMatrix, 0, mViewMatrices[i], 0);
+            }
+
+            Matrix.multiplyMM(mAmbientVpMatrix, 0, mProjectionMatrix, 0, mAmbientViewMatrix, 0);
+        }
+
+        /**
+         * Creates a triangle for a hand on the watch face.
+         *
+         * @param program program for drawing triangles
+         * @param width width of base of triangle
+         * @param length length of triangle
+         * @param color color in RGBA order, each in the range [0, 1]
+         */
+        private Gles2ColoredTriangleList createHand(Gles2ColoredTriangleList.Program program,
+                float width, float length, float[] color) {
+            // Create the data for the VBO.
+            float[] triangleCoords = new float[]{
+                    // in counterclockwise order:
+                    0, length, 0,   // top
+                    -width / 2, 0, 0,   // bottom left
+                    width / 2, 0, 0    // bottom right
+            };
+            return new Gles2ColoredTriangleList(program, triangleCoords, color);
+        }
+
+        /**
+         * Creates a triangle list for the major ticks on the watch face.
+         *
+         * @param program program for drawing triangles
+         */
+        private Gles2ColoredTriangleList createMajorTicks(
+                Gles2ColoredTriangleList.Program program) {
+            // Create the data for the VBO.
+            float[] trianglesCoords = new float[9 * 4];
+            for (int i = 0; i < 4; i++) {
+                float[] triangleCoords = getMajorTickTriangleCoords(i);
+                System.arraycopy(triangleCoords, 0, trianglesCoords, i * 9, triangleCoords.length);
+            }
+
+            return new Gles2ColoredTriangleList(program, trianglesCoords,
+                    new float[]{
+                            1.0f /* red */,
+                            1.0f /* green */,
+                            1.0f /* blue */,
+                            1.0f /* alpha */
+                    }
+            );
+        }
+
+        /**
+         * Creates a triangle list for the minor ticks on the watch face.
+         *
+         * @param program program for drawing triangles
+         */
+        private Gles2ColoredTriangleList createMinorTicks(
+                Gles2ColoredTriangleList.Program program) {
+            // Create the data for the VBO.
+            float[] trianglesCoords = new float[9 * (12 - 4)];
+            int index = 0;
+            for (int i = 0; i < 12; i++) {
+                if (i % 3 == 0) {
+                    // This is where a major tick goes, so skip it.
+                    continue;
+                }
+                float[] triangleCoords = getMinorTickTriangleCoords(i);
+                System.arraycopy(triangleCoords, 0, trianglesCoords, index, triangleCoords.length);
+                index += 9;
+            }
+
+            return new Gles2ColoredTriangleList(program, trianglesCoords,
+                    new float[]{
+                            0.5f /* red */,
+                            0.5f /* green */,
+                            0.5f /* blue */,
+                            1.0f /* alpha */
+                    }
+            );
+        }
+
+        private float[] getMajorTickTriangleCoords(int index) {
+            return getTickTriangleCoords(0.03f /* width */, 0.09f /* length */,
+                    index * 360 / 4 /* angleDegrees */);
+        }
+
+        private float[] getMinorTickTriangleCoords(int index) {
+            return getTickTriangleCoords(0.02f /* width */, 0.06f /* length */,
+                    index * 360 / 12 /* angleDegrees */);
+        }
+
+        private float[] getTickTriangleCoords(float width, float length, int angleDegrees) {
+            // Create the data for the VBO.
+            float[] coords = new float[]{
+                    // in counterclockwise order:
+                    0, 1, 0,   // top
+                    width / 2, length + 1, 0,   // bottom left
+                    -width / 2, length + 1, 0    // bottom right
+            };
+
+            rotateCoords(coords, angleDegrees);
+            return coords;
+        }
+
+        /**
+         * Destructively rotates the given coordinates in the XY plane about the origin by the given
+         * angle.
+         *
+         * @param coords flattened 3D coordinates
+         * @param angleDegrees angle in degrees clockwise when viewed from negative infinity on the
+         *                     Z axis
+         */
+        private void rotateCoords(float[] coords, int angleDegrees) {
+            double angleRadians = Math.toRadians(angleDegrees);
+            double cos = Math.cos(angleRadians);
+            double sin = Math.sin(angleRadians);
+            for (int i = 0; i < coords.length; i += 3) {
+                float x = coords[i];
+                float y = coords[i + 1];
+                coords[i] = (float) (cos * x - sin * y);
+                coords[i + 1] = (float) (sin * x + cos * y);
+            }
+        }
+
+        @Override
+        public void onAmbientModeChanged(boolean inAmbientMode) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+            }
+            super.onAmbientModeChanged(inAmbientMode);
+            invalidate();
+        }
+
+        @Override
+        public void onVisibilityChanged(boolean visible) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onVisibilityChanged: " + visible);
+            }
+            super.onVisibilityChanged(visible);
+            if (visible) {
+                registerReceiver();
+
+                // Update time zone in case it changed while we were detached.
+                mTime.clear(TimeZone.getDefault().getID());
+                mTime.setToNow();
+
+                invalidate();
+            } else {
+                unregisterReceiver();
+            }
+        }
+
+        private void registerReceiver() {
+            if (mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = true;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+            TiltWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+        }
+
+        private void unregisterReceiver() {
+            if (!mRegisteredTimeZoneReceiver) {
+                return;
+            }
+            mRegisteredTimeZoneReceiver = false;
+            TiltWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+        }
+
+        @Override
+        public void onTimeTick() {
+            super.onTimeTick();
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+            }
+            invalidate();
+        }
+
+        @Override
+        public void onDraw() {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "onDraw");
+            }
+            super.onDraw();
+            final float[] vpMatrix;
+
+            // Draw background color and select the appropriate view projection matrix. The
+            // background should always be black in ambient mode. The view projection matrix used is
+            // overhead in ambient. In interactive mode, it's tilted depending on the current time.
+            if (isInAmbientMode()) {
+                GLES20.glClearColor(0, 0, 0, 1);
+                vpMatrix = mAmbientVpMatrix;
+            } else {
+                GLES20.glClearColor(0.5f, 0.2f, 0.2f, 1);
+                final int cameraIndex =
+                        (int) ((System.currentTimeMillis() / FRAME_PERIOD_MS) % mNumCameraAngles);
+                vpMatrix = mVpMatrices[cameraIndex];
+            }
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+
+            // Compute angle indices for the three hands.
+            mTime.setToNow();
+            final int secIndex = mTime.second * 360 / 60;
+            final int minIndex = mTime.minute * 360 / 60;
+            final int hoursIndex = (mTime.hour % 12) * 360 / 12 + mTime.minute * 360 / 60 / 12;
+
+            // Draw triangles from back to front. Don't draw the second hand in ambient mode.
+            {
+                // Combine the model matrix with the projection and camera view.
+                Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[hoursIndex], 0);
+
+                // Draw the triangle.
+                mHourHandTriangle.draw(mMvpMatrix);
+            }
+            {
+                // Combine the model matrix with the projection and camera view.
+                Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[minIndex], 0);
+
+                // Draw the triangle.
+                mMinuteHandTriangle.draw(mMvpMatrix);
+            }
+            if (!isInAmbientMode()) {
+                // Combine the model matrix with the projection and camera view.
+                Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[secIndex], 0);
+
+                // Draw the triangle.
+                mSecondHandTriangle.draw(mMvpMatrix);
+            }
+            {
+                // Draw the major and minor ticks.
+                mMajorTickTriangles.draw(vpMatrix);
+                mMinorTickTriangles.draw(vpMatrix);
+            }
+
+            // Draw every frame as long as we're visible.
+            if (isVisible()) {
+                invalidate();
+            }
+        }
+    }
+}
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/bg.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/bg.png
new file mode 100644
index 0000000..5199af2
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/bg.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/ic_launcher.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..589f229
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_analog.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_analog.png
new file mode 100644
index 0000000..ed6960d
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_analog.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_calendar.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_calendar.png
new file mode 100644
index 0000000..928aa1f
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_calendar.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_card_bounds.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_card_bounds.png
new file mode 100644
index 0000000..f87b6c5
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_card_bounds.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_digital.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_digital.png
new file mode 100644
index 0000000..4853a64
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_digital.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_tilt.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_tilt.png
new file mode 100644
index 0000000..aab5f18
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-hdpi/preview_tilt.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-mdpi/ic_launcher.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..77dd571
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..fe34ebe
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..ab80bcd
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/layout/activity_digital_config.xml b/wearable/wear/WatchFace/Wearable/src/main/res/layout/activity_digital_config.xml
new file mode 100644
index 0000000..eef7aae
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/layout/activity_digital_config.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="#ffffff">
+    <TextView
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="16dp"
+        android:layout_marginTop="14dp"
+        android:layout_gravity="center_horizontal"
+        android:textSize="22sp"
+        android:textColor="#a2a2a2"
+        android:text="@string/digital_background_color"
+        android:fontFamily="sans-serif-condensed-light"/>
+    <android.support.wearable.view.WearableListView
+        android:id="@+id/color_picker"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/transparent"/>
+</LinearLayout>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/layout/color_picker_item.xml b/wearable/wear/WatchFace/Wearable/src/main/res/layout/color_picker_item.xml
new file mode 100644
index 0000000..e7edb39
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/layout/color_picker_item.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/label"
+    android:fontFamily="sans-serif-condensed-light"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:paddingTop="20dp"
+    android:paddingBottom="20dp"
+    android:textColor="#000000"
+    android:textSize="18sp"/>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/values/color.xml b/wearable/wear/WatchFace/Wearable/src/main/res/values/color.xml
new file mode 100644
index 0000000..2ba1355
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/values/color.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+    <color name="digital_am_pm">#aaaaa0</color>
+    <color name="digital_colons">#aaaaa0</color>
+</resources>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml b/wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..b9c4c86
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+    <dimen name="digital_text_size">40dp</dimen>
+    <dimen name="digital_text_size_round">45dp</dimen>
+    <dimen name="digital_am_pm_size">25dp</dimen>
+    <dimen name="digital_am_pm_size_round">30dp</dimen>
+    <dimen name="digital_x_offset">15dp</dimen>
+    <dimen name="digital_x_offset_round">25dp</dimen>
+    <dimen name="digital_y_offset">90dp</dimen>
+    <dimen name="digital_config_color_picker_item_margin">32dp</dimen>
+</resources>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml b/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e54591f
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/values/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+    <string name="app_name">WatchFace</string>
+    <string name="tilt_name">Sample Tilt</string>
+    <string name="analog_name">Sample Analog</string>
+    <string name="sweep_name">Sample Sweep</string>
+    <string name="card_bounds_name">Sample Card Bounds</string>
+    <string name="digital_name">Sample Digital</string>
+    <string name="digital_background_color">Background color</string>
+    <string name="digital_config_name">Digital watch face configuration</string>
+    <string name="digital_am">AM</string>
+    <string name="digital_pm">PM</string>
+    <string name="calendar_name">Sample Calendar</string>
+    <plurals name="calendar_meetings">
+        <item quantity="one">&lt;br&gt;&lt;br&gt;&lt;br&gt;You have &lt;b&gt;%1$d&lt;/b&gt; meeting in the next 24 hours.</item>
+        <item quantity="other">&lt;br&gt;&lt;br&gt;&lt;br&gt;You have &lt;b&gt;%1$d&lt;/b&gt; meetings in the next 24 hours.</item>
+    </plurals>
+
+    <!-- TODO: this should be shared (needs covering all the samples with Gradle build model) -->
+    <string name="color_black">Black</string>
+    <string name="color_blue">Blue</string>
+    <string name="color_gray">Gray</string>
+    <string name="color_green">Green</string>
+    <string name="color_navy">Navy</string>
+    <string name="color_red">Red</string>
+    <string name="color_white">White</string>
+
+    <string-array name="color_array">
+        <item>@string/color_black</item>
+        <item>@string/color_blue</item>
+        <item>@string/color_gray</item>
+        <item>@string/color_green</item>
+        <item>@string/color_navy</item>
+        <item>@string/color_red</item>
+        <item>@string/color_white</item>
+    </string-array>
+</resources>
diff --git a/wearable/wear/WatchFace/Wearable/src/main/res/xml/watch_face.xml b/wearable/wear/WatchFace/Wearable/src/main/res/xml/watch_face.xml
new file mode 100644
index 0000000..aa2e343
--- /dev/null
+++ b/wearable/wear/WatchFace/Wearable/src/main/res/xml/watch_face.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<wallpaper xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/wearable/wear/WatchFace/build.gradle b/wearable/wear/WatchFace/build.gradle
new file mode 100644
index 0000000..be1fa82
--- /dev/null
+++ b/wearable/wear/WatchFace/build.gradle
@@ -0,0 +1,14 @@
+
+
+
+
+// BEGIN_EXCLUDE
+import com.example.android.samples.build.SampleGenPlugin
+apply plugin: SampleGenPlugin
+
+samplegen {
+  pathToBuild "../../../../../build"
+  pathToSamplesCommon "../../../common"
+}
+apply from: "../../../../../build/build.gradle"
+// END_EXCLUDE
diff --git a/wearable/wear/WatchFace/buildSrc/build.gradle b/wearable/wear/WatchFace/buildSrc/build.gradle
new file mode 100644
index 0000000..e344a8c
--- /dev/null
+++ b/wearable/wear/WatchFace/buildSrc/build.gradle
@@ -0,0 +1,18 @@
+
+
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile 'org.freemarker:freemarker:2.3.20'
+}
+
+sourceSets {
+    main {
+        groovy {
+            srcDir new File(rootDir, "../../../../../../build/buildSrc/src/main/groovy")
+        }
+    }
+}
+
diff --git a/wearable/wear/WatchFace/gradle/wrapper/gradle-wrapper.jar b/wearable/wear/WatchFace/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/wearable/wear/WatchFace/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/wearable/wear/WatchFace/gradle/wrapper/gradle-wrapper.properties b/wearable/wear/WatchFace/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d7f03cf
--- /dev/null
+++ b/wearable/wear/WatchFace/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-bin.zip
diff --git a/wearable/wear/WatchFace/gradlew b/wearable/wear/WatchFace/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/wearable/wear/WatchFace/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/wearable/wear/WatchFace/gradlew.bat b/wearable/wear/WatchFace/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/wearable/wear/WatchFace/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/wearable/wear/WatchFace/screenshots/analog_and_sweep_face.png b/wearable/wear/WatchFace/screenshots/analog_and_sweep_face.png
new file mode 100644
index 0000000..df0820e
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/analog_and_sweep_face.png
Binary files differ
diff --git a/wearable/wear/WatchFace/screenshots/calendar_face.png b/wearable/wear/WatchFace/screenshots/calendar_face.png
new file mode 100644
index 0000000..94214c0
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/calendar_face.png
Binary files differ
diff --git a/wearable/wear/WatchFace/screenshots/card_bounds_face.png b/wearable/wear/WatchFace/screenshots/card_bounds_face.png
new file mode 100644
index 0000000..0ee827e
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/card_bounds_face.png
Binary files differ
diff --git a/wearable/wear/WatchFace/screenshots/card_bounds_face2.png b/wearable/wear/WatchFace/screenshots/card_bounds_face2.png
new file mode 100644
index 0000000..c9c107e
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/card_bounds_face2.png
Binary files differ
diff --git a/wearable/wear/WatchFace/screenshots/digital_face.png b/wearable/wear/WatchFace/screenshots/digital_face.png
new file mode 100644
index 0000000..f2d1723
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/digital_face.png
Binary files differ
diff --git a/wearable/wear/WatchFace/screenshots/digital_phoneside_config.png b/wearable/wear/WatchFace/screenshots/digital_phoneside_config.png
new file mode 100644
index 0000000..f614d10
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/digital_phoneside_config.png
Binary files differ
diff --git a/wearable/wear/WatchFace/screenshots/digital_watchside_config.png b/wearable/wear/WatchFace/screenshots/digital_watchside_config.png
new file mode 100644
index 0000000..d2813c4
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/digital_watchside_config.png
Binary files differ
diff --git a/wearable/wear/WatchFace/screenshots/tilt_face.png b/wearable/wear/WatchFace/screenshots/tilt_face.png
new file mode 100644
index 0000000..69601a7
--- /dev/null
+++ b/wearable/wear/WatchFace/screenshots/tilt_face.png
Binary files differ
diff --git a/wearable/wear/WatchFace/settings.gradle b/wearable/wear/WatchFace/settings.gradle
new file mode 100644
index 0000000..19d00ac
--- /dev/null
+++ b/wearable/wear/WatchFace/settings.gradle
@@ -0,0 +1 @@
+include ':Application', ':Wearable'
diff --git a/wearable/wear/WatchFace/template-params.xml b/wearable/wear/WatchFace/template-params.xml
new file mode 100644
index 0000000..0e6a7b3
--- /dev/null
+++ b/wearable/wear/WatchFace/template-params.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2014 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.
+-->
+
+
+
+<sample>
+    <name>WatchFace</name>
+    <group>Wearable</group>
+    <package>com.example.android.wearable.watchface</package>
+
+
+
+    <!-- change minSdk if needed-->
+    <minSdk>18</minSdk>
+
+    <dependency>com.google.android.support:wearable:1.1.+</dependency>
+
+
+    <strings>
+        <intro>
+            <![CDATA[
+This sample demonstrates how to create watch faces for android wear and includes a phone app
+and a wearable app. The wearable app has a variety of watch faces including analog, digital,
+opengl, calendar, etc. It also includes a watch-side configuration example. The phone app
+includes a phone-side configuration example.
+            ]]>
+        </intro>
+    </strings>
+
+    <template src="base"/>
+    <template src="Wear"/>
+
+</sample>