chromium/components/translate/content/android/translate_message.h

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

#ifndef COMPONENTS_TRANSLATE_CONTENT_ANDROID_TRANSLATE_MESSAGE_H_
#define COMPONENTS_TRANSLATE_CONTENT_ANDROID_TRANSLATE_MESSAGE_H_

#include <jni.h>

#include <memory>
#include <string>

#include "base/android/scoped_java_ref.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/translate/core/browser/translate_step.h"

namespace content {
class WebContents;
}

namespace translate {

class TranslateManager;
class TranslateUIDelegate;
class TranslateUILanguagesManager;

BASE_DECLARE_FEATURE(kTranslateMessageUI);
extern const char kTranslateMessageUISnackbarParam[];

class TranslateMessage {
 public:
  TranslateMessage(content::WebContents* web_contents,
                   const base::WeakPtr<TranslateManager>& translate_manager,
                   base::RepeatingCallback<void()> on_dismiss_callback);

  TranslateMessage(const TranslateMessage&) = delete;
  TranslateMessage& operator=(const TranslateMessage&) = delete;

  // Dismiss message on destruction if it is shown.
  ~TranslateMessage();

  // Show the Translate message UI in the specified state, or update an
  // existing visible message to have the specified state if one is already
  // visible.
  void ShowTranslateStep(TranslateStep step,
                         const std::string& source_language,
                         const std::string& target_language);

  // Called by Java in response to the user clicking the primary button.
  void HandlePrimaryAction(JNIEnv* env);
  // Called by Java in response to the message being dismissed.
  void HandleDismiss(JNIEnv* env, jint dismiss_reason);

  // Called by Java in order to build the secondary overflow menu.
  base::android::ScopedJavaLocalRef<jobjectArray> BuildOverflowMenu(
      JNIEnv* env);

  // Called by Java in response to a secondary menu item being clicked. A
  // non-null return value means that another secondary menu with the returned
  // list of menu items should be shown immediately, e.g. in order to show a
  // language picker menu. The |overflow_menu_item_id| will indicate which
  // overflow menu item that this click is related to, and the |language_code|
  // indicates which language was clicked in a language picker menu (or the
  // empty string if this was the overflow menu).
  //
  // For example, if the user clicks "More languages" on the overflow menu, then
  // |overflow_menu_item_id| would be kChangeTargetLanguage, and |language_code|
  // would be an empty string. If the user clicks "French" on the "More
  // languages" language picker menu, then |overflow_menu_item_id| would be
  // kChangeTargetLanguage and |language_code| would be "fr".
  base::android::ScopedJavaLocalRef<jobjectArray>
  HandleSecondaryMenuItemClicked(
      JNIEnv* env,
      jint overflow_menu_item_id,
      const base::android::JavaRef<jstring>& language_code,
      jboolean had_checkmark);

  // Passes on JNI calls to the stored Java TranslateMessage object, if
  // applicable. This interface exists in order to make it easier to test
  // TranslateMessage.
  class Bridge {
   public:
    virtual ~Bridge();

    // A raw content::WebContents pointer is passed in instead of the Java
    // WebContents object in order to make testing easier, so that tests can
    // just use nullptr as the content::WebContents.
    virtual bool CreateTranslateMessage(
        JNIEnv* env,
        content::WebContents* web_contents,
        TranslateMessage* native_translate_message,
        jint dismissal_duration_seconds) = 0;

    virtual void ShowTranslateError(JNIEnv* env,
                                    content::WebContents* web_contents) = 0;

    virtual void ShowMessage(
        JNIEnv* env,
        base::android::ScopedJavaLocalRef<jstring> title,
        base::android::ScopedJavaLocalRef<jstring> description,
        base::android::ScopedJavaLocalRef<jstring> primary_button_text,
        jboolean has_overflow_menu) = 0;

    virtual base::android::ScopedJavaLocalRef<jobjectArray>
    ConstructMenuItemArray(
        JNIEnv* env,
        base::android::ScopedJavaLocalRef<jobjectArray> titles,
        base::android::ScopedJavaLocalRef<jobjectArray> subtitles,
        base::android::ScopedJavaLocalRef<jbooleanArray> has_checkmarks,
        base::android::ScopedJavaLocalRef<jintArray> overflow_menu_item_ids,
        base::android::ScopedJavaLocalRef<jobjectArray> language_codes) = 0;

    virtual void ClearNativePointer(JNIEnv* env) = 0;
    virtual void Dismiss(JNIEnv* env) = 0;
  };

  // Test-only constructor with a custom JavaMethodCaller.
  TranslateMessage(content::WebContents* web_contents,
                   const base::WeakPtr<TranslateManager>& translate_manager,
                   base::RepeatingCallback<void()> on_dismiss_callback,
                   std::unique_ptr<Bridge> bridge);

  // This enum is only visible for testing purposes - the Java TranslateMessage
  // treats these ids as opaque integers.
  enum class OverflowMenuItemId {
    kInvalid,
    kChangeSourceLanguage,
    kChangeTargetLanguage,
    kToggleAlwaysTranslateLanguage,
    kToggleNeverTranslateLanguage,
    kToggleNeverTranslateSite,
  };

 private:
  enum class State {
    kDismissed,

    kBeforeTranslate,
    kTranslating,
    kAfterTranslate,

    kAfterTranslateWithAutoAlwaysConfirmation,
    kAutoNeverTranslateConfirmation,
  };

  void RevertTranslationAndUpdateMessage();

  base::android::ScopedJavaLocalRef<jobjectArray> ConstructLanguagePickerMenu(
      JNIEnv* env,
      OverflowMenuItemId overflow_menu_item_id,
      base::span<const std::string> content_language_codes,
      base::span<const std::string> skip_language_codes) const;

  raw_ptr<content::WebContents> web_contents_;
  base::WeakPtr<TranslateManager> translate_manager_;
  base::RepeatingCallback<void()> on_dismiss_callback_;

  std::unique_ptr<Bridge> bridge_;

  // Constructed the first time ShowTranslateStep is called.
  std::unique_ptr<TranslateUIDelegate> ui_delegate_;
  raw_ptr<TranslateUILanguagesManager> ui_languages_manager_;
  State state_ = State::kDismissed;

  // Keeps track of whether or not this TranslateMessage has ever been
  // interacted with in any way aside from dismissing it.
  bool has_been_interacted_with_ = false;

  // Keeps track of whether or not a translation is in progress that could
  // trigger automatically setting "always translate language".
  bool is_translation_eligible_for_auto_always_translate_ = false;
};

}  // namespace translate

#endif  // COMPONENTS_TRANSLATE_CONTENT_ANDROID_TRANSLATE_MESSAGE_H_