diff options
20 files changed, 1034 insertions, 183 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 0b5b363ddecd..d10b24f832b6 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6435,7 +6435,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; @@ -6470,11 +6470,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; @@ -6485,21 +6485,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", @@ -6566,7 +6569,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; } diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java new file mode 100644 index 000000000000..71d3bb5d6b5c --- /dev/null +++ b/core/java/com/android/internal/widget/LocalImageResolver.java @@ -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); + } +} diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 5577d6e86fee..239beaa220e0 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -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++; + } + } + if (isolatedMessage != null) { + if (removeFromParentIfDifferent(isolatedMessage, mImageContainer)) { + mImageContainer.removeAllViews(); + mImageContainer.addView(isolatedMessage.getView()); } - message.setTextColor(mTextColor); + 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; + } } diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java new file mode 100644 index 000000000000..d5ebf9cbf2cd --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingImageMessage.java @@ -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; + } +} diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index d45c086e3b52..5279636ae2a0 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -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); @@ -127,6 +128,11 @@ public class MessagingLayout extends FrameLayout { } @RemotableViewMethod + public void setIsCollapsed(boolean isCollapsed) { + mIsCollapsed = isCollapsed; + } + + @RemotableViewMethod public void setData(Bundle extras) { Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); List<Notification.MessagingStyle.Message> newMessages @@ -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); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index f0ef37076618..991e3e7a80f9 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -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 { diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index f09621f544bc..bf1c5ca747a4 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -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 { - - private static Pools.SimplePool<MessagingMessage> sInstancePool - = new Pools.SynchronizedPool<>(10); - private Notification.MessagingStyle.Message mMessage; - private MessagingGroup mGroup; - private boolean mIsHistoric; - private boolean mIsHidingAnimated; +public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { - public MessagingMessage(@NonNull Context context) { - super(context); - } + /** + * Prefix for supported image MIME types + **/ + String IMAGE_MIME_TYPE_PREFIX = "image/"; - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) { - super(context, attrs); + 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, - @AttrRes int defStyleAttr) { - super(context, attrs, defStyleAttr); + static void dropCache() { + MessagingTextMessage.dropCache(); + MessagingImageMessage.dropCache(); } - public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs, - @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + static boolean hasImage(Notification.MessagingStyle.Message m) { + return m.getDataUri() != null + && m.getDataMimeType() != null + && m.getDataMimeType().startsWith(IMAGE_MIME_TYPE_PREFIX); } - private void setMessage(Notification.MessagingStyle.Message message) { - mMessage = message; - setText(message.getText()); + /** + * 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; } - public Notification.MessagingStyle.Message getMessage() { - return mMessage; + default Notification.MessagingStyle.Message getMessage() { + return getState().getMessage(); } - 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; - } - - public void removeMessage() { - mGroup.removeMessage(this); - } - - public void recycle() { - mGroup = null; - mMessage = null; - setAlpha(1.0f); - setTranslationY(0); - sInstancePool.release(this); + default void removeMessage() { + getGroup().removeMessage(this); } - public void setMessagingGroup(MessagingGroup group) { - mGroup = group; + default void setMessagingGroup(MessagingGroup group) { + getState().setGroup(group); } - public static void dropCache() { - sInstancePool = new Pools.SynchronizedPool<>(10); + default void setIsHistoric(boolean isHistoric) { + getState().setIsHistoric(isHistoric); } - public void setIsHistoric(boolean isHistoric) { - mIsHistoric = isHistoric; + default MessagingGroup getGroup() { + return getState().getGroup(); } - 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(); - } + default boolean hasOverlappingRendering() { + return false; } - @Override - public boolean isHidingAnimated() { - return mIsHidingAnimated; + default void recycle() { + getState().reset(); } - @Override - public void setMaxDisplayedLines(int lines) { - setMaxLines(lines); + default View getView() { + return (View) this; } - @Override - public int getConsumedLines() { - return getLineCount(); - } + default void setColor(int textColor) {} - public int getLayoutHeight() { - Layout layout = getLayout(); - if (layout == null) { - return 0; - } - return layout.getHeight(); - } + MessagingMessageState getState(); - @Override - public boolean hasOverlappingRendering() { - return false; - } + void setVisibility(int visibility); } diff --git a/core/java/com/android/internal/widget/MessagingMessageState.java b/core/java/com/android/internal/widget/MessagingMessageState.java new file mode 100644 index 000000000000..ac624728689d --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingMessageState.java @@ -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; + } +} diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java new file mode 100644 index 000000000000..794cc1dc66f7 --- /dev/null +++ b/core/java/com/android/internal/widget/MessagingTextMessage.java @@ -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); + } +} diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml index 4ac308a60d93..bd1030ee14f5 100644 --- a/core/res/res/layout/notification_template_messaging_group.xml +++ b/core/res/res/layout/notification_template_messaging_group.xml @@ -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> diff --git a/core/res/res/layout/notification_template_messaging_image_message.xml b/core/res/res/layout/notification_template_messaging_image_message.xml new file mode 100644 index 000000000000..6ca4dcdd7c9b --- /dev/null +++ b/core/res/res/layout/notification_template_messaging_image_message.xml @@ -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" +/> diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_text_message.xml index ab6466cc7b3c..e728e6912dd4 100644 --- a/core/res/res/layout/notification_template_messaging_message.xml +++ b/core/res/res/layout/notification_template_messaging_text_message.xml @@ -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" diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index e57b8b242490..952e4bc23830 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9442d15dfff2..25726eaf32d3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3164,7 +3164,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" /> @@ -3179,6 +3180,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" /> diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java index 3919fdd7082f..41082b7c7593 100644 --- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java @@ -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; diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index edda613f2fbc..1975ba41265a 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -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"/> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java index 8227b7797706..d3a325d3b91a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java new file mode 100644 index 000000000000..b97995dd5f93 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java @@ -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; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index 113118a1c8c5..314a31d336fd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -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(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java index 918b6edc0c30..fc8ceb6620a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java @@ -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); |