chromium/components/messages/android/java/src/org/chromium/components/messages/MessageWrapper.java

// Copyright 2020 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.components.messages;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

import androidx.annotation.DrawableRes;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.listmenu.ListMenu;
import org.chromium.ui.listmenu.ListMenuItemProperties;
import org.chromium.ui.modelutil.PropertyModel;

/** Java side of native MessageWrapper class that represents a message for native features. */
@JNINamespace("messages")
public final class MessageWrapper implements ListMenu.Delegate {
    private long mNativeMessageWrapper;
    private final PropertyModel mMessageProperties;
    private MessageSecondaryMenuItems mMessageSecondaryMenuItems;

    /**
     * Creates an instance of MessageWrapper and links it with native MessageWrapper object.
     * @param nativeMessageWrapper Pointer to native MessageWrapper.
     * @param messageIdentifier Message identifier of the new message.
     * @return reference to created MessageWrapper.
     */
    @CalledByNative
    @VisibleForTesting
    public static MessageWrapper create(long nativeMessageWrapper, int messageIdentifier) {
        return new MessageWrapper(nativeMessageWrapper, messageIdentifier);
    }

    private MessageWrapper(long nativeMessageWrapper, int messageIdentifier) {
        mNativeMessageWrapper = nativeMessageWrapper;
        mMessageProperties =
                new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
                        .with(MessageBannerProperties.MESSAGE_IDENTIFIER, messageIdentifier)
                        .with(MessageBannerProperties.ON_PRIMARY_ACTION, this::handleActionClick)
                        .with(
                                MessageBannerProperties.ON_SECONDARY_ACTION,
                                this::handleSecondaryActionClick)
                        .with(MessageBannerProperties.ON_DISMISSED, this::handleMessageDismissed)
                        .build();
    }

    /**
     * Get the {@link PropertyModel} wrapped inside.
     * Note that actions for this property model are linked from the native side creator, so making
     * updates for actions are not advised.
     */
    public PropertyModel getMessageProperties() {
        return mMessageProperties;
    }

    @CalledByNative
    String getTitle() {
        return mMessageProperties.get(MessageBannerProperties.TITLE);
    }

    @CalledByNative
    void setTitle(String title) {
        mMessageProperties.set(MessageBannerProperties.TITLE, title);
    }

    @CalledByNative
    String getDescription() {
        CharSequence description = mMessageProperties.get(MessageBannerProperties.DESCRIPTION);
        return description == null ? null : description.toString();
    }

    @CalledByNative
    void setDescription(CharSequence description) {
        mMessageProperties.set(MessageBannerProperties.DESCRIPTION, description);
    }

    @CalledByNative
    int getDescriptionMaxLines() {
        return mMessageProperties.get(MessageBannerProperties.DESCRIPTION_MAX_LINES);
    }

    @CalledByNative
    void setDescriptionMaxLines(int maxLines) {
        mMessageProperties.set(MessageBannerProperties.DESCRIPTION_MAX_LINES, maxLines);
    }

    @CalledByNative
    String getPrimaryButtonText() {
        return mMessageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT);
    }

    @CalledByNative
    void setPrimaryButtonText(String primaryButtonText) {
        mMessageProperties.set(MessageBannerProperties.PRIMARY_BUTTON_TEXT, primaryButtonText);
    }

    @CalledByNative
    int getPrimaryButtonTextMaxLines() {
        return mMessageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT_MAX_LINES);
    }

    @CalledByNative
    void setPrimaryButtonTextMaxLines(int maxLines) {
        mMessageProperties.set(MessageBannerProperties.PRIMARY_BUTTON_TEXT_MAX_LINES, maxLines);
    }

    @CalledByNative
    String getSecondaryButtonMenuText() {
        return mMessageProperties.get(MessageBannerProperties.SECONDARY_BUTTON_MENU_TEXT);
    }

    @CalledByNative
    void setSecondaryButtonMenuText(String secondaryButtonMenuText) {
        mMessageProperties.set(
                MessageBannerProperties.SECONDARY_BUTTON_MENU_TEXT, secondaryButtonMenuText);
    }

    @CalledByNative
    void initializeSecondaryMenu(WindowAndroid windowAndroid, @SecondaryMenuMaxSize int maxSize) {
        Context context = windowAndroid.getActivity().get();
        assert context != null;
        if (mMessageSecondaryMenuItems != null) {
            mMessageProperties.set(MessageBannerProperties.SECONDARY_MENU_MAX_SIZE, maxSize);
            mMessageProperties.set(
                    MessageBannerProperties.SECONDARY_MENU_BUTTON_DELEGATE,
                    () -> mMessageSecondaryMenuItems.createListMenu(context, this));
        }
    }

    @CalledByNative
    PropertyModel addSecondaryMenuItem(int itemId, int resourceId, String itemText) {
        return addSecondaryMenuItem(itemId, resourceId, itemText, itemText);
    }

    @CalledByNative
    PropertyModel addSecondaryMenuItem(
            int itemId, int resourceId, String itemText, String itemDescription) {
        if (mMessageSecondaryMenuItems == null) {
            mMessageSecondaryMenuItems = new MessageSecondaryMenuItems();
        }
        return mMessageSecondaryMenuItems.addMenuItem(
                itemId, resourceId, itemText, itemDescription);
    }

    MessageSecondaryMenuItems getMessageSecondaryMenuItemsForTesting() {
        return mMessageSecondaryMenuItems;
    }

    @CalledByNative
    void clearSecondaryMenuItems() {
        if (mMessageSecondaryMenuItems == null) return;
        mMessageSecondaryMenuItems.clearMenuItems();
    }

    @CalledByNative
    void addSecondaryMenuItemDivider() {
        if (mMessageSecondaryMenuItems == null) return;
        mMessageSecondaryMenuItems.addMenuDivider();
    }

    @CalledByNative
    @DrawableRes
    int getIconResourceId() {
        return mMessageProperties.get(MessageBannerProperties.ICON_RESOURCE_ID);
    }

    @CalledByNative
    void setIconResourceId(@DrawableRes int resourceId) {
        mMessageProperties.set(MessageBannerProperties.ICON_RESOURCE_ID, resourceId);
    }

    @CalledByNative
    boolean isValidIcon() {
        return mMessageProperties.get(MessageBannerProperties.ICON) != null;
    }

    @CalledByNative
    void setIcon(Bitmap iconBitmap) {
        mMessageProperties.set(MessageBannerProperties.ICON, new BitmapDrawable(iconBitmap));
    }

    @CalledByNative
    void setLargeIcon(boolean enabled) {
        mMessageProperties.set(MessageBannerProperties.LARGE_ICON, enabled);
    }

    @CalledByNative
    void setIconRoundedCornerRadius(int radius) {
        mMessageProperties.set(MessageBannerProperties.ICON_ROUNDED_CORNER_RADIUS_PX, radius);
    }

    @CalledByNative
    void disableIconTint() {
        mMessageProperties.set(
                MessageBannerProperties.ICON_TINT_COLOR, MessageBannerProperties.TINT_NONE);
    }

    @CalledByNative
    @DrawableRes
    int getSecondaryIconResourceId() {
        return mMessageProperties.get(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID);
    }

    @CalledByNative
    void setSecondaryIconResourceId(@DrawableRes int resourceId) {
        mMessageProperties.set(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID, resourceId);
    }

    @CalledByNative
    void setDuration(long customDuration) {
        mMessageProperties.set(MessageBannerProperties.DISMISSAL_DURATION, customDuration);
    }

    @CalledByNative
    void clearNativePtr() {
        mNativeMessageWrapper = 0;
    }

    @CalledByNative
    Bitmap getIconBitmap() {
        Drawable drawable = mMessageProperties.get(MessageBannerProperties.ICON);
        assert drawable instanceof BitmapDrawable;
        return ((BitmapDrawable) drawable).getBitmap();
    }

    private @PrimaryActionClickBehavior int handleActionClick() {
        if (mNativeMessageWrapper != 0) {
            MessageWrapperJni.get().handleActionClick(mNativeMessageWrapper);
        }
        return PrimaryActionClickBehavior.DISMISS_IMMEDIATELY;
    }

    private void handleSecondaryActionClick() {
        if (mNativeMessageWrapper == 0) return;
        MessageWrapperJni.get().handleSecondaryActionClick(mNativeMessageWrapper);
    }

    private void handleMessageDismissed(@DismissReason int dismissReason) {
        // mNativeMessageWrapper can be null if the message was dismissed from native API.
        // In this case dismiss callback should have already been called.
        if (mNativeMessageWrapper == 0) return;
        MessageWrapperJni.get().handleDismissCallback(mNativeMessageWrapper, dismissReason);
    }

    @Override
    public void onItemSelected(PropertyModel item) {
        assert item.getAllSetProperties().contains(ListMenuItemProperties.MENU_ITEM_ID);
        int itemId = item.get(ListMenuItemProperties.MENU_ITEM_ID);
        MessageWrapperJni.get().handleSecondaryMenuItemSelected(mNativeMessageWrapper, itemId);
    }

    @NativeMethods
    interface Natives {
        void handleActionClick(long nativeMessageWrapper);

        void handleSecondaryActionClick(long nativeMessageWrapper);

        void handleSecondaryMenuItemSelected(long nativeMessageWrapper, int itemId);

        void handleDismissCallback(long nativeMessageWrapper, @DismissReason int dismissReason);
    }
}