Merge "Adding chat module" into nyc-mr1-dev
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ChatActivity.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ChatActivity.java
new file mode 100644
index 0000000..5f031ed
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ChatActivity.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.wearable.wear.messaging.chat;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.speech.RecognizerIntent;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.wearable.view.WearableRecyclerView;
+import android.support.wearable.view.drawer.WearableActionDrawer;
+import android.support.wearable.view.drawer.WearableDrawerLayout;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
+import com.example.android.wearable.wear.messaging.GoogleSignedInActivity;
+import com.example.android.wearable.wear.messaging.R;
+import com.example.android.wearable.wear.messaging.mock.MockDatabase;
+import com.example.android.wearable.wear.messaging.model.Chat;
+import com.example.android.wearable.wear.messaging.model.Message;
+import com.example.android.wearable.wear.messaging.util.Constants;
+import com.example.android.wearable.wear.messaging.util.DividerItemDecoration;
+import com.example.android.wearable.wear.messaging.util.MenuTinter;
+import com.example.android.wearable.wear.messaging.util.PrescrollToBottom;
+import com.example.android.wearable.wear.messaging.util.SchedulerHelper;
+import java.util.UUID;
+
+/**
+ * Handles chat functionality. Activity uses chat id (passed as extra) to load chat details
+ * (messages, etc.) and displays them.
+ *
+ * <p>Action drawer allows several inputs (speech, text) and can be easily extended to offer more
+ * (gif, etc.).
+ */
+public class ChatActivity extends GoogleSignedInActivity {
+
+    private static final String TAG = "ChatActivity";
+
+    private static final int SPEECH_REQUEST_CODE = 9001;
+
+    private WearableRecyclerView mRecyclerView;
+    private WearableDrawerLayout mDrawerLayout;
+    private EditText mInput;
+    private TextView mNoMessagesView;
+    private ChatAdapter mAdapter;
+
+    private InputMethodManager mInputMethodManager;
+
+    private Chat mChat;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_chat);
+
+        if (!getIntent().hasExtra(Constants.EXTRA_CHAT)) {
+            finish();
+            return;
+        }
+
+        mChat = getIntent().getParcelableExtra(Constants.EXTRA_CHAT);
+
+        mNoMessagesView = (TextView) findViewById(R.id.no_messages_view);
+        mInput = (EditText) findViewById(R.id.edit_text_input);
+        mDrawerLayout = (WearableDrawerLayout) findViewById(R.id.drawer_layout);
+
+        mRecyclerView = (WearableRecyclerView) findViewById(R.id.recycler_list);
+        mRecyclerView.addItemDecoration(new DividerItemDecoration(this, R.drawable.divider));
+        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
+        mRecyclerView.setLayoutManager(layoutManager);
+
+        mAdapter = new ChatAdapter(this, mChat, getUser());
+        mRecyclerView.setAdapter(mAdapter);
+        mRecyclerView
+                .getViewTreeObserver()
+                .addOnPreDrawListener(new PrescrollToBottom(mRecyclerView, mAdapter));
+
+        WearableActionDrawer actionDrawer =
+                (WearableActionDrawer) findViewById(R.id.bottom_action_drawer);
+        configureWearableActionDrawer(actionDrawer);
+
+        mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
+
+        // Automatically sends updated content as a message when input changes to streamline
+        // communication.
+        mInput.setOnEditorActionListener(
+                new TextView.OnEditorActionListener() {
+                    @Override
+                    public boolean onEditorAction(TextView editText, int actionId, KeyEvent event) {
+                        if (actionId != EditorInfo.IME_ACTION_NONE) {
+
+                            mInputMethodManager.hideSoftInputFromWindow(
+                                    editText.getWindowToken(), 0);
+                            editText.setVisibility(View.INVISIBLE);
+
+                            String text = editText.getText().toString();
+                            if (!text.isEmpty()) {
+                                sendMessage(text);
+                                mInputMethodManager.hideSoftInputFromInputMethod(
+                                        mInput.getWindowToken(), 0);
+                            }
+                            return true;
+                        }
+                        return false;
+                    }
+                });
+    }
+
+    private void configureWearableActionDrawer(WearableActionDrawer actionDrawer) {
+        Menu menu = actionDrawer.getMenu();
+        MenuTinter.tintMenu(this, menu, R.color.blue_15);
+        actionDrawer.setOnMenuItemClickListener(
+                new WearableActionDrawer.OnMenuItemClickListener() {
+                    @Override
+                    public boolean onMenuItemClick(MenuItem menuItem) {
+                        return handleMenuItems(menuItem);
+                    }
+                });
+
+        actionDrawer.setBackgroundColor(ContextCompat.getColor(this, R.color.blue_65));
+
+        View peek = getLayoutInflater().inflate(R.layout.drawer_chat_action, mDrawerLayout, false);
+        actionDrawer.setPeekContent(peek);
+
+        // Handles peek interactions.
+        peek.findViewById(R.id.button_speech)
+                .setOnClickListener(
+                        new View.OnClickListener() {
+                            @Override
+                            public void onClick(View v) {
+                                displayVoiceToTextInput();
+                            }
+                        });
+
+        peek.findViewById(R.id.button_keyboard)
+                .setOnClickListener(
+                        new View.OnClickListener() {
+                            @Override
+                            public void onClick(View v) {
+                                showKeyboard();
+                            }
+                        });
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        // Peeks drawer on page start.
+        mDrawerLayout.peekDrawer(Gravity.BOTTOM);
+        // After messages are added, re-scroll to the bottom.
+        mRecyclerView
+                .getViewTreeObserver()
+                .addOnPreDrawListener(new PrescrollToBottom(mRecyclerView, mAdapter));
+
+        //TODO: move to background
+        mAdapter.addMessages(MockDatabase.getAllMessagesForChat(mChat.getId()));
+        // Displays welcome message if no messages in chat.
+        if (mAdapter.getItemCount() == 0) {
+            mRecyclerView.setVisibility(View.GONE);
+            mNoMessagesView.setVisibility(View.VISIBLE);
+        } else {
+            mRecyclerView.setVisibility(View.VISIBLE);
+            mNoMessagesView.setVisibility(View.GONE);
+        }
+    }
+
+    /*
+     * Takes a menu item and delegates to the appropriate function based on
+     * the menu item id. Used with click handlers.
+     */
+    private boolean handleMenuItems(MenuItem menuItem) {
+        int id = menuItem.getItemId();
+        if (id == R.id.item_voice) {
+            displayVoiceToTextInput();
+        } else if (id == R.id.item_keyboard) {
+            showKeyboard();
+        }
+        mDrawerLayout.closeDrawer(Gravity.BOTTOM);
+        return false;
+    }
+
+    private void showKeyboard() {
+        Log.d(TAG, "Showing keyboard");
+        mInput.setText("");
+        mInput.setVisibility(View.VISIBLE);
+        mInput.requestFocus();
+        mInputMethodManager.showSoftInput(mInput, InputMethodManager.SHOW_FORCED);
+    }
+
+    private void displayVoiceToTextInput() {
+        Log.d(TAG, "Starting speech recognizer");
+        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        intent.putExtra(
+                RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
+        startActivityForResult(intent, SPEECH_REQUEST_CODE);
+    }
+
+    private void sendMessage(String text) {
+        Message message =
+                new Message.Builder()
+                        .id(UUID.randomUUID().toString())
+                        .senderId(getUser().getId())
+                        .text(text)
+                        .build();
+        sendMessage(message);
+    }
+
+    private void sendMessage(Message message) {
+        MockDatabase.saveMessage(mChat, message);
+        mAdapter.addMessage(message);
+        mRecyclerView.smoothScrollToPosition(mAdapter.getItemCount());
+
+        SchedulerHelper.scheduleMockNotification(this, mChat, message);
+    }
+}
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ChatAdapter.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ChatAdapter.java
new file mode 100644
index 0000000..6e2831b
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ChatAdapter.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.wearable.wear.messaging.chat;
+
+import android.content.Context;
+import android.support.percent.PercentRelativeLayout;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.util.SortedList;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.util.SortedListAdapterCallback;
+import android.support.wearable.view.WearableRecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import com.bumptech.glide.Glide;
+import com.example.android.wearable.wear.messaging.R;
+import com.example.android.wearable.wear.messaging.model.Chat;
+import com.example.android.wearable.wear.messaging.model.Message;
+import com.example.android.wearable.wear.messaging.model.Profile;
+import de.hdodenhof.circleimageview.CircleImageView;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Locale;
+
+/**
+ * Adapter for chat view. Uses a SortedList of Messages by sent time. Determines if the senderId is
+ * the current user and chooses the corresponding senderId layout.
+ *
+ * <p>The adapter will tint the background of a message so that there is an alternating visual
+ * difference to make reading messages easier by providing better visual clues
+ */
+class ChatAdapter extends WearableRecyclerView.Adapter<ChatAdapter.MessageViewHolder> {
+
+    private final Context mContext;
+    private final Chat mChat;
+    private final Profile mUser;
+    private final SortedList<Message> mMessages;
+
+    private final int mBlue30;
+    private final int mBlue15;
+
+    ChatAdapter(Context context, Chat chat, Profile user) {
+        this.mContext = context;
+        this.mChat = chat;
+        this.mUser = user;
+
+        mBlue15 = ContextCompat.getColor(mContext, R.color.blue_15);
+        mBlue30 = ContextCompat.getColor(mContext, R.color.blue_30);
+
+        mMessages =
+                new SortedList<>(
+                        Message.class,
+                        new SortedListAdapterCallback<Message>(this) {
+                            @Override
+                            public int compare(Message m1, Message m2) {
+                                return (int) (m1.getSentTime() - m2.getSentTime());
+                            }
+
+                            @Override
+                            public boolean areContentsTheSame(Message oldItem, Message newItem) {
+                                return oldItem.equals(newItem);
+                            }
+
+                            @Override
+                            public boolean areItemsTheSame(Message item1, Message item2) {
+                                return item1.getId().equals(item2.getId());
+                            }
+                        });
+    }
+
+    @Override
+    public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new MessageViewHolder(
+                LayoutInflater.from(mContext).inflate(R.layout.chat_message, parent, false));
+    }
+
+    @Override
+    public void onBindViewHolder(MessageViewHolder holder, int position) {
+        Message message = mMessages.get(position);
+        Profile sender = mChat.getParticipants().get(mMessages.get(position).getSenderId());
+        if (sender == null) {
+            sender = mUser;
+        }
+
+        Glide.with(mContext)
+                .load(sender.getProfileImageUri())
+                .placeholder(R.drawable.ic_face_white_24dp)
+                .into(holder.profileImage);
+
+        // Convert to just the first name of the sender or short hand it if the sender is you.
+        String name;
+        if (isUser(sender.getId())) {
+            name = "You";
+        } else {
+            name = sender.getName().split("\\s")[0];
+        }
+        holder.textName.setText(name);
+
+        // Odd messages have a darker background.
+        if (position % 2 == 0) {
+            holder.parentLayout.setBackgroundColor(mBlue15);
+        } else {
+            holder.parentLayout.setBackgroundColor(mBlue30);
+        }
+
+        holder.textContent.setText(message.getText());
+        holder.textTime.setText(millisToDateTime(message.getSentTime()));
+    }
+
+    @Override
+    public int getItemCount() {
+        return mMessages.size();
+    }
+
+    public void addMessage(Message message) {
+        mMessages.add(message);
+    }
+
+    public void addMessages(Collection<Message> messages) {
+        // There is a bug with {@link SortedList#addAll} that will add new items to the end
+        // of the list allowing duplications in the view which is unexpected behavior
+        // https://code.google.com/p/android/issues/detail?id=201618
+        // so we will mimic the add all operation (add individually but execute in one batch)
+        mMessages.beginBatchedUpdates();
+        for (Message message : messages) {
+            mMessages.add(message);
+        }
+        mMessages.endBatchedUpdates();
+    }
+
+    /**
+     * Converts time since epoch to Month Date Time
+     *
+     * @param time since epoch
+     * @return String formatted in Month Date HH:MM
+     */
+    private String millisToDateTime(long time) {
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(time);
+        String month = cal.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.US);
+        int date = cal.get(Calendar.DAY_OF_MONTH);
+        int hour = cal.get(Calendar.HOUR_OF_DAY);
+        int minute = cal.get(Calendar.MINUTE);
+
+        return month + " " + date + " " + hour + ":" + String.format(Locale.US, "%02d", minute);
+    }
+
+    private boolean isUser(String id) {
+        return id.equals(mUser.getId());
+    }
+
+    /** View holder to encapsulate the details of a chat message. */
+    class MessageViewHolder extends RecyclerView.ViewHolder {
+
+        final ViewGroup parentLayout;
+        final TextView textContent;
+        final TextView textName;
+        final CircleImageView profileImage;
+        final TextView textTime;
+
+        public MessageViewHolder(View itemView) {
+            super(itemView);
+
+            parentLayout = (PercentRelativeLayout) itemView.findViewById(R.id.layout_container);
+            textContent = (TextView) itemView.findViewById(R.id.text_content);
+            textName = (TextView) itemView.findViewById(R.id.text_name);
+            textTime = (TextView) itemView.findViewById(R.id.text_time);
+            profileImage = (CircleImageView) itemView.findViewById(R.id.profile_img);
+        }
+    }
+}
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/MockIncomingMessageReceiver.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/MockIncomingMessageReceiver.java
new file mode 100644
index 0000000..a1d8b76
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/MockIncomingMessageReceiver.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.wearable.wear.messaging.chat;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.NotificationManagerCompat;
+import android.support.v4.app.RemoteInput;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import com.example.android.wearable.wear.messaging.R;
+import com.example.android.wearable.wear.messaging.mock.MockDatabase;
+import com.example.android.wearable.wear.messaging.model.Chat;
+import com.example.android.wearable.wear.messaging.model.Message;
+import com.example.android.wearable.wear.messaging.model.Profile;
+import com.example.android.wearable.wear.messaging.util.Constants;
+import java.util.UUID;
+
+/**
+ * This broadcast receiver will take a message and create a notification mocking out the behavior
+ * what would be expected with a push notification backend.
+ *
+ * <p>It will append the original message to an introduction sentence. This way, there will be
+ * enough content in the notification to demonstrate scrolling.
+ */
+public class MockIncomingMessageReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "MockIncomingMessageRcr";
+
+    public static final int NOTIFICATION_ID = 888;
+
+    private NotificationManagerCompat mNotificationManagerCompat;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.d(TAG, "onReceive(): " + intent);
+        MockDatabase.init(context);
+        mNotificationManagerCompat = NotificationManagerCompat.from(context);
+
+        String chatId = intent.getStringExtra(Constants.EXTRA_CHAT);
+        String messageId = intent.getStringExtra(Constants.EXTRA_MESSAGE);
+
+        Chat chat = MockDatabase.findChatById(chatId);
+        if (chat == null) {
+            Log.e(TAG, "Could not find chat with id " + chatId);
+            return;
+        }
+
+        Message message = MockDatabase.findMessageById(chatId, messageId);
+        if (message == null) {
+            Log.d(TAG, "No message found in chat with id " + messageId);
+            return;
+        }
+
+        mockReply(context, chat, message);
+    }
+
+    private void mockReply(Context context, Chat chat, Message message) {
+
+        String replierId = chat.getParticipants().keySet().iterator().next();
+
+        String[] mockReplyMessages =
+                context.getResources().getStringArray(R.array.mock_reply_messages);
+        int index = (int) (Math.random() * mockReplyMessages.length);
+        String mockReplyMessage = mockReplyMessages[index];
+
+        Message replyMessage =
+                new Message.Builder()
+                        .id(UUID.randomUUID().toString())
+                        .senderId(replierId)
+                        .text(mockReplyMessage + " " + message.getText())
+                        .build();
+
+        MockDatabase.saveMessage(chat, replyMessage);
+
+        generateMessagingStyleNotification(context, chat, replyMessage);
+    }
+
+    /**
+     * See https://github.com/googlesamples/android-WearNotifications for more examples.
+     *
+     * @param context used for obtaining resources and creating Intents
+     * @param chat is the context for all of the messages. The top of the hierarchy
+     * @param message that will be used to be displayed in the notification
+     */
+    private void generateMessagingStyleNotification(Context context, Chat chat, Message message) {
+
+        Log.d(TAG, "generateMessagingStyleNotification()");
+
+        NotificationCompat.MessagingStyle messagingStyle =
+                new android.support.v7.app.NotificationCompat.MessagingStyle("Me")
+                        .setConversationTitle(chat.getAlias());
+
+        Profile sender = chat.getParticipants().get(message.getSenderId());
+        String senderId = (sender != null) ? sender.getName() : "Me";
+        messagingStyle.addMessage(message.getText(), message.getSentTime(), senderId);
+
+        // Set up a RemoteInput Action, so users can input (keyboard, drawing, voice) directly
+        // from the notification without entering the app.
+
+        // Create the RemoteInput specifying this key.
+        String replyLabel = context.getString(R.string.reply_label);
+        RemoteInput remoteInput =
+                new RemoteInput.Builder(Constants.EXTRA_REPLY).setLabel(replyLabel).build();
+
+        // Create PendingIntent for service that handles input.
+        Intent intent = new Intent(context, ReplyToMessageIntentService.class);
+        intent.setAction(Constants.ACTION_REPLY);
+        intent.putExtra(Constants.EXTRA_CHAT, chat.getId());
+
+        PendingIntent replyActionPendingIntent =
+                PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+
+        // Enable action to appear inline on Wear 2.0 (24+). This means it will appear over the
+        // lower portion of the Notification for easy action (only possible for one action).
+        final android.support.v7.app.NotificationCompat.Action.WearableExtender inlineAction =
+                new android.support.v7.app.NotificationCompat.Action.WearableExtender()
+                        .setHintDisplayActionInline(true)
+                        .setHintLaunchesActivity(false);
+        android.support.v7.app.NotificationCompat.Action replyAction =
+                new android.support.v7.app.NotificationCompat.Action.Builder(
+                                R.drawable.ic_reply_white_18dp,
+                                replyLabel,
+                                replyActionPendingIntent)
+                        .addRemoteInput(remoteInput)
+                        // Allows system to generate replies by context of conversation
+                        .setAllowGeneratedReplies(true)
+                        // Add WearableExtender to enable inline actions
+                        .extend(inlineAction)
+                        .build();
+
+        android.support.v7.app.NotificationCompat.Builder notificationCompatBuilder =
+                new android.support.v7.app.NotificationCompat.Builder(context);
+
+        // Builds and issues notification
+        notificationCompatBuilder
+                // MESSAGING_STYLE sets title and content for API 24+ (Wear 2.0) devices
+                .setStyle(messagingStyle)
+                .setSmallIcon(R.drawable.ic_launcher)
+                .setLargeIcon(
+                        BitmapFactory.decodeResource(
+                                context.getResources(), R.drawable.ic_person_blue_48dp))
+                // Set primary color (important for Wear 2.0 Notifications)
+                .setColor(ContextCompat.getColor(context, R.color.colorPrimary))
+                .addAction(replyAction)
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setPriority(Notification.PRIORITY_HIGH)
+                // Hides content on the lock-screen
+                .setVisibility(Notification.VISIBILITY_PRIVATE);
+
+        // If the phone is in "Do not disturb mode, the user will still be notified if
+        // the sender(s) is starred as a favorite.
+        for (Profile participant : chat.getParticipants().values()) {
+            notificationCompatBuilder.addPerson(participant.getName());
+        }
+
+        Notification notification = notificationCompatBuilder.build();
+        mNotificationManagerCompat.notify(NOTIFICATION_ID, notification);
+    }
+}
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ReplyToMessageIntentService.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ReplyToMessageIntentService.java
new file mode 100644
index 0000000..ed09d56
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/chat/ReplyToMessageIntentService.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.wearable.wear.messaging.chat;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.RemoteInput;
+import android.util.Log;
+import com.example.android.wearable.wear.messaging.mock.MockDatabase;
+import com.example.android.wearable.wear.messaging.model.Chat;
+import com.example.android.wearable.wear.messaging.model.Message;
+import com.example.android.wearable.wear.messaging.model.Profile;
+import com.example.android.wearable.wear.messaging.util.Constants;
+import com.example.android.wearable.wear.messaging.util.SharedPreferencesHelper;
+import java.io.IOException;
+import java.util.UUID;
+
+/** Handles replies directly from Notification. */
+public class ReplyToMessageIntentService extends IntentService {
+
+    private static final String TAG = "ReplyToMessageIntentSvc";
+
+    private Profile mUser;
+
+    public ReplyToMessageIntentService() {
+        super("ReplyToMessageIntentService");
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        MockDatabase.init(this);
+
+        try {
+            mUser = SharedPreferencesHelper.readUserFromJsonPref(getApplicationContext());
+        } catch (IOException e) {
+            Log.e(TAG, "User is not stored locally");
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    protected void onHandleIntent(@Nullable Intent intent) {
+
+        if (intent != null) {
+            if (intent.hasExtra(Constants.EXTRA_CHAT)) {
+                String action = intent.getAction();
+                if (Constants.ACTION_REPLY.equals(action)) {
+                    handleReply(getMessage(intent), extractChat(intent));
+                }
+            }
+        }
+    }
+
+    /*
+     * When we get a reply, all we need to do is convert the text of the reply
+     * into an object, give it context of who sent it, and save it
+     */
+    private void handleReply(CharSequence reply, Chat chat) {
+        Log.d(TAG, "handling reply reply: " + reply);
+
+        Message message =
+                new Message.Builder()
+                        .id(UUID.randomUUID().toString())
+                        .senderId(mUser.getId())
+                        .text(reply.toString())
+                        .build();
+
+        MockDatabase.saveMessage(chat, message);
+    }
+
+    private Chat extractChat(Intent intent) {
+        String chatId = intent.getStringExtra(Constants.EXTRA_CHAT);
+        return MockDatabase.findChatById(chatId);
+    }
+
+    /*
+     * Extracts CharSequence created from the RemoteInput associated with the Notification.
+     */
+    private CharSequence getMessage(Intent intent) {
+        Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
+        if (remoteInput != null) {
+            return remoteInput.getCharSequence(Constants.EXTRA_REPLY);
+        }
+        return null;
+    }
+}
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/SchedulerHelper.java b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/SchedulerHelper.java
new file mode 100644
index 0000000..208054a
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/java/com/example/android/wearable/wear/messaging/util/SchedulerHelper.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.example.android.wearable.wear.messaging.util;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.example.android.wearable.wear.messaging.chat.MockIncomingMessageReceiver;
+import com.example.android.wearable.wear.messaging.model.Chat;
+import com.example.android.wearable.wear.messaging.model.Message;
+
+/**
+ * Manage an alarm manager to trigger a notification after 5 seconds.
+ *
+ * <p>Demonstrates the receiving of a notification. In a real app, you would want to use FCM to
+ * handle pushing notifications to a device.
+ */
+public class SchedulerHelper {
+
+    private static final String TAG = "SchedulerHelper";
+
+    public static void scheduleMockNotification(Context context, Chat chat, Message message) {
+        AlarmManager alarmManger = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        PendingIntent alarmIntent = createPendingIntentToNotifyMessage(context, chat, message);
+
+        Log.d(TAG, "Setting up alarm to be triggered shortly.");
+        alarmManger.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + (5 * 1000), alarmIntent);
+    }
+
+    private static PendingIntent createPendingIntentToNotifyMessage(
+            Context context,
+            Chat chat,
+            Message message) {
+        Intent intent = new Intent(context, MockIncomingMessageReceiver.class);
+        intent.setAction(Constants.ACTION_RECEIVE_MESSAGE);
+        intent.putExtra(Constants.EXTRA_CHAT, chat.getId());
+        intent.putExtra(Constants.EXTRA_MESSAGE, message.getId());
+
+        return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+    }
+}
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/action_new_chat.xml b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/action_new_chat.xml
new file mode 100644
index 0000000..c1a4d51
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/action_new_chat.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017 Google Inc.
+  ~
+  ~ 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.percent.PercentRelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/percent_layout"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@drawable/ripple_rect_40"
+    android:paddingBottom="@dimen/vertical_spacing"
+    android:paddingTop="@dimen/vertical_spacing">
+
+    <ImageView
+        android:id="@+id/image_plus_icon"
+        android:layout_width="@dimen/add_button_diameter"
+        android:layout_height="@dimen/add_button_diameter"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"
+        android:background="@drawable/circle_add"
+        android:contentDescription="@string/plus_icon"
+        android:scaleType="center"
+        android:src="@drawable/ic_add_black_24dp"
+        android:tint="@color/white"
+        app:layout_marginStartPercent="@dimen/padding_15" />
+
+    <TextView
+        android:id="@+id/text_new_chat"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_marginStart="@dimen/keyline_large"
+        android:layout_toEndOf="@id/image_plus_icon"
+        android:text="@string/new_chat"
+        app:layout_marginEndPercent="@dimen/padding_10" />
+</android.support.percent.PercentRelativeLayout>
\ No newline at end of file
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/activity_chat.xml b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/activity_chat.xml
new file mode 100644
index 0000000..d1ede45
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/activity_chat.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright 2017 Google Inc.
+  ~
+  ~ 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.drawer.WearableDrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/drawer_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/no_messages_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="12dp"
+        android:layout_marginEnd="@dimen/horizontal_spacing"
+        android:text="@string/you_have_no_messages"
+        android:layout_gravity="center_vertical"
+        android:visibility="gone"
+        tools:visibility="visible" />
+
+    <android.support.wearable.view.WearableRecyclerView
+        android:id="@+id/recycler_list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@null"
+        android:clipToPadding="false"
+        android:paddingBottom="@dimen/list_item_height"
+        android:paddingTop="@dimen/list_item_height"
+        android:scrollbars="vertical" />
+
+    <android.support.wearable.view.drawer.WearableActionDrawer
+        android:id="@+id/bottom_action_drawer"
+        android:layout_width="match_parent"
+        app:action_menu="@menu/chat"
+        android:layout_height="match_parent" />
+
+    <EditText
+        android:id="@+id/edit_text_input"
+        android:layout_width="0sp"
+        android:layout_height="0sp"
+        android:imeOptions="actionSend"
+        android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
+        android:visibility="invisible"
+        tools:ignore="LabelFor" />
+</android.support.wearable.view.drawer.WearableDrawerLayout>
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/chat_message.xml b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/chat_message.xml
new file mode 100644
index 0000000..fcad164
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/chat_message.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017 Google Inc.
+  ~
+  ~ 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.percent.PercentRelativeLayout
+    xmlns:tools="http://schemas.android.com/tools"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/layout_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="@dimen/vertical_spacing"
+    android:paddingTop="@dimen/vertical_spacing">
+
+    <LinearLayout
+        android:id="@+id/layout_profile"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        app:layout_marginEndPercent="@dimen/padding_10"
+        app:layout_marginStartPercent="@dimen/padding_10">
+
+        <de.hdodenhof.circleimageview.CircleImageView
+            android:id="@+id/profile_img"
+            android:layout_width="@dimen/chat_profile_diameter"
+            android:layout_height="@dimen/chat_profile_diameter"
+            android:contentDescription="@string/profile_image"
+            tools:src="@drawable/ic_face_white_24dp"
+            app:circle_border_color="@color/blue_15"
+            app:circle_border_width="2dp"
+            app:layout_marginStartPercent="@dimen/padding_15" />
+
+        <TextView
+            android:id="@+id/text_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/keyline_large"
+            android:ellipsize="end"
+            tools:text="John Doe, Jane Doe, John Doe Jr."
+            android:textColor="@color/blue_100"
+            android:textSize="@dimen/font_small" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/layout_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/layout_profile"
+        android:layout_marginTop="@dimen/keyline_large"
+        android:orientation="vertical"
+        app:layout_marginEndPercent="@dimen/padding_10"
+        app:layout_marginStartPercent="@dimen/padding_10">
+
+        <TextView
+            android:id="@+id/text_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/holder_text" />
+
+    </LinearLayout>
+
+    <TextView
+        android:id="@+id/text_time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@id/layout_content"
+        android:layout_marginTop="@dimen/keyline_large"
+        android:textColor="@color/blue_100"
+        android:textSize="@dimen/font_small"
+        tools:text="Mar 17 9:15 am"
+        app:layout_marginEndPercent="@dimen/padding_10"
+        app:layout_marginStartPercent="@dimen/padding_10" />
+</android.support.percent.PercentRelativeLayout>
\ No newline at end of file
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/drawer_chat_action.xml b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/drawer_chat_action.xml
new file mode 100644
index 0000000..3fe6730
--- /dev/null
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/layout/drawer_chat_action.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2017 Google Inc.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:orientation="horizontal">
+
+    <ImageButton
+        android:id="@+id/button_speech"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@null"
+        android:src="@drawable/ic_mic_black_24dp"
+        android:tint="@color/blue_15"
+        android:contentDescription="@string/open_speech_input"
+        android:layout_margin="@dimen/action_drawer_item_margin" />
+
+    <ImageButton
+        android:id="@+id/button_keyboard"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@null"
+        android:src="@drawable/ic_keyboard_black_24dp"
+        android:contentDescription="@string/open_keyboard_input"
+        android:tint="@color/blue_15"
+        android:layout_margin="@dimen/action_drawer_item_margin" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml b/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml
index 7a024fe..8da274d 100644
--- a/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml
+++ b/wearable/wear/WearMessagingApp/Wearable/src/main/res/values/strings.xml
@@ -41,4 +41,10 @@
     <string name="google_signin_failed">Google Sign-In Failed.</string>
     <string name="authentication_failed">Authentication Failed.</string>
     <string name="connection_failed">Connection failed.</string>
+
+    <string-array name="mock_reply_messages">
+        <item>I like the sound of that!</item>
+        <item>That's a great idea!</item>
+        <item>I do not think so.</item>
+    </string-array>
 </resources>
\ No newline at end of file