// 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.
#include "components/webxr/android/arcore_install_helper.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/time/time.h"
#include "components/messages/android/message_dispatcher_bridge.h"
#include "components/resources/android/theme_resources.h"
#include "components/strings/grit/components_strings.h"
#include "components/webxr/android/webxr_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/webxr/android/xr_jni_headers/ArCoreInstallUtils_jni.h"
using base::android::AttachCurrentThread;
namespace webxr {
namespace {
// Increase the timeout for the message to 60s from the default 10s.
constexpr base::TimeDelta kMessageTimeout = base::Seconds(60);
} // namespace
ArCoreInstallHelper::ArCoreInstallHelper() {
// As per documentation, it's recommended to issue a call to
// ArCoreApk.checkAvailability() early in application lifecycle & ignore the
// result so that subsequent calls can return cached result:
// https://developers.google.com/ar/develop/java/enable-arcore
// In the event that a remote call is required, it will not block on that
// remote call per:
// https://developers.google.com/ar/reference/java/arcore/reference/com/google/ar/core/ArCoreApk#checkAvailability
Java_ArCoreInstallUtils_shouldRequestInstallSupportedArCore(
AttachCurrentThread());
java_install_utils_ = Java_ArCoreInstallUtils_create(
AttachCurrentThread(), reinterpret_cast<jlong>(this));
}
ArCoreInstallHelper::~ArCoreInstallHelper() {
if (!java_install_utils_.is_null()) {
Java_ArCoreInstallUtils_onNativeDestroy(AttachCurrentThread(),
java_install_utils_);
}
RunInstallFinishedCallback(false);
}
void ArCoreInstallHelper::EnsureInstalled(
int render_process_id,
int render_frame_id,
base::OnceCallback<void(bool)> install_callback) {
DVLOG(1) << __func__ << ": java_install_utils_.is_null()="
<< java_install_utils_.is_null();
DCHECK(!install_finished_callback_);
install_finished_callback_ = std::move(install_callback);
if (java_install_utils_.is_null()) {
RunInstallFinishedCallback(false);
return;
}
// ARCore is not installed or requires an update.
if (Java_ArCoreInstallUtils_shouldRequestInstallSupportedArCore(
AttachCurrentThread())) {
ShowMessage(render_process_id, render_frame_id);
return;
}
// ARCore did not need to be installed/updated so mock out that its
// installation succeeded.
OnRequestInstallSupportedArCoreResult(nullptr, true);
}
void ArCoreInstallHelper::ShowMessage(int render_process_id,
int render_frame_id) {
DVLOG(1) << __func__;
ArCoreAvailability availability = static_cast<ArCoreAvailability>(
Java_ArCoreInstallUtils_getArCoreInstallStatus(AttachCurrentThread()));
int message_title = -1;
int button_text = -1;
switch (availability) {
case ArCoreAvailability::kUnsupportedDeviceNotCapable: {
RunInstallFinishedCallback(false);
return; // No need to process further
}
case ArCoreAvailability::kUnknownChecking:
case ArCoreAvailability::kUnknownError:
case ArCoreAvailability::kUnknownTimedOut:
case ArCoreAvailability::kSupportedNotInstalled: {
message_title = IDS_AR_CORE_CHECK_MESSAGE_INSTALL_TITLE;
button_text = IDS_INSTALL;
break;
}
case ArCoreAvailability::kSupportedApkTooOld: {
message_title = IDS_AR_CORE_CHECK_MESSAGE_UPDATE_TITLE;
button_text = IDS_UPDATE;
break;
}
case ArCoreAvailability::kSupportedInstalled:
NOTREACHED_IN_MIGRATION();
break;
}
DCHECK_NE(-1, message_title);
DCHECK_NE(-1, button_text);
message_ = std::make_unique<messages::MessageWrapper>(
messages::MessageIdentifier::AR_CORE_UPGRADE,
base::BindOnce(&ArCoreInstallHelper::HandleMessagePrimaryAction,
base::Unretained(this), render_process_id,
render_frame_id),
base::BindOnce(&ArCoreInstallHelper::HandleMessageDismissed,
base::Unretained(this)));
message_->SetTitle(l10n_util::GetStringUTF16(message_title));
message_->SetDescription(
l10n_util::GetStringUTF16(IDS_AR_CORE_CHECK_MESSAGE_DESCRIPTION));
message_->SetPrimaryButtonText(l10n_util::GetStringUTF16(button_text));
messages::MessageDispatcherBridge* message_dispatcher_bridge =
messages::MessageDispatcherBridge::Get();
message_->SetIconResourceId(message_dispatcher_bridge->MapToJavaDrawableId(
IDR_ANDROID_AR_CORE_INSALL_ICON));
message_->SetDuration(kMessageTimeout.InMilliseconds());
message_dispatcher_bridge->EnqueueMessage(
message_.get(), GetWebContents(render_process_id, render_frame_id),
messages::MessageScopeType::NAVIGATION,
messages::MessagePriority::kNormal);
}
void ArCoreInstallHelper::HandleMessageDismissed(
messages::DismissReason dismiss_reason) {
// If the message is dismissed for any reason other than the primary action
// button click to install/update ARCore, indicate to the deferred callback
// that no installation/update was facilitated.
if (dismiss_reason != messages::DismissReason::PRIMARY_ACTION) {
OnRequestInstallSupportedArCoreResult(nullptr, false);
}
DCHECK(message_);
message_.reset();
}
void ArCoreInstallHelper::HandleMessagePrimaryAction(int render_process_id,
int render_frame_id) {
// When completed, java will call: OnRequestInstallSupportedArCoreResult
Java_ArCoreInstallUtils_requestInstallSupportedArCore(
AttachCurrentThread(), java_install_utils_,
GetJavaWebContents(render_process_id, render_frame_id));
}
void ArCoreInstallHelper::OnRequestInstallSupportedArCoreResult(JNIEnv* env,
bool success) {
DVLOG(1) << __func__;
// Nothing else to do, simply call the deferred callback.
RunInstallFinishedCallback(success);
}
void ArCoreInstallHelper::RunInstallFinishedCallback(bool succeeded) {
if (install_finished_callback_) {
std::move(install_finished_callback_).Run(succeeded);
}
}
} // namespace webxr