blob: 27689d4d43f95b4bd5e88a960653466880a5ddf3 [file] [log] [blame]
/*
* Copyright (C) 2018 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 android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.app.Notification;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pools;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RemoteViews;
import com.android.internal.R;
import java.io.IOException;
/**
* A message of a {@link MessagingLayout} that is an image.
*/
@RemoteViews.RemoteView
public class MessagingImageMessage extends ImageView implements MessagingMessage {
private static final String TAG = "MessagingImageMessage";
private static Pools.SimplePool<MessagingImageMessage> sInstancePool
= new Pools.SynchronizedPool<>(10);
private final MessagingMessageState mState = new MessagingMessageState(this);
private final int mMinImageHeight;
private final Path mPath = new Path();
private final int mImageRounding;
private final int mMaxImageHeight;
private final int mIsolatedSize;
private final int mExtraSpacing;
private Drawable mDrawable;
private float mAspectRatio;
private int mActualWidth;
private int mActualHeight;
private boolean mIsIsolated;
private ImageResolver mImageResolver;
public MessagingImageMessage(@NonNull Context context) {
this(context, null);
}
public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mMinImageHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.messaging_image_min_size);
mMaxImageHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.messaging_image_max_height);
mImageRounding = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.messaging_image_rounding);
mExtraSpacing = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.messaging_image_extra_spacing);
setMaxHeight(mMaxImageHeight);
mIsolatedSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
}
@Override
public MessagingMessageState getState() {
return mState;
}
@Override
public boolean setMessage(Notification.MessagingStyle.Message message) {
MessagingMessage.super.setMessage(message);
Drawable drawable;
try {
Uri uri = message.getDataUri();
drawable = mImageResolver != null ? mImageResolver.loadImage(uri) :
LocalImageResolver.resolveImage(uri, getContext());
} catch (IOException | SecurityException e) {
e.printStackTrace();
return false;
}
if (drawable == null) {
return false;
}
int intrinsicHeight = drawable.getIntrinsicHeight();
if (intrinsicHeight == 0) {
Log.w(TAG, "Drawable with 0 intrinsic height was returned");
return false;
}
mDrawable = drawable;
mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
setImageDrawable(drawable);
setContentDescription(message.getText());
return true;
}
static MessagingMessage createMessage(IMessagingLayout layout,
Notification.MessagingStyle.Message m, ImageResolver resolver) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingImageMessage createdMessage = sInstancePool.acquire();
if (createdMessage == null) {
createdMessage = (MessagingImageMessage) LayoutInflater.from(
layout.getContext()).inflate(
R.layout.notification_template_messaging_image_message,
messagingLinearLayout,
false);
createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
}
createdMessage.setImageResolver(resolver);
boolean created = createdMessage.setMessage(m);
if (!created) {
createdMessage.recycle();
return MessagingTextMessage.createMessage(layout, m);
}
return createdMessage;
}
private void setImageResolver(ImageResolver resolver) {
mImageResolver = resolver;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.clipPath(getRoundedRectPath());
// Calculate the right sizing ensuring that the image is nicely centered in the layout
// during transitions
int width = (int) Math.max((Math.min(getHeight(), getActualHeight()) * mAspectRatio),
getActualWidth());
int height = (int) Math.max((Math.min(getWidth(), getActualWidth()) / mAspectRatio),
getActualHeight());
height = (int) Math.max(height, width / mAspectRatio);
int left = (int) ((getActualWidth() - width) / 2.0f);
int top = (int) ((getActualHeight() - height) / 2.0f);
mDrawable.setBounds(left, top, left + width, top + height);
mDrawable.draw(canvas);
canvas.restore();
}
public Path getRoundedRectPath() {
int left = 0;
int right = getActualWidth();
int top = 0;
int bottom = getActualHeight();
mPath.reset();
int width = right - left;
float roundnessX = mImageRounding;
float roundnessY = mImageRounding;
roundnessX = Math.min(width / 2, roundnessX);
roundnessY = Math.min((bottom - top) / 2, roundnessY);
mPath.moveTo(left, top + roundnessY);
mPath.quadTo(left, top, left + roundnessX, top);
mPath.lineTo(right - roundnessX, top);
mPath.quadTo(right, top, right, top + roundnessY);
mPath.lineTo(right, bottom - roundnessY);
mPath.quadTo(right, bottom, right - roundnessX, bottom);
mPath.lineTo(left + roundnessX, bottom);
mPath.quadTo(left, bottom, left, bottom - roundnessY);
mPath.close();
return mPath;
}
public void recycle() {
MessagingMessage.super.recycle();
setImageBitmap(null);
mDrawable = null;
sInstancePool.release(this);
}
public static void dropCache() {
sInstancePool = new Pools.SynchronizedPool<>(10);
}
@Override
public int getMeasuredType() {
int measuredHeight = getMeasuredHeight();
int minImageHeight;
if (mIsIsolated) {
minImageHeight = mIsolatedSize;
} else {
minImageHeight = mMinImageHeight;
}
boolean measuredTooSmall = measuredHeight < minImageHeight
&& measuredHeight != mDrawable.getIntrinsicHeight();
if (measuredTooSmall) {
return MEASURED_TOO_SMALL;
} else {
if (!mIsIsolated && measuredHeight != mDrawable.getIntrinsicHeight()) {
return MEASURED_SHORTENED;
} else {
return MEASURED_NORMAL;
}
}
}
@Override
public void setMaxDisplayedLines(int lines) {
// Nothing to do, this should be handled automatically.
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mIsIsolated) {
// When isolated we have a fixed size, let's use that sizing.
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
} else {
// If we are displaying inline, we never want to go wider than actual size of the
// image, otherwise it will look quite blurry.
int width = Math.min(MeasureSpec.getSize(widthMeasureSpec),
mDrawable.getIntrinsicWidth());
int height = (int) Math.min(MeasureSpec.getSize(heightMeasureSpec), width
/ mAspectRatio);
setMeasuredDimension(width, height);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// TODO: ensure that this isn't called when transforming
setActualWidth(getWidth());
setActualHeight(getHeight());
}
@Override
public int getConsumedLines() {
return 3;
}
public void setActualWidth(int actualWidth) {
mActualWidth = actualWidth;
invalidate();
}
public int getActualWidth() {
return mActualWidth;
}
public void setActualHeight(int actualHeight) {
mActualHeight = actualHeight;
invalidate();
}
public int getActualHeight() {
return mActualHeight;
}
public void setIsolated(boolean isolated) {
if (mIsIsolated != isolated) {
mIsIsolated = isolated;
// update the layout params not to have margins
ViewGroup.MarginLayoutParams layoutParams =
(ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.topMargin = isolated ? 0 : mExtraSpacing;
setLayoutParams(layoutParams);
}
}
@Override
public int getExtraSpacing() {
return mExtraSpacing;
}
}