• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.AttrRes;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.StyleRes;
23 import android.app.Notification;
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.graphics.Path;
27 import android.graphics.drawable.Drawable;
28 import android.net.Uri;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.view.LayoutInflater;
32 import android.view.ViewGroup;
33 import android.widget.ImageView;
34 import android.widget.RemoteViews;
35 
36 import com.android.internal.R;
37 
38 import java.io.IOException;
39 
40 /**
41  * A message of a {@link MessagingLayout} that is an image.
42  */
43 @RemoteViews.RemoteView
44 public class MessagingImageMessage extends ImageView implements MessagingMessage {
45     private static final String TAG = "MessagingImageMessage";
46     private static final MessagingPool<MessagingImageMessage> sInstancePool =
47             new MessagingPool<>(10);
48     private final MessagingMessageState mState = new MessagingMessageState(this);
49     private final int mMinImageHeight;
50     private final Path mPath = new Path();
51     private final int mImageRounding;
52     private final int mMaxImageHeight;
53     private final int mIsolatedSize;
54     private final int mExtraSpacing;
55     private Drawable mDrawable;
56     private float mAspectRatio;
57     private int mActualWidth;
58     private int mActualHeight;
59     private boolean mIsIsolated;
60     private ImageResolver mImageResolver;
61 
MessagingImageMessage(@onNull Context context)62     public MessagingImageMessage(@NonNull Context context) {
63         this(context, null);
64     }
65 
MessagingImageMessage(@onNull Context context, @Nullable AttributeSet attrs)66     public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
67         this(context, attrs, 0);
68     }
69 
MessagingImageMessage(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr)70     public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
71             @AttrRes int defStyleAttr) {
72         this(context, attrs, defStyleAttr, 0);
73     }
74 
MessagingImageMessage(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)75     public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
76             @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
77         super(context, attrs, defStyleAttr, defStyleRes);
78         mMinImageHeight = context.getResources().getDimensionPixelSize(
79                 com.android.internal.R.dimen.messaging_image_min_size);
80         mMaxImageHeight = context.getResources().getDimensionPixelSize(
81                 com.android.internal.R.dimen.messaging_image_max_height);
82         mImageRounding = context.getResources().getDimensionPixelSize(
83                 com.android.internal.R.dimen.messaging_image_rounding);
84         mExtraSpacing = context.getResources().getDimensionPixelSize(
85                 com.android.internal.R.dimen.messaging_image_extra_spacing);
86         setMaxHeight(mMaxImageHeight);
87         mIsolatedSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
88     }
89 
90     @Override
getState()91     public MessagingMessageState getState() {
92         return mState;
93     }
94 
95     @Override
setMessage(Notification.MessagingStyle.Message message)96     public boolean setMessage(Notification.MessagingStyle.Message message) {
97         MessagingMessage.super.setMessage(message);
98         Drawable drawable;
99         try {
100             Uri uri = message.getDataUri();
101             drawable = mImageResolver != null ? mImageResolver.loadImage(uri) :
102                     LocalImageResolver.resolveImage(uri, getContext());
103         } catch (IOException | SecurityException e) {
104             e.printStackTrace();
105             return false;
106         }
107         if (drawable == null) {
108             return false;
109         }
110         int intrinsicHeight = drawable.getIntrinsicHeight();
111         if (intrinsicHeight == 0) {
112             Log.w(TAG, "Drawable with 0 intrinsic height was returned");
113             return false;
114         }
115         mDrawable = drawable;
116         mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
117         setImageDrawable(drawable);
118         setContentDescription(message.getText());
119         return true;
120     }
121 
createMessage(IMessagingLayout layout, Notification.MessagingStyle.Message m, ImageResolver resolver)122     static MessagingMessage createMessage(IMessagingLayout layout,
123             Notification.MessagingStyle.Message m, ImageResolver resolver) {
124         MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
125         MessagingImageMessage createdMessage = sInstancePool.acquire();
126         if (createdMessage == null) {
127             createdMessage = (MessagingImageMessage) LayoutInflater.from(
128                     layout.getContext()).inflate(
129                             R.layout.notification_template_messaging_image_message,
130                             messagingLinearLayout,
131                             false);
132             createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
133         }
134         createdMessage.setImageResolver(resolver);
135         boolean created = createdMessage.setMessage(m);
136         if (!created) {
137             createdMessage.recycle();
138             return MessagingTextMessage.createMessage(layout, m);
139         }
140         return createdMessage;
141     }
142 
setImageResolver(ImageResolver resolver)143     private void setImageResolver(ImageResolver resolver) {
144         mImageResolver = resolver;
145     }
146 
147     @Override
onDraw(Canvas canvas)148     protected void onDraw(Canvas canvas) {
149         canvas.save();
150         canvas.clipPath(getRoundedRectPath());
151         // Calculate the right sizing ensuring that the image is nicely centered in the layout
152         // during transitions
153         int width = (int) Math.max((Math.min(getHeight(), getActualHeight()) * mAspectRatio),
154                 getActualWidth());
155         int height = (int) Math.max((Math.min(getWidth(), getActualWidth()) / mAspectRatio),
156                 getActualHeight());
157         height = (int) Math.max(height, width / mAspectRatio);
158         int left = (int) ((getActualWidth() - width) / 2.0f);
159         int top = (int) ((getActualHeight() - height) / 2.0f);
160         mDrawable.setBounds(left, top, left + width, top + height);
161         mDrawable.draw(canvas);
162         canvas.restore();
163     }
164 
getRoundedRectPath()165     public Path getRoundedRectPath() {
166         int left = 0;
167         int right = getActualWidth();
168         int top = 0;
169         int bottom = getActualHeight();
170         mPath.reset();
171         int width = right - left;
172         float roundnessX = mImageRounding;
173         float roundnessY = mImageRounding;
174         roundnessX = Math.min(width / 2, roundnessX);
175         roundnessY = Math.min((bottom - top) / 2, roundnessY);
176         mPath.moveTo(left, top + roundnessY);
177         mPath.quadTo(left, top, left + roundnessX, top);
178         mPath.lineTo(right - roundnessX, top);
179         mPath.quadTo(right, top, right, top + roundnessY);
180         mPath.lineTo(right, bottom - roundnessY);
181         mPath.quadTo(right, bottom, right - roundnessX, bottom);
182         mPath.lineTo(left + roundnessX, bottom);
183         mPath.quadTo(left, bottom, left, bottom - roundnessY);
184         mPath.close();
185         return mPath;
186     }
187 
recycle()188     public void recycle() {
189         MessagingMessage.super.recycle();
190         setImageBitmap(null);
191         mDrawable = null;
192         sInstancePool.release(this);
193     }
194 
dropCache()195     public static void dropCache() {
196         sInstancePool.clear();
197     }
198 
199     @Override
getMeasuredType()200     public int getMeasuredType() {
201         int measuredHeight = getMeasuredHeight();
202         int minImageHeight;
203         if (mIsIsolated) {
204             minImageHeight = mIsolatedSize;
205         } else {
206             minImageHeight = mMinImageHeight;
207         }
208         boolean measuredTooSmall = measuredHeight < minImageHeight
209                 && measuredHeight != mDrawable.getIntrinsicHeight();
210         if (measuredTooSmall) {
211             return MEASURED_TOO_SMALL;
212         } else {
213             if (!mIsIsolated && measuredHeight != mDrawable.getIntrinsicHeight()) {
214                 return MEASURED_SHORTENED;
215             } else {
216                 return MEASURED_NORMAL;
217             }
218         }
219     }
220 
221     @Override
222     public void setMaxDisplayedLines(int lines) {
223         // Nothing to do, this should be handled automatically.
224     }
225 
226     @Override
227     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
228         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
229 
230         if (mDrawable == null) {
231             Log.e(TAG, "onMeasure() after recycle()!");
232             setMeasuredDimension(0, 0);
233             return;
234         }
235 
236         if (mIsIsolated) {
237             // When isolated we have a fixed size, let's use that sizing.
238             setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
239                     MeasureSpec.getSize(heightMeasureSpec));
240         } else {
241             // If we are displaying inline, we never want to go wider than actual size of the
242             // image, otherwise it will look quite blurry.
243             int width = Math.min(MeasureSpec.getSize(widthMeasureSpec),
244                     mDrawable.getIntrinsicWidth());
245             int height = (int) Math.min(MeasureSpec.getSize(heightMeasureSpec), width
246                     / mAspectRatio);
247             setMeasuredDimension(width, height);
248         }
249     }
250 
251     @Override
252     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
253         super.onLayout(changed, left, top, right, bottom);
254         // TODO: ensure that this isn't called when transforming
255         setActualWidth(getWidth());
256         setActualHeight(getHeight());
257     }
258 
259     @Override
260     public int getConsumedLines() {
261         return 3;
262     }
263 
264     public void setActualWidth(int actualWidth) {
265         mActualWidth = actualWidth;
266         invalidate();
267     }
268 
269     public int getActualWidth() {
270         return mActualWidth;
271     }
272 
273     public void setActualHeight(int actualHeight) {
274         mActualHeight = actualHeight;
275         invalidate();
276     }
277 
278     public int getActualHeight() {
279         return mActualHeight;
280     }
281 
282     public void setIsolated(boolean isolated) {
283         if (mIsIsolated != isolated) {
284             mIsIsolated = isolated;
285             // update the layout params not to have margins
286             ViewGroup.MarginLayoutParams layoutParams =
287                     (ViewGroup.MarginLayoutParams) getLayoutParams();
288             layoutParams.topMargin = isolated ? 0 : mExtraSpacing;
289             setLayoutParams(layoutParams);
290         }
291     }
292 
293     @Override
294     public int getExtraSpacing() {
295         return mExtraSpacing;
296     }
297 }
298