diff --git a/res/drawable-hdpi/msg_bubble_incoming.9.png b/res/drawable-hdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..7fed0a806a0
Binary files /dev/null and b/res/drawable-hdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-hdpi/msg_bubble_outgoing.9.png b/res/drawable-hdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..8e7ccc0cc81
Binary files /dev/null and b/res/drawable-hdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..8e7ccc0cc81
Binary files /dev/null and b/res/drawable-ldrtl-hdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..7fed0a806a0
Binary files /dev/null and b/res/drawable-ldrtl-hdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..5ca9b14d641
Binary files /dev/null and b/res/drawable-ldrtl-mdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..4e7e4640376
Binary files /dev/null and b/res/drawable-ldrtl-mdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..52cb936a8ee
Binary files /dev/null and b/res/drawable-ldrtl-xhdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..0661fdba789
Binary files /dev/null and b/res/drawable-ldrtl-xhdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..5d8fc747ee3
Binary files /dev/null and b/res/drawable-ldrtl-xxhdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..009c721919e
Binary files /dev/null and b/res/drawable-ldrtl-xxhdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..8378db57676
Binary files /dev/null and b/res/drawable-ldrtl-xxxhdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png b/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..f0d0d9ee5c9
Binary files /dev/null and b/res/drawable-ldrtl-xxxhdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-mdpi/msg_bubble_incoming.9.png b/res/drawable-mdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..4e7e4640376
Binary files /dev/null and b/res/drawable-mdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-mdpi/msg_bubble_outgoing.9.png b/res/drawable-mdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..5ca9b14d641
Binary files /dev/null and b/res/drawable-mdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-xhdpi/msg_bubble_incoming.9.png b/res/drawable-xhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..0661fdba789
Binary files /dev/null and b/res/drawable-xhdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-xhdpi/msg_bubble_outgoing.9.png b/res/drawable-xhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..52cb936a8ee
Binary files /dev/null and b/res/drawable-xhdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-xxhdpi/msg_bubble_incoming.9.png b/res/drawable-xxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..ab693b3e7c1
Binary files /dev/null and b/res/drawable-xxhdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-xxhdpi/msg_bubble_outgoing.9.png b/res/drawable-xxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..5b2a2fffc0d
Binary files /dev/null and b/res/drawable-xxhdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable-xxxhdpi/msg_bubble_incoming.9.png b/res/drawable-xxxhdpi/msg_bubble_incoming.9.png
new file mode 100644
index 00000000000..34130fc6d1b
Binary files /dev/null and b/res/drawable-xxxhdpi/msg_bubble_incoming.9.png differ
diff --git a/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png b/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png
new file mode 100644
index 00000000000..cfd5734f106
Binary files /dev/null and b/res/drawable-xxxhdpi/msg_bubble_outgoing.9.png differ
diff --git a/res/drawable/conversation_message_icon.xml b/res/drawable/conversation_message_icon.xml
new file mode 100644
index 00000000000..a1192ec5f1e
--- /dev/null
+++ b/res/drawable/conversation_message_icon.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/res/layout/conversation_message_content.xml b/res/layout/conversation_message_content.xml
new file mode 100644
index 00000000000..7bc53e5074a
--- /dev/null
+++ b/res/layout/conversation_message_content.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/conversation_message_icon.xml b/res/layout/conversation_message_icon.xml
new file mode 100644
index 00000000000..4ffd2859cee
--- /dev/null
+++ b/res/layout/conversation_message_icon.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/res/layout/screen_zoom_preview_1.xml b/res/layout/screen_zoom_preview_1.xml
index ca2ecca738c..62cd0933007 100644
--- a/res/layout/screen_zoom_preview_1.xml
+++ b/res/layout/screen_zoom_preview_1.xml
@@ -1,5 +1,5 @@
-
-
+ android:background="@color/conversation_background"
+ android:padding="@dimen/conversation_message_list_padding"
+ android:orientation="vertical" >
-
+
+
+
+
+
+
+
+
+
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index d94ff13d1be..c6dd2461314 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -106,6 +106,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 90884c9b96b..f27b693bf5c 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -111,7 +111,20 @@
#4285F4
#3367D6
+ #ffffffff
+ #ff323232
+ #99323232
+ #99ffffff
+ #689f38
+ #ffffffff
+ #eeeeee
+ #689f38
+ #ffffffff
+ #4285f4
+ #ffffffff
+
+ #fff
- #CC000000
+ #cc000000
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 14aac55209f..53dd515d324 100755
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -258,6 +258,18 @@
240dp
88dp
+ 10dp
+ 42dp
+ 32sp
+ 16sp
+ 12sp
+ 20dp
+ 9dp
+ 18dp
+ 14dp
+ 10dp
+ 12dp
+ 4dp
24dp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0e7ca629610..383506e6275 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6883,6 +6883,27 @@
density in raw pixels per inch rather than using a relative description. [CHAR LIMIT=24] -->
Custom (%d)
+
+ A
+
+ P
+
+ Hi Pete!
+
+ Hey, want to grab coffee and catch up today?
+
+ Sounds great. I know of a good place not too far from here.
+
+ Perfect!
+
+ Tue 6:00PM
+
+ Tue 6:01PM
+
+ Tue 6:02PM
+
+ Tue 6:03PM
+
See all
diff --git a/src/com/android/settings/display/ConversationMessageView.java b/src/com/android/settings/display/ConversationMessageView.java
new file mode 100644
index 00000000000..a0889c4ecf7
--- /dev/null
+++ b/src/com/android/settings/display/ConversationMessageView.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+/**
+ * The view for a single entry in a conversation. This is a simplified version of
+ * com.android.messaging.ui.conversation.ConversationMessageView class.
+ */
+public class ConversationMessageView extends FrameLayout {
+ private final boolean mIncoming;
+ private final CharSequence mMessageText;
+ private final CharSequence mTimestampText;
+ private final CharSequence mIconText;
+ private final int mIconTextColor;
+ private final int mIconBackgroundColor;
+
+ private LinearLayout mMessageBubble;
+ private ViewGroup mMessageTextAndInfoView;
+ private TextView mMessageTextView;
+ private TextView mStatusTextView;
+ private TextView mContactIconView;
+
+ public ConversationMessageView(Context context) {
+ this(context, null);
+ }
+
+ public ConversationMessageView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.ConversationMessageView);
+
+ mIncoming = a.getBoolean(R.styleable.ConversationMessageView_incoming, true);
+ mMessageText = a.getString(R.styleable.ConversationMessageView_messageText);
+ mTimestampText = a.getString(R.styleable.ConversationMessageView_timestampText);
+ mIconText = a.getString(R.styleable.ConversationMessageView_iconText);
+ mIconTextColor = a.getColor(R.styleable.ConversationMessageView_iconTextColor, 0);
+ mIconBackgroundColor = a.getColor(R.styleable.ConversationMessageView_iconBackgroundColor,
+ 0);
+
+ LayoutInflater.from(context).inflate(R.layout.conversation_message_icon, this);
+ LayoutInflater.from(context).inflate(R.layout.conversation_message_content, this);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mMessageBubble = (LinearLayout) findViewById(R.id.message_content);
+ mMessageTextAndInfoView = (ViewGroup) findViewById(R.id.message_text_and_info);
+ mMessageTextView = (TextView) findViewById(R.id.message_text);
+ mStatusTextView = (TextView) findViewById(R.id.message_status);
+ mContactIconView = (TextView) findViewById(R.id.conversation_icon);
+ updateViewContent();
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ updateViewAppearance();
+
+ final int horizontalSpace = MeasureSpec.getSize(widthMeasureSpec);
+ final int iconSize = getResources()
+ .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size);
+
+ final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int iconMeasureSpec = MeasureSpec.makeMeasureSpec(iconSize, MeasureSpec.EXACTLY);
+
+ mContactIconView.measure(iconMeasureSpec, iconMeasureSpec);
+
+ final int arrowWidth =
+ getResources().getDimensionPixelSize(R.dimen.message_bubble_arrow_width);
+
+ // We need to subtract contact icon width twice from the horizontal space to get
+ // the max leftover space because we want the message bubble to extend no further than the
+ // starting position of the message bubble in the opposite direction.
+ final int maxLeftoverSpace = horizontalSpace - mContactIconView.getMeasuredWidth() * 2
+ - arrowWidth - getPaddingLeft() - getPaddingRight();
+ final int messageContentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxLeftoverSpace,
+ MeasureSpec.AT_MOST);
+
+ mMessageBubble.measure(messageContentWidthMeasureSpec, unspecifiedMeasureSpec);
+
+ final int maxHeight = Math.max(mContactIconView.getMeasuredHeight(),
+ mMessageBubble.getMeasuredHeight());
+ setMeasuredDimension(horizontalSpace, maxHeight + getPaddingBottom() + getPaddingTop());
+ }
+
+ @Override
+ protected void onLayout(final boolean changed, final int left, final int top, final int right,
+ final int bottom) {
+ final boolean isRtl = isLayoutRtl(this);
+
+ final int iconWidth = mContactIconView.getMeasuredWidth();
+ final int iconHeight = mContactIconView.getMeasuredHeight();
+ final int iconTop = getPaddingTop();
+ final int contentWidth = (right -left) - iconWidth - getPaddingLeft() - getPaddingRight();
+ final int contentHeight = mMessageBubble.getMeasuredHeight();
+ final int contentTop = iconTop;
+
+ final int iconLeft;
+ final int contentLeft;
+
+ if (mIncoming) {
+ if (isRtl) {
+ iconLeft = (right - left) - getPaddingRight() - iconWidth;
+ contentLeft = iconLeft - contentWidth;
+ } else {
+ iconLeft = getPaddingLeft();
+ contentLeft = iconLeft + iconWidth;
+ }
+ } else {
+ if (isRtl) {
+ iconLeft = getPaddingLeft();
+ contentLeft = iconLeft + iconWidth;
+ } else {
+ iconLeft = (right - left) - getPaddingRight() - iconWidth;
+ contentLeft = iconLeft - contentWidth;
+ }
+ }
+
+ mContactIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
+
+ mMessageBubble.layout(contentLeft, contentTop, contentLeft + contentWidth,
+ contentTop + contentHeight);
+ }
+
+ private static boolean isLayoutRtl(final View view) {
+ return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
+ }
+
+ private void updateViewContent() {
+ mMessageTextView.setText(mMessageText);
+ mStatusTextView.setText(mTimestampText);
+ mContactIconView.setText(mIconText);
+
+ mContactIconView.setTextColor(mIconTextColor);
+ final Drawable iconBase = getContext().getDrawable(R.drawable.conversation_message_icon);
+ mContactIconView
+ .setBackground(getTintedDrawable(getContext(), iconBase, mIconBackgroundColor));
+ }
+
+ private void updateViewAppearance() {
+ final Resources res = getResources();
+
+ final int arrowWidth = res.getDimensionPixelOffset(
+ R.dimen.message_bubble_arrow_width);
+ final int messageTextLeftRightPadding = res.getDimensionPixelOffset(
+ R.dimen.message_text_left_right_padding);
+ final int textTopPadding = res.getDimensionPixelOffset(
+ R.dimen.message_text_top_padding);
+ final int textBottomPadding = res.getDimensionPixelOffset(
+ R.dimen.message_text_bottom_padding);
+
+ final int textLeftPadding, textRightPadding;
+
+ if (mIncoming) {
+ textLeftPadding = messageTextLeftRightPadding + arrowWidth;
+ textRightPadding = messageTextLeftRightPadding;
+ } else {
+ textLeftPadding = messageTextLeftRightPadding;
+ textRightPadding = messageTextLeftRightPadding + arrowWidth;
+ }
+
+ // These values do not depend on whether the message includes attachments
+ final int gravity = mIncoming ? (Gravity.START | Gravity.CENTER_VERTICAL) :
+ (Gravity.END | Gravity.CENTER_VERTICAL);
+ final int messageTopPadding = res.getDimensionPixelSize(
+ R.dimen.message_padding_default);
+ final int metadataTopPadding = res.getDimensionPixelOffset(
+ R.dimen.message_metadata_top_padding);
+
+ // Update the message text/info views
+ final int bubbleDrawableResId = mIncoming ? R.drawable.msg_bubble_incoming
+ : R.drawable.msg_bubble_outgoing;
+ final int bubbleColorResId = mIncoming ? R.color.message_bubble_incoming
+ : R.color.message_bubble_outgoing;
+ final Context context = getContext();
+
+ final Drawable textBackgroundDrawable = getTintedDrawable(context,
+ context.getDrawable(bubbleDrawableResId),
+ context.getColor(bubbleColorResId));
+ mMessageTextAndInfoView.setBackground(textBackgroundDrawable);
+
+ if (isLayoutRtl(this)) {
+ // Need to switch right and left padding in RtL mode
+ mMessageTextAndInfoView.setPadding(textRightPadding,
+ textTopPadding + metadataTopPadding,
+ textLeftPadding, textBottomPadding);
+ } else {
+ mMessageTextAndInfoView.setPadding(textLeftPadding,
+ textTopPadding + metadataTopPadding,
+ textRightPadding, textBottomPadding);
+ }
+
+ // Update the message row and message bubble views
+ setPadding(getPaddingLeft(), messageTopPadding, getPaddingRight(), 0);
+ mMessageBubble.setGravity(gravity);
+
+ updateTextAppearance();
+ }
+
+ private void updateTextAppearance() {
+ final int messageColorResId = (mIncoming ? R.color.message_text_incoming
+ : R.color.message_text_outgoing);
+ final int timestampColorResId = mIncoming ? R.color.timestamp_text_incoming
+ : R.color.timestamp_text_outgoing;
+ final int messageColor = getContext().getColor(messageColorResId);
+
+ mMessageTextView.setTextColor(messageColor);
+ mMessageTextView.setLinkTextColor(messageColor);
+ mStatusTextView.setTextColor(timestampColorResId);
+ }
+
+ private static Drawable getTintedDrawable(final Context context, final Drawable drawable,
+ final int color) {
+ // For some reason occassionally drawables on JB has a null constant state
+ final Drawable.ConstantState constantStateDrawable = drawable.getConstantState();
+ final Drawable retDrawable = (constantStateDrawable != null)
+ ? constantStateDrawable.newDrawable(context.getResources()).mutate()
+ : drawable;
+ retDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ return retDrawable;
+ }
+}
diff --git a/src/com/android/settings/display/MessageBubbleBackground.java b/src/com/android/settings/display/MessageBubbleBackground.java
new file mode 100644
index 00000000000..b2e1e9701c1
--- /dev/null
+++ b/src/com/android/settings/display/MessageBubbleBackground.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.display;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+
+public class MessageBubbleBackground extends LinearLayout {
+ private final int mSnapWidthPixels;
+
+ public MessageBubbleBackground(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mSnapWidthPixels = context.getResources().getDimensionPixelSize(
+ R.dimen.conversation_bubble_width_snap);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final int widthPadding = getPaddingLeft() + getPaddingRight();
+ int bubbleWidth = getMeasuredWidth() - widthPadding;
+ final int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - widthPadding;
+ // Round up to next snapWidthPixels
+ bubbleWidth = Math.min(maxWidth,
+ (int) (Math.ceil(bubbleWidth / (float) mSnapWidthPixels) * mSnapWidthPixels));
+ super.onMeasure(
+ MeasureSpec.makeMeasureSpec(bubbleWidth + widthPadding, MeasureSpec.EXACTLY),
+ heightMeasureSpec);
+ Log.w(this.getClass().getSimpleName(),
+ String.format("onMeasure called; width:%d, height:%d", this.getMeasuredWidth(),
+ this.getMeasuredHeight()));
+ }
+}