content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.internal.widget;
  
  import android.annotation.AttrRes;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.StyleRes;
  import android.app.Notification;
  import android.content.Context;
  import android.graphics.Canvas;
  import android.graphics.Path;
  import android.graphics.drawable.Drawable;
  import android.net.Uri;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.Pools;
  import android.view.LayoutInflater;
  import android.view.ViewGroup;
  import android.widget.ImageView;
  import android.widget.RemoteViews;
  
  import com.android.internal.R;
  
  import java.io.IOException;
  
  /**
   * A message of a {@link MessagingLayout} that is an image.
   */
  @RemoteViews.RemoteView
  public class MessagingImageMessage extends ImageView implements MessagingMessage {
      private static final String TAG = "MessagingImageMessage";
      private static Pools.SimplePool<MessagingImageMessage> sInstancePool
              = new Pools.SynchronizedPool<>(10);
      private final MessagingMessageState mState = new MessagingMessageState(this);
      private final int mMinImageHeight;
      private final Path mPath = new Path();
      private final int mImageRounding;
      private final int mMaxImageHeight;
      private final int mIsolatedSize;
      private final int mExtraSpacing;
      private Drawable mDrawable;
      private float mAspectRatio;
      private int mActualWidth;
      private int mActualHeight;
      private boolean mIsIsolated;
      private ImageResolver mImageResolver;
  
      public MessagingImageMessage(@NonNull Context context) {
          this(context, null);
      }
  
      public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
          this(context, attrs, 0);
      }
  
      public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
              @AttrRes int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
      public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
              @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          mMinImageHeight = context.getResources().getDimensionPixelSize(
                  com.android.internal.R.dimen.messaging_image_min_size);
          mMaxImageHeight = context.getResources().getDimensionPixelSize(
                  com.android.internal.R.dimen.messaging_image_max_height);
          mImageRounding = context.getResources().getDimensionPixelSize(
                  com.android.internal.R.dimen.messaging_image_rounding);
          mExtraSpacing = context.getResources().getDimensionPixelSize(
                  com.android.internal.R.dimen.messaging_image_extra_spacing);
          setMaxHeight(mMaxImageHeight);
          mIsolatedSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
      }
  
      @Override
      public MessagingMessageState getState() {
          return mState;
      }
  
      @Override
      public boolean setMessage(Notification.MessagingStyle.Message message) {
          MessagingMessage.super.setMessage(message);
          Drawable drawable;
          try {
              Uri uri = message.getDataUri();
              drawable = mImageResolver != null ? mImageResolver.loadImage(uri) :
                      LocalImageResolver.resolveImage(uri, getContext());
          } catch (IOException | SecurityException e) {
              e.printStackTrace();
              return false;
          }
          if (drawable == null) {
              return false;
          }
          int intrinsicHeight = drawable.getIntrinsicHeight();
          if (intrinsicHeight == 0) {
              Log.w(TAG, "Drawable with 0 intrinsic height was returned");
              return false;
          }
          mDrawable = drawable;
          mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
          setImageDrawable(drawable);
          setContentDescription(message.getText());
          return true;
      }
  
      static MessagingMessage createMessage(MessagingLayout layout,
              Notification.MessagingStyle.Message m, ImageResolver resolver) {
          MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
          MessagingImageMessage createdMessage = sInstancePool.acquire();
          if (createdMessage == null) {
              createdMessage = (MessagingImageMessage) LayoutInflater.from(
                      layout.getContext()).inflate(
                              R.layout.notification_template_messaging_image_message,
                              messagingLinearLayout,
                              false);
              createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
          }
          createdMessage.setImageResolver(resolver);
          boolean created = createdMessage.setMessage(m);
          if (!created) {
              createdMessage.recycle();
              return MessagingTextMessage.createMessage(layout, m);
          }
          return createdMessage;
      }
  
      private void setImageResolver(ImageResolver resolver) {
          mImageResolver = resolver;
      }
  
      @Override
      protected void onDraw(Canvas canvas) {
          canvas.save();
          canvas.clipPath(getRoundedRectPath());
          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();
          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;
      }
  }
  