Merge "Initial commit for XYZ Tourist Attractions Wear sample." into lmp-docs
diff --git a/wearable/wear/XYZTouristAttractions/Application/.gitignore b/wearable/wear/XYZTouristAttractions/Application/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2013 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/XYZTouristAttractions/Application/proguard-project.txt b/wearable/wear/XYZTouristAttractions/Application/proguard-project.txt
new file mode 100644
index 0000000..0d8f171
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/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/XYZTouristAttractions/Application/src/main/AndroidManifest.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5f330d5
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/AndroidManifest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.xyztouristattractions" >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/XYZAppTheme" >
+
+        <activity
+            android:name=".ui.AttractionListActivity"
+            android:label="@string/app_name" >
+
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+        </activity>
+
+        <activity
+            android:name=".ui.DetailActivity"
+            android:label="@string/app_name"
+            android:theme="@style/XYZAppTheme.Detail"
+            android:parentActivityName=".ui.AttractionListActivity" />
+
+        <receiver android:name=".service.UtilityReceiver" />
+
+        <service android:name=".service.UtilityService" />
+
+        <service android:name=".service.ListenerService">
+            <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/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/provider/TouristAttractions.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/provider/TouristAttractions.java
new file mode 100644
index 0000000..f035336
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/provider/TouristAttractions.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.provider;
+
+import android.net.Uri;
+
+import com.example.android.xyztouristattractions.BuildConfig;
+import com.example.android.xyztouristattractions.common.Attraction;
+import com.google.android.gms.location.Geofence;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.maps.android.SphericalUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Static data content provider.
+ */
+public class TouristAttractions {
+
+    public static final String CITY_SYDNEY = "Sydney";
+
+    public static final String TEST_CITY = CITY_SYDNEY;
+
+    private static final float TRIGGER_RADIUS = 2000; // 2KM
+    private static final int TRIGGER_TRANSITION = Geofence.GEOFENCE_TRANSITION_ENTER |
+            Geofence.GEOFENCE_TRANSITION_EXIT;
+    private static final long EXPIRATION_DURATION = Geofence.NEVER_EXPIRE;
+
+    public static final Map<String, LatLng> CITY_LOCATIONS = new HashMap<String, LatLng>() {{
+        put(CITY_SYDNEY, new LatLng(-33.873651, 151.2068896));
+    }};
+
+    /**
+     * All photos used with permission under the Creative Commons Attribution-ShareAlike License.
+     */
+    public static final HashMap<String, List<Attraction>> ATTRACTIONS =
+            new HashMap<String, List<Attraction>>() {{
+
+        put(CITY_SYDNEY, new ArrayList<Attraction>() {{
+            add(new Attraction(
+                    "Sydney Opera House",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae bibendum justo, vitae cursus velit. Suspendisse potenti.",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vitae bibendum justo, vitae cursus velit. Suspendisse potenti. Suspendisse scelerisque risus justo, non tincidunt nibh blandit et. Vivamus elit lacus, luctus nec erat in, pharetra semper turpis. Quisque viverra nulla ligula, non pulvinar ante dictum sit amet. Vestibulum aliquet tortor mauris, vel suscipit nisl malesuada eget. Aliquam maximus dictum euismod. Maecenas leo quam, volutpat id diam eget, placerat fringilla ipsum. Nam pretium vehicula augue quis euismod.\n\nNam sed blandit magna. Vestibulum a fermentum arcu. Vestibulum et ligula at nisi luctus facilisis. Proin fermentum enim a nibh commodo finibus. Suspendisse justo elit, vulputate ut ipsum at, pellentesque auctor massa. Praesent vestibulum erat interdum imperdiet dapibus. In hac habitasse platea dictumst. Proin varius orci vitae tempor vulputate.\n\nEtiam sed mollis orci. Integer et ex sed tortor scelerisque blandit semper id libero. Nulla facilisi. Pellentesque tempor magna eget massa ultrices, et efficitur lectus finibus.",
+                    Uri.parse("https://lh5.googleusercontent.com/-7fb5ybQhUbo/VGLWjIL4RmI/AAAAAAAAACM/2jLe_msj_tk/w600-no/IMG_0049.JPG"),
+                    Uri.parse("https://lh3.googleusercontent.com/-EFEw6s7mT6I/VGLkCH4Xt4I/AAAAAAAAADY/ZlznhaQvb8E/w600-no/DSC_2775.JPG"),
+                    new LatLng(-33.858667, 151.214028),
+                    CITY_SYDNEY
+            ));
+
+            add(new Attraction(
+                    "Sydney Harbour Bridge",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ut nulla neque. Morbi nec felis vel neque rhoncus malesuada.",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris ut nulla neque. Morbi nec felis vel neque rhoncus malesuada. Mauris non nisi est. Nunc in ipsum euismod, suscipit dolor eget, efficitur nisi. Integer venenatis mauris mauris, quis luctus risus pellentesque a. Duis tempus est at ligula vehicula fermentum. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.\n\nNam ut sodales nibh, euismod aliquet lectus. Curabitur ornare dictum nisi, at faucibus magna. Morbi tempus nibh sed sodales volutpat. Etiam sodales, turpis sit amet porttitor tristique, libero libero faucibus est, viverra dictum risus ipsum vel augue. Nulla dolor magna, iaculis ac ornare id, fermentum eget massa. Sed mattis, odio nec sodales vehicula, neque metus ullamcorper nulla, sit amet ullamcorper risus lectus a ipsum. Curabitur venenatis feugiat quam nec elementum. Curabitur a interdum urna. Curabitur tincidunt tortor eget neque condimentum blandit. Etiam imperdiet, enim nec blandit convallis, nunc augue.",
+                    Uri.parse("https://lh6.googleusercontent.com/-ORRJtfLQlaw/VGLmQPv3n8I/AAAAAAAAAD8/2TzSCCPzl9k/w600-no/DSC04114.JPG"),
+                    Uri.parse("https://lh4.googleusercontent.com/-ch9Kk-7pD68/VGLkCNh5niI/AAAAAAAAADc/ztxkRHWX-po/w600-no/DSC_2739.JPG"),
+                    new LatLng(-33.852222, 151.210556),
+                    CITY_SYDNEY
+            ));
+
+            add(new Attraction(
+                    "Darling Harbour",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ut dui in ipsum suscipit aliquet pretium aliquet odio.",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam ut dui in ipsum suscipit aliquet pretium aliquet odio. Nam posuere nunc sed risus molestie varius. Suspendisse posuere faucibus urna, id vestibulum ante iaculis et. Vivamus placerat suscipit sem, a tempor nunc vehicula ac. Ut libero velit, dapibus sit amet euismod vel, dignissim a nisl.\n\nDonec non dui non felis laoreet malesuada. Fusce ac metus ultrices, fermentum felis quis, varius velit. Donec ac felis semper, scelerisque diam sed, dignissim risus. Maecenas vel semper sapien. Fusce euismod justo posuere, efficitur risus tincidunt, congue tellus. In hac habitasse platea dictumst. Sed lobortis risus consequat vehicula facilisis.\n\nIn hendrerit, neque in gravida rutrum, purus enim aliquet lectus, sit amet vulputate tortor lacus at sem. Aenean lorem metus, finibus rhoncus eros at, ullamcorper fringilla velit. Nulla vitae porttitor metus, quis gravida lectus. In rhoncus, diam a elementum luctus, erat nisi tempus ex, in porta est.",
+                    Uri.parse("https://lh5.googleusercontent.com/-qX43g6s92LY/VGLaTT3N35I/AAAAAAAAAC8/BbueQmch0Rw/w600-no/68001.jpg"),
+                    Uri.parse("https://lh6.googleusercontent.com/-SQ6T1Ure6l8/VGLaTg2iGuI/AAAAAAAAACo/m6_RkTW2G1o/w600-no/IMG_20140201_082851.jpg"),
+                    new LatLng(-33.8723, 151.19896),
+                    CITY_SYDNEY
+            ));
+
+            add(new Attraction(
+                    "Bondi Beach",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam et nunc in leo laoreet placerat. Interdum et malesuada fames ac ante ipsum primis in faucibus.",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam et nunc in leo laoreet placerat. Interdum et malesuada fames ac ante ipsum primis in faucibus. Proin vestibulum laoreet odio nec posuere. Quisque ante arcu, malesuada vitae velit a, auctor tincidunt ante. Mauris varius eros eros, eget scelerisque mi scelerisque ut. Donec vehicula vitae urna ac hendrerit. Phasellus egestas risus nec euismod auctor.\n\nInteger fermentum velit et dolor varius sagittis. Proin et viverra sapien. Nulla aliquet ante et hendrerit egestas. Duis vulputate libero in nisi gravida cursus. Praesent laoreet nec dolor non iaculis. Aliquam eleifend ultricies ipsum, eu pellentesque libero rutrum non. Mauris et purus erat. Nullam semper mi id tincidunt viverra. Ut porta sem congue lectus luctus ultricies. Suspendisse iaculis lacinia nibh, eu accumsan magna volutpat vel. Sed id interdum mi, vel sollicitudin elit. Fusce facilisis elementum gravida. Duis at volutpat odio. Integer porta convallis tincidunt. Donec aliquam, leo ut.",
+                    Uri.parse("https://lh4.googleusercontent.com/-wbNgVdUkBiE/VHe99hGVtNI/AAAAAAAAAFY/fAHfhchNLJw/w600-no/IMG_20141124_143747.jpg"),
+                    Uri.parse("https://lh6.googleusercontent.com/-sjY_xlEOic4/VHe9-I4DD9I/AAAAAAAAAFI/Mt0VnjU7SxQ/w600-no/IMG_20141124_144008.jpg"),
+                    new LatLng(-33.89102, 151.277726),
+                    CITY_SYDNEY
+            ));
+
+            add(new Attraction(
+                    "Taronga Zoo",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam eros velit, faucibus in mi in, accumsan eleifend magna. Fusce efficitur volutpat leo nec finibus. Vivamus luctus quis dolor ac interdum.",
+                    "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam eros velit, faucibus in mi in, accumsan eleifend magna. Fusce efficitur volutpat leo nec finibus. Vivamus luctus quis dolor ac interdum. Donec iaculis, orci quis semper vulputate, tortor nisi porttitor tortor, at pretium ante quam ut odio. Donec fringilla sapien et dolor pharetra ultrices. Aenean faucibus felis non vulputate iaculis.\n\nEtiam eget dapibus ligula. Nunc facilisis dignissim tortor et elementum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam condimentum pellentesque mollis. Aliquam finibus urna ipsum, sed accumsan ante blandit quis. Vestibulum vel lacinia ligula. Nunc justo ex, volutpat nec justo ut, efficitur gravida lectus. Mauris cursus dui libero, vel tristique purus laoreet non.\n\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ultrices ullamcorper est, at consequat massa. Nam egestas at urna at pellentesque. Quisque lacus quam, efficitur vel erat eget, placerat feugiat eros. Mauris.",
+                    Uri.parse("https://lh6.googleusercontent.com/-kypwDfnk674/VGLWpQPm4VI/AAAAAAAAAB0/SrfL0fE9DnE/w600-no/OI000020_2.jpg"),
+                    Uri.parse("https://lh3.googleusercontent.com/-6_Ioko2ysgU/VHva2PjmRCI/AAAAAAAAAGM/cHjJC7ney4Q/w600-no/PC190054.JPG"),
+                    new LatLng(-33.843333, 151.241111),
+                    CITY_SYDNEY
+            ));
+        }});
+
+    }};
+
+    /**
+     * Creates a list of geofences based on the city locations
+     */
+    public static List<Geofence> getGeofenceList() {
+        List<Geofence> geofenceList = new ArrayList<Geofence>();
+        for (String city : CITY_LOCATIONS.keySet()) {
+            LatLng cityLatLng = CITY_LOCATIONS.get(city);
+            geofenceList.add(new Geofence.Builder()
+                    .setCircularRegion(cityLatLng.latitude, cityLatLng.longitude, TRIGGER_RADIUS)
+                    .setRequestId(city)
+                    .setTransitionTypes(TRIGGER_TRANSITION)
+                    .setExpirationDuration(EXPIRATION_DURATION)
+                    .build());
+        }
+        return geofenceList;
+    }
+
+    public static String getClosestCity(LatLng curLatLng) {
+        if (curLatLng == null) {
+            // In debug build still return a city so some data is displayed
+            if (BuildConfig.DEBUG) {
+                return TEST_CITY;
+            }
+            return null;
+        }
+
+        double minDistance = 0;
+        String closestCity = null;
+        for (Map.Entry<String, LatLng> entry: CITY_LOCATIONS.entrySet()) {
+            double distance = SphericalUtil.computeDistanceBetween(curLatLng, entry.getValue());
+            if (minDistance == 0 || distance < minDistance) {
+                minDistance = distance;
+                closestCity = entry.getKey();
+            }
+        }
+        return closestCity;
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/ListenerService.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/ListenerService.java
new file mode 100644
index 0000000..c8a5790
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/ListenerService.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.service;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+import com.example.android.xyztouristattractions.ui.DetailActivity;
+import com.google.android.gms.wearable.MessageEvent;
+import com.google.android.gms.wearable.WearableListenerService;
+import com.example.android.xyztouristattractions.common.Constants;
+
+/**
+ * A Wear listener service, used to receive inbound messages from
+ * the Wear device.
+ */
+public class ListenerService extends WearableListenerService {
+    private static final String TAG = ListenerService.class.getSimpleName();
+
+    @Override
+    public void onMessageReceived(MessageEvent messageEvent) {
+        Log.v(TAG, "onMessageReceived: " + messageEvent);
+
+        if (Constants.CLEAR_NOTIFICATIONS_PATH.equals(messageEvent.getPath())) {
+            // Request for this device to clear its notifications
+            UtilityService.clearNotification(this);
+        } else if (Constants.START_ATTRACTION_PATH.equals(messageEvent.getPath())) {
+            // Request for this device open the attraction detail screen
+            // to a specific tourist attraction
+            String attractionName = new String(messageEvent.getData());
+            Intent intent = DetailActivity.getLaunchIntent(this, attractionName);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(intent);
+        } else if (Constants.START_NAVIGATION_PATH.equals(messageEvent.getPath())) {
+            // Request for this device to start Maps walking navigation to
+            // specific tourist attraction
+            String attractionQuery = new String(messageEvent.getData());
+            Uri uri = Uri.parse(Constants.MAPS_NAVIGATION_INTENT_URI + Uri.encode(attractionQuery));
+            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(intent);
+        }
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/UtilityReceiver.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/UtilityReceiver.java
new file mode 100644
index 0000000..0a10ba2
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/UtilityReceiver.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.service;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.v4.content.WakefulBroadcastReceiver;
+
+/**
+ * A simply utility receiver used to ensure the device stays awake for the
+ * duration of the work being done by
+ * {@link com.example.android.xyztouristattractions.service.UtilityService}.
+ */
+public class UtilityReceiver extends WakefulBroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Pass right over to UtilityService class, the wakeful receiver is
+        // just needed in case the geofence is triggered while the device
+        // is asleep otherwise the service may not have time to trigger the
+        // notification.
+        intent.setClass(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_GEOFENCE_TRIGGERED);
+        startWakefulService(context, intent);
+    }
+
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/UtilityService.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/UtilityService.java
new file mode 100644
index 0000000..c011013
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/service/UtilityService.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.service;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.location.Location;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+
+import com.bumptech.glide.Glide;
+import com.example.android.xyztouristattractions.R;
+import com.example.android.xyztouristattractions.provider.TouristAttractions;
+import com.example.android.xyztouristattractions.ui.DetailActivity;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.location.FusedLocationProviderApi;
+import com.google.android.gms.location.Geofence;
+import com.google.android.gms.location.GeofencingEvent;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.PutDataMapRequest;
+import com.google.android.gms.wearable.PutDataRequest;
+import com.google.android.gms.wearable.Wearable;
+import com.example.android.xyztouristattractions.common.Attraction;
+import com.example.android.xyztouristattractions.common.Constants;
+import com.example.android.xyztouristattractions.common.Utils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import static com.example.android.xyztouristattractions.provider.TouristAttractions.ATTRACTIONS;
+import static com.google.android.gms.location.LocationServices.FusedLocationApi;
+import static com.google.android.gms.location.LocationServices.GeofencingApi;
+
+/**
+ * A utility IntentService, used for a variety of asynchronous background
+ * operations that do not necessarily need to be tied to a UI.
+ */
+public class UtilityService extends IntentService {
+    private static final String TAG = UtilityService.class.getSimpleName();
+
+    public static final String ACTION_GEOFENCE_TRIGGERED = "geofence_triggered";
+    private static final String ACTION_LOCATION_UPDATED = "location_updated";
+    private static final String ACTION_REQUEST_LOCATION = "request_location";
+    private static final String ACTION_ADD_GEOFENCES = "add_geofences";
+    private static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
+    private static final String ACTION_CLEAR_REMOTE_NOTIFICATIONS = "clear_remote_notifications";
+    private static final String ACTION_FAKE_UPDATE = "fake_update";
+    private static final String EXTRA_TEST_MICROAPP = "test_microapp";
+
+    public static IntentFilter getLocationUpdatedIntentFilter() {
+        return new IntentFilter(UtilityService.ACTION_LOCATION_UPDATED);
+    }
+
+    public static void triggerWearTest(Context context, boolean microApp) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_FAKE_UPDATE);
+        intent.putExtra(EXTRA_TEST_MICROAPP, microApp);
+        context.startService(intent);
+    }
+
+    public static void addGeofences(Context context) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_ADD_GEOFENCES);
+        context.startService(intent);
+    }
+
+    public static void requestLocation(Context context) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_REQUEST_LOCATION);
+        context.startService(intent);
+    }
+
+    public static void clearNotification(Context context) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_CLEAR_NOTIFICATION);
+        context.startService(intent);
+    }
+
+    public static Intent getClearRemoteNotificationsIntent(Context context) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_CLEAR_REMOTE_NOTIFICATIONS);
+        return intent;
+    }
+
+    public UtilityService() {
+        super(TAG);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        String action = intent != null ? intent.getAction() : null;
+        if (ACTION_ADD_GEOFENCES.equals(action)) {
+            addGeofencesInternal();
+        } else if (ACTION_GEOFENCE_TRIGGERED.equals(action)) {
+            geofenceTriggered(intent);
+        } else if (ACTION_REQUEST_LOCATION.equals(action)) {
+            requestLocationInternal();
+        } else if (ACTION_LOCATION_UPDATED.equals(action)) {
+            locationUpdated(intent);
+        } else if (ACTION_CLEAR_NOTIFICATION.equals(action)) {
+            clearNotificationInternal();
+        } else if (ACTION_CLEAR_REMOTE_NOTIFICATIONS.equals(action)) {
+            clearRemoteNotifications();
+        } else if (ACTION_FAKE_UPDATE.equals(action)) {
+            LatLng currentLocation = Utils.getLocation(this);
+
+            // If location unknown use test city, otherwise use closest city
+            String city = currentLocation == null ? TouristAttractions.TEST_CITY :
+                    TouristAttractions.getClosestCity(currentLocation);
+
+            showNotification(city,
+                    intent.getBooleanExtra(EXTRA_TEST_MICROAPP, Constants.USE_MICRO_APP));
+        }
+    }
+
+    /**
+     * Add geofences using Play Services
+     */
+    private void addGeofencesInternal() {
+        Log.v(TAG, ACTION_ADD_GEOFENCES);
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(LocationServices.API)
+                .build();
+
+        // It's OK to use blockingConnect() here as we are running in an
+        // IntentService that executes work on a separate (background) thread.
+        ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                    this, 0, new Intent(this, UtilityReceiver.class), 0);
+            GeofencingApi.addGeofences(googleApiClient,
+                    TouristAttractions.getGeofenceList(), pendingIntent);
+            googleApiClient.disconnect();
+        } else {
+            Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG,
+                    connectionResult.getErrorCode()));
+        }
+    }
+
+    /**
+     * Called when a geofence is triggered
+     */
+    private void geofenceTriggered(Intent intent) {
+        Log.v(TAG, ACTION_GEOFENCE_TRIGGERED);
+
+        // Check if geofences are enabled
+        boolean geofenceEnabled = Utils.getGeofenceEnabled(this);
+
+        // Extract the geofences from the intent
+        GeofencingEvent event = GeofencingEvent.fromIntent(intent);
+        List<Geofence> geofences = event.getTriggeringGeofences();
+
+        if (geofenceEnabled && geofences != null && geofences.size() > 0) {
+            if (event.getGeofenceTransition() == Geofence.GEOFENCE_TRANSITION_ENTER) {
+                // Trigger the notification based on the first geofence
+                showNotification(geofences.get(0).getRequestId(), Constants.USE_MICRO_APP);
+            } else if (event.getGeofenceTransition() == Geofence.GEOFENCE_TRANSITION_EXIT) {
+                // Clear notifications
+                clearNotificationInternal();
+                clearRemoteNotifications();
+            }
+        }
+        UtilityReceiver.completeWakefulIntent(intent);
+    }
+
+    /**
+     * Called when a location update is requested
+     */
+    private void requestLocationInternal() {
+        Log.v(TAG, ACTION_REQUEST_LOCATION);
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(LocationServices.API)
+                .build();
+
+        // It's OK to use blockingConnect() here as we are running in an
+        // IntentService that executes work on a separate (background) thread.
+        ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
+
+            Intent locationUpdatedIntent = new Intent(this, UtilityService.class);
+            locationUpdatedIntent.setAction(ACTION_LOCATION_UPDATED);
+
+            // Send last known location out first if available
+            Location location = FusedLocationApi.getLastLocation(googleApiClient);
+            if (location != null) {
+                Intent lastLocationIntent = new Intent(locationUpdatedIntent);
+                lastLocationIntent.putExtra(
+                        FusedLocationProviderApi.KEY_LOCATION_CHANGED, location);
+                startService(lastLocationIntent);
+            }
+
+            // Request new location
+            LocationRequest mLocationRequest = new LocationRequest()
+                    .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
+            FusedLocationApi.requestLocationUpdates(
+                    googleApiClient, mLocationRequest,
+                    PendingIntent.getService(this, 0, locationUpdatedIntent, 0));
+
+            googleApiClient.disconnect();
+        } else {
+            Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG,
+                    connectionResult.getErrorCode()));
+        }
+    }
+
+    /**
+     * Called when the location has been updated
+     */
+    private void locationUpdated(Intent intent) {
+        Log.v(TAG, ACTION_LOCATION_UPDATED);
+
+        // Extra new location
+        Location location =
+                intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
+
+        if (location != null) {
+            LatLng latLngLocation = new LatLng(location.getLatitude(), location.getLongitude());
+
+            // Store in a local preference as well
+            Utils.storeLocation(this, latLngLocation);
+
+            // Send a local broadcast so if an Activity is open it can respond
+            // to the updated location
+            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
+        }
+    }
+
+    /**
+     * Clears the local device notification
+     */
+    private void clearNotificationInternal() {
+        Log.v(TAG, ACTION_CLEAR_NOTIFICATION);
+        NotificationManagerCompat.from(this).cancel(Constants.MOBILE_NOTIFICATION_ID);
+    }
+
+    /**
+     * Clears remote device notifications using the Wearable message API
+     */
+    private void clearRemoteNotifications() {
+        Log.v(TAG, ACTION_CLEAR_REMOTE_NOTIFICATIONS);
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(Wearable.API)
+                .build();
+
+        // It's OK to use blockingConnect() here as we are running in an
+        // IntentService that executes work on a separate (background) thread.
+        ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
+
+            // Loop through all nodes and send a clear notification message
+            Iterator<String> itr = Utils.getNodes(googleApiClient).iterator();
+            while (itr.hasNext()) {
+                Wearable.MessageApi.sendMessage(
+                        googleApiClient, itr.next(), Constants.CLEAR_NOTIFICATIONS_PATH, null);
+            }
+            googleApiClient.disconnect();
+        }
+    }
+
+
+    /**
+     * Show the notification. Either the regular notification with wearable features
+     * added to enhance, or trigger the full micro app on the wearable.
+     *
+     * @param cityId The city to trigger the notification for
+     * @param microApp If the micro app should be triggered or just enhanced notifications
+     */
+    private void showNotification(String cityId, boolean microApp) {
+
+        List<Attraction> attractions = ATTRACTIONS.get(cityId);
+
+        if (microApp) {
+            // If micro app we first need to transfer some data over
+            sendDataToWearable(attractions);
+        }
+
+        // The first (closest) tourist attraction
+        Attraction attraction = attractions.get(0);
+
+        // Limit attractions to send
+        int count = attractions.size() > Constants.MAX_ATTRACTIONS ?
+                Constants.MAX_ATTRACTIONS : attractions.size();
+
+        // Pull down the tourist attraction images from the network and store
+        HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
+        try {
+            for (int i = 0; i < count; i++) {
+                bitmaps.put(attractions.get(i).name,
+                        Glide.with(this)
+                                .load(attractions.get(i).imageUrl)
+                                .asBitmap()
+                                .into(Constants.WEAR_IMAGE_SIZE, Constants.WEAR_IMAGE_SIZE)
+                                .get());
+            }
+        } catch (InterruptedException | ExecutionException e) {
+            Log.e(TAG, "Error fetching image from network: " + e);
+        }
+
+        // The intent to trigger when the notification is tapped
+        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
+                DetailActivity.getLaunchIntent(this, attraction.name),
+                PendingIntent.FLAG_UPDATE_CURRENT);
+
+        // The intent to trigger when the notification is dismissed, in this case
+        // we want to clear remote notifications as well
+        PendingIntent deletePendingIntent =
+                PendingIntent.getService(this, 0, getClearRemoteNotificationsIntent(this), 0);
+
+        // Construct the main notification
+        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
+                .setStyle(new NotificationCompat.BigPictureStyle()
+                                .bigPicture(bitmaps.get(attraction.name))
+                                .setBigContentTitle(attraction.name)
+                                .setSummaryText(getString(R.string.nearby_attraction))
+                )
+                .setLocalOnly(microApp)
+                .setContentTitle(attraction.name)
+                .setContentText(getString(R.string.nearby_attraction))
+                .setSmallIcon(R.drawable.ic_stat_icon)
+                .setContentIntent(pendingIntent)
+                .setDeleteIntent(deletePendingIntent)
+                .setColor(getResources().getColor(R.color.colorPrimary))
+                .setCategory(Notification.CATEGORY_RECOMMENDATION)
+                .setAutoCancel(true);
+
+        if (!microApp) {
+            // If not a micro app, create some wearable pages for
+            // the other nearby tourist attractions.
+            ArrayList<Notification> pages = new ArrayList<Notification>();
+            for (int i = 1; i < count; i++) {
+
+                // Calculate the distance from current location to tourist attraction
+                String distance = Utils.formatDistanceBetween(
+                        Utils.getLocation(this), attractions.get(i).location);
+
+                // Construct the notification and add it as a page
+                pages.add(new NotificationCompat.Builder(this)
+                        .setContentTitle(attractions.get(i).name)
+                        .setContentText(distance)
+                        .setSmallIcon(R.drawable.ic_stat_icon)
+                        .extend(new NotificationCompat.WearableExtender()
+                                .setBackground(bitmaps.get(attractions.get(i).name))
+                        )
+                        .build());
+            }
+            builder.extend(new NotificationCompat.WearableExtender().addPages(pages));
+        }
+
+        // Trigger the notification
+        NotificationManagerCompat.from(this).notify(
+                Constants.MOBILE_NOTIFICATION_ID, builder.build());
+    }
+
+    /**
+     * Transfer the required data over to the wearable
+     * @param attractions list of attraction data to transfer over
+     */
+    private void sendDataToWearable(List<Attraction> attractions) {
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(Wearable.API)
+                .build();
+
+        // It's OK to use blockingConnect() here as we are running in an
+        // IntentService that executes work on a separate (background) thread.
+        ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+        // Limit attractions to send
+        int count = attractions.size() > Constants.MAX_ATTRACTIONS ?
+                Constants.MAX_ATTRACTIONS : attractions.size();
+
+        ArrayList<DataMap> attractionsData = new ArrayList<DataMap>(count);
+
+        for (int i = 0; i < count; i++) {
+            Attraction attraction = attractions.get(i);
+
+            Bitmap image = null;
+            Bitmap secondaryImage = null;
+
+            try {
+                // Fetch and resize attraction image bitmap
+                image = Glide.with(this)
+                        .load(attraction.imageUrl)
+                        .asBitmap()
+                        .into(Constants.WEAR_IMAGE_SIZE_PARALLAX_WIDTH, Constants.WEAR_IMAGE_SIZE)
+                        .get();
+
+                secondaryImage = Glide.with(this)
+                        .load(attraction.secondaryImageUrl)
+                        .asBitmap()
+                        .into(Constants.WEAR_IMAGE_SIZE_PARALLAX_WIDTH, Constants.WEAR_IMAGE_SIZE)
+                        .get();
+            } catch (InterruptedException | ExecutionException e) {
+                Log.e(TAG, "Exception loading bitmap from network");
+            }
+
+            if (image != null && secondaryImage != null) {
+
+                DataMap attractionData = new DataMap();
+
+                String distance = Utils.formatDistanceBetween(
+                        Utils.getLocation(this), attraction.location);
+
+                attractionData.putString(Constants.EXTRA_TITLE, attraction.name);
+                attractionData.putString(Constants.EXTRA_DESCRIPTION, attraction.description);
+                attractionData.putDouble(
+                        Constants.EXTRA_LOCATION_LAT, attraction.location.latitude);
+                attractionData.putDouble(
+                        Constants.EXTRA_LOCATION_LNG, attraction.location.longitude);
+                attractionData.putString(Constants.EXTRA_DISTANCE, distance);
+                attractionData.putString(Constants.EXTRA_CITY, attraction.city);
+                attractionData.putAsset(Constants.EXTRA_IMAGE,
+                        Utils.createAssetFromBitmap(image));
+                attractionData.putAsset(Constants.EXTRA_IMAGE_SECONDARY,
+                        Utils.createAssetFromBitmap(secondaryImage));
+
+                attractionsData.add(attractionData);
+            }
+        }
+
+        if (connectionResult.isSuccess() && googleApiClient.isConnected()
+                && attractionsData.size() > 0) {
+
+            PutDataMapRequest dataMap = PutDataMapRequest.create(Constants.ATTRACTION_PATH);
+            dataMap.getDataMap().putDataMapArrayList(Constants.EXTRA_ATTRACTIONS, attractionsData);
+            dataMap.getDataMap().putLong(Constants.EXTRA_TIMESTAMP, new Date().getTime());
+            PutDataRequest request = dataMap.asPutDataRequest();
+
+            // Send the data over
+            DataApi.DataItemResult result =
+                    Wearable.DataApi.putDataItem(googleApiClient, request).await();
+
+            if (!result.getStatus().isSuccess()) {
+                Log.e(TAG, String.format("Error sending data using DataApi (error code = %d)",
+                        result.getStatus().getStatusCode()));
+            }
+
+        } else {
+            Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG,
+                    connectionResult.getErrorCode()));
+        }
+        googleApiClient.disconnect();
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListActivity.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListActivity.java
new file mode 100644
index 0000000..4bbfcab
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListActivity.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.ui;
+
+import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import com.example.android.xyztouristattractions.R;
+import com.example.android.xyztouristattractions.common.Utils;
+import com.example.android.xyztouristattractions.service.UtilityService;
+
+/**
+ * The main tourist attraction activity screen which contains a list of
+ * attractions sorted by distance.
+ */
+public class AttractionListActivity extends ActionBarActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        if (savedInstanceState == null) {
+            getSupportFragmentManager().beginTransaction()
+                    .add(R.id.container, new AttractionListFragment())
+                    .commit();
+        }
+
+        UtilityService.addGeofences(this);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        UtilityService.requestLocation(this);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        switch (item.getItemId()) {
+            case R.id.test_notification:
+                UtilityService.triggerWearTest(this, false);
+                return true;
+            case R.id.test_microapp:
+                UtilityService.triggerWearTest(this, true);
+                return true;
+            case R.id.test_toggle_geofence:
+                boolean geofenceEnabled = Utils.getGeofenceEnabled(this);
+                Utils.storeGeofenceEnabled(this, !geofenceEnabled);
+                Toast.makeText(this, geofenceEnabled ?
+                        "Debug: Geofencing trigger disabled" :
+                        "Debug: Geofencing trigger enabled", Toast.LENGTH_SHORT).show();
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListFragment.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListFragment.java
new file mode 100644
index 0000000..b5e5041
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionListFragment.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.ui;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Location;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.LocalBroadcastManager;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.example.android.xyztouristattractions.R;
+import com.example.android.xyztouristattractions.common.Attraction;
+import com.example.android.xyztouristattractions.common.Constants;
+import com.example.android.xyztouristattractions.common.Utils;
+import com.example.android.xyztouristattractions.provider.TouristAttractions;
+import com.example.android.xyztouristattractions.service.UtilityService;
+import com.google.android.gms.location.FusedLocationProviderApi;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.maps.android.SphericalUtil;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import static com.example.android.xyztouristattractions.provider.TouristAttractions.ATTRACTIONS;
+
+/**
+ * The main tourist attraction fragment which contains a list of attractions
+ * sorted by distance (contained inside
+ * {@link com.example.android.xyztouristattractions.ui.AttractionListActivity}).
+ */
+public class AttractionListFragment extends Fragment {
+
+    private AttractionAdapter mAdapter;
+    private LatLng mLatestLocation;
+    private int mImageSize;
+
+    public AttractionListFragment() {}
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        // Load a larger size image to make the activity transition to the detail screen smooth
+        mImageSize = getResources().getDimensionPixelSize(R.dimen.image_size)
+                * Constants.IMAGE_ANIM_MULTIPLIER;
+
+        mLatestLocation = Utils.getLocation(getActivity());
+        List<Attraction> attractions = loadAttractionsFromLocation(mLatestLocation);
+        mAdapter = new AttractionAdapter(getActivity(), attractions);
+
+        View view = inflater.inflate(R.layout.fragment_main, container, false);
+        AttractionsRecyclerView recyclerView =
+                (AttractionsRecyclerView) view.findViewById(android.R.id.list);
+        recyclerView.setEmptyView(view.findViewById(android.R.id.empty));
+        recyclerView.setHasFixedSize(true);
+        recyclerView.setLayoutManager(new GridLayoutManager(
+                getActivity(), getResources().getInteger(R.integer.list_columns)));
+        recyclerView.setAdapter(mAdapter);
+
+        return view;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
+                mBroadcastReceiver, UtilityService.getLocationUpdatedIntentFilter());
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mBroadcastReceiver);
+    }
+
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Location location =
+                    intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
+            if (location != null) {
+                mLatestLocation = new LatLng(location.getLatitude(), location.getLongitude());
+                mAdapter.mAttractionList = loadAttractionsFromLocation(mLatestLocation);
+                mAdapter.notifyDataSetChanged();
+            }
+        }
+    };
+
+    private static List<Attraction> loadAttractionsFromLocation(final LatLng curLatLng) {
+        String closestCity = TouristAttractions.getClosestCity(curLatLng);
+        if (closestCity != null) {
+            List<Attraction> attractions = ATTRACTIONS.get(closestCity);
+            if (curLatLng != null) {
+                Collections.sort(attractions,
+                        new Comparator<Attraction>() {
+                            @Override
+                            public int compare(Attraction lhs, Attraction rhs) {
+                                double lhsDistance = SphericalUtil.computeDistanceBetween(
+                                        lhs.location, curLatLng);
+                                double rhsDistance = SphericalUtil.computeDistanceBetween(
+                                        rhs.location, curLatLng);
+                                return (int) (lhsDistance - rhsDistance);
+                            }
+                        }
+                );
+            }
+            return attractions;
+        }
+        return null;
+    }
+
+    private class AttractionAdapter extends RecyclerView.Adapter<ViewHolder>
+            implements ItemClickListener {
+
+        public List<Attraction> mAttractionList;
+        private Context mContext;
+
+        public AttractionAdapter(Context context, List<Attraction> attractions) {
+            super();
+            mContext = context;
+            mAttractionList = attractions;
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            LayoutInflater inflater = LayoutInflater.from(mContext);
+            View view = inflater.inflate(R.layout.list_row, parent, false);
+            return new ViewHolder(view, this);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+            Attraction attraction = mAttractionList.get(position);
+
+            holder.mTitleTextView.setText(attraction.name);
+            holder.mDescriptionTextView.setText(attraction.description);
+            Glide.with(mContext)
+                    .load(attraction.imageUrl)
+                    .placeholder(R.drawable.empty_photo)
+                    .override(mImageSize, mImageSize)
+                    .into(holder.mImageView);
+
+            String distance =
+                    Utils.formatDistanceBetween(mLatestLocation, attraction.location);
+            if (TextUtils.isEmpty(distance)) {
+                holder.mOverlayTextView.setVisibility(View.GONE);
+            } else {
+                holder.mOverlayTextView.setVisibility(View.VISIBLE);
+                holder.mOverlayTextView.setText(distance);
+            }
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public int getItemCount() {
+            return mAttractionList == null ? 0 : mAttractionList.size();
+        }
+
+        @Override
+        public void onItemClick(View view, int position) {
+            View heroView = view.findViewById(android.R.id.icon);
+            DetailActivity.launch(
+                    getActivity(), mAdapter.mAttractionList.get(position).name, heroView);
+        }
+    }
+
+    private static class ViewHolder extends RecyclerView.ViewHolder
+            implements View.OnClickListener {
+
+        TextView mTitleTextView;
+        TextView mDescriptionTextView;
+        TextView mOverlayTextView;
+        ImageView mImageView;
+        ItemClickListener mItemClickListener;
+
+        public ViewHolder(View view, ItemClickListener itemClickListener) {
+            super(view);
+            mTitleTextView = (TextView) view.findViewById(android.R.id.text1);
+            mDescriptionTextView = (TextView) view.findViewById(android.R.id.text2);
+            mOverlayTextView = (TextView) view.findViewById(R.id.overlaytext);
+            mImageView = (ImageView) view.findViewById(android.R.id.icon);
+            mItemClickListener = itemClickListener;
+            view.setOnClickListener(this);
+        }
+
+        @Override
+        public void onClick(View v) {
+            mItemClickListener.onItemClick(v, getPosition());
+        }
+    }
+
+    interface ItemClickListener {
+        void onItemClick(View view, int position);
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsRecyclerView.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsRecyclerView.java
new file mode 100644
index 0000000..cca3bf1
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsRecyclerView.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.ui;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Simple RecyclerView subclass that supports providing an empty view (which
+ * is displayed when the adapter has no data and hidden otherwise).
+ */
+public class AttractionsRecyclerView extends RecyclerView {
+    private View mEmptyView;
+
+    private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
+        @Override
+        public void onChanged() {
+            super.onChanged();
+            updateEmptyView();
+        }
+    };
+
+    public AttractionsRecyclerView(Context context) {
+        super(context);
+    }
+
+    public AttractionsRecyclerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AttractionsRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    /**
+     * Designate a view as the empty view. When the backing adapter has no
+     * data this view will be made visible and the recycler view hidden.
+     *
+     */
+    public void setEmptyView(View emptyView) {
+        mEmptyView = emptyView;
+    }
+
+    @Override
+    public void setAdapter(RecyclerView.Adapter adapter) {
+        if (getAdapter() != null) {
+            getAdapter().unregisterAdapterDataObserver(mDataObserver);
+        }
+        if (adapter != null) {
+            adapter.registerAdapterDataObserver(mDataObserver);
+        }
+        super.setAdapter(adapter);
+        updateEmptyView();
+    }
+
+    private void updateEmptyView() {
+        if (mEmptyView != null && getAdapter() != null) {
+            boolean showEmptyView = getAdapter().getItemCount() == 0;
+            mEmptyView.setVisibility(showEmptyView ? VISIBLE : GONE);
+            setVisibility(showEmptyView ? GONE : VISIBLE);
+        }
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailActivity.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailActivity.java
new file mode 100644
index 0000000..a83f480
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailActivity.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.ui;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.ActivityOptionsCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.view.View;
+
+import com.example.android.xyztouristattractions.R;
+
+/**
+ * The tourist attraction detail activity screen which contains the details of
+ * a single attraction.
+ */
+public class DetailActivity extends ActionBarActivity {
+
+    private static final String EXTRA_ATTRACTION = "attraction";
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public static void launch(Activity activity, String attraction, View heroView) {
+        Intent intent = getLaunchIntent(activity, attraction);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(
+                    activity, heroView, heroView.getTransitionName());
+            ActivityCompat.startActivity(activity, intent, options.toBundle());
+        } else {
+            activity.startActivity(intent);
+        }
+    }
+
+    public static Intent getLaunchIntent(Context context, String attraction) {
+        Intent intent = new Intent(context, DetailActivity.class);
+        intent.putExtra(EXTRA_ATTRACTION, attraction);
+        return intent;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        String attraction = getIntent().getStringExtra(EXTRA_ATTRACTION);
+        if (savedInstanceState == null) {
+            getSupportFragmentManager().beginTransaction()
+                    .add(R.id.container, DetailFragment.createInstance(attraction))
+                    .commit();
+        }
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailFragment.java b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailFragment.java
new file mode 100644
index 0000000..0df7772
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/java/com/example/android/xyztouristattractions/ui/DetailFragment.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.ui;
+
+import android.app.TaskStackBuilder;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.NavUtils;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.example.android.xyztouristattractions.R;
+import com.example.android.xyztouristattractions.common.Attraction;
+import com.example.android.xyztouristattractions.common.Constants;
+import com.example.android.xyztouristattractions.common.Utils;
+import com.google.android.gms.maps.model.LatLng;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.example.android.xyztouristattractions.provider.TouristAttractions.ATTRACTIONS;
+
+/**
+ * The tourist attraction detail fragment which contains the details of a
+ * a single attraction (contained inside
+ * {@link com.example.android.xyztouristattractions.ui.DetailActivity}).
+ */
+public class DetailFragment extends Fragment {
+
+    private static final String EXTRA_ATTRACTION = "attraction";
+    private Attraction mAttraction;
+
+    public static DetailFragment createInstance(String attractionName) {
+        DetailFragment detailFragment = new DetailFragment();
+        Bundle bundle = new Bundle();
+        bundle.putString(EXTRA_ATTRACTION, attractionName);
+        detailFragment.setArguments(bundle);
+        return detailFragment;
+    }
+
+    public DetailFragment() {}
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        setHasOptionsMenu(true);
+        View view = inflater.inflate(R.layout.fragment_detail, container, false);
+        String attractionName = getArguments().getString(EXTRA_ATTRACTION);
+        mAttraction = findAttraction(attractionName);
+
+        if (mAttraction == null) {
+            getActivity().finish();
+            return null;
+        }
+
+        TextView nameTextView = (TextView) view.findViewById(R.id.nameTextView);
+        TextView descTextView = (TextView) view.findViewById(R.id.descriptionTextView);
+        TextView distanceTextView = (TextView) view.findViewById(R.id.distanceTextView);
+        ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
+
+        LatLng location = Utils.getLocation(getActivity());
+        String distance = Utils.formatDistanceBetween(location, mAttraction.location);
+        if (TextUtils.isEmpty(distance)) {
+            distanceTextView.setVisibility(View.GONE);
+        }
+
+        nameTextView.setText(attractionName);
+        distanceTextView.setText(distance);
+        descTextView.setText(mAttraction.longDescription);
+
+        int imageSize = getResources().getDimensionPixelSize(R.dimen.image_size)
+                * Constants.IMAGE_ANIM_MULTIPLIER;
+        Glide.with(getActivity())
+                .load(mAttraction.imageUrl)
+                .placeholder(R.color.lighter_gray)
+                .override(imageSize, imageSize)
+                .into(imageView);
+        return view;
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        inflater.inflate(R.menu.detail, menu);
+        super.onCreateOptionsMenu(menu, inflater);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                // Some small additions to handle "up" navigation correctly
+                Intent upIntent = NavUtils.getParentActivityIntent(getActivity());
+                upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+                // Check if up activity needs to be created (usually when
+                // detail screen is opened from a notification or from the
+                // Wearable app
+                if (NavUtils.shouldUpRecreateTask(getActivity(), upIntent)
+                        || getActivity().isTaskRoot()) {
+
+                    // Synthesize parent stack
+                    TaskStackBuilder.create(getActivity())
+                            .addNextIntentWithParentStack(upIntent)
+                            .startActivities();
+                }
+
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                    // On Lollipop+ we finish so to run the nice animation
+                    getActivity().finishAfterTransition();
+                    return true;
+                }
+
+                // Otherwise let the system handle navigating "up"
+                return false;
+            case R.id.map:
+                Intent intent = new Intent(Intent.ACTION_VIEW);
+                intent.setData(Uri.parse(Constants.MAPS_INTENT_URI +
+                        Uri.encode(mAttraction.name + ", " + mAttraction.city)));
+                startActivity(intent);
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    /**
+     * Really hacky loop for finding attraction in our static content provider.
+     * Obviously would not be used in a production app.
+     */
+    private Attraction findAttraction(String attractionName) {
+        for (Map.Entry<String, List<Attraction>> attractionsList : ATTRACTIONS.entrySet()) {
+            List<Attraction> attractions = attractionsList.getValue();
+            for (Attraction attraction : attractions) {
+                if (attractionName.equals(attraction.name)) {
+                    return attraction;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-hdpi/ic_stat_icon.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-hdpi/ic_stat_icon.png
new file mode 100644
index 0000000..a13c449
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-hdpi/ic_stat_icon.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-mdpi/ic_stat_icon.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-mdpi/ic_stat_icon.png
new file mode 100644
index 0000000..fd79493
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-mdpi/ic_stat_icon.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-nodpi/empty_photo.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-nodpi/empty_photo.png
new file mode 100644
index 0000000..da1478a
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-nodpi/empty_photo.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xhdpi/ic_stat_icon.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xhdpi/ic_stat_icon.png
new file mode 100644
index 0000000..3893e7e
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xhdpi/ic_stat_icon.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xxhdpi/ic_action_map.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xxhdpi/ic_action_map.png
new file mode 100644
index 0000000..ddac35d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xxhdpi/ic_action_map.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xxhdpi/ic_stat_icon.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xxhdpi/ic_stat_icon.png
new file mode 100644
index 0000000..0b0a76c
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/drawable-xxhdpi/ic_stat_icon.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/activity_main.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..8661610
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".MainActivity">
+
+</LinearLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/fragment_detail.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/fragment_detail.xml
new file mode 100644
index 0000000..dffeb4e
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/fragment_detail.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:weightSum="100">
+
+    <ImageView
+        android:id="@+id/imageView"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="50"
+        android:scaleType="centerCrop"
+        android:transitionName="image" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="?colorPrimary"
+        android:orientation="vertical"
+        android:paddingStart="@dimen/keyline2"
+        android:paddingEnd="@dimen/keyline3"
+        android:paddingTop="@dimen/standard_margin"
+        android:paddingBottom="@dimen/standard_margin"
+        android:elevation="2dp">
+
+        <TextView
+            android:id="@+id/nameTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:maxLines="2"
+            android:ellipsize="end"
+            android:textIsSelectable="true"
+            style="@style/TextAppearance.AppCompat.Title.Inverse"
+            android:transitionName="title" />
+
+        <TextView
+            android:id="@+id/distanceTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/TextAppearance.AppCompat.Subhead.Inverse" />
+
+    </LinearLayout>
+    
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="50"
+        android:paddingTop="@dimen/standard_margin"
+        android:paddingBottom="@dimen/standard_margin"
+        android:scrollbarStyle="outsideOverlay"
+        android:clipToPadding="false">
+
+        <TextView
+            android:id="@+id/descriptionTextView"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:textIsSelectable="true"
+            style="@style/TextAppearance.AppCompat.Body1"
+            android:paddingStart="@dimen/keyline2"
+            android:paddingEnd="@dimen/keyline3" />
+
+    </ScrollView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/fragment_main.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/fragment_main.xml
new file mode 100644
index 0000000..94d110a
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/fragment_main.xml
@@ -0,0 +1,39 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".AttractionListFragment">
+
+    <com.example.android.xyztouristattractions.ui.AttractionsRecyclerView
+        android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scrollbars="vertical" />
+
+    <TextView
+        android:id="@android:id/empty"
+        android:text="@string/empty_list"
+        style="?android:attr/textAppearanceMedium"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center"
+        android:gravity="center" />
+
+</LinearLayout>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/list_row.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/list_row.xml
new file mode 100644
index 0000000..bb719d8
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/layout/list_row.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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:foreground="?android:attr/selectableItemBackground"
+    android:layout_height="@dimen/image_size">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="@dimen/image_size"
+            android:layout_height="match_parent"
+            android:src="@drawable/empty_photo"
+            android:scaleType="centerCrop"
+            android:transitionName="image" />
+
+        <TextView
+            android:id="@+id/overlaytext"
+            android:layout_width="@dimen/image_size"
+            android:layout_height="wrap_content"
+            android:layout_alignBottom="@android:id/icon"
+            android:gravity="center"
+            android:padding="@dimen/tiny_margin"
+            style="?android:textAppearanceSmallInverse"
+            android:background="@color/text_background"
+            tools:text="Overlay"/>
+
+        <TextView
+            android:id="@android:id/text1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@android:id/icon"
+            android:paddingTop="@dimen/small_margin"
+            android:paddingLeft="@dimen/small_margin"
+            android:paddingRight="@dimen/small_margin"
+            android:maxLines="1"
+            android:ellipsize="end"
+            style="?android:textAppearanceMedium"
+            tools:text="Title 1"
+            android:transitionName="image" />
+
+        <TextView
+            android:id="@android:id/text2"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_toRightOf="@android:id/icon"
+            android:layout_below="@android:id/text1"
+            android:padding="@dimen/small_margin"
+            android:ellipsize="end"
+            android:maxLines="4"
+            style="?android:textAppearanceSmall"
+            tools:text="Description goes here" />
+
+    </RelativeLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/menu/detail.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/menu/detail.xml
new file mode 100644
index 0000000..aeb5c98
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/menu/detail.xml
@@ -0,0 +1,28 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".MainActivity" >
+
+    <item android:id="@+id/map"
+        android:title="@string/action_map"
+        android:orderInCategory="100"
+        android:icon="@drawable/ic_action_map"
+        app:showAsAction="ifRoom" />
+
+</menu>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/menu/main.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/menu/main.xml
new file mode 100644
index 0000000..1f840a7
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/menu/main.xml
@@ -0,0 +1,37 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context=".MainActivity" >
+
+    <item android:id="@+id/test_notification"
+        android:title="@string/action_test_notification"
+        android:orderInCategory="100"
+        app:showAsAction="never" />
+
+    <item android:id="@+id/test_microapp"
+        android:title="@string/action_test_microapp"
+        android:orderInCategory="100"
+        app:showAsAction="never" />
+
+    <item android:id="@+id/test_toggle_geofence"
+        android:title="@string/action_test_toggle_geofence"
+        android:orderInCategory="100"
+        app:showAsAction="never" />
+
+</menu>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-hdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..45e7a43
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-mdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..d94f86f
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xhdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..61e67d6
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..3fb154d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..bd81bc1
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/transition-v21/explode.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/transition-v21/explode.xml
new file mode 100644
index 0000000..5dfa717
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/transition-v21/explode.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <explode>
+        <targets>
+            <target android:targetClass="android.widget.TextView" />
+            <target android:targetClass="android.widget.FrameLayout" />
+            <target android:targetClass="android.widget.LinearLayout" />
+            <target android:targetClass="android.widget.ImageView" />
+        </targets>
+    </explode>
+
+</transitionSet>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/transition-v21/fade.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/transition-v21/fade.xml
new file mode 100644
index 0000000..6c70ec5
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/transition-v21/fade.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <fade>
+        <targets>
+            <target android:targetClass="android.widget.TextView" />
+            <target android:targetClass="android.widget.FrameLayout" />
+            <target android:targetClass="android.widget.LinearLayout" />
+            <target android:targetClass="android.widget.ImageView" />
+        </targets>
+    </fade>
+
+</transitionSet>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw400dp/dimens.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw400dp/dimens.xml
new file mode 100644
index 0000000..ad90eaa
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw400dp/dimens.xml
@@ -0,0 +1,24 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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="standard_margin">24dp</dimen>
+    <dimen name="small_margin">12dp</dimen>
+    <dimen name="tiny_margin">8dp</dimen>
+    <dimen name="image_size">140dp</dimen>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw600dp/dimens.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..4e3a48e
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,25 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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="standard_margin">24dp</dimen>
+    <dimen name="small_margin">12dp</dimen>
+    <dimen name="tiny_margin">8dp</dimen>
+    <dimen name="image_size">200dp</dimen>
+    <dimen name="keyline2">80dp</dimen>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw720dp/dimens.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..73f0b8d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-sw720dp/dimens.xml
@@ -0,0 +1,24 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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="standard_margin">24dp</dimen>
+    <dimen name="small_margin">12dp</dimen>
+    <dimen name="tiny_margin">12dp</dimen>
+    <dimen name="image_size">250dp</dimen>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-v21/styles.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..319d664
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-v21/styles.xml
@@ -0,0 +1,31 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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>
+
+    <style name="XYZAppTheme" parent="XYZAppTheme.Base">
+        <item name="android:windowContentTransitions">true</item>
+        <item name="android:windowActivityTransitions">true</item>
+        <item name="android:windowExitTransition">@transition/fade</item>
+        <item name="android:windowReenterTransition">@transition/fade</item>
+        <item name="android:windowEnterTransition">@transition/explode</item>
+        <item name="android:windowReturnTransition">@transition/explode</item>
+        <item name="android:windowAllowEnterTransitionOverlap">false</item>
+        <item name="android:windowAllowReturnTransitionOverlap">false</item>
+        <item name="android:windowSharedElementsUseOverlay">false</item>
+    </style>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-w820dp/dimens.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..069102e
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values-w820dp/dimens.xml
@@ -0,0 +1,21 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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>
+
+    <integer name="list_columns">2</integer>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/colors.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/colors.xml
new file mode 100644
index 0000000..142f548
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/colors.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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="colorPrimary">#4e6cef</color>
+    <color name="colorPrimaryDark">#2a36b1</color>
+    <color name="colorAccent">#ff7043</color>
+
+    <color name="text_background">#90000000</color>
+    <color name="transparent_actionbar_background">#22000000</color>
+    <color name="lighter_gray">#ddd</color>
+
+</resources>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/dimens.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..03acfef
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/dimens.xml
@@ -0,0 +1,29 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="standard_margin">16dp</dimen>
+    <dimen name="small_margin">8dp</dimen>
+    <dimen name="tiny_margin">4dp</dimen>
+    <dimen name="image_size">120dp</dimen>
+    <item type="dimen" name="keyline1">@dimen/standard_margin</item>
+    <dimen name="keyline2">72dp</dimen>
+    <item type="dimen" name="keyline3">@dimen/standard_margin</item>
+    <integer name="list_columns">1</integer>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/strings.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c93e9dc
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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="empty_list">No attractions found nearby!</string>
+    <string name="nearby_attraction">Nearby tourist attraction</string>
+    <string name="action_test_notification">Test Notification</string>
+    <string name="action_test_microapp">Test Micro App</string>
+    <string name="action_test_toggle_geofence">Toggle Geofence Trigger</string>
+    <string name="action_map">Show on Map</string>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/styles.xml b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/styles.xml
new file mode 100644
index 0000000..05f1e47
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Application/src/main/res/values/styles.xml
@@ -0,0 +1,39 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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>
+
+    <!-- Base application theme. -->
+    <style name="XYZAppTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+        <item name="colorControlHighlight">?colorAccent</item>
+    </style>
+
+    <style name="XYZAppTheme" parent="XYZAppTheme.Base" />
+
+    <style name="XYZAppTheme.Detail">
+        <item name="windowActionBarOverlay">true</item>
+        <item name="actionBarStyle">@style/ActionBar.Detail</item>
+    </style>
+
+    <style name="ActionBar.Detail" parent="Widget.AppCompat.Light.ActionBar.Solid.Inverse">
+        <item name="background">@color/transparent_actionbar_background</item>
+        <item name="displayOptions">showHome|homeAsUp</item>
+    </style>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Shared/.gitignore b/wearable/wear/XYZTouristAttractions/Shared/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Shared/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2013 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/XYZTouristAttractions/Shared/proguard-rules.pro b/wearable/wear/XYZTouristAttractions/Shared/proguard-rules.pro
new file mode 100644
index 0000000..0661c7b
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Shared/proguard-rules.pro
@@ -0,0 +1,17 @@
+# 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 proguardFiles
+# directive in build.gradle.
+#
+# 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/XYZTouristAttractions/Shared/src/main/AndroidManifest.xml b/wearable/wear/XYZTouristAttractions/Shared/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..fe07629
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Shared/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.xyztouristattractions.common">
+
+    <application android:allowBackup="true"/>
+
+</manifest>
diff --git a/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Attraction.java b/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Attraction.java
new file mode 100644
index 0000000..d65359c
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Attraction.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.common;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+
+import com.google.android.gms.maps.model.LatLng;
+
+/**
+ * A simple shared tourist attraction class to easily pass data around. Used
+ * in both the mobile app and wearable app.
+ */
+public class Attraction {
+    public String name;
+    public String description;
+    public String longDescription;
+    public Uri imageUrl;
+    public Uri secondaryImageUrl;
+    public LatLng location;
+    public String city;
+
+    public Bitmap image;
+    public Bitmap secondaryImage;
+    public String distance;
+
+    public Attraction() {}
+
+    public Attraction(String name, String description, String longDescription, Uri imageUrl,
+                      Uri secondaryImageUrl, LatLng location, String city) {
+        this.name = name;
+        this.description = description;
+        this.longDescription = longDescription;
+        this.imageUrl = imageUrl;
+        this.secondaryImageUrl = secondaryImageUrl;
+        this.location = location;
+        this.city = city;
+    }
+}
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Constants.java b/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Constants.java
new file mode 100644
index 0000000..4d96436
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Constants.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.common;
+
+public class Constants {
+
+    private Constants() {};
+
+    // Set to false to have the geofence trigger use the enhanced notifications instead
+    public static final boolean USE_MICRO_APP = true;
+
+    public static final int GOOGLE_API_CLIENT_TIMEOUT_S = 10; // 10 seconds
+    public static final String GOOGLE_API_CLIENT_ERROR_MSG =
+            "Failed to connect to GoogleApiClient (error code = %d)";
+
+    // Used to size the images in the mobile app so they can animate cleanly from list to detail
+    public static final int IMAGE_ANIM_MULTIPLIER = 2;
+
+    // Resize images sent to Wear to 400x400px
+    public static final int WEAR_IMAGE_SIZE = 400;
+
+    // Except images that can be set as a background with parallax, set width 640x instead
+    public static final int WEAR_IMAGE_SIZE_PARALLAX_WIDTH = 640;
+
+    // The minimum bottom inset percent to use on a round screen device
+    public static final float WEAR_ROUND_MIN_INSET_PERCENT = 0.08f;
+
+    // Max # of attractions to show at once
+    public static final int MAX_ATTRACTIONS = 4;
+
+    // Notification IDs
+    public static final int MOBILE_NOTIFICATION_ID = 100;
+    public static final int WEAR_NOTIFICATION_ID = 200;
+
+    // Intent and bundle extras
+    public static final String EXTRA_ATTRACTIONS = "extra_attractions";
+    public static final String EXTRA_ATTRACTIONS_URI = "extra_attractions_uri";
+    public static final String EXTRA_TITLE = "extra_title";
+    public static final String EXTRA_DESCRIPTION = "extra_description";
+    public static final String EXTRA_LOCATION_LAT = "extra_location_lat";
+    public static final String EXTRA_LOCATION_LNG = "extra_location_lng";
+    public static final String EXTRA_DISTANCE = "extra_distance";
+    public static final String EXTRA_CITY = "extra_city";
+    public static final String EXTRA_IMAGE = "extra_image";
+    public static final String EXTRA_IMAGE_SECONDARY = "extra_image_secondary";
+    public static final String EXTRA_TIMESTAMP = "extra_timestamp";
+
+    // Wear Data API paths
+    public static final String ATTRACTION_PATH = "/attraction";
+    public static final String START_PATH = "/start";
+    public static final String START_ATTRACTION_PATH = START_PATH + "/attraction";
+    public static final String START_NAVIGATION_PATH = START_PATH + "/navigation";
+    public static final String CLEAR_NOTIFICATIONS_PATH = "/clear";
+
+    // Maps values
+    public static final String MAPS_INTENT_URI = "geo:0,0?q=";
+    public static final String MAPS_NAVIGATION_INTENT_URI = "google.navigation:mode=w&q=";
+
+}
diff --git a/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Utils.java b/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Utils.java
new file mode 100644
index 0000000..70e05bf
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Shared/src/main/java/com/example/android/xyztouristattractions/common/Utils.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.common;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Display;
+
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.wearable.Asset;
+import com.google.android.gms.wearable.Node;
+import com.google.android.gms.wearable.NodeApi;
+import com.google.android.gms.wearable.Wearable;
+import com.google.maps.android.SphericalUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.text.NumberFormat;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * This class contains shared static utility methods that both the mobile and
+ * wearable apps can use.
+ */
+public class Utils {
+    private static final String TAG = Utils.class.getSimpleName();
+
+    private static final String PREFERENCES_LAT = "lat";
+    private static final String PREFERENCES_LNG = "lng";
+    private static final String PREFERENCES_GEOFENCE_ENABLED = "geofence";
+    private static final String DISTANCE_KM_POSTFIX = "km";
+    private static final String DISTANCE_M_POSTFIX = "m";
+
+    /**
+     * Calculate distance between two LatLng points and format it nicely for
+     * display. As this is a sample, it only statically supports metric units.
+     * A production app should check locale and support the correct units.
+     */
+    public static String formatDistanceBetween(LatLng point1, LatLng point2) {
+        if (point1 == null || point2 == null) {
+            return null;
+        }
+
+        NumberFormat numberFormat = NumberFormat.getNumberInstance();
+        double distance = Math.round(SphericalUtil.computeDistanceBetween(point1, point2));
+
+        // Adjust to KM if M goes over 1000 (see javadoc of method for note
+        // on only supporting metric)
+        if (distance >= 1000) {
+            numberFormat.setMaximumFractionDigits(1);
+            return numberFormat.format(distance / 1000) + DISTANCE_KM_POSTFIX;
+        }
+        return numberFormat.format(distance) + DISTANCE_M_POSTFIX;
+    }
+
+    /**
+     * Store the location in the app preferences.
+     */
+    public static void storeLocation(Context context, LatLng location) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putLong(PREFERENCES_LAT, Double.doubleToRawLongBits(location.latitude));
+        editor.putLong(PREFERENCES_LNG, Double.doubleToRawLongBits(location.longitude));
+        editor.apply();
+    }
+
+    /**
+     * Fetch the location from app preferences.
+     */
+    public static LatLng getLocation(Context context) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        Long lat = prefs.getLong(PREFERENCES_LAT, Long.MAX_VALUE);
+        Long lng = prefs.getLong(PREFERENCES_LNG, Long.MAX_VALUE);
+        if (lat != Long.MAX_VALUE && lng != Long.MAX_VALUE) {
+            Double latDbl = Double.longBitsToDouble(lat);
+            Double lngDbl = Double.longBitsToDouble(lng);
+            return new LatLng(latDbl, lngDbl);
+        }
+        return null;
+    }
+
+    /**
+     * Store if geofencing triggers will show a notification in app preferences.
+     */
+    public static void storeGeofenceEnabled(Context context, boolean enable) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        SharedPreferences.Editor editor = prefs.edit();
+        editor.putBoolean(PREFERENCES_GEOFENCE_ENABLED, enable);
+        editor.apply();
+    }
+
+    /**
+     * Retrieve if geofencing triggers should show a notification from app preferences.
+     */
+    public static boolean getGeofenceEnabled(Context context) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        return prefs.getBoolean(PREFERENCES_GEOFENCE_ENABLED, true);
+    }
+
+    /**
+     * Convert an asset into a bitmap object synchronously. Only call this
+     * method from a background thread (it should never be called from the
+     * main/UI thread as it blocks).
+     */
+    public static Bitmap loadBitmapFromAsset(GoogleApiClient googleApiClient, Asset asset) {
+        if (asset == null) {
+            throw new IllegalArgumentException("Asset must be non-null");
+        }
+        // convert asset into a file descriptor and block until it's ready
+        InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
+                googleApiClient, asset).await().getInputStream();
+
+        if (assetInputStream == null) {
+            Log.w(TAG, "Requested an unknown Asset.");
+            return null;
+        }
+        // decode the stream into a bitmap
+        return BitmapFactory.decodeStream(assetInputStream);
+    }
+
+    /**
+     * Create a wearable asset from a bitmap.
+     */
+    public static Asset createAssetFromBitmap(Bitmap bitmap) {
+        if (bitmap != null) {
+            final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
+            return Asset.createFromBytes(byteStream.toByteArray());
+        }
+        return null;
+    }
+
+    /**
+     * Get a list of all wearable nodes that are connected synchronously.
+     * Only call this method from a background thread (it should never be
+     * called from the main/UI thread as it blocks).
+     */
+    public static Collection<String> getNodes(GoogleApiClient client) {
+        Collection<String> results= new HashSet<String>();
+        NodeApi.GetConnectedNodesResult nodes =
+                Wearable.NodeApi.getConnectedNodes(client).await();
+        for (Node node : nodes.getNodes()) {
+            results.add(node.getId());
+        }
+        return results;
+    }
+
+    /**
+     * Calculates the square insets on a round device. If the system insets are not set
+     * (set to 0) then the inner square of the circle is applied instead.
+     *
+     * @param display device default display
+     * @param systemInsets the system insets
+     * @return adjusted square insets for use on a round device
+     */
+    public static Rect calculateBottomInsetsOnRoundDevice(Display display, Rect systemInsets) {
+        Point size = new Point();
+        display.getSize(size);
+        int width = size.x + systemInsets.left + systemInsets.right;
+        int height = size.y + systemInsets.top + systemInsets.bottom;
+
+        // Minimum inset to use on a round screen, calculated as a fixed percent of screen height
+        int minInset = (int) (height * Constants.WEAR_ROUND_MIN_INSET_PERCENT);
+
+        // Use system inset if it is larger than min inset, otherwise use min inset
+        int bottomInset = systemInsets.bottom > minInset ? systemInsets.bottom : minInset;
+
+        // Calculate left and right insets based on bottom inset
+        double radius = width / 2;
+        double apothem = radius - bottomInset;
+        double chord = Math.sqrt(Math.pow(radius, 2) - Math.pow(apothem, 2)) * 2;
+        int leftRightInset = (int) ((width - chord) / 2);
+
+        Log.d(TAG, "calculateBottomInsetsOnRoundDevice: " + bottomInset + ", " + leftRightInset);
+
+        return new Rect(leftRightInset, 0, leftRightInset, bottomInset);
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/.gitignore b/wearable/wear/XYZTouristAttractions/Wearable/.gitignore
new file mode 100644
index 0000000..6eb878d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/.gitignore
@@ -0,0 +1,16 @@
+# Copyright 2013 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/XYZTouristAttractions/Wearable/proguard-rules.pro b/wearable/wear/XYZTouristAttractions/Wearable/proguard-rules.pro
new file mode 100644
index 0000000..0661c7b
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/proguard-rules.pro
@@ -0,0 +1,17 @@
+# 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 proguardFiles
+# directive in build.gradle.
+#
+# 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/XYZTouristAttractions/Wearable/src/main/AndroidManifest.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..36749d1
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/AndroidManifest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.xyztouristattractions" >
+
+    <uses-feature android:name="android.hardware.type.watch" />
+
+    <uses-sdk
+        android:minSdkVersion="21"
+        android:targetSdkVersion="21" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@android:style/Theme.DeviceDefault" >
+
+        <activity
+            android:name=".ui.AttractionsActivity"
+            android:exported="true"
+            android:allowEmbedded="true"
+            android:taskAffinity=""
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="android.support.wearable.activity.ConfirmationActivity"
+            android:theme="@android:style/Theme.Translucent" />
+
+        <service android:name=".service.ListenerService">
+            <intent-filter>
+                <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
+            </intent-filter>
+        </service>
+
+        <service android:name=".service.UtilityService" />
+
+        <meta-data android:name="com.google.android.gms.version"
+            android:value="@integer/google_play_services_version" />
+
+    </application>
+
+</manifest>
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/service/ListenerService.java b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/service/ListenerService.java
new file mode 100644
index 0000000..b8af0a2
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/service/ListenerService.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.service;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.Log;
+
+import com.example.android.xyztouristattractions.R;
+import com.example.android.xyztouristattractions.common.Constants;
+import com.example.android.xyztouristattractions.common.Utils;
+import com.example.android.xyztouristattractions.ui.AttractionsActivity;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.data.FreezableUtils;
+import com.google.android.gms.wearable.DataEvent;
+import com.google.android.gms.wearable.DataEventBuffer;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.MessageEvent;
+import com.google.android.gms.wearable.Wearable;
+import com.google.android.gms.wearable.WearableListenerService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Wear listener service, used to receive inbound messages from
+ * other devices.
+ */
+public class ListenerService extends WearableListenerService {
+    private static final String TAG = ListenerService.class.getSimpleName();
+
+    @Override
+    public void onDataChanged(DataEventBuffer dataEvents) {
+        Log.d(TAG, "onDataChanged: " + dataEvents);
+
+        final List<DataEvent> events = FreezableUtils.freezeIterable(dataEvents);
+
+        for (DataEvent event : events) {
+            if (event.getType() == DataEvent.TYPE_CHANGED
+                    && event.getDataItem() != null
+                    && Constants.ATTRACTION_PATH.equals(event.getDataItem().getUri().getPath())) {
+
+                DataMapItem dataMapItem = DataMapItem.fromDataItem(event.getDataItem());
+                ArrayList<DataMap> attractionsData =
+                        dataMapItem.getDataMap().getDataMapArrayList(Constants.EXTRA_ATTRACTIONS);
+                showNotification(dataMapItem.getUri(), attractionsData);
+            }
+        }
+    }
+
+    @Override
+    public void onMessageReceived(MessageEvent messageEvent) {
+        Log.v(TAG, "onMessageReceived: " + messageEvent);
+
+        if (Constants.CLEAR_NOTIFICATIONS_PATH.equals(messageEvent.getPath())) {
+            // Clear the local notification
+            UtilityService.clearNotification(this);
+        }
+    }
+
+    private void showNotification(Uri attractionsUri, ArrayList<DataMap> attractions) {
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(Wearable.API)
+                .build();
+
+        ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+        if (!connectionResult.isSuccess() || !googleApiClient.isConnected()) {
+            Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG,
+                    connectionResult.getErrorCode()));
+            return;
+        }
+
+        Intent intent = new Intent(this, AttractionsActivity.class);
+        // Pass through the data Uri as an extra
+        intent.putExtra(Constants.EXTRA_ATTRACTIONS_URI, attractionsUri);
+        PendingIntent pendingIntent =
+                PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        int count = attractions.size();
+
+        DataMap attraction = attractions.get(0);
+
+        Bitmap bitmap = Utils.loadBitmapFromAsset(
+                googleApiClient, attraction.getAsset(Constants.EXTRA_IMAGE));
+
+        PendingIntent deletePendingIntent = PendingIntent.getService(
+                this, 0, UtilityService.getClearRemoteNotificationsIntent(this), 0);
+
+        Notification notification = new Notification.Builder(this)
+                .setContentText(getResources().getQuantityString(
+                        R.plurals.attractions_found, count, count))
+                .setSmallIcon(R.drawable.ic_launcher)
+                .setDeleteIntent(deletePendingIntent)
+                .addAction(R.drawable.ic_full_explore,
+                        getString(R.string.action_explore),
+                        pendingIntent)
+                .extend(new Notification.WearableExtender()
+                        .setBackground(bitmap)
+                )
+                .build();
+
+        NotificationManager notificationManager =
+                (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+        notificationManager.notify(Constants.WEAR_NOTIFICATION_ID, notification);
+
+        googleApiClient.disconnect();
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/service/UtilityService.java b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/service/UtilityService.java
new file mode 100644
index 0000000..234002c
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/service/UtilityService.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.service;
+
+import android.app.IntentService;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+
+import com.example.android.xyztouristattractions.common.Constants;
+import com.example.android.xyztouristattractions.common.Utils;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.wearable.Wearable;
+
+import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A utility IntentService, used for a variety of asynchronous background
+ * operations that do not necessarily need to be tied to a UI.
+ */
+public class UtilityService extends IntentService {
+    private static final String TAG = UtilityService.class.getSimpleName();
+
+    private static final String ACTION_CLEAR_NOTIFICATION = "clear_notification";
+    private static final String ACTION_CLEAR_REMOTE_NOTIFICATIONS = "clear_remote_notifications";
+    private static final String ACTION_START_DEVICE_ACTIVITY = "start_device_activity";
+    private static final String EXTRA_START_PATH = "start_path";
+    private static final String EXTRA_START_ACTIVITY_INFO = "start_activity_info";
+
+    public static void clearNotification(Context context) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_CLEAR_NOTIFICATION);
+        context.startService(intent);
+    }
+
+    public static void clearRemoteNotifications(Context context) {
+        context.startService(getClearRemoteNotificationsIntent(context));
+    }
+
+    public static Intent getClearRemoteNotificationsIntent(Context context) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_CLEAR_REMOTE_NOTIFICATIONS);
+        return intent;
+    }
+
+    /**
+     * Trigger a message that asks the master device to start an activity.
+     *
+     * @param context the context
+     * @param path the path that will be sent via the wearable message API
+     * @param name the tourist attraction name
+     * @param city the tourist attraction city
+     */
+    public static void startDeviceActivity(Context context, String path, String name, String city) {
+        Intent intent = new Intent(context, UtilityService.class);
+        intent.setAction(UtilityService.ACTION_START_DEVICE_ACTIVITY);
+        String extraInfo;
+        if (Constants.START_ATTRACTION_PATH.equals(path)) {
+            extraInfo = name;
+        } else {
+            extraInfo = name + ", " + city;
+        }
+        intent.putExtra(EXTRA_START_ACTIVITY_INFO, extraInfo);
+        intent.putExtra(EXTRA_START_PATH, path);
+        context.startService(intent);
+    }
+
+    public UtilityService() {
+        super(TAG);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        String action = intent != null ? intent.getAction() : null;
+        if (ACTION_CLEAR_NOTIFICATION.equals(action)) {
+            clearNotificationInternal();
+        } else if (ACTION_CLEAR_REMOTE_NOTIFICATIONS.equals(action)) {
+            clearRemoteNotificationsInternal();
+        } else if (ACTION_START_DEVICE_ACTIVITY.equals(action)) {
+            startDeviceActivityInternal(intent.getStringExtra(EXTRA_START_PATH),
+                    intent.getStringExtra(EXTRA_START_ACTIVITY_INFO));
+        }
+    }
+
+    /**
+     * Clear the local notifications
+     */
+    private void clearNotificationInternal() {
+        NotificationManager notificationManager =
+                (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+        notificationManager.cancel(Constants.WEAR_NOTIFICATION_ID);
+    }
+
+    /**
+     * Trigger a message to ask other devices to clear their notifications
+     */
+    private void clearRemoteNotificationsInternal() {
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(Wearable.API)
+                .build();
+
+        ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
+            Iterator<String> itr = Utils.getNodes(googleApiClient).iterator();
+            while (itr.hasNext()) {
+                // Loop through all connected nodes
+                Wearable.MessageApi.sendMessage(
+                        googleApiClient, itr.next(), Constants.CLEAR_NOTIFICATIONS_PATH, null);
+            }
+        }
+
+        googleApiClient.disconnect();
+    }
+
+    /**
+     * Sends the actual message to ask other devices to start an activity
+     *
+     * @param path the path to pass to the wearable message API
+     * @param extraInfo extra info that varies based on the path being sent
+     */
+    private void startDeviceActivityInternal(String path, String extraInfo) {
+        GoogleApiClient googleApiClient = new GoogleApiClient.Builder(this)
+                .addApi(Wearable.API)
+                .build();
+
+        ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+        if (connectionResult.isSuccess() && googleApiClient.isConnected()) {
+            Iterator<String> itr = Utils.getNodes(googleApiClient).iterator();
+            while (itr.hasNext()) {
+                // Loop through all connected nodes
+                Wearable.MessageApi.sendMessage(
+                        googleApiClient, itr.next(), path, extraInfo.getBytes());
+            }
+        }
+        googleApiClient.disconnect();
+    }
+
+}
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsActivity.java b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsActivity.java
new file mode 100644
index 0000000..21d6425
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsActivity.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v4.view.GestureDetectorCompat;
+import android.support.wearable.view.DismissOverlayView;
+import android.support.wearable.view.DotsPageIndicator;
+import android.support.wearable.view.GridViewPager;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowInsets;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+
+import com.example.android.xyztouristattractions.R;
+import com.example.android.xyztouristattractions.common.Attraction;
+import com.example.android.xyztouristattractions.common.Constants;
+import com.example.android.xyztouristattractions.common.Utils;
+import com.example.android.xyztouristattractions.service.UtilityService;
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.wearable.DataApi;
+import com.google.android.gms.wearable.DataMap;
+import com.google.android.gms.wearable.DataMapItem;
+import com.google.android.gms.wearable.Wearable;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The main Wear activity that displays nearby attractions in a
+ * {@link android.support.wearable.view.GridViewPager}. Each row shows
+ * one attraction and each column shows information or actions for that
+ * particular attraction.
+ */
+public class AttractionsActivity extends Activity
+        implements AttractionsGridPagerAdapter.OnChromeFadeListener {
+    private static final String TAG = AttractionsActivity.class.getSimpleName();
+
+    private GestureDetectorCompat mGestureDetector;
+    private DismissOverlayView mDismissOverlayView;
+    private GridViewPager mGridViewPager;
+    private AttractionsGridPagerAdapter mAdapter;
+    private DotsPageIndicator mDotsPageIndicator;
+    private ProgressBar mProgressBar;
+    private Rect mInsets = new Rect(0, 0, 0, 0);
+
+    private ArrayList<Attraction> mAttractions = new ArrayList<Attraction>();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_main);
+        final FrameLayout topFrameLayout = (FrameLayout) findViewById(R.id.topFrameLayout);
+        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
+        mGridViewPager = (GridViewPager) findViewById(R.id.gridViewPager);
+        mDotsPageIndicator = (DotsPageIndicator) findViewById(R.id.dotsPageIndicator);
+        mAdapter = new AttractionsGridPagerAdapter(this, mAttractions);
+        mAdapter.setOnChromeFadeListener(this);
+        mGridViewPager.setAdapter(mAdapter);
+
+        topFrameLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+            @Override
+            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+                // Call through to super implementation
+                insets = topFrameLayout.onApplyWindowInsets(insets);
+
+                boolean round = insets.isRound();
+
+                // Store system window insets regardless of screen shape
+                mInsets.set(insets.getSystemWindowInsetLeft(),
+                        insets.getSystemWindowInsetTop(),
+                        insets.getSystemWindowInsetRight(),
+                        insets.getSystemWindowInsetBottom());
+
+                if (round) {
+                    // On a round screen calculate the square inset to use.
+                    // Alternatively could use BoxInsetLayout, although calculating
+                    // the inset ourselves lets us position views outside the center
+                    // box. For example, slightly lower on the round screen (by giving
+                    // up some horizontal space).
+                    mInsets = Utils.calculateBottomInsetsOnRoundDevice(
+                            getWindowManager().getDefaultDisplay(), mInsets);
+
+                    // Boost the dots indicator up by the bottom inset
+                    FrameLayout.LayoutParams params =
+                            (FrameLayout.LayoutParams) mDotsPageIndicator.getLayoutParams();
+                    params.bottomMargin = mInsets.bottom;
+                    mDotsPageIndicator.setLayoutParams(params);
+                }
+
+                mAdapter.setInsets(mInsets);
+                return insets;
+            }
+        });
+
+        // Set up the DismissOverlayView
+        mDismissOverlayView = (DismissOverlayView) findViewById(R.id.dismiss_overlay);
+        mDismissOverlayView.setIntroText(getString(R.string.exit_intro_text));
+        mDismissOverlayView.showIntroIfNecessary();
+        mGestureDetector = new GestureDetectorCompat(this, new LongPressListener());
+
+        Uri attractionsUri = getIntent().getParcelableExtra(Constants.EXTRA_ATTRACTIONS_URI);
+        if (attractionsUri != null) {
+            new FetchDataAsyncTask(this).execute(attractionsUri);
+            UtilityService.clearNotification(this);
+            UtilityService.clearRemoteNotifications(this);
+        } else {
+            finish();
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        return mGestureDetector.onTouchEvent(event) || super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event);
+    }
+
+    @Override
+    public void onChromeFadeIn() {
+        // As the custom UI chrome fades in, also fade the DotsPageIndicator in
+        mDotsPageIndicator.animate().alpha(1).setDuration(
+                AttractionsGridPagerAdapter.FADE_IN_TIME_MS).start();
+    }
+
+    @Override
+    public void onChromeFadeOut() {
+        // As the custom UI chrome fades out, also fade the DotsPageIndicator out
+        mDotsPageIndicator.animate().alpha(0).setDuration(
+                AttractionsGridPagerAdapter.FADE_OUT_TIME_MS).start();
+    }
+
+    private class LongPressListener extends GestureDetector.SimpleOnGestureListener {
+        @Override
+        public void onLongPress(MotionEvent event) {
+            mDismissOverlayView.show();
+        }
+    }
+
+    /**
+     * A background task to load the attraction data via the Wear DataApi.
+     * This can take a second or two sometimes as several images need to
+     * be loaded.
+     */
+    private class FetchDataAsyncTask extends
+            AsyncTask<Uri, Void, ArrayList<Attraction>> {
+
+        private Context mContext;
+
+        public FetchDataAsyncTask(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        protected ArrayList<Attraction> doInBackground(Uri... params) {
+            mAttractions.clear();
+
+            // Connect to Play Services and the Wearable API
+            GoogleApiClient googleApiClient = new GoogleApiClient.Builder(mContext)
+                    .addApi(Wearable.API)
+                    .build();
+
+            ConnectionResult connectionResult = googleApiClient.blockingConnect(
+                    Constants.GOOGLE_API_CLIENT_TIMEOUT_S, TimeUnit.SECONDS);
+
+            if (!connectionResult.isSuccess() || !googleApiClient.isConnected()) {
+                Log.e(TAG, String.format(Constants.GOOGLE_API_CLIENT_ERROR_MSG,
+                        connectionResult.getErrorCode()));
+                return null;
+            }
+
+            Uri attractionsUri = params[0];
+            DataApi.DataItemResult dataItemResult =
+                    Wearable.DataApi.getDataItem(googleApiClient, attractionsUri).await();
+
+            if (dataItemResult.getStatus().isSuccess()) {
+                DataMapItem dataMapItem = DataMapItem.fromDataItem(dataItemResult.getDataItem());
+                List<DataMap> attractionsData =
+                        dataMapItem.getDataMap().getDataMapArrayList(Constants.EXTRA_ATTRACTIONS);
+
+                // Loop through each attraction, adding them to the list
+                Iterator<DataMap> itr = attractionsData.iterator();
+                while (itr.hasNext()) {
+                    DataMap attractionData = itr.next();
+
+                    Attraction attraction = new Attraction();
+                    attraction.name = attractionData.getString(Constants.EXTRA_TITLE);
+                    attraction.description =
+                            attractionData.getString(Constants.EXTRA_DESCRIPTION);
+                    attraction.city = attractionData.get(Constants.EXTRA_CITY);
+                    attraction.distance =
+                            attractionData.getString(Constants.EXTRA_DISTANCE);
+                    attraction.location = new LatLng(
+                            attractionData.getDouble(Constants.EXTRA_LOCATION_LAT),
+                            attractionData.getDouble(Constants.EXTRA_LOCATION_LNG));
+                    attraction.image = Utils.loadBitmapFromAsset(googleApiClient,
+                            attractionData.getAsset(Constants.EXTRA_IMAGE));
+                    attraction.secondaryImage = Utils.loadBitmapFromAsset(googleApiClient,
+                            attractionData.getAsset(Constants.EXTRA_IMAGE_SECONDARY));
+
+                    mAttractions.add(attraction);
+                }
+            }
+
+            googleApiClient.disconnect();
+
+            return mAttractions;
+        }
+
+        @Override
+        protected void onPostExecute(ArrayList<Attraction> result) {
+            if (result != null && result.size() > 0) {
+                // Update UI based on the result of the background processing
+                mAdapter.setData(result);
+                mAdapter.notifyDataSetChanged();
+                mDotsPageIndicator.setPager(mGridViewPager);
+                mDotsPageIndicator.setOnPageChangeListener(mAdapter);
+                mProgressBar.setVisibility(View.GONE);
+                mDotsPageIndicator.setVisibility(View.VISIBLE);
+                mGridViewPager.setVisibility(View.VISIBLE);
+            } else {
+                finish();
+            }
+        }
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsGridPagerAdapter.java b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsGridPagerAdapter.java
new file mode 100644
index 0000000..99737f4
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/java/com/example/android/xyztouristattractions/ui/AttractionsGridPagerAdapter.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * 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.xyztouristattractions.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.support.wearable.activity.ConfirmationActivity;
+import android.support.wearable.view.CardFrame;
+import android.support.wearable.view.CardScrollView;
+import android.support.wearable.view.GridPagerAdapter;
+import android.support.wearable.view.GridViewPager;
+import android.support.wearable.view.WatchViewStub;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.example.android.xyztouristattractions.R;
+import com.example.android.xyztouristattractions.common.Attraction;
+import com.example.android.xyztouristattractions.common.Constants;
+import com.example.android.xyztouristattractions.service.UtilityService;
+
+import java.util.ArrayList;
+
+/**
+ * This adapter backs the main GridViewPager component found in
+ * {@link com.example.android.xyztouristattractions.ui.AttractionsActivity}.
+ */
+public class AttractionsGridPagerAdapter extends GridPagerAdapter
+        implements GridViewPager.OnPageChangeListener {
+
+    public static final int FADE_IN_TIME_MS = 250;
+    public static final int FADE_OUT_TIME_MS = 500;
+    private static final int GRID_COLUMN_COUNT = 5;
+    private static final int FADE_OUT_DELAY_MS = 1500;
+    private static final int PAGER_PRIMARY_IMAGE_COLUMN = 0;
+    private static final int PAGER_SECONDARY_IMAGE_COLUMN = 1;
+    private static final int PAGER_DESCRIPTION_COLUMN = 2;
+    private static final int PAGER_NAVIGATE_ACTION_COLUMN = 3;
+    private static final int PAGER_OPEN_ACTION_COLUMN = 4;
+
+    private Context mContext;
+    private LayoutInflater mLayoutInflater;
+    private ArrayList<Attraction> mAttractions;
+    private Rect mInsets = new Rect();
+    private DelayedHide mDelayedHide = new DelayedHide();
+    private OnChromeFadeListener mOnChromeFadeListener;
+
+    public AttractionsGridPagerAdapter(
+            Context context, ArrayList<Attraction> attractions) {
+        super();
+        mContext = context;
+        mLayoutInflater = LayoutInflater.from(context);
+        mAttractions = attractions;
+    }
+
+    public void setData(ArrayList<Attraction> attractions) {
+        mAttractions = attractions;
+    }
+
+    public void setInsets(Rect insets) {
+        mInsets = insets;
+    }
+
+    @Override
+    public int getRowCount() {
+        return (mAttractions != null && mAttractions.size() > 0) ? mAttractions.size() : 1;
+    }
+
+    @Override
+    public int getColumnCount(int i) {
+        return GRID_COLUMN_COUNT;
+    }
+
+    @Override
+    protected Object instantiateItem(ViewGroup container, int row, final int column) {
+        if (mAttractions != null && mAttractions.size() > 0) {
+            final Attraction attraction = mAttractions.get(row);
+            switch (column) {
+                case PAGER_PRIMARY_IMAGE_COLUMN:
+                case PAGER_SECONDARY_IMAGE_COLUMN:
+                    // Two pages of full screen images, one with the attraction name
+                    // and one with the distance to the attraction
+                    final View view = mLayoutInflater.inflate(
+                            R.layout.gridpager_fullscreen_image, container, false);
+                    ImageView imageView = (ImageView) view.findViewById(R.id.imageView);
+                    TextView textView = (TextView) view.findViewById(R.id.textView);
+                    FrameLayout overlayTextLayout =
+                            (FrameLayout) view.findViewById(R.id.overlaytext);
+
+                    mDelayedHide.add(overlayTextLayout);
+                    view.setOnClickListener(mDelayedHide);
+
+                    FrameLayout.LayoutParams params =
+                            (FrameLayout.LayoutParams) textView.getLayoutParams();
+                    params.bottomMargin = params.bottomMargin + mInsets.bottom;
+                    params.leftMargin = mInsets.left;
+                    params.rightMargin = mInsets.right;
+                    textView.setLayoutParams(params);
+
+                    if (column == PAGER_PRIMARY_IMAGE_COLUMN) {
+                        imageView.setImageBitmap(attraction.image);
+                        textView.setText(attraction.name);
+                    } else {
+                        imageView.setImageBitmap(attraction.secondaryImage);
+                        if (TextUtils.isEmpty(attraction.distance)) {
+                            overlayTextLayout.setVisibility(View.GONE);
+                        } else {
+                            textView.setText(mContext.getString(
+                                    R.string.map_caption, attraction.distance));
+                        }
+                    }
+                    container.addView(view);
+                    return view;
+                case PAGER_DESCRIPTION_COLUMN:
+                    // The description card page
+                    CardScrollView cardScrollView = (CardScrollView) mLayoutInflater.inflate(
+                            R.layout.gridpager_card, container, false);
+                    TextView descTextView = (TextView) cardScrollView.findViewById(R.id.textView);
+                    descTextView.setText(attraction.description);
+                    cardScrollView.setCardGravity(Gravity.BOTTOM);
+                    cardScrollView.setExpansionEnabled(true);
+                    cardScrollView.setExpansionDirection(CardFrame.EXPAND_DOWN);
+                    cardScrollView.setExpansionFactor(10);
+                    container.addView(cardScrollView);
+                    return cardScrollView;
+                case PAGER_NAVIGATE_ACTION_COLUMN:
+                    // The navigate action
+                    final WatchViewStub navStub = (WatchViewStub) mLayoutInflater.inflate(
+                            R.layout.gridpager_action, container, false);
+
+                    navStub.setOnClickListener(getStartActionClickListener(
+                            attraction, Constants.START_NAVIGATION_PATH,
+                            ConfirmationActivity.SUCCESS_ANIMATION));
+
+                    navStub.setOnLayoutInflatedListener(
+                            new WatchViewStub.OnLayoutInflatedListener() {
+                        @Override
+                        public void onLayoutInflated(WatchViewStub watchViewStub) {
+                            ImageView imageView = (ImageView) navStub.findViewById(R.id.imageView);
+                            imageView.setImageResource(R.drawable.ic_full_directions_walking);
+                            TextView textView = (TextView) navStub.findViewById(R.id.textView);
+                            textView.setText(R.string.action_navigate);
+                        }
+                    });
+
+                    container.addView(navStub);
+                    return navStub;
+                case PAGER_OPEN_ACTION_COLUMN:
+                    // The "open on device" action
+                    final WatchViewStub openStub = (WatchViewStub) mLayoutInflater.inflate(
+                            R.layout.gridpager_action, container, false);
+
+                    openStub.setOnClickListener(getStartActionClickListener(
+                            attraction, Constants.START_ATTRACTION_PATH,
+                            ConfirmationActivity.OPEN_ON_PHONE_ANIMATION));
+
+                    openStub.setOnLayoutInflatedListener(
+                            new WatchViewStub.OnLayoutInflatedListener() {
+                        @Override
+                        public void onLayoutInflated(WatchViewStub watchViewStub) {
+                            ImageView imageView = (ImageView) openStub.findViewById(R.id.imageView);
+                            imageView.setImageResource(R.drawable.ic_full_open_on_device);
+                            TextView textView = (TextView) openStub.findViewById(R.id.textView);
+                            textView.setText(R.string.action_open);
+                        }
+                    });
+
+                    container.addView(openStub);
+                    return openStub;
+            }
+        }
+        return new View(mContext);
+    }
+
+    @Override
+    public Drawable getBackgroundForPage(int row, int column) {
+        if (column == 0) {
+            return new ColorDrawable(0); // Empty black drawable
+        }
+        if (mAttractions.size() > 0 && mAttractions.get(row).image != null) {
+            return new BitmapDrawable(mContext.getResources(), mAttractions.get(row).image);
+        }
+        return super.getBackgroundForPage(row, column);
+    }
+
+    @Override
+    protected void destroyItem(ViewGroup viewGroup, int row, int column, Object object) {
+        mDelayedHide.remove((View) object);
+        viewGroup.removeView((View)object);
+    }
+
+    @Override
+    public boolean isViewFromObject(View view, Object object) {
+        return view == object;
+    }
+
+    @Override
+    public void onPageScrolled(int posX, int posY, float posOffsetX, float posOffsetY,
+                               int posOffsetPixelsX, int posOffsetPixelsY) {}
+
+    @Override
+    public void onPageSelected(int row, int col) {}
+
+    @Override
+    public void onPageScrollStateChanged(int state) {
+        mDelayedHide.show();
+    }
+
+    /**
+     * Use the Wear Message API to execute an action. Clears local and remote notifications and
+     * also runs a confirmation animation before finishing the Wear activity.
+     *
+     * @param attraction The attraction to start the action on
+     * @param pathName The Wear Message API pathname
+     * @param confirmAnimationType The confirmation animation type from ConfirmationActivity
+     */
+    private void startAction(Attraction attraction, String pathName, int confirmAnimationType) {
+        Intent intent = new Intent(mContext, ConfirmationActivity.class);
+        intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE, confirmAnimationType);
+        mContext.startActivity(intent);
+
+        UtilityService.clearNotification(mContext);
+        UtilityService.clearRemoteNotifications(mContext);
+        UtilityService.startDeviceActivity(mContext, pathName, attraction.name, attraction.city);
+
+        ((Activity)mContext).finish();
+    }
+
+    /**
+     * Helper method to generate the OnClickListener for the attraction actions.
+     */
+    private View.OnClickListener getStartActionClickListener(final Attraction attraction,
+            final String pathName, final int confirmAnimationType) {
+        View.OnClickListener clickListener = new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                startAction(attraction, pathName, confirmAnimationType);
+            }
+        };
+        return clickListener;
+    }
+
+    public void setOnChromeFadeListener(OnChromeFadeListener listener) {
+        mOnChromeFadeListener = listener;
+    }
+
+    public interface OnChromeFadeListener {
+        abstract void onChromeFadeIn();
+        abstract void onChromeFadeOut();
+    }
+
+    /**
+     * Helper class to fade out views based on a delay and fade them back in if needed as well.
+     */
+    private class DelayedHide implements View.OnClickListener {
+
+        ArrayList<View> hideViews = new ArrayList<View>(GRID_COLUMN_COUNT);
+        Handler mHideHandler = new Handler();
+        boolean mIsHidden = false;
+
+        Runnable mHideRunnable = new Runnable() {
+            @Override
+            public void run() {
+                hide();
+            }
+        };
+
+        void add(View newView) {
+            hideViews.add(newView);
+            delayedHide();
+        }
+
+        void remove(View removeView) {
+            hideViews.remove(removeView);
+        }
+
+        void show() {
+            mIsHidden = false;
+            if (mOnChromeFadeListener != null) {
+                mOnChromeFadeListener.onChromeFadeIn();
+            }
+            for (View view : hideViews) {
+                if (view != null) {
+                    view.animate().alpha(1).setDuration(FADE_IN_TIME_MS).start();
+                }
+            }
+            delayedHide();
+        }
+
+        void hide() {
+            mIsHidden = true;
+            mHideHandler.removeCallbacks(mHideRunnable);
+            if (mOnChromeFadeListener != null) {
+                mOnChromeFadeListener.onChromeFadeOut();
+            }
+            for (int i=0; i<hideViews.size(); i++) {
+                if (hideViews.get(i) != null) {
+                    hideViews.get(i).animate().alpha(0).setDuration(FADE_OUT_TIME_MS).start();
+                }
+            }
+        }
+
+        void delayedHide() {
+            mHideHandler.removeCallbacks(mHideRunnable);
+            mHideHandler.postDelayed(mHideRunnable, FADE_OUT_DELAY_MS);
+        }
+
+        @Override
+        public void onClick(View v) {
+            if (mIsHidden) {
+                show();
+            } else {
+                hide();
+            }
+        }
+    }
+}
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/color/action_color.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/color/action_color.xml
new file mode 100644
index 0000000..634d806
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/color/action_color.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <item android:state_pressed="true"
+        android:color="#ee3c4b90" /> <!-- pressed -->
+
+    <item android:color="#ee5c6bc0" /> <!-- default -->
+
+</selector>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_explore.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_explore.png
new file mode 100644
index 0000000..30353ef
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_full_explore.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..45e7a43
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_result_open.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_result_open.png
new file mode 100644
index 0000000..db8af57
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-hdpi/ic_result_open.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_directions_walking.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_directions_walking.png
new file mode 100644
index 0000000..d61f241
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_directions_walking.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_explore.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_explore.png
new file mode 100644
index 0000000..57b85ae
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_explore.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_open_on_device.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_open_on_device.png
new file mode 100644
index 0000000..2f6f056
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_open_on_device.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_openonphone.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_openonphone.png
new file mode 100644
index 0000000..19c21e1
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_full_openonphone.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..61e67d6
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_result_open.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_result_open.png
new file mode 100644
index 0000000..7d3c785
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xhdpi/ic_result_open.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xxhdpi/ic_full_explore.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xxhdpi/ic_full_explore.png
new file mode 100644
index 0000000..82894b3
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xxhdpi/ic_full_explore.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..3fb154d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/activity_main.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..a4ef94b
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/activity_main.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/topFrameLayout">
+
+    <ProgressBar
+        android:id="@+id/progressBar"
+        android:layout_gravity="center"
+        android:indeterminate="true"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <android.support.wearable.view.GridViewPager
+        android:id="@+id/gridViewPager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone" />
+
+    <android.support.wearable.view.DotsPageIndicator
+        android:id="@+id/dotsPageIndicator"
+        android:layout_gravity="center_horizontal|bottom"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        app:dotFadeWhenIdle="false"
+        app:dotFadeInDuration="0"
+        app:dotFadeOutDuration="0"
+        app:dotFadeOutDelay="0"
+        android:visibility="gone" />
+
+    <android.support.wearable.view.DismissOverlayView
+        android:id="@+id/dismiss_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
+
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action.xml
new file mode 100644
index 0000000..4b3bbaf
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+
+<android.support.wearable.view.WatchViewStub
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:rectLayout="@layout/gridpager_action_square"
+    app:roundLayout="@layout/gridpager_action_round"
+    android:clickable="true" />
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_round.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_round.xml
new file mode 100644
index 0000000..70cec1a
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_round.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#7F000000"/>
+
+    <android.support.wearable.view.CircledImageView
+        android:id="@+id/circleImageView"
+        android:layout_width="112dp"
+        android:layout_height="112dp"
+        android:layout_centerInParent="true"
+        app:circle_radius="52dp"
+        app:circle_radius_pressed="56dp"
+        app:circle_color="@color/action_color">
+
+        <ImageView
+            android:id="@+id/imageView"
+            android:layout_width="64dp"
+            android:layout_height="64dp"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_full_open_on_device"
+            android:scaleType="centerCrop" />
+
+    </android.support.wearable.view.CircledImageView>
+
+    <TextView
+        android:id="@+id/textView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        style="@style/ActionTextStyleRound"
+        android:layout_below="@id/circleImageView"
+        android:layout_centerHorizontal="true"
+        android:gravity="center"
+        tools:text="Navigate" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_square.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_square.xml
new file mode 100644
index 0000000..362671b
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_action_square.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#7F000000"/>
+
+    <android.support.wearable.view.CircledImageView
+        android:id="@+id/circleImageView"
+        android:layout_width="112dp"
+        android:layout_height="112dp"
+        android:layout_alignParentTop="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginTop="24dp"
+        app:circle_radius="52dp"
+        app:circle_radius_pressed="56dp"
+        app:circle_color="@color/action_color">
+
+        <ImageView
+            android:id="@+id/imageView"
+            android:layout_width="64dp"
+            android:layout_height="64dp"
+            android:layout_gravity="center"
+            android:src="@drawable/ic_full_open_on_device"
+            android:scaleType="centerCrop" />
+
+    </android.support.wearable.view.CircledImageView>
+
+    <TextView
+        android:id="@+id/textView"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        style="@style/ActionTextStyle"
+        android:layout_below="@id/circleImageView"
+        android:layout_marginBottom="12dp"
+        android:layout_centerHorizontal="true"
+        android:maxLines="2"
+        android:gravity="center"
+        tools:text="Navigate" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_card.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_card.xml
new file mode 100644
index 0000000..4b5f463
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_card.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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.
+  -->
+
+<android.support.wearable.view.CardScrollView
+    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"
+    android:clipToPadding="false"
+    android:paddingBottom="@dimen/indicator_margin">
+
+    <android.support.wearable.view.CardFrame
+        android:id="@+id/cardFrame"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <TextView
+            android:id="@+id/textView"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="@style/CardFontStyle"
+            tools:text="Sample Text" />
+
+    </android.support.wearable.view.CardFrame>
+
+</android.support.wearable.view.CardScrollView>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_fullscreen_image.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_fullscreen_image.xml
new file mode 100644
index 0000000..e70bcc8
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/layout/gridpager_fullscreen_image.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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">
+
+    <ImageView
+        android:id="@+id/imageView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerCrop" />
+
+    <FrameLayout
+        android:id="@+id/overlaytext"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom"
+        android:background="#66000000">
+
+        <TextView
+            android:id="@+id/textView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            style="@style/PagerTitleStyle"
+            android:maxLines="2"
+            android:layout_marginBottom="@dimen/indicator_margin"
+            tools:text="Sample Text" />
+
+    </FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/dimens.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..b07a27e
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/dimens.xml
@@ -0,0 +1,26 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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="standard_margin">4dp</dimen>
+    <dimen name="card_margin">8dp</dimen>
+    <dimen name="indicator_margin">12dp</dimen>
+
+    <dimen name="page_row_margin">50dp</dimen>
+    <dimen name="page_column_margin">50dp</dimen>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/strings.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2b265a3
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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">XYZ Tourist Attractions</string>
+    <string name="action_explore">Explore</string>
+    <string name="action_navigate">Start walking navigation</string>
+    <string name="action_open">Open on phone</string>
+    <string name="exit_intro_text">Long-press anywhere to exit</string>
+    <string name="map_caption">%s away</string>
+
+    <plurals name="attractions_found">
+        <item quantity="one">One tourist attraction nearby!</item>
+        <item quantity="other">%d tourist attractions nearby!</item>
+    </plurals>
+
+</resources>
diff --git a/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/styles.xml b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cebe1c7
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/Wearable/src/main/res/values/styles.xml
@@ -0,0 +1,48 @@
+<!--
+  Copyright 2015 Google Inc. All rights reserved.
+
+  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>
+
+    <style name="CardFontStyle" parent="@android:style/TextAppearance.Medium">
+        <item name="android:textColor">#434343</item>
+        <item name="android:fontFamily">sans-serif-condensed-light</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:padding">@dimen/card_margin</item>
+    </style>
+
+    <style name="PagerTitleStyle" parent="@android:style/TextAppearance.Large">
+        <item name="android:fontFamily">sans-serif-condensed-light</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">22sp</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:ellipsize">end</item>
+    </style>
+
+    <style name="ActionTextStyle" parent="@android:style/TextAppearance.Large">
+        <item name="android:fontFamily">sans-serif-condensed-light</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:maxLines">2</item>
+        <item name="android:ellipsize">end</item>
+        <item name="android:textColor">#FFFFFF</item>
+    </style>
+
+    <style name="ActionTextStyleRound" parent="ActionTextStyle">
+        <item name="android:textSize">14sp</item>
+        <item name="android:maxLines">1</item>
+    </style>
+
+</resources>
\ No newline at end of file
diff --git a/wearable/wear/XYZTouristAttractions/build.gradle b/wearable/wear/XYZTouristAttractions/build.gradle
new file mode 100644
index 0000000..18f393f
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/build.gradle
@@ -0,0 +1,11 @@
+
+// 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/XYZTouristAttractions/buildSrc/build.gradle b/wearable/wear/XYZTouristAttractions/buildSrc/build.gradle
new file mode 100644
index 0000000..7cebf71
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/buildSrc/build.gradle
@@ -0,0 +1,15 @@
+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/XYZTouristAttractions/gradle/wrapper/gradle-wrapper.jar b/wearable/wear/XYZTouristAttractions/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8c0fb64
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/gradle/wrapper/gradle-wrapper.properties b/wearable/wear/XYZTouristAttractions/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7d3b483
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Dec 22 11:24:44 EST 2014
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/wearable/wear/XYZTouristAttractions/gradlew b/wearable/wear/XYZTouristAttractions/gradlew
new file mode 100755
index 0000000..91a7e26
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/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/XYZTouristAttractions/gradlew.bat b/wearable/wear/XYZTouristAttractions/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/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/XYZTouristAttractions/screenshots/icon-web.png b/wearable/wear/XYZTouristAttractions/screenshots/icon-web.png
new file mode 100755
index 0000000..7e1c3f6
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/icon-web.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/mobile-1-list-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/mobile-1-list-unframed.png
new file mode 100644
index 0000000..db88dc3
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/mobile-1-list-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/mobile-1-list.png b/wearable/wear/XYZTouristAttractions/screenshots/mobile-1-list.png
new file mode 100644
index 0000000..6a2f777
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/mobile-1-list.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/mobile-2-detail-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/mobile-2-detail-unframed.png
new file mode 100644
index 0000000..088c756
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/mobile-2-detail-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/mobile-2-detail.png b/wearable/wear/XYZTouristAttractions/screenshots/mobile-2-detail.png
new file mode 100644
index 0000000..dc70ee6
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/mobile-2-detail.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-1-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-1-unframed.png
new file mode 100644
index 0000000..4dede5c
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-1-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-1.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-1.png
new file mode 100644
index 0000000..49bd581
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-1.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-2-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-2-unframed.png
new file mode 100644
index 0000000..85e8492
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-2-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-2.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-2.png
new file mode 100644
index 0000000..7c166e7
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-2.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-3-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-3-unframed.png
new file mode 100644
index 0000000..4542f1f
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-3-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-3.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-3.png
new file mode 100644
index 0000000..b294efe
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-3.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-4-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-4-unframed.png
new file mode 100644
index 0000000..b866186
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-4-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-4.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-4.png
new file mode 100644
index 0000000..bcc3ff3
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-4.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-5-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-5-unframed.png
new file mode 100644
index 0000000..df82ae0
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-5-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-5.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-5.png
new file mode 100644
index 0000000..d3cd76d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-5.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-6-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-6-unframed.png
new file mode 100644
index 0000000..31f271c
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-6-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-round-6.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-6.png
new file mode 100644
index 0000000..ecbaf57
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-round-6.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-1-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-1-unframed.png
new file mode 100644
index 0000000..c816f77
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-1-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-1.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-1.png
new file mode 100644
index 0000000..24e5534
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-1.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-2-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-2-unframed.png
new file mode 100644
index 0000000..d549151
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-2-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-2.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-2.png
new file mode 100644
index 0000000..a7d72c5
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-2.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-3-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-3-unframed.png
new file mode 100644
index 0000000..2c252e6
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-3-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-3.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-3.png
new file mode 100644
index 0000000..b0ead30
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-3.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-4-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-4-unframed.png
new file mode 100644
index 0000000..c4ed121
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-4-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-4.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-4.png
new file mode 100644
index 0000000..d3a35aa
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-4.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-5-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-5-unframed.png
new file mode 100644
index 0000000..1bfcd1d
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-5-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-5.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-5.png
new file mode 100644
index 0000000..56acaa4
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-5.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-6-unframed.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-6-unframed.png
new file mode 100644
index 0000000..aa5127f
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-6-unframed.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/screenshots/wear-square-6.png b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-6.png
new file mode 100644
index 0000000..3bda193
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/screenshots/wear-square-6.png
Binary files differ
diff --git a/wearable/wear/XYZTouristAttractions/settings.gradle b/wearable/wear/XYZTouristAttractions/settings.gradle
new file mode 100644
index 0000000..8522c57
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/settings.gradle
@@ -0,0 +1 @@
+include ':Application', ':Wearable', ':Shared'
diff --git a/wearable/wear/XYZTouristAttractions/template-params.xml b/wearable/wear/XYZTouristAttractions/template-params.xml
new file mode 100644
index 0000000..388f2d6
--- /dev/null
+++ b/wearable/wear/XYZTouristAttractions/template-params.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright 2015 Google Inc. All rights reserved.
+
+ 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>XYZ Tourist Attractions</name>
+    <group>Wearable</group>
+    <package>com.example.android.xyztouristattractions</package>
+    <minSdk>18</minSdk>
+
+    <dependency>com.android.support:appcompat-v7:21.0.3</dependency>
+    <dependency>com.google.android.gms:play-services-wearable:6.5.+</dependency>
+    <dependency>com.google.android.gms:play-services-location:6.5.+</dependency>
+    <dependency>com.google.maps.android:android-maps-utils:0.3.2</dependency>
+    <dependency>com.github.bumptech.glide:glide:3.5.1</dependency>
+    <dependency>com.android.support:recyclerview-v7:21.0.0</dependency>
+    <dependency_wearable>com.google.android.gms:play-services-wearable:6.5.+</dependency_wearable>
+    <dependency_wearable>com.google.android.gms:play-services-location:6.5.+</dependency_wearable>
+    <dependency_wearable>com.google.android.support:wearable:1.1.0</dependency_wearable>
+    <dependency_shared>com.google.android.gms:play-services-wearable:6.5.+</dependency_shared>
+    <dependency_shared>com.google.android.gms:play-services-location:6.5.+</dependency_shared>
+    <dependency_shared>com.google.maps.android:android-maps-utils:0.3.2</dependency_shared>
+
+    <strings>
+        <intro>
+            <![CDATA[
+            This sample aims to be as close to a real world example of a mobile
+            and wear app combination as possible. It has a more refined design
+            and also provides a practical example of how a mobile app would
+            interact and communicate with its wear counterpart.
+
+            The app itself is modeled after a hypothetical tourist attractions
+            app that notifies the user when they are in close proximty to
+            interesting points of interest.
+
+            The wear component loads a full wearable app that shows images and
+            summary information about these tourist attractions in a
+            GridViewPager UI component.
+            ]]>
+        </intro>
+    </strings>
+
+    <template src="base" />
+    <template src="WearPlusShared"/>
+
+    <metadata>
+        <!-- Values: {DRAFT | PUBLISHED | INTERNAL | DEPRECATED | SUPERCEDED} -->
+        <status>DRAFT</status>
+        <!-- See http://go/sample-categories for details on the next 4 fields. -->
+        <categories>Wearable, UI</categories>
+        <technologies>Android</technologies>
+        <languages>Java</languages>
+        <solutions>Mobile</solutions>
+        <!-- Values: {BEGINNER | INTERMEDIATE | ADVANCED | EXPERT} -->
+        <level>INTERMEDIATE</level>
+        <!-- Dimensions: 512x512, PNG fomrat -->
+        <icon>screenshots/icon-web.png</icon>
+        <!-- Path to screenshots. Use <img> tags for each. -->
+        <screenshots>
+            <img>screenshots/mobile-1-list.png</img>
+            <img>screenshots/mobile-2-detail.png</img>
+            <img>wear-round-1.png</img>
+            <img>wear-round-2.png</img>
+            <img>wear-round-3.png</img>
+            <img>wear-round-4.png</img>
+            <img>wear-round-5.png</img>
+            <img>wear-round-6.png</img>
+            <img>wear-square-1.png</img>
+            <img>wear-square-2.png</img>
+            <img>wear-square-3.png</img>
+            <img>wear-square-4.png</img>
+            <img>wear-square-5.png</img>
+            <img>wear-square-6.png</img>
+        </screenshots>
+        <!-- List of APIs that this sample should be cross-referenced under. Use <android>
+        for fully-qualified Framework class names ("android:" namespace).
+
+        Use <ext> for custom namespaces, if needed. See "Samples Index API" documentation
+        for more details. -->
+        <api_refs>
+            <android>android.support.v7.appcompat</android>
+            <android>android.support.v7.widget.RecyclerView</android>
+            <android>android.support.v7.widget.GridLayoutManager</android>
+            <android>com.google.android.gms.common.api.GoogleApiClient</android>
+            <android>com.google.android.gms.wearable.Wearable</android>
+            <android>com.google.android.gms.wearable.DataApi</android>
+            <android>com.google.android.gms.wearable.NodeApi</android>
+            <android>com.google.android.gms.wearable.WearableListenerService</android>
+            <android>com.google.android.gms.location.LocationServices.GeofencingApi</android>
+            <android>com.google.android.gms.location.LocationServices.FusedLocationApi</android>
+            <android>android.support.wearable.view.DismissOverlayView</android>
+            <android>android.support.wearable.view.DotsPageIndicator</android>
+            <android>android.support.wearable.view.GridViewPager</android>
+            <android>android.support.wearable.activity.ConfirmationActivity</android>
+            <android>android.support.wearable.view.CardFrame</android>
+            <android>android.support.wearable.view.CardScrollView</android>
+            <android>android.support.wearable.view.GridPagerAdapter</android>
+            <android>android.support.wearable.view.WatchViewStub</android>
+        </api_refs>
+
+        <!-- 1-3 line description of the sample here.
+
+            Avoid simply rearranging the sample's title. What does this sample actually
+            accomplish, and how does it do it? -->
+        <description>
+            This sample aims to be as close to a real world example of a mobile
+            and wear app combination as possible. It has a more refined design
+            and also provides a practical example of how a mobile app would
+            interact and communicate with its wear counterpart.
+
+            The app itself is modeled after a hypothetical tourist attractions
+            app that notifies the user when they are in close proximty to
+            interesting points of interest.
+
+            The wear component loads a full wearable app that shows images and
+            summary information about these tourist attractions in a
+            GridViewPager UI component.
+        </description>
+
+        <!-- Multi-paragraph introduction to sample, from an educational point-of-view.
+        Makrdown formatting allowed. This will be used to generate a mini-article for the
+        sample on DAC. -->
+        <intro>
+            TODO
+
+            Multi-paragraph introduction to sample, from an educational point-of-view.
+            *Makrdown* formatting allowed. See [Markdown Documentation][1]
+            for details.
+
+            [1]: http://daringfireball.net/projects/markdown/syntax
+        </intro>
+    </metadata>
+</sample>