chromium/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/MessageService.java

// Copyright 2019 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.tasks.tab_management;

import androidx.annotation.IntDef;

import org.chromium.base.ObserverList;
import org.chromium.base.metrics.RecordHistogram;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Ideally, for each of the {@link MessageType} requires a MessageService class. This is the base
 * class. All the concrete subclass should contain logic that convert the data from the
 * corresponding external service to a data structure that the TabGridMessageCardProvider
 * understands.
 */
public class MessageService {
    @IntDef({
        MessageType.IPH,
        MessageType.PRICE_MESSAGE,
        MessageType.INCOGNITO_REAUTH_PROMO_MESSAGE,
        MessageType.ARCHIVED_TABS_MESSAGE,
        MessageType.ARCHIVED_TABS_IPH_MESSAGE,
        MessageType.COLLABORATION_ACTIVITY,
        MessageType.ALL
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface MessageType {
        int FOR_TESTING = 0;
        int IPH = 1;
        int PRICE_MESSAGE = 2;
        int INCOGNITO_REAUTH_PROMO_MESSAGE = 3;
        int ARCHIVED_TABS_MESSAGE = 4;
        int ARCHIVED_TABS_IPH_MESSAGE = 5;
        int COLLABORATION_ACTIVITY = 6;
        int ALL = 7;
    }

    /**
     * The reason why we disable the message in grid tab switcher and no longer show it.
     *
     * <p>Needs to stay in sync with GridTabSwitcherMessageDisableReason in enums.xml. These values
     * are persisted to logs. Entries should not be renumbered and numeric values should never be
     * reused.
     */
    @IntDef({
        MessageDisableReason.UNKNOWN,
        MessageDisableReason.MESSAGE_ACCEPTED,
        MessageDisableReason.MESSAGE_DISMISSED,
        MessageDisableReason.MESSAGE_IGNORED
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface MessageDisableReason {
        int UNKNOWN = 0;
        // User accepts the message by tapping the primary button on it.
        int MESSAGE_ACCEPTED = 1;
        // User dismisses the message by tapping the close button on it.
        int MESSAGE_DISMISSED = 2;
        // We no longer show the message because the message is ignored by users many times.
        int MESSAGE_IGNORED = 3;
        // Always update MAX_VALUE to match the last item in the list.
        int MAX_VALUE = 3;
    }

    // This identifier is used to serve messages that have no subtype, such as IPH. If one message
    // type has multiple subtypes such as PRICE_MESSAGE, its service needs to define its own
    // identifiers which should be used when creating the message card view model.
    public static final int DEFAULT_MESSAGE_IDENTIFIER = -1;

    /**
     * This is a data wrapper. Implement this interface to send notification with data to all the
     * observers.
     *
     * @see #sendAvailabilityNotification(MessageData).
     */
    public interface MessageData {}

    /**
     * Extends {@link MessageData} for CUSTOM_MESSAGE types which require a {@link
     * CustomMessageCardProvider}.
     */
    public interface CustomMessageData extends MessageData {
        /** Returns a provider of information used for custom messages. */
        CustomMessageCardProvider getProvider();
    }

    /**
     * An interface to be notified about changes to a Message. TODO(meiliang): Need to define this
     * interface in more detail.
     */
    public interface MessageObserver {
        /**
         * Called when a message is available. TODO(meiliang): message data is needed.
         *
         * @param type The type of the message.
         * @param data {@link MessageData} associated with the message.
         */
        void messageReady(@MessageType int type, MessageData data);

        /**
         * Called when a message is invalidated.
         * @param type The type of the message.
         */
        void messageInvalidate(@MessageType int type);
    }

    ObserverList<MessageObserver> mObservers = new ObserverList<>();
    @MessageType int mMessageType;

    MessageService(@MessageType int mMessageType) {
        this.mMessageType = mMessageType;
    }

    /**
     * Add a {@link MessageObserver} to be notified when message from external service is changes.
     * @param observer a {@link MessageObserver} to add.
     */
    public void addObserver(MessageObserver observer) {
        mObservers.addObserver(observer);
    }

    /**
     * Remove a {@link MessageObserver}.
     * @param observer The {@link MessageObserver} to remove.
     */
    public void removeObserver(MessageObserver observer) {
        mObservers.removeObserver(observer);
    }

    protected ObserverList<MessageObserver> getObserversForTesting() {
        return mObservers;
    }

    /**
     * Notifies all {@link MessageObserver} that a message is available.
     * @param data {@link MessageData} to send to all the observers.
     */
    public void sendAvailabilityNotification(MessageData data) {
        for (MessageObserver observer : mObservers) {
            observer.messageReady(mMessageType, data);
        }
    }

    /** Notifies all {@link MessageObserver} that a message was invalidated. */
    public void sendInvalidNotification() {
        for (MessageObserver observer : mObservers) {
            observer.messageInvalidate(mMessageType);
        }
    }

    /**
     * Log metrics related to the message disable reason.
     * @param messageType the message type or identifier.
     * @param reason the message disable reason.
     */
    void logMessageDisableMetrics(String messageType, @MessageDisableReason int reason) {
        RecordHistogram.recordEnumeratedHistogram(
                String.format("GridTabSwitcher.%s.DisableReason", messageType),
                reason,
                MessageDisableReason.MAX_VALUE + 1);
    }
}