Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4db2f1f6 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Notification MessagingStyle: Add handset views" into nyc-dev

parents 1b2e403f c1a80b08
Loading
Loading
Loading
Loading
+115 −11
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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);
@@ -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);
@@ -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);
            }

@@ -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;
        }
@@ -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;
+17 −0
Original line number Diff line number Diff line
@@ -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
@@ -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);
@@ -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
+32 −6
Original line number Diff line number Diff line
@@ -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;
@@ -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);
@@ -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 {
@@ -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;
    }
}
+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);
        }
    }
}
+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