chromium/components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.cc

// 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.

#include "components/webapps/browser/android/bottomsheet/pwa_bottom_sheet_controller.h"

#include <string>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/url_formatter/elide_url.h"
#include "components/webapps/browser/android/app_banner_manager_android.h"
#include "components/webapps/browser/banners/install_banner_config.h"
#include "components/webapps/browser/installable/installable_data.h"
#include "components/webapps/browser/webapps_client.h"
#include "components/webapps/common/constants.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/text_elider.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/webapps/browser/android/webapps_jni_headers/PwaBottomSheetControllerProvider_jni.h"
#include "components/webapps/browser/android/webapps_jni_headers/PwaBottomSheetController_jni.h"

using base::ASCIIToUTF16;
using base::android::ConvertUTF16ToJavaString;
using base::android::JavaParamRef;
using base::android::ScopedJavaLocalRef;

namespace {

bool CanShowBottomSheet(content::WebContents* web_contents,
                        const std::vector<webapps::Screenshot>& screenshots) {
  if (screenshots.size() == 0)
    return false;

  JNIEnv* env = base::android::AttachCurrentThread();
  return Java_PwaBottomSheetControllerProvider_canShowPwaBottomSheetInstaller(
      env, web_contents->GetJavaWebContents());
}

}  // anonymous namespace

namespace webapps {

PwaBottomSheetController::~PwaBottomSheetController() = default;

// static
jboolean JNI_PwaBottomSheetController_RequestOrExpandBottomSheetInstaller(
    JNIEnv* env,
    const JavaParamRef<jobject>& jweb_contents,
    int install_trigger) {
  content::WebContents* web_contents =
      content::WebContents::FromJavaWebContents(jweb_contents);
  auto* app_banner_manager = static_cast<AppBannerManagerAndroid*>(
      WebappsClient::Get()->GetAppBannerManager(web_contents));

  std::optional<InstallBannerConfig> install_config =
      app_banner_manager->GetCurrentBannerConfig();
  if (!install_config.has_value()) {
    return false;
  }

  WebappInstallSource install_source = InstallableMetrics::GetInstallSource(
      web_contents, static_cast<InstallTrigger>(install_trigger));
  return app_banner_manager->MaybeShowPwaBottomSheetController(
      /* expand_sheet= */ true, install_source, install_config.value());
}

// static
bool PwaBottomSheetController::MaybeShow(
    content::WebContents* web_contents,
    const WebAppBannerData& web_app_banner_data,
    bool expand_sheet,
    base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
                                 const AddToHomescreenParams&)>
        a2hs_event_callback,
    std::unique_ptr<AddToHomescreenParams> a2hs_params) {
  if (!CanShowBottomSheet(web_contents, web_app_banner_data.screenshots)) {
    return false;
  }

  JNIEnv* env = base::android::AttachCurrentThread();
  if (Java_PwaBottomSheetControllerProvider_doesBottomSheetExist(
          env, web_contents->GetJavaWebContents())) {
    Java_PwaBottomSheetControllerProvider_updateState(
        env, web_contents->GetJavaWebContents(),
        jint(a2hs_params->install_source), expand_sheet);
  } else {
    // Lifetime of this object is managed by the Java counterpart, iff bottom
    // sheets can be shown (otherwise an infobar is used and this class is no
    // longer needed).
    PwaBottomSheetController* controller = new PwaBottomSheetController(
        web_app_banner_data, std::move(a2hs_params),
        std::move(a2hs_event_callback));
    controller->ShowBottomSheetInstaller(web_contents, expand_sheet);
  }
  return true;
}

PwaBottomSheetController::PwaBottomSheetController(
    const WebAppBannerData& web_app_banner_data,
    std::unique_ptr<AddToHomescreenParams> a2hs_params,
    base::RepeatingCallback<void(AddToHomescreenInstaller::Event,
                                 const AddToHomescreenParams&)>
        a2hs_event_callback)
    : web_app_banner_data_(web_app_banner_data),
      a2hs_params_(std::move(a2hs_params)),
      a2hs_event_callback_(a2hs_event_callback) {}

void PwaBottomSheetController::Destroy(JNIEnv* env) {
  // When the bottom sheet hasn't been expanded, it is considered equivalent to
  // the regular install infobar and the expanded state equivalent
  // to the regular install dialog prompt. Therefore, we send UI_CANCELLED
  // only if the bottom sheet was ever expanded and not closed.
  if (!install_triggered_ && !sheet_closed_ && sheet_expanded_) {
    a2hs_event_callback_.Run(AddToHomescreenInstaller::Event::UI_CANCELLED,
                             *a2hs_params_);
  }
  delete this;
}

void PwaBottomSheetController::UpdateInstallSource(JNIEnv* env,
                                                   int install_source) {
  a2hs_params_->install_source =
      static_cast<WebappInstallSource>(install_source);
}

void PwaBottomSheetController::OnSheetClosedWithSwipe(JNIEnv* env) {
  a2hs_event_callback_.Run(AddToHomescreenInstaller::Event::UI_CANCELLED,
                           *a2hs_params_);
  sheet_closed_ = true;
}

void PwaBottomSheetController::OnSheetExpanded(JNIEnv* env) {
  a2hs_event_callback_.Run(AddToHomescreenInstaller::Event::UI_SHOWN,
                           *a2hs_params_);
  sheet_expanded_ = true;
}

void PwaBottomSheetController::OnAddToHomescreen(
    JNIEnv* env,
    const JavaParamRef<jobject>& jweb_contents) {
  content::WebContents* web_contents =
      content::WebContents::FromJavaWebContents(jweb_contents);
  if (!web_contents)
    return;

  install_triggered_ = true;
  AddToHomescreenInstaller::Install(web_contents, *a2hs_params_,
                                    std::move(a2hs_event_callback_));
  PwaInstallPathTracker::TrackInstallPath(/* bottom_sheet= */ true,
                                          a2hs_params_->install_source);
}

void PwaBottomSheetController::ShowBottomSheetInstaller(
    content::WebContents* web_contents,
    bool expand_sheet) {
  JNIEnv* env = base::android::AttachCurrentThread();
  ScopedJavaLocalRef<jstring> j_user_title =
      ConvertUTF16ToJavaString(env, web_app_banner_data_.GetAppName());
  // Trim down the app URL to the origin. Elide cryptographic schemes so HTTP
  // is still shown.
  ScopedJavaLocalRef<jstring> j_url = ConvertUTF16ToJavaString(
      env, url_formatter::FormatUrlForSecurityDisplay(
               web_app_banner_data_.manifest().start_url,
               url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));

  std::u16string elided_description = gfx::TruncateString(
      web_app_banner_data_.manifest().description.value_or(u""),
      webapps::kMaximumDescriptionLength, gfx::CHARACTER_BREAK);
  ScopedJavaLocalRef<jstring> j_description =
      ConvertUTF16ToJavaString(env, elided_description);

  ScopedJavaLocalRef<jobject> j_bitmap =
      gfx::ConvertToJavaBitmap(web_app_banner_data_.primary_icon);

  Java_PwaBottomSheetControllerProvider_showPwaBottomSheetInstaller(
      env, reinterpret_cast<intptr_t>(this), web_contents->GetJavaWebContents(),
      j_bitmap, web_app_banner_data_.has_maskable_primary_icon, j_user_title,
      j_url, j_description);

  for (const auto& screenshot : web_app_banner_data_.screenshots) {
    if (!screenshot.image.isNull())
      UpdateScreenshot(screenshot.image, web_contents);
  }

  if (expand_sheet) {
    Java_PwaBottomSheetControllerProvider_expandPwaBottomSheetInstaller(
        env, web_contents->GetJavaWebContents());
  }
}

void PwaBottomSheetController::UpdateScreenshot(
    const SkBitmap& screenshot,
    content::WebContents* web_contents) {
  JNIEnv* env = base::android::AttachCurrentThread();
  ScopedJavaLocalRef<jobject> java_screenshot =
      gfx::ConvertToJavaBitmap(screenshot);
  // TODO(crbug.com/40870351): support passing label to use as
  // the accessibility string.
  Java_PwaBottomSheetController_addWebAppScreenshot(
      env, java_screenshot, web_contents->GetJavaWebContents());
}

}  // namespace webapps