summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Notification.java17
-rw-r--r--core/java/com/android/internal/widget/LocalImageResolver.java76
-rw-r--r--core/java/com/android/internal/widget/MessagingGroup.java161
-rw-r--r--core/java/com/android/internal/widget/MessagingImageMessage.java273
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java8
-rw-r--r--core/java/com/android/internal/widget/MessagingLinearLayout.java9
-rw-r--r--core/java/com/android/internal/widget/MessagingMessage.java187
-rw-r--r--core/java/com/android/internal/widget/MessagingMessageState.java81
-rw-r--r--core/java/com/android/internal/widget/MessagingTextMessage.java145
-rw-r--r--core/res/res/layout/notification_template_messaging_group.xml8
-rw-r--r--core/res/res/layout/notification_template_messaging_image_message.xml24
-rw-r--r--core/res/res/layout/notification_template_messaging_text_message.xml (renamed from core/res/res/layout/notification_template_messaging_message.xml)2
-rw-r--r--core/res/res/values/dimens.xml12
-rw-r--r--core/res/res/values/symbols.xml8
-rw-r--r--core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java2
-rw-r--r--packages/SystemUI/res/values/ids.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java134
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java10
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);