314 lines
11 KiB
Java
314 lines
11 KiB
Java
/*
|
|
* 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.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 final MessagingPool<MessagingImageMessage> sInstancePool =
|
|
new MessagingPool<>(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,
|
|
boolean usePrecomputedText) {
|
|
MessagingMessage.super.setMessage(message, usePrecomputedText);
|
|
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;
|
|
if (!usePrecomputedText) {
|
|
finalizeInflate();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static MessagingMessage createMessage(IMessagingLayout layout,
|
|
Notification.MessagingStyle.Message m, ImageResolver resolver,
|
|
boolean usePrecomputedText) {
|
|
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);
|
|
// MessagingImageMessage does not use usePrecomputedText.
|
|
boolean populated = createdMessage.setMessage(m, /* usePrecomputedText= */false);
|
|
if (!populated) {
|
|
createdMessage.recycle();
|
|
return MessagingTextMessage.createMessage(layout, m, usePrecomputedText);
|
|
}
|
|
return createdMessage;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void finalizeInflate() {
|
|
setImageDrawable(mDrawable);
|
|
setContentDescription(getMessage().getText());
|
|
}
|
|
|
|
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.clear();
|
|
}
|
|
|
|
@Override
|
|
public int getMeasuredType() {
|
|
if (mDrawable == null) {
|
|
Log.e(TAG, "getMeasuredType() after recycle()!");
|
|
return MEASURED_NORMAL;
|
|
}
|
|
|
|
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 (mDrawable == null) {
|
|
Log.e(TAG, "onMeasure() after recycle()!");
|
|
setMeasuredDimension(0, 0);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|