Merge "Implemented support for inline images"

This commit is contained in:
Selim Cinek
2018-02-06 23:15:00 +00:00
committed by Android (Google) Code Review
20 changed files with 1040 additions and 189 deletions

View File

@@ -6434,7 +6434,7 @@ public class Notification implements Parcelable
public RemoteViews makeContentView(boolean increasedHeight) {
mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
mBuilder.mActions = mBuilder.mOriginalActions;
mBuilder.mOriginalActions = null;
return remoteViews;
@@ -6469,11 +6469,11 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
return makeBigContentView(false /* showRightIcon */);
return makeMessagingView(false /* isCollapsed */);
}
@NonNull
private RemoteViews makeBigContentView(boolean showRightIcon) {
private RemoteViews makeMessagingView(boolean isCollapsed) {
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
@@ -6484,21 +6484,24 @@ public class Notification implements Parcelable
nameReplacement = conversationTitle;
conversationTitle = null;
}
boolean hideLargeIcon = !isCollapsed || isOneToOne;
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
.hideLargeIcon(!showRightIcon || isOneToOne)
.hideLargeIcon(hideLargeIcon)
.headerTextSecondary(conversationTitle)
.alwaysShowReply(showRightIcon));
.alwaysShowReply(isCollapsed));
addExtras(mBuilder.mN.extras);
// also update the end margin if there is an image
int endMargin = R.dimen.notification_content_margin_end;
if (mBuilder.mN.hasLargeIcon() && showRightIcon) {
if (isCollapsed) {
endMargin = R.dimen.notification_content_plus_picture_margin_end;
}
contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
mBuilder.resolveContrastColor());
contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
isCollapsed);
contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
mBuilder.mN.mLargeIcon);
contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
@@ -6565,7 +6568,7 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
return remoteViews;
}

View File

@@ -0,0 +1,76 @@
/*
* 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.Nullable;
import android.app.Notification;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
/**
* A class to extract Bitmaps from a MessagingStyle message.
*/
public class LocalImageResolver {
private static final int MAX_SAFE_ICON_SIZE_PX = 480;
@Nullable
public static Drawable resolveImage(Uri uri, Context context) throws IOException {
BitmapFactory.Options onlyBoundsOptions = getBoundsOptionsForImage(uri, context);
if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
return null;
}
int originalSize =
(onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth)
? onlyBoundsOptions.outHeight
: onlyBoundsOptions.outWidth;
double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX)
? (originalSize / MAX_SAFE_ICON_SIZE_PX)
: 1.0;
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
InputStream input = context.getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
input.close();
return new BitmapDrawable(context.getResources(), bitmap);
}
private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context)
throws IOException {
InputStream input = context.getContentResolver().openInputStream(uri);
BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
onlyBoundsOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
input.close();
return onlyBoundsOptions;
}
private static int getPowerOfTwoForSampleRatio(double ratio) {
int k = Integer.highestOneBit((int) Math.floor(ratio));
return Math.max(1, k);
}
}

View File

@@ -22,9 +22,12 @@ import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.app.Notification;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pools;
import android.view.LayoutInflater;
import android.view.View;
@@ -61,6 +64,11 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
private boolean mIsHidingAnimated;
private boolean mNeedsGeneratedAvatar;
private Notification.Person mSender;
private boolean mAvatarsAtEnd;
private ViewGroup mImageContainer;
private MessagingImageMessage mIsolatedMessage;
private boolean mTransformingImages;
private Point mDisplaySize = new Point();
public MessagingGroup(@NonNull Context context) {
super(context);
@@ -87,6 +95,35 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
mSenderName = findViewById(R.id.message_name);
mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
mAvatarView = findViewById(R.id.message_icon);
mImageContainer = findViewById(R.id.messaging_group_icon_container);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
mDisplaySize.x = displayMetrics.widthPixels;
mDisplaySize.y = displayMetrics.heightPixels;
}
public void updateClipRect() {
// We want to clip to the senderName if it's available, otherwise our images will come
// from a weird position
Rect clipRect;
if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) {
ViewGroup parent = (ViewGroup) mSenderName.getParent();
int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent(
mMessageContainer, parent) + mSenderName.getHeight();
clipRect = new Rect(0, top, mDisplaySize.x, mDisplaySize.y);
} else {
clipRect = null;
}
mMessageContainer.setClipBounds(clipRect);
}
private int getDistanceFromParent(View searchedView, ViewGroup parent) {
int position = 0;
View view = searchedView;
while(view != parent) {
position += view.getTop() + view.getTranslationY();
view = (View) view.getParent();
}
return position;
}
public void setSender(Notification.Person sender, CharSequence nameOverride) {
@@ -129,12 +166,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
public void removeMessage(MessagingMessage messagingMessage) {
mMessageContainer.removeView(messagingMessage);
ViewGroup messageParent = (ViewGroup) messagingMessage.getView().getParent();
messageParent.removeView(messagingMessage.getView());
Runnable recycleRunnable = () -> {
mMessageContainer.removeTransientView(messagingMessage);
messageParent.removeTransientView(messagingMessage.getView());
messagingMessage.recycle();
if (mMessageContainer.getChildCount() == 0
&& mMessageContainer.getTransientViewCount() == 0) {
&& mMessageContainer.getTransientViewCount() == 0
&& mImageContainer.getChildCount() == 0) {
ViewParent parent = getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(MessagingGroup.this);
@@ -148,9 +187,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
};
if (isShown()) {
mMessageContainer.addTransientView(messagingMessage, 0);
performRemoveAnimation(messagingMessage, recycleRunnable);
if (mMessageContainer.getChildCount() == 0) {
messageParent.addTransientView(messagingMessage.getView(), 0);
performRemoveAnimation(messagingMessage.getView(), recycleRunnable);
if (mMessageContainer.getChildCount() == 0
&& mImageContainer.getChildCount() == 0) {
removeGroupAnimated(null);
}
} else {
@@ -160,12 +200,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
private void removeGroupAnimated(Runnable endAction) {
MessagingPropertyAnimator.fadeOut(mAvatarView, null);
MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
(int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
MessagingPropertyAnimator.fadeOut(mSenderName, null);
MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
(int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
performRemoveAnimation(mAvatarView, null);
performRemoveAnimation(mSenderName, null);
boolean endActionTriggered = false;
for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
View child = mMessageContainer.getChildAt(i);
@@ -182,14 +218,17 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
performRemoveAnimation(child, childEndAction);
endActionTriggered = true;
}
if (mIsolatedMessage != null) {
performRemoveAnimation(mIsolatedMessage, !endActionTriggered ? endAction : null);
endActionTriggered = true;
}
if (!endActionTriggered && endAction != null) {
endAction.run();
}
}
public void performRemoveAnimation(View message,
Runnable recycleRunnable) {
MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
public void performRemoveAnimation(View message, Runnable endAction) {
MessagingPropertyAnimator.fadeOut(message, endAction);
MessagingPropertyAnimator.startLocalTranslationTo(message,
(int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
}
@@ -222,6 +261,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
}
}
if (mMessageContainer.getChildCount() == 0 && mIsolatedMessage != null) {
return mIsolatedMessage.getMeasuredType();
}
return MEASURED_NORMAL;
}
@@ -234,6 +276,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
}
}
result = mIsolatedMessage != null ? Math.max(result, 1) : result;
// A group is usually taking up quite some space with the padding and the name, let's add 1
return result + 1;
}
@@ -289,26 +332,67 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
public void setMessages(List<MessagingMessage> group) {
// Let's now make sure all children are added and in the correct order
int textMessageIndex = 0;
MessagingImageMessage isolatedMessage = null;
for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
MessagingMessage message = group.get(messageIndex);
message.setColor(mTextColor);
if (message.getGroup() != this) {
message.setMessagingGroup(this);
ViewParent parent = mMessageContainer.getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(message);
}
mMessageContainer.addView(message, messageIndex);
mAddedMessages.add(message);
}
if (messageIndex != mMessageContainer.indexOfChild(message)) {
mMessageContainer.removeView(message);
mMessageContainer.addView(message, messageIndex);
boolean isImage = message instanceof MessagingImageMessage;
if (mAvatarsAtEnd && isImage) {
isolatedMessage = (MessagingImageMessage) message;
} else {
if (removeFromParentIfDifferent(message, mMessageContainer)) {
ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams();
if (layoutParams != null
&& !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) {
message.getView().setLayoutParams(
mMessageContainer.generateDefaultLayoutParams());
}
mMessageContainer.addView(message.getView(), textMessageIndex);
}
if (isImage) {
((MessagingImageMessage) message).setIsolated(false);
}
// Let's sort them properly
if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) {
mMessageContainer.removeView(message.getView());
mMessageContainer.addView(message.getView(), textMessageIndex);
}
textMessageIndex++;
}
message.setTextColor(mTextColor);
}
if (isolatedMessage != null) {
if (removeFromParentIfDifferent(isolatedMessage, mImageContainer)) {
mImageContainer.removeAllViews();
mImageContainer.addView(isolatedMessage.getView());
}
isolatedMessage.setIsolated(true);
} else if (mIsolatedMessage != null) {
mImageContainer.removeAllViews();
}
mIsolatedMessage = isolatedMessage;
mMessages = group;
}
/**
* Remove the message from the parent if the parent isn't the one provided
* @return whether the message was removed
*/
private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) {
ViewParent parent = message.getView().getParent();
if (parent != newParent) {
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(message.getView());
}
return true;
}
return false;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
@@ -317,13 +401,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
@Override
public boolean onPreDraw() {
for (MessagingMessage message : mAddedMessages) {
if (!message.isShown()) {
if (!message.getView().isShown()) {
continue;
}
MessagingPropertyAnimator.fadeIn(message);
MessagingPropertyAnimator.fadeIn(message.getView());
if (!mFirstLayout) {
MessagingPropertyAnimator.startLocalTranslationFrom(message,
message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(),
message.getView().getHeight(),
MessagingLayout.LINEAR_OUT_SLOW_IN);
}
}
mAddedMessages.clear();
@@ -333,6 +418,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
});
}
mFirstLayout = false;
updateClipRect();
}
/**
@@ -372,6 +458,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
return mMessageContainer;
}
public MessagingImageMessage getIsolatedMessage() {
return mIsolatedMessage;
}
public boolean needsGeneratedAvatar() {
return mNeedsGeneratedAvatar;
}
@@ -379,4 +469,19 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
public Notification.Person getSender() {
return mSender;
}
public void setTransformingImages(boolean transformingImages) {
mTransformingImages = transformingImages;
}
public void setDisplayAvatarsAtEnd(boolean atEnd) {
if (mAvatarsAtEnd != atEnd) {
mAvatarsAtEnd = atEnd;
mImageContainer.setVisibility(atEnd ? View.VISIBLE : View.GONE);
}
}
public List<MessagingMessage> getMessages() {
return mMessages;
}
}

View File

@@ -0,0 +1,273 @@
/*
* 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.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;
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 {
drawable = LocalImageResolver.resolveImage(message.getDataUri(), getContext());
} catch (IOException e) {
e.printStackTrace();
return false;
}
int intrinsicHeight = mDrawable.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(MessagingLayout layout,
Notification.MessagingStyle.Message m) {
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);
}
boolean created = createdMessage.setMessage(m);
if (!created) {
createdMessage.recycle();
return MessagingTextMessage.createMessage(layout, m);
}
return createdMessage;
}
@Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.clipPath(getRoundedRectPath());
int width = (int) Math.max(getActualWidth(), getActualHeight() * mAspectRatio);
int height = (int) (width / mAspectRatio);
int left = (int) ((getActualWidth() - width) / 2.0f);
mDrawable.setBounds(left, 0, left + width, 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();
setAlpha(1.0f);
setTranslationY(0);
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) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}
}
@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(getStaticWidth());
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 int getStaticWidth() {
if (mIsIsolated) {
return getWidth();
}
return (int) (getHeight() * mAspectRatio);
}
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;
}
}

View File

@@ -81,6 +81,7 @@ public class MessagingLayout extends FrameLayout {
private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
private Notification.Person mUser;
private CharSequence mNameReplacement;
private boolean mIsCollapsed;
public MessagingLayout(@NonNull Context context) {
super(context);
@@ -126,6 +127,11 @@ public class MessagingLayout extends FrameLayout {
mNameReplacement = nameReplacement;
}
@RemotableViewMethod
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
}
@RemotableViewMethod
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
@@ -331,6 +337,7 @@ public class MessagingLayout extends FrameLayout {
newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
mAddedGroups.add(newGroup);
}
newGroup.setDisplayAvatarsAtEnd(mIsCollapsed);
newGroup.setLayoutColor(mLayoutColor);
Notification.Person sender = senders.get(groupIndex);
CharSequence nameOverride = null;
@@ -392,7 +399,6 @@ public class MessagingLayout extends FrameLayout {
MessagingMessage message = findAndRemoveMatchingMessage(m);
if (message == null) {
message = MessagingMessage.createMessage(this, m);
message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
}
message.setIsHistoric(historic);
result.add(message);

View File

@@ -75,7 +75,6 @@ public class MessagingLinearLayout extends ViewGroup {
targetHeight = Integer.MAX_VALUE;
break;
}
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
// Now that we know which views to take, fix up the indents and see what width we get.
int measuredWidth = mPaddingLeft + mPaddingRight;
@@ -90,7 +89,6 @@ public class MessagingLinearLayout extends ViewGroup {
totalHeight = mPaddingTop + mPaddingBottom;
boolean first = true;
int linesRemaining = mMaxDisplayedLines;
// 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--) {
@@ -100,11 +98,13 @@ public class MessagingLinearLayout extends ViewGroup {
final View child = getChildAt(i);
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
MessagingChild messagingChild = null;
int spacing = mSpacing;
if (child instanceof MessagingChild) {
messagingChild = (MessagingChild) child;
messagingChild.setMaxDisplayedLines(linesRemaining);
spacing += messagingChild.getExtraSpacing();
}
int spacing = first ? 0 : mSpacing;
spacing = first ? 0 : spacing;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
- mPaddingTop - mPaddingBottom + spacing);
@@ -254,6 +254,9 @@ public class MessagingLinearLayout extends ViewGroup {
void setMaxDisplayedLines(int lines);
void hideAnimated();
boolean isHidingAnimated();
default int getExtraSpacing() {
return 0;
}
}
public static class LayoutParams extends MarginLayoutParams {

View File

@@ -16,182 +16,125 @@
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.text.Layout;
import android.util.AttributeSet;
import android.util.Pools;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.RemoteViews;
import com.android.internal.R;
import android.view.View;
import java.util.Objects;
/**
* A message of a {@link MessagingLayout}.
*/
@RemoteViews.RemoteView
public class MessagingMessage extends ImageFloatingTextView implements
MessagingLinearLayout.MessagingChild {
public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
private static Pools.SimplePool<MessagingMessage> sInstancePool
= new Pools.SynchronizedPool<>(10);
private Notification.MessagingStyle.Message mMessage;
private MessagingGroup mGroup;
private boolean mIsHistoric;
private boolean mIsHidingAnimated;
/**
* Prefix for supported image MIME types
**/
String IMAGE_MIME_TYPE_PREFIX = "image/";
public MessagingMessage(@NonNull Context context) {
super(context);
static MessagingMessage createMessage(MessagingLayout layout,
Notification.MessagingStyle.Message m) {
if (hasImage(m)) {
return MessagingImageMessage.createMessage(layout, m);
} else {
return MessagingTextMessage.createMessage(layout, m);
}
}
public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
static void dropCache() {
MessagingTextMessage.dropCache();
MessagingImageMessage.dropCache();
}
public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
static boolean hasImage(Notification.MessagingStyle.Message m) {
return m.getDataUri() != null
&& m.getDataMimeType() != null
&& m.getDataMimeType().startsWith(IMAGE_MIME_TYPE_PREFIX);
}
public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
/**
* Set a message for this view.
* @return true if setting the message worked
*/
default boolean setMessage(Notification.MessagingStyle.Message message) {
getState().setMessage(message);
return true;
}
private void setMessage(Notification.MessagingStyle.Message message) {
mMessage = message;
setText(message.getText());
default Notification.MessagingStyle.Message getMessage() {
return getState().getMessage();
}
public Notification.MessagingStyle.Message getMessage() {
return mMessage;
}
boolean sameAs(Notification.MessagingStyle.Message message) {
if (!Objects.equals(message.getText(), mMessage.getText())) {
default boolean sameAs(Notification.MessagingStyle.Message message) {
Notification.MessagingStyle.Message ownMessage = getMessage();
if (!Objects.equals(message.getText(), ownMessage.getText())) {
return false;
}
if (!Objects.equals(message.getSender(), mMessage.getSender())) {
if (!Objects.equals(message.getSender(), ownMessage.getSender())) {
return false;
}
if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
if (!Objects.equals(message.getTimestamp(), ownMessage.getTimestamp())) {
return false;
}
if (!Objects.equals(message.getDataMimeType(), ownMessage.getDataMimeType())) {
return false;
}
if (!Objects.equals(message.getDataUri(), ownMessage.getDataUri())) {
return false;
}
return true;
}
boolean sameAs(MessagingMessage message) {
default boolean sameAs(MessagingMessage message) {
return sameAs(message.getMessage());
}
static MessagingMessage createMessage(MessagingLayout layout,
Notification.MessagingStyle.Message m) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingMessage createdMessage = sInstancePool.acquire();
if (createdMessage == null) {
createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
R.layout.notification_template_messaging_message, messagingLinearLayout,
false);
}
createdMessage.setMessage(m);
return createdMessage;
default void removeMessage() {
getGroup().removeMessage(this);
}
public void removeMessage() {
mGroup.removeMessage(this);
default void setMessagingGroup(MessagingGroup group) {
getState().setGroup(group);
}
public void recycle() {
mGroup = null;
mMessage = null;
setAlpha(1.0f);
setTranslationY(0);
sInstancePool.release(this);
default void setIsHistoric(boolean isHistoric) {
getState().setIsHistoric(isHistoric);
}
public void setMessagingGroup(MessagingGroup group) {
mGroup = group;
default MessagingGroup getGroup() {
return getState().getGroup();
}
public static void dropCache() {
sInstancePool = new Pools.SynchronizedPool<>(10);
}
public void setIsHistoric(boolean isHistoric) {
mIsHistoric = isHistoric;
}
public MessagingGroup getGroup() {
return mGroup;
default void setIsHidingAnimated(boolean isHiding) {
getState().setIsHidingAnimated(isHiding);
}
@Override
public int getMeasuredType() {
boolean measuredTooSmall = getMeasuredHeight()
< getLayoutHeight() + getPaddingTop() + getPaddingBottom();
if (measuredTooSmall) {
return MEASURED_TOO_SMALL;
} else {
Layout layout = getLayout();
if (layout == null) {
return MEASURED_TOO_SMALL;
}
if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
return MEASURED_SHORTENED;
} else {
return MEASURED_NORMAL;
}
}
default boolean isHidingAnimated() {
return getState().isHidingAnimated();
}
@Override
public void hideAnimated() {
default void hideAnimated() {
setIsHidingAnimated(true);
mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
getGroup().performRemoveAnimation(getState().getHostView(),
() -> setIsHidingAnimated(false));
}
private void setIsHidingAnimated(boolean isHiding) {
ViewParent parent = getParent();
mIsHidingAnimated = isHiding;
invalidate();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).invalidate();
}
}
@Override
public boolean isHidingAnimated() {
return mIsHidingAnimated;
}
@Override
public void setMaxDisplayedLines(int lines) {
setMaxLines(lines);
}
@Override
public int getConsumedLines() {
return getLineCount();
}
public int getLayoutHeight() {
Layout layout = getLayout();
if (layout == null) {
return 0;
}
return layout.getHeight();
}
@Override
public boolean hasOverlappingRendering() {
default boolean hasOverlappingRendering() {
return false;
}
default void recycle() {
getState().reset();
}
default View getView() {
return (View) this;
}
default void setColor(int textColor) {}
MessagingMessageState getState();
void setVisibility(int visibility);
}

View File

@@ -0,0 +1,81 @@
/*
* 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.app.Notification;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
/**
* Shared state and implementation for MessagingMessages. Used to share common implementations.
*/
public class MessagingMessageState {
private final View mHostView;
private Notification.MessagingStyle.Message mMessage;
private MessagingGroup mGroup;
private boolean mIsHistoric;
private boolean mIsHidingAnimated;
MessagingMessageState(View hostView) {
mHostView = hostView;
}
public void setMessage(Notification.MessagingStyle.Message message) {
mMessage = message;
}
public Notification.MessagingStyle.Message getMessage() {
return mMessage;
}
public void setGroup(MessagingGroup group) {
mGroup = group;
}
public MessagingGroup getGroup() {
return mGroup;
}
public void setIsHistoric(boolean isHistoric) {
mIsHistoric = isHistoric;
}
public void setIsHidingAnimated(boolean isHiding) {
ViewParent parent = mHostView.getParent();
mIsHidingAnimated = isHiding;
mHostView.invalidate();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).invalidate();
}
}
public boolean isHidingAnimated() {
return mIsHidingAnimated;
}
public View getHostView() {
return mHostView;
}
public void reset() {
mIsHidingAnimated = false;
mIsHistoric = false;
mGroup = null;
mMessage = null;
}
}

View File

@@ -0,0 +1,145 @@
/*
* 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.text.Layout;
import android.util.AttributeSet;
import android.util.Pools;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.RemoteViews;
import com.android.internal.R;
import java.util.Objects;
/**
* A message of a {@link MessagingLayout}.
*/
@RemoteViews.RemoteView
public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
private static Pools.SimplePool<MessagingTextMessage> sInstancePool
= new Pools.SynchronizedPool<>(20);
private final MessagingMessageState mState = new MessagingMessageState(this);
public MessagingTextMessage(@NonNull Context context) {
super(context);
}
public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public MessagingMessageState getState() {
return mState;
}
@Override
public boolean setMessage(Notification.MessagingStyle.Message message) {
MessagingMessage.super.setMessage(message);
setText(message.getText());
return true;
}
static MessagingMessage createMessage(MessagingLayout layout,
Notification.MessagingStyle.Message m) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingTextMessage createdMessage = sInstancePool.acquire();
if (createdMessage == null) {
createdMessage = (MessagingTextMessage) LayoutInflater.from(
layout.getContext()).inflate(
R.layout.notification_template_messaging_text_message,
messagingLinearLayout,
false);
createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
}
createdMessage.setMessage(m);
return createdMessage;
}
public void recycle() {
MessagingMessage.super.recycle();
setAlpha(1.0f);
setTranslationY(0);
sInstancePool.release(this);
}
public static void dropCache() {
sInstancePool = new Pools.SynchronizedPool<>(10);
}
@Override
public int getMeasuredType() {
boolean measuredTooSmall = getMeasuredHeight()
< getLayoutHeight() + getPaddingTop() + getPaddingBottom();
if (measuredTooSmall) {
return MEASURED_TOO_SMALL;
} else {
Layout layout = getLayout();
if (layout == null) {
return MEASURED_TOO_SMALL;
}
if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
return MEASURED_SHORTENED;
} else {
return MEASURED_NORMAL;
}
}
}
@Override
public void setMaxDisplayedLines(int lines) {
setMaxLines(lines);
}
@Override
public int getConsumedLines() {
return getLineCount();
}
public int getLayoutHeight() {
Layout layout = getLayout();
if (layout == null) {
return 0;
}
return layout.getHeight();
}
@Override
public void setColor(int color) {
setTextColor(color);
}
}

View File

@@ -28,9 +28,9 @@
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/message_group_and_sender_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<com.android.internal.widget.ImageFloatingTextView
android:id="@+id/message_name"
@@ -44,4 +44,10 @@
android:spacing="2dp"
android:layout_weight="1"/>
</com.android.internal.widget.RemeasuringLinearLayout>
<FrameLayout
android:id="@+id/messaging_group_icon_container"
android:layout_width="@dimen/messaging_avatar_size"
android:layout_height="@dimen/messaging_avatar_size"
android:layout_marginStart="12dp"
android:visibility="gone"/>
</com.android.internal.widget.MessagingGroup>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<com.android.internal.widget.MessagingImageMessage
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/message_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/messaging_image_extra_spacing"
android:scaleType="fitStart"
/>

View File

@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<com.android.internal.widget.MessagingMessage
<com.android.internal.widget.MessagingTextMessage
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/message_text"
style="@style/Widget.Material.Notification.MessagingText"

View File

@@ -262,6 +262,18 @@
<!-- The spacing between messages in Notification.MessagingStyle -->
<dimen name="notification_messaging_spacing">6dp</dimen>
<!-- The rounding for messaging images -->
<dimen name="messaging_image_rounding">4dp</dimen>
<!-- The minimum size for any image in messaging style in order to be displayed -->
<dimen name="messaging_image_min_size">44dp</dimen>
<!-- The maximum size for any image in messaging style in order to be displayed -->
<dimen name="messaging_image_max_height">136dp</dimen>
<!-- Extra spacing before and after images in messaging style -->
<dimen name="messaging_image_extra_spacing">8dp</dimen>
<!-- Preferred width and height of the search view. -->
<dimen name="search_view_preferred_width">320dip</dimen>
<dimen name="search_view_preferred_height">48dip</dimen>

View File

@@ -3168,7 +3168,8 @@
<java-symbol type="dimen" name="chooser_service_spacing" />
<java-symbol type="bool" name="config_showSysuiShutdown" />
<java-symbol type="layout" name="notification_template_messaging_message" />
<java-symbol type="layout" name="notification_template_messaging_text_message" />
<java-symbol type="layout" name="notification_template_messaging_image_message" />
<java-symbol type="layout" name="notification_template_messaging_group" />
<java-symbol type="id" name="message_text" />
<java-symbol type="id" name="message_name" />
@@ -3183,6 +3184,11 @@
<java-symbol type="id" name="clip_children_tag" />
<java-symbol type="drawable" name="ic_reply_notification_large" />
<java-symbol type="dimen" name="messaging_avatar_size" />
<java-symbol type="dimen" name="messaging_image_rounding" />
<java-symbol type="dimen" name="messaging_image_min_size" />
<java-symbol type="dimen" name="messaging_image_max_height" />
<java-symbol type="dimen" name="messaging_image_extra_spacing" />
<java-symbol type="id" name="messaging_group_icon_container" />
<java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
<java-symbol type="integer" name="config_stableDeviceDisplayHeight" />

View File

@@ -127,7 +127,7 @@ public class MessagingLinearLayoutTest {
assertEquals(355, mView.getMeasuredHeight());;
}
private class FakeImageFloatingTextView extends MessagingMessage {
private class FakeImageFloatingTextView extends MessagingTextMessage {
public static final int LINE_HEIGHT = 50;
private final int mNumLines;

View File

@@ -58,6 +58,8 @@
<item type="id" name="notification_plugin"/>
<item type="id" name="transformation_start_x_tag"/>
<item type="id" name="transformation_start_y_tag"/>
<item type="id" name="transformation_start_actual_width"/>
<item type="id" name="transformation_start_actual_height"/>
<item type="id" name="transformation_start_scale_x_tag"/>
<item type="id" name="transformation_start_scale_y_tag"/>
<item type="id" name="continuous_clipping_tag"/>

View File

@@ -21,6 +21,8 @@ import android.util.Pools;
import android.view.View;
import android.widget.ImageView;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingMessage;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -117,13 +119,15 @@ public class ImageTransformState extends TransformState {
@Override
protected boolean transformScale(TransformState otherState) {
return true;
return sameAs(otherState);
}
@Override
public void recycle() {
super.recycle();
sInstancePool.release(this);
if (getClass() == ImageTransformState.class) {
sInstancePool.release(this);
}
}
@Override

View File

@@ -0,0 +1,134 @@
/*
* 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.systemui.statusbar.notification;
import android.util.Pools;
import android.view.View;
import com.android.internal.widget.MessagingImageMessage;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ViewTransformationHelper;
/**
* A transform state of a image view.
*/
public class MessagingImageTransformState extends ImageTransformState {
private static Pools.SimplePool<MessagingImageTransformState> sInstancePool
= new Pools.SimplePool<>(40);
private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width;
private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height;
private MessagingImageMessage mImageMessage;
@Override
public void initFrom(View view, TransformInfo transformInfo) {
super.initFrom(view, transformInfo);
mImageMessage = (MessagingImageMessage) view;
}
@Override
protected boolean sameAs(TransformState otherState) {
if (super.sameAs(otherState)) {
return true;
}
if (otherState instanceof MessagingImageTransformState) {
MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState;
return mImageMessage.sameAs(otherMessage.mImageMessage);
}
return false;
}
public static MessagingImageTransformState obtain() {
MessagingImageTransformState instance = sInstancePool.acquire();
if (instance != null) {
return instance;
}
return new MessagingImageTransformState();
}
@Override
protected boolean transformScale(TransformState otherState) {
return false;
}
@Override
protected void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
super.transformViewFrom(otherState, transformationFlags, customTransformation,
transformationAmount);
float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) {
MessagingImageMessage otherMessage
= ((MessagingImageTransformState) otherState).mImageMessage;
if (transformationAmount == 0.0f) {
setStartActualWidth(otherMessage.getActualWidth());
setStartActualHeight(otherMessage.getActualHeight());
}
float startActualWidth = getStartActualWidth();
mImageMessage.setActualWidth(
(int) NotificationUtils.interpolate(startActualWidth,
mImageMessage.getStaticWidth(),
interpolatedValue));
float startActualHeight = getStartActualHeight();
mImageMessage.setActualHeight(
(int) NotificationUtils.interpolate(startActualHeight,
mImageMessage.getHeight(),
interpolatedValue));
}
}
public int getStartActualWidth() {
Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH);
return tag == null ? -1 : (int) tag;
}
public void setStartActualWidth(int actualWidth) {
mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth);
}
public int getStartActualHeight() {
Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT);
return tag == null ? -1 : (int) tag;
}
public void setStartActualHeight(int actualWidth) {
mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth);
}
@Override
public void recycle() {
super.recycle();
if (getClass() == MessagingImageTransformState.class) {
sInstancePool.release(this);
}
}
@Override
protected void resetTransformedView() {
super.resetTransformedView();
mImageMessage.setActualWidth(mImageMessage.getStaticWidth());
mImageMessage.setActualHeight(mImageMessage.getHeight());
}
@Override
protected void reset() {
super.reset();
mImageMessage = null;
}
}

View File

@@ -22,6 +22,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.internal.widget.MessagingGroup;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.internal.widget.MessagingMessage;
@@ -30,6 +31,7 @@ import com.android.systemui.Interpolators;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* A transform state of the action list
@@ -156,6 +158,7 @@ public class MessagingLayoutTransformState extends TransformState {
}
appear(ownGroup.getAvatar(), transformationAmount);
appear(ownGroup.getSenderView(), transformationAmount);
appear(ownGroup.getIsolatedMessage(), transformationAmount);
setClippingDeactivated(ownGroup.getSenderView(), true);
setClippingDeactivated(ownGroup.getAvatar(), true);
}
@@ -187,12 +190,13 @@ public class MessagingLayoutTransformState extends TransformState {
}
disappear(ownGroup.getAvatar(), transformationAmount);
disappear(ownGroup.getSenderView(), transformationAmount);
disappear(ownGroup.getIsolatedMessage(), transformationAmount);
setClippingDeactivated(ownGroup.getSenderView(), true);
setClippingDeactivated(ownGroup.getAvatar(), true);
}
private void appear(View child, float transformationAmount) {
if (child.getVisibility() == View.GONE) {
if (child == null || child.getVisibility() == View.GONE) {
return;
}
TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -201,7 +205,7 @@ public class MessagingLayoutTransformState extends TransformState {
}
private void disappear(View child, float transformationAmount) {
if (child.getVisibility() == View.GONE) {
if (child == null || child.getVisibility() == View.GONE) {
return;
}
TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -224,22 +228,24 @@ public class MessagingLayoutTransformState extends TransformState {
private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
float transformationAmount, boolean to) {
boolean useLinearTransformation =
otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating();
transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(),
true /* sameAsAny */);
true /* sameAsAny */, useLinearTransformation);
transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
true /* sameAsAny */);
MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
true /* sameAsAny */, useLinearTransformation);
List<MessagingMessage> ownMessages = ownGroup.getMessages();
List<MessagingMessage> otherMessages = otherGroup.getMessages();
float previousTranslation = 0;
for (int i = 0; i < ownMessages.getChildCount(); i++) {
View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
for (int i = 0; i < ownMessages.size(); i++) {
View child = ownMessages.get(ownMessages.size() - 1 - i).getView();
if (isGone(child)) {
continue;
}
int otherIndex = otherMessages.getChildCount() - 1 - i;
int otherIndex = otherMessages.size() - 1 - i;
View otherChild = null;
if (otherIndex >= 0) {
otherChild = otherMessages.getChildAt(otherIndex);
otherChild = otherMessages.get(otherIndex).getView();
if (isGone(otherChild)) {
otherChild = null;
}
@@ -252,7 +258,12 @@ public class MessagingLayoutTransformState extends TransformState {
transformationAmount = 1.0f - transformationAmount;
}
}
transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */
useLinearTransformation);
if (transformationAmount == 0.0f
&& otherGroup.getIsolatedMessage() == otherChild) {
ownGroup.setTransformingImages(true);
}
if (otherChild == null) {
child.setTranslationY(previousTranslation);
setClippingDeactivated(child, true);
@@ -264,12 +275,13 @@ public class MessagingLayoutTransformState extends TransformState {
previousTranslation = child.getTranslationY();
}
}
ownGroup.updateClipRect();
}
private void transformView(float transformationAmount, boolean to, View ownView,
View otherView, boolean sameAsAny) {
View otherView, boolean sameAsAny, boolean useLinearTransformation) {
TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
if (!mTransformInfo.isAnimating()) {
if (useLinearTransformation) {
ownState.setDefaultInterpolator(Interpolators.LINEAR);
}
ownState.setIsSameAsAnyView(sameAsAny);
@@ -339,11 +351,15 @@ public class MessagingLayoutTransformState extends TransformState {
if (!isGone(ownGroup)) {
MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
for (int j = 0; j < ownMessages.getChildCount(); j++) {
MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
View child = ownMessages.getChildAt(j);
setVisible(child, visible, force);
}
setVisible(ownGroup.getAvatar(), visible, force);
setVisible(ownGroup.getSenderView(), visible, force);
MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
if (isolatedMessage != null) {
setVisible(isolatedMessage, visible, force);
}
}
}
}
@@ -375,11 +391,17 @@ public class MessagingLayoutTransformState extends TransformState {
}
resetTransformedView(ownGroup.getAvatar());
resetTransformedView(ownGroup.getSenderView());
MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
if (isolatedMessage != null) {
resetTransformedView(isolatedMessage);
}
setClippingDeactivated(ownGroup.getAvatar(), false);
setClippingDeactivated(ownGroup.getSenderView(), false);
ownGroup.setTranslationY(0);
ownGroup.getMessageContainer().setTranslationY(0);
}
ownGroup.setTransformingImages(false);
ownGroup.updateClipRect();
}
}

View File

@@ -26,6 +26,7 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingPropertyAnimator;
import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
@@ -80,7 +81,7 @@ public class TransformState {
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
@@ -131,7 +132,7 @@ public class TransformState {
transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
}
private void transformViewFrom(TransformState otherState, int transformationFlags,
protected void transformViewFrom(TransformState otherState, int transformationFlags,
ViewTransformationHelper.CustomTransformation customTransformation,
float transformationAmount) {
final View transformedView = mTransformedView;
@@ -449,6 +450,11 @@ public class TransformState {
result.initFrom(view, transformInfo);
return result;
}
if (view instanceof MessagingImageMessage) {
MessagingImageTransformState result = MessagingImageTransformState.obtain();
result.initFrom(view, transformInfo);
return result;
}
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
result.initFrom(view, transformInfo);