Loading core/java/android/app/Notification.java +115 −11 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; Loading @@ -43,6 +44,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.os.UserHandle; import android.text.BidiFormatter; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; Loading Loading @@ -2227,7 +2229,8 @@ public class Notification implements Parcelable Log.d(TAG, "Unknown style class: " + templateClass); } else { try { final Constructor<? extends Style> ctor = styleClass.getConstructor(); final Constructor<? extends Style> ctor = styleClass.getDeclaredConstructor(); ctor.setAccessible(true); final Style style = ctor.newInstance(); style.restoreFromExtras(mN.extras); Loading Loading @@ -3126,6 +3129,18 @@ public class Notification implements Parcelable * @param hasProgress whether the progress bar should be shown and set */ private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) { final Bundle ex = mN.extras; CharSequence title = processLegacyText(ex.getCharSequence(EXTRA_TITLE)); CharSequence text = processLegacyText(ex.getCharSequence(EXTRA_TEXT)); return applyStandardTemplate(resId, hasProgress, title, text); } /** * @param hasProgress whether the progress bar should be shown and set */ private RemoteViews applyStandardTemplate(int resId, boolean hasProgress, CharSequence title, CharSequence text) { RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); resetStandardTemplate(contentView); Loading @@ -3134,17 +3149,15 @@ public class Notification implements Parcelable bindNotificationHeader(contentView); bindLargeIcon(contentView); if (ex.getCharSequence(EXTRA_TITLE) != null) { if (title != null) { contentView.setViewVisibility(R.id.title, View.VISIBLE); contentView.setTextViewText(R.id.title, processLegacyText(ex.getCharSequence(EXTRA_TITLE))); contentView.setTextViewText(R.id.title, title); } boolean showProgress = handleProgressBar(hasProgress, contentView, ex); if (ex.getCharSequence(EXTRA_TEXT) != null) { if (text != null) { int textId = showProgress ? com.android.internal.R.id.text_line_1 : com.android.internal.R.id.text; contentView.setTextViewText(textId, processLegacyText( ex.getCharSequence(EXTRA_TEXT))); contentView.setTextViewText(textId, text); contentView.setViewVisibility(textId, View.VISIBLE); } Loading Loading @@ -3749,6 +3762,10 @@ public class Notification implements Parcelable return R.layout.notification_template_material_inbox; } private int getMessagingLayoutResource() { return R.layout.notification_template_material_messaging; } private int getActionLayoutResource() { return R.layout.notification_material_action; } Loading Loading @@ -4375,13 +4392,100 @@ public class Notification implements Parcelable /** * @hide */ @Override public RemoteViews makeContentView() { Message m = findLatestIncomingMessage(); CharSequence title = mConversationTitle != null ? mConversationTitle : (m == null) ? null : m.mSender; CharSequence text = (m == null) ? null : mConversationTitle != null ? makeMessageLine(m) : m.mText; return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(), false /* hasProgress */, title, text); } private Message findLatestIncomingMessage() { for (int i = mMessages.size() - 1; i >= 0; i--) { Message m = mMessages.get(i); // Incoming messages have a non-empty sender. if (!TextUtils.isEmpty(m.mSender)) { return m; } } return null; } /** * @hide */ @Override public RemoteViews makeBigContentView() { // TODO handset to write implementation RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource()); CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; boolean hasTitle = !TextUtils.isEmpty(title); RemoteViews contentView = mBuilder.applyStandardTemplate( mBuilder.getMessagingLayoutResource(), false /* hasProgress */, title, null /* text */); int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; // Make sure all rows are gone in case we reuse a view. for (int rowId : rowIds) { contentView.setViewVisibility(rowId, View.GONE); } int i=0; int titlePadding = mBuilder.mContext.getResources().getDimensionPixelSize( R.dimen.notification_messaging_spacing); contentView.setViewLayoutMarginBottom(R.id.line1, hasTitle ? titlePadding : 0); contentView.setInt(R.id.notification_messaging, "setNumIndentLines", mBuilder.mN.mLargeIcon == null ? 0 : (hasTitle ? 1 : 2)); int firstMessage = Math.max(0, mMessages.size() - rowIds.length); while (firstMessage + i < mMessages.size() && i < rowIds.length) { Message m = mMessages.get(firstMessage + i); int rowId = rowIds[i]; contentView.setViewVisibility(rowId, View.VISIBLE); contentView.setTextViewText(rowId, makeMessageLine(m)); i++; } return contentView; } private CharSequence makeMessageLine(Message m) { BidiFormatter bidi = BidiFormatter.getInstance(); SpannableStringBuilder sb = new SpannableStringBuilder(); if (TextUtils.isEmpty(m.mSender)) { CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName; sb.append(bidi.unicodeWrap(replyName), makeFontColorSpan(mBuilder.resolveContrastColor()), 0 /* flags */); } else { sb.append(bidi.unicodeWrap(m.mSender), makeFontColorSpan(Color.BLACK), 0 /* flags */); } CharSequence text = m.mText == null ? "" : m.mText; sb.append(" ").append(bidi.unicodeWrap(text)); return sb; } private static TextAppearanceSpan makeFontColorSpan(int color) { return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null); } public static final class Message implements Parcelable { private final CharSequence mText; Loading core/java/android/widget/RemoteViews.java +17 −0 Original line number Diff line number Diff line Loading @@ -1856,6 +1856,7 @@ public class RemoteViews implements Parcelable, Filter { public static final int LAYOUT_MARGIN_END = 1; /** Set width */ public static final int LAYOUT_WIDTH = 2; public static final int LAYOUT_MARGIN_BOTTOM = 3; /** * @param viewId ID of the view alter Loading Loading @@ -1898,6 +1899,12 @@ public class RemoteViews implements Parcelable, Filter { target.setLayoutParams(layoutParams); } break; case LAYOUT_MARGIN_BOTTOM: if (layoutParams instanceof ViewGroup.MarginLayoutParams) { ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = value; target.setLayoutParams(layoutParams); } break; case LAYOUT_WIDTH: layoutParams.width = value; target.setLayoutParams(layoutParams); Loading Loading @@ -2869,6 +2876,16 @@ public class RemoteViews implements Parcelable, Filter { addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END, endMargin)); } /** * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. * * @hide */ public void setViewLayoutMarginBottom(int viewId, int bottomMargin) { addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM, bottomMargin)); } /** * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. * @hide Loading core/java/com/android/internal/widget/ImageFloatingTextView.java +32 −6 Original line number Diff line number Diff line Loading @@ -16,13 +16,18 @@ package com.android.internal.widget; import com.android.internal.R; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.text.BoringLayout; import android.text.Layout; import android.text.StaticLayout; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.RemotableViewMethod; import android.widget.RemoteViews; import android.widget.TextView; Loading @@ -35,7 +40,8 @@ import android.widget.TextView; @RemoteViews.RemoteView public class ImageFloatingTextView extends TextView { private boolean mHasImage; /** Number of lines from the top to indent */ private int mIndentLines; public ImageFloatingTextView(Context context) { this(context, null); Loading Loading @@ -69,10 +75,16 @@ public class ImageFloatingTextView extends TextView { .setEllipsizedWidth(ellipsisWidth) .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); // we set the endmargin on the first 2 lines. this works just in our case but that's // sufficient for now. int endMargin = (int) (getResources().getDisplayMetrics().density * 52); int[] margins = mHasImage ? new int[] {endMargin, endMargin, 0} : null; // we set the endmargin on the requested number of lines. int endMargin = getContext().getResources().getDimensionPixelSize( R.dimen.notification_content_picture_margin); int[] margins = null; if (mIndentLines > 0) { margins = new int[mIndentLines + 1]; for (int i = 0; i < mIndentLines; i++) { margins[i] = endMargin; } } if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { builder.setIndents(margins, null); } else { Loading @@ -84,8 +96,22 @@ public class ImageFloatingTextView extends TextView { @RemotableViewMethod public void setHasImage(boolean hasImage) { mHasImage = hasImage; mIndentLines = hasImage ? 2 : 0; // The new layout will be automatically created when the text is // set again by the notification. } /** * @param lines the number of lines at the top that should be indented by indentEnd * @return whether a change was made */ public boolean setNumIndentLines(int lines) { if (mIndentLines != lines) { mIndentLines = lines; // Invalidate layout. setHint(getHint()); return true; } return false; } } core/java/com/android/internal/widget/MessagingLinearLayout.java 0 → 100644 +278 −0 Original line number Diff line number Diff line /* * 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.internal.widget; import com.android.internal.R; import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; /** * A custom-built layout for the Notification.MessagingStyle. * * Evicts children until they all fit. */ @RemoteViews.RemoteView public class MessagingLinearLayout extends ViewGroup { /** * Spacing to be applied between views. */ private int mSpacing; /** * The maximum height allowed. */ private int mMaxHeight; private int mIndentLines; public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MessagingLinearLayout, 0, 0); final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.MessagingLinearLayout_maxHeight: mMaxHeight = a.getDimensionPixelSize(i, 0); break; case R.styleable.MessagingLinearLayout_spacing: mSpacing = a.getDimensionPixelSize(i, 0); break; } } if (mMaxHeight <= 0) { throw new IllegalStateException( "MessagingLinearLayout: Must specify positive maxHeight"); } a.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // This is essentially a bottom-up linear layout that only adds children that fit entirely // up to a maximum height. switch (MeasureSpec.getMode(heightMeasureSpec)) { case MeasureSpec.AT_MOST: heightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.min(mMaxHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST); break; case MeasureSpec.UNSPECIFIED: heightMeasureSpec = MeasureSpec.makeMeasureSpec( mMaxHeight, MeasureSpec.AT_MOST); break; case MeasureSpec.EXACTLY: break; } final int targetHeight = MeasureSpec.getSize(heightMeasureSpec); final int count = getChildCount(); for (int i = 0; i < count; ++i) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.hide = true; } int totalHeight = mPaddingTop + mPaddingBottom; boolean first = true; // Starting from the bottom: we measure every view as if it were the only one. If it still // fits, we take it, otherwise we stop there. for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { if (getChildAt(i).getVisibility() == GONE) { continue; } final View child = getChildAt(i); LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); if (child instanceof ImageFloatingTextView) { // Pretend we need the image padding for all views, we don't know which // one will end up needing to do this (might end up not using all the space, // but calculating this exactly would be more expensive). ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines); } measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final int childHeight = child.getMeasuredHeight(); int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing)); first = false; if (newHeight <= targetHeight) { totalHeight = newHeight; lp.hide = false; } else { break; } } // Now that we know which views to take, fix up the indents and see what width we get. int measuredWidth = mPaddingLeft + mPaddingRight; int imageLines = mIndentLines; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (child.getVisibility() == GONE || lp.hide) { continue; } if (child instanceof ImageFloatingTextView) { ImageFloatingTextView textChild = (ImageFloatingTextView) child; if (imageLines == 2 && textChild.getLineCount() > 2) { // HACK: If we need indent for two lines, and they're coming from the same // view, we need extra spacing to compensate for the lack of margins, // so add an extra line of indent. imageLines = 3; } boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines)); if (changed) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); } imageLines -= textChild.getLineCount(); } measuredWidth = Math.max(measuredWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + mPaddingLeft + mPaddingRight); } setMeasuredDimension( resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec), resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight), heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; // Where right end of child should go final int width = right - left; final int childRight = width - mPaddingRight; final int layoutDirection = getLayoutDirection(); final int count = getChildCount(); childTop = mPaddingTop; boolean first = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (child.getVisibility() == GONE || lp.hide) { continue; } final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); int childLeft; if (layoutDirection == LAYOUT_DIRECTION_RTL) { childLeft = childRight - childWidth - lp.rightMargin; } else { childLeft = paddingLeft + lp.leftMargin; } if (!first) { childTop += mSpacing; } childTop += lp.topMargin; child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); childTop += childHeight + lp.bottomMargin; first = false; } } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.hide) { return true; } return super.drawChild(canvas, child, drawingTime); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(mContext, attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { LayoutParams copy = new LayoutParams(lp.width, lp.height); if (lp instanceof MarginLayoutParams) { copy.copyMarginsFrom((MarginLayoutParams) lp); } return copy; } @RemotableViewMethod /** * Sets how many lines should be indented to avoid a floating image. */ public void setNumIndentLines(int numberLines) { mIndentLines = numberLines; } public static class LayoutParams extends MarginLayoutParams { boolean hide = false; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int width, int height) { super(width, height); } } } core/res/res/layout/notification_template_material_messaging.xml 0 → 100644 +80 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ 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 --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:tag="messaging" > <include layout="@layout/notification_template_header" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top" android:layout_marginTop="@dimen/notification_content_margin_top" android:clipToPadding="false" android:orientation="vertical"> <LinearLayout android:id="@+id/notification_main_column" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:paddingStart="@dimen/notification_content_margin_start" android:paddingEnd="@dimen/notification_content_margin_end" android:minHeight="@dimen/notification_min_content_height" android:clipToPadding="false" android:orientation="vertical" > <include layout="@layout/notification_template_part_line1" android:layout_width="match_parent" android:layout_height="wrap_content" /> <com.android.internal.widget.MessagingLinearLayout android:id="@+id/notification_messaging" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="@dimen/notification_content_margin_bottom" android:spacing="@dimen/notification_messaging_spacing" android:maxHeight="212dp"> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6" style="@style/Widget.Material.Notification.MessagingText" /> </com.android.internal.widget.MessagingLinearLayout> </LinearLayout> <include layout="@layout/notification_material_action_list" /> </LinearLayout> <include layout="@layout/notification_template_right_icon" /> </FrameLayout> Loading
core/java/android/app/Notification.java +115 −11 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.ColorStateList; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; Loading @@ -43,6 +44,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; import android.os.UserHandle; import android.text.BidiFormatter; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; Loading Loading @@ -2227,7 +2229,8 @@ public class Notification implements Parcelable Log.d(TAG, "Unknown style class: " + templateClass); } else { try { final Constructor<? extends Style> ctor = styleClass.getConstructor(); final Constructor<? extends Style> ctor = styleClass.getDeclaredConstructor(); ctor.setAccessible(true); final Style style = ctor.newInstance(); style.restoreFromExtras(mN.extras); Loading Loading @@ -3126,6 +3129,18 @@ public class Notification implements Parcelable * @param hasProgress whether the progress bar should be shown and set */ private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) { final Bundle ex = mN.extras; CharSequence title = processLegacyText(ex.getCharSequence(EXTRA_TITLE)); CharSequence text = processLegacyText(ex.getCharSequence(EXTRA_TEXT)); return applyStandardTemplate(resId, hasProgress, title, text); } /** * @param hasProgress whether the progress bar should be shown and set */ private RemoteViews applyStandardTemplate(int resId, boolean hasProgress, CharSequence title, CharSequence text) { RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); resetStandardTemplate(contentView); Loading @@ -3134,17 +3149,15 @@ public class Notification implements Parcelable bindNotificationHeader(contentView); bindLargeIcon(contentView); if (ex.getCharSequence(EXTRA_TITLE) != null) { if (title != null) { contentView.setViewVisibility(R.id.title, View.VISIBLE); contentView.setTextViewText(R.id.title, processLegacyText(ex.getCharSequence(EXTRA_TITLE))); contentView.setTextViewText(R.id.title, title); } boolean showProgress = handleProgressBar(hasProgress, contentView, ex); if (ex.getCharSequence(EXTRA_TEXT) != null) { if (text != null) { int textId = showProgress ? com.android.internal.R.id.text_line_1 : com.android.internal.R.id.text; contentView.setTextViewText(textId, processLegacyText( ex.getCharSequence(EXTRA_TEXT))); contentView.setTextViewText(textId, text); contentView.setViewVisibility(textId, View.VISIBLE); } Loading Loading @@ -3749,6 +3762,10 @@ public class Notification implements Parcelable return R.layout.notification_template_material_inbox; } private int getMessagingLayoutResource() { return R.layout.notification_template_material_messaging; } private int getActionLayoutResource() { return R.layout.notification_material_action; } Loading Loading @@ -4375,13 +4392,100 @@ public class Notification implements Parcelable /** * @hide */ @Override public RemoteViews makeContentView() { Message m = findLatestIncomingMessage(); CharSequence title = mConversationTitle != null ? mConversationTitle : (m == null) ? null : m.mSender; CharSequence text = (m == null) ? null : mConversationTitle != null ? makeMessageLine(m) : m.mText; return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(), false /* hasProgress */, title, text); } private Message findLatestIncomingMessage() { for (int i = mMessages.size() - 1; i >= 0; i--) { Message m = mMessages.get(i); // Incoming messages have a non-empty sender. if (!TextUtils.isEmpty(m.mSender)) { return m; } } return null; } /** * @hide */ @Override public RemoteViews makeBigContentView() { // TODO handset to write implementation RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource()); CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle) ? super.mBigContentTitle : mConversationTitle; boolean hasTitle = !TextUtils.isEmpty(title); RemoteViews contentView = mBuilder.applyStandardTemplate( mBuilder.getMessagingLayoutResource(), false /* hasProgress */, title, null /* text */); int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; // Make sure all rows are gone in case we reuse a view. for (int rowId : rowIds) { contentView.setViewVisibility(rowId, View.GONE); } int i=0; int titlePadding = mBuilder.mContext.getResources().getDimensionPixelSize( R.dimen.notification_messaging_spacing); contentView.setViewLayoutMarginBottom(R.id.line1, hasTitle ? titlePadding : 0); contentView.setInt(R.id.notification_messaging, "setNumIndentLines", mBuilder.mN.mLargeIcon == null ? 0 : (hasTitle ? 1 : 2)); int firstMessage = Math.max(0, mMessages.size() - rowIds.length); while (firstMessage + i < mMessages.size() && i < rowIds.length) { Message m = mMessages.get(firstMessage + i); int rowId = rowIds[i]; contentView.setViewVisibility(rowId, View.VISIBLE); contentView.setTextViewText(rowId, makeMessageLine(m)); i++; } return contentView; } private CharSequence makeMessageLine(Message m) { BidiFormatter bidi = BidiFormatter.getInstance(); SpannableStringBuilder sb = new SpannableStringBuilder(); if (TextUtils.isEmpty(m.mSender)) { CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName; sb.append(bidi.unicodeWrap(replyName), makeFontColorSpan(mBuilder.resolveContrastColor()), 0 /* flags */); } else { sb.append(bidi.unicodeWrap(m.mSender), makeFontColorSpan(Color.BLACK), 0 /* flags */); } CharSequence text = m.mText == null ? "" : m.mText; sb.append(" ").append(bidi.unicodeWrap(text)); return sb; } private static TextAppearanceSpan makeFontColorSpan(int color) { return new TextAppearanceSpan(null, 0, 0, ColorStateList.valueOf(color), null); } public static final class Message implements Parcelable { private final CharSequence mText; Loading
core/java/android/widget/RemoteViews.java +17 −0 Original line number Diff line number Diff line Loading @@ -1856,6 +1856,7 @@ public class RemoteViews implements Parcelable, Filter { public static final int LAYOUT_MARGIN_END = 1; /** Set width */ public static final int LAYOUT_WIDTH = 2; public static final int LAYOUT_MARGIN_BOTTOM = 3; /** * @param viewId ID of the view alter Loading Loading @@ -1898,6 +1899,12 @@ public class RemoteViews implements Parcelable, Filter { target.setLayoutParams(layoutParams); } break; case LAYOUT_MARGIN_BOTTOM: if (layoutParams instanceof ViewGroup.MarginLayoutParams) { ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = value; target.setLayoutParams(layoutParams); } break; case LAYOUT_WIDTH: layoutParams.width = value; target.setLayoutParams(layoutParams); Loading Loading @@ -2869,6 +2876,16 @@ public class RemoteViews implements Parcelable, Filter { addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END, endMargin)); } /** * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. * * @hide */ public void setViewLayoutMarginBottom(int viewId, int bottomMargin) { addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM, bottomMargin)); } /** * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. * @hide Loading
core/java/com/android/internal/widget/ImageFloatingTextView.java +32 −6 Original line number Diff line number Diff line Loading @@ -16,13 +16,18 @@ package com.android.internal.widget; import com.android.internal.R; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.content.res.TypedArray; import android.text.BoringLayout; import android.text.Layout; import android.text.StaticLayout; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; import android.view.RemotableViewMethod; import android.widget.RemoteViews; import android.widget.TextView; Loading @@ -35,7 +40,8 @@ import android.widget.TextView; @RemoteViews.RemoteView public class ImageFloatingTextView extends TextView { private boolean mHasImage; /** Number of lines from the top to indent */ private int mIndentLines; public ImageFloatingTextView(Context context) { this(context, null); Loading Loading @@ -69,10 +75,16 @@ public class ImageFloatingTextView extends TextView { .setEllipsizedWidth(ellipsisWidth) .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL); // we set the endmargin on the first 2 lines. this works just in our case but that's // sufficient for now. int endMargin = (int) (getResources().getDisplayMetrics().density * 52); int[] margins = mHasImage ? new int[] {endMargin, endMargin, 0} : null; // we set the endmargin on the requested number of lines. int endMargin = getContext().getResources().getDimensionPixelSize( R.dimen.notification_content_picture_margin); int[] margins = null; if (mIndentLines > 0) { margins = new int[mIndentLines + 1]; for (int i = 0; i < mIndentLines; i++) { margins[i] = endMargin; } } if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { builder.setIndents(margins, null); } else { Loading @@ -84,8 +96,22 @@ public class ImageFloatingTextView extends TextView { @RemotableViewMethod public void setHasImage(boolean hasImage) { mHasImage = hasImage; mIndentLines = hasImage ? 2 : 0; // The new layout will be automatically created when the text is // set again by the notification. } /** * @param lines the number of lines at the top that should be indented by indentEnd * @return whether a change was made */ public boolean setNumIndentLines(int lines) { if (mIndentLines != lines) { mIndentLines = lines; // Invalidate layout. setHint(getHint()); return true; } return false; } }
core/java/com/android/internal/widget/MessagingLinearLayout.java 0 → 100644 +278 −0 Original line number Diff line number Diff line /* * 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.internal.widget; import com.android.internal.R; import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.util.AttributeSet; import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; /** * A custom-built layout for the Notification.MessagingStyle. * * Evicts children until they all fit. */ @RemoteViews.RemoteView public class MessagingLinearLayout extends ViewGroup { /** * Spacing to be applied between views. */ private int mSpacing; /** * The maximum height allowed. */ private int mMaxHeight; private int mIndentLines; public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MessagingLinearLayout, 0, 0); final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); switch (attr) { case R.styleable.MessagingLinearLayout_maxHeight: mMaxHeight = a.getDimensionPixelSize(i, 0); break; case R.styleable.MessagingLinearLayout_spacing: mSpacing = a.getDimensionPixelSize(i, 0); break; } } if (mMaxHeight <= 0) { throw new IllegalStateException( "MessagingLinearLayout: Must specify positive maxHeight"); } a.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // This is essentially a bottom-up linear layout that only adds children that fit entirely // up to a maximum height. switch (MeasureSpec.getMode(heightMeasureSpec)) { case MeasureSpec.AT_MOST: heightMeasureSpec = MeasureSpec.makeMeasureSpec( Math.min(mMaxHeight, MeasureSpec.getSize(heightMeasureSpec)), MeasureSpec.AT_MOST); break; case MeasureSpec.UNSPECIFIED: heightMeasureSpec = MeasureSpec.makeMeasureSpec( mMaxHeight, MeasureSpec.AT_MOST); break; case MeasureSpec.EXACTLY: break; } final int targetHeight = MeasureSpec.getSize(heightMeasureSpec); final int count = getChildCount(); for (int i = 0; i < count; ++i) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.hide = true; } int totalHeight = mPaddingTop + mPaddingBottom; boolean first = true; // Starting from the bottom: we measure every view as if it were the only one. If it still // fits, we take it, otherwise we stop there. for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) { if (getChildAt(i).getVisibility() == GONE) { continue; } final View child = getChildAt(i); LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); if (child instanceof ImageFloatingTextView) { // Pretend we need the image padding for all views, we don't know which // one will end up needing to do this (might end up not using all the space, // but calculating this exactly would be more expensive). ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines); } measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final int childHeight = child.getMeasuredHeight(); int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing)); first = false; if (newHeight <= targetHeight) { totalHeight = newHeight; lp.hide = false; } else { break; } } // Now that we know which views to take, fix up the indents and see what width we get. int measuredWidth = mPaddingLeft + mPaddingRight; int imageLines = mIndentLines; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (child.getVisibility() == GONE || lp.hide) { continue; } if (child instanceof ImageFloatingTextView) { ImageFloatingTextView textChild = (ImageFloatingTextView) child; if (imageLines == 2 && textChild.getLineCount() > 2) { // HACK: If we need indent for two lines, and they're coming from the same // view, we need extra spacing to compensate for the lack of margins, // so add an extra line of indent. imageLines = 3; } boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines)); if (changed) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); } imageLines -= textChild.getLineCount(); } measuredWidth = Math.max(measuredWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin + mPaddingLeft + mPaddingRight); } setMeasuredDimension( resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth), widthMeasureSpec), resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight), heightMeasureSpec)); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; // Where right end of child should go final int width = right - left; final int childRight = width - mPaddingRight; final int layoutDirection = getLayoutDirection(); final int count = getChildCount(); childTop = mPaddingTop; boolean first = true; for (int i = 0; i < count; i++) { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (child.getVisibility() == GONE || lp.hide) { continue; } final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); int childLeft; if (layoutDirection == LAYOUT_DIRECTION_RTL) { childLeft = childRight - childWidth - lp.rightMargin; } else { childLeft = paddingLeft + lp.leftMargin; } if (!first) { childTop += mSpacing; } childTop += lp.topMargin; child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); childTop += childHeight + lp.bottomMargin; first = false; } } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.hide) { return true; } return super.drawChild(canvas, child, drawingTime); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(mContext, attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { LayoutParams copy = new LayoutParams(lp.width, lp.height); if (lp instanceof MarginLayoutParams) { copy.copyMarginsFrom((MarginLayoutParams) lp); } return copy; } @RemotableViewMethod /** * Sets how many lines should be indented to avoid a floating image. */ public void setNumIndentLines(int numberLines) { mIndentLines = numberLines; } public static class LayoutParams extends MarginLayoutParams { boolean hide = false; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(int width, int height) { super(width, height); } } }
core/res/res/layout/notification_template_material_messaging.xml 0 → 100644 +80 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ 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 --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:tag="messaging" > <include layout="@layout/notification_template_header" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="top" android:layout_marginTop="@dimen/notification_content_margin_top" android:clipToPadding="false" android:orientation="vertical"> <LinearLayout android:id="@+id/notification_main_column" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top" android:paddingStart="@dimen/notification_content_margin_start" android:paddingEnd="@dimen/notification_content_margin_end" android:minHeight="@dimen/notification_min_content_height" android:clipToPadding="false" android:orientation="vertical" > <include layout="@layout/notification_template_part_line1" android:layout_width="match_parent" android:layout_height="wrap_content" /> <com.android.internal.widget.MessagingLinearLayout android:id="@+id/notification_messaging" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="@dimen/notification_content_margin_bottom" android:spacing="@dimen/notification_messaging_spacing" android:maxHeight="212dp"> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5" style="@style/Widget.Material.Notification.MessagingText" /> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6" style="@style/Widget.Material.Notification.MessagingText" /> </com.android.internal.widget.MessagingLinearLayout> </LinearLayout> <include layout="@layout/notification_material_action_list" /> </LinearLayout> <include layout="@layout/notification_template_right_icon" /> </FrameLayout>