chromium/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandImageView.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.hub;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.ImageView;

import androidx.annotation.Nullable;

import org.chromium.base.BuildInfo;
import org.chromium.ui.display.DisplayUtil;

/** {@link ImageView} for the Shrink, Expand, and New Tab animations. */
// TODO(crbug.com/40286625): Move to hub/internal/ once TabSwitcherLayout no longer depends on this.
public class ShrinkExpandImageView extends ImageView implements RunOnNextLayout {
    private final RunOnNextLayoutDelegate mRunOnNextLayoutDelegate;

    /**
     * @param context The Android {@link Context} for constructing the view.
     */
    public ShrinkExpandImageView(Context context) {
        super(context);
        mRunOnNextLayoutDelegate = new RunOnNextLayoutDelegate(this);
    }

    /**
     * Reset the view from a previous animation.
     *
     * @param layoutRect The {@link Rect} to position the view. The top and left will be used to set
     *     margins and position the view. The width and height will be used to set the dimensions of
     *     the view.
     */
    public void reset(@Nullable Rect layoutRect) {
        resetKeepingBitmap(layoutRect);
        setImageBitmap(null);
    }

    /**
     * Reset the view from a previous animation keeping the bitmap.
     *
     * @param layoutRect The {@link Rect} to position the view. The top and left will be used to set
     *     margins and position the view. The width and height will be used to set the dimensions of
     *     the view.
     */
    @SuppressLint("RtlHardcoded")
    public void resetKeepingBitmap(@Nullable Rect layoutRect) {
        if (layoutRect != null) {
            FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
            if (layoutParams != null) {
                // Don't use Gravity.START here as the animation logic is all top/left aligned.
                layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
                layoutParams.width = layoutRect.width();
                layoutParams.height = layoutRect.height();
                layoutParams.setMargins(layoutRect.left, layoutRect.top, 0, 0);
                setLayoutParams(layoutParams);
            }
        }
        setImageMatrix(new Matrix());
        setScaleX(1.0f);
        setScaleY(1.0f);
        setTranslationX(0.0f);
        setTranslationY(0.0f);
    }

    /** Returns the bitmap contained in this image view or null if one is not set. */
    public @Nullable Bitmap getBitmap() {
        Drawable drawable = getDrawable();
        if (drawable instanceof BitmapDrawable bitmapDrawable) {
            return bitmapDrawable.getBitmap();
        } else {
            return null;
        }
    }

    @Override
    public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r, b);
        runOnNextLayoutRunnables();
    }

    @Override
    public void runOnNextLayout(Runnable runnable) {
        mRunOnNextLayoutDelegate.runOnNextLayout(runnable);
    }

    @Override
    public void runOnNextLayoutRunnables() {
        mRunOnNextLayoutDelegate.runOnNextLayoutRunnables();
    }

    @Override
    public void setImageBitmap(Bitmap bitmap) {
        if (BuildInfo.getInstance().isAutomotive && bitmap != null) {
            bitmap.setDensity(
                    DisplayUtil.getUiDensityForAutomotive(getContext(), bitmap.getDensity()));
        }
        super.setImageBitmap(bitmap);
    }
}