1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.internal.widget;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.text.BoringLayout;
22 import android.text.Layout;
23 import android.text.StaticLayout;
24 import android.text.TextUtils;
25 import android.util.AttributeSet;
26 import android.view.RemotableViewMethod;
27 import android.widget.RemoteViews;
28 import android.widget.TextView;
29 
30 import com.android.internal.R;
31 
32 /**
33  * A TextView that can float around an image on the end.
34  *
35  * @hide
36  */
37 @RemoteViews.RemoteView
38 public class ImageFloatingTextView extends TextView {
39 
40     /** Number of lines from the top to indent */
41     private int mIndentLines;
42 
43     /** Resolved layout direction */
44     private int mResolvedDirection = LAYOUT_DIRECTION_UNDEFINED;
45     private int mMaxLinesForHeight = -1;
46     private boolean mFirstMeasure = true;
47     private int mLayoutMaxLines = -1;
48     private boolean mBlockLayouts;
49 
ImageFloatingTextView(Context context)50     public ImageFloatingTextView(Context context) {
51         this(context, null);
52     }
53 
ImageFloatingTextView(Context context, @Nullable AttributeSet attrs)54     public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs) {
55         this(context, attrs, 0);
56     }
57 
ImageFloatingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)58     public ImageFloatingTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
59         this(context, attrs, defStyleAttr, 0);
60     }
61 
ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)62     public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
63             int defStyleRes) {
64         super(context, attrs, defStyleAttr, defStyleRes);
65     }
66 
67     @Override
makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TextUtils.TruncateAt effectiveEllipsize, boolean useSaved)68     protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
69             Layout.Alignment alignment, boolean shouldEllipsize,
70             TextUtils.TruncateAt effectiveEllipsize, boolean useSaved) {
71         CharSequence text = getText() == null ? "" : getText();
72         StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
73                 getPaint(), wantWidth)
74                 .setAlignment(alignment)
75                 .setTextDirection(getTextDirectionHeuristic())
76                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
77                 .setIncludePad(getIncludeFontPadding())
78                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
79                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
80         int maxLines;
81         if (mMaxLinesForHeight > 0) {
82             maxLines = mMaxLinesForHeight;
83         } else {
84             maxLines = getMaxLines() >= 0 ? getMaxLines() : Integer.MAX_VALUE;
85         }
86         builder.setMaxLines(maxLines);
87         mLayoutMaxLines = maxLines;
88         if (shouldEllipsize) {
89             builder.setEllipsize(effectiveEllipsize)
90                     .setEllipsizedWidth(ellipsisWidth);
91         }
92 
93         // we set the endmargin on the requested number of lines.
94         int endMargin = getContext().getResources().getDimensionPixelSize(
95                 R.dimen.notification_content_picture_margin);
96         int[] margins = null;
97         if (mIndentLines > 0) {
98             margins = new int[mIndentLines + 1];
99             for (int i = 0; i < mIndentLines; i++) {
100                 margins[i] = endMargin;
101             }
102         }
103         if (mResolvedDirection == LAYOUT_DIRECTION_RTL) {
104             builder.setIndents(margins, null);
105         } else {
106             builder.setIndents(null, margins);
107         }
108 
109         return builder.build();
110     }
111 
112     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)113     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
114         int height = MeasureSpec.getSize(heightMeasureSpec);
115         // Lets calculate how many lines the given measurement allows us.
116         int availableHeight = height - mPaddingTop - mPaddingBottom;
117         int maxLines = availableHeight / getLineHeight();
118         maxLines = Math.max(1, maxLines);
119         if (getMaxLines() > 0) {
120             maxLines = Math.min(getMaxLines(), maxLines);
121         }
122         if (maxLines != mMaxLinesForHeight) {
123             mMaxLinesForHeight = maxLines;
124             if (getLayout() != null && mMaxLinesForHeight != mLayoutMaxLines) {
125                 // Invalidate layout.
126                 mBlockLayouts = true;
127                 setHint(getHint());
128                 mBlockLayouts = false;
129             }
130         }
131         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
132     }
133 
134     @Override
requestLayout()135     public void requestLayout() {
136         if (!mBlockLayouts) {
137             super.requestLayout();
138         }
139     }
140 
141     @Override
onRtlPropertiesChanged(int layoutDirection)142     public void onRtlPropertiesChanged(int layoutDirection) {
143         super.onRtlPropertiesChanged(layoutDirection);
144 
145         if (layoutDirection != mResolvedDirection && isLayoutDirectionResolved()) {
146             mResolvedDirection = layoutDirection;
147             if (mIndentLines > 0) {
148                 // Invalidate layout.
149                 setHint(getHint());
150             }
151         }
152     }
153 
154     @RemotableViewMethod
setHasImage(boolean hasImage)155     public void setHasImage(boolean hasImage) {
156         setNumIndentLines(hasImage ? 2 : 0);
157     }
158 
159     /**
160      * @param lines the number of lines at the top that should be indented by indentEnd
161      * @return whether a change was made
162      */
setNumIndentLines(int lines)163     public boolean setNumIndentLines(int lines) {
164         if (mIndentLines != lines) {
165             mIndentLines = lines;
166             // Invalidate layout.
167             setHint(getHint());
168             return true;
169         }
170         return false;
171     }
172 
getLayoutHeight()173     public int getLayoutHeight() {
174         return getLayout().getHeight();
175     }
176 }
177