chromium/components/feature_engagement/internal/android/tracker_impl_android.cc

// Copyright 2017 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/feature_engagement/internal/android/tracker_impl_android.h"

#include <memory>
#include <optional>
#include <utility>

#include "base/android/callback_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "components/feature_engagement/public/feature_list.h"
#include "components/feature_engagement/public/tracker.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/feature_engagement/internal/jni_headers/TrackerImpl_jni.h"

namespace feature_engagement {

namespace {

const char kTrackerImplAndroidKey[] = "tracker_impl_android";

// Create mapping from feature name to base::Feature.
TrackerImplAndroid::FeatureMap CreateMapFromNameToFeature(
    FeatureVector features) {
  TrackerImplAndroid::FeatureMap feature_map;
  for (auto it = features.begin(); it != features.end(); ++it) {
    feature_map[(*it)->name] = *it;
  }
  return feature_map;
}

TrackerImplAndroid* FromTracker(Tracker* tracker) {
  TrackerImplAndroid* impl_android = static_cast<TrackerImplAndroid*>(
      tracker->GetUserData(kTrackerImplAndroidKey));
  if (!impl_android) {
    impl_android = new TrackerImplAndroid(tracker, GetAllFeatures());
    tracker->SetUserData(kTrackerImplAndroidKey,
                         base::WrapUnique(impl_android));
  }
  return impl_android;
}

}  // namespace

// static
TrackerImplAndroid* TrackerImplAndroid::FromJavaObject(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj) {
  return reinterpret_cast<TrackerImplAndroid*>(
      Java_TrackerImpl_getNativePtr(env, jobj));
}

// This function is declared in //components/feature_engagement/public/tracker.h
// and should be linked in to any binary using Tracker::GetJavaObject.
// static
base::android::ScopedJavaLocalRef<jobject> Tracker::GetJavaObject(
    Tracker* feature_engagement) {
  return FromTracker(feature_engagement)->GetJavaObject();
}

TrackerImplAndroid::TrackerImplAndroid(Tracker* tracker, FeatureVector features)
    : features_(CreateMapFromNameToFeature(features)), tracker_(tracker) {
  JNIEnv* env = base::android::AttachCurrentThread();

  java_obj_.Reset(
      env,
      Java_TrackerImpl_create(env, reinterpret_cast<intptr_t>(this)).obj());
}

TrackerImplAndroid::~TrackerImplAndroid() {
  Java_TrackerImpl_clearNativePtr(base::android::AttachCurrentThread(),
                                  java_obj_);
}

base::android::ScopedJavaLocalRef<jobject> TrackerImplAndroid::GetJavaObject() {
  return base::android::ScopedJavaLocalRef<jobject>(java_obj_);
}

void TrackerImplAndroid::NotifyEvent(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jevent) {
  std::string event = base::android::ConvertJavaStringToUTF8(env, jevent);
  tracker_->NotifyEvent(event);
}

bool TrackerImplAndroid::ShouldTriggerHelpUI(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  return tracker_->ShouldTriggerHelpUI(*features_[feature]);
}

base::android::ScopedJavaLocalRef<jobject>
TrackerImplAndroid::ShouldTriggerHelpUIWithSnooze(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  Tracker::TriggerDetails triggerDetails =
      tracker_->ShouldTriggerHelpUIWithSnooze(*features_[feature]);
  return Java_TrackerImpl_createTriggerDetails(
      env, triggerDetails.ShouldShowIph(), triggerDetails.ShouldShowSnooze());
}

bool TrackerImplAndroid::WouldTriggerHelpUI(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  return tracker_->WouldTriggerHelpUI(*features_[feature]);
}

bool TrackerImplAndroid::HasEverTriggered(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature,
    const jboolean j_from_window) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  return tracker_->HasEverTriggered(*features_[feature], j_from_window);
}

jint TrackerImplAndroid::GetTriggerState(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  return static_cast<int>(tracker_->GetTriggerState(*features_[feature]));
}

void TrackerImplAndroid::Dismissed(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  tracker_->Dismissed(*features_[feature]);
}

void TrackerImplAndroid::DismissedWithSnooze(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature,
    const jint snooze_action) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  tracker_->DismissedWithSnooze(
      *features_[feature],
      std::make_optional(static_cast<Tracker::SnoozeAction>(snooze_action)));
}

base::android::ScopedJavaLocalRef<jobject>
TrackerImplAndroid::AcquireDisplayLock(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj) {
  std::unique_ptr<DisplayLockHandle> lock_handle =
      tracker_->AcquireDisplayLock();
  if (!lock_handle)
    return nullptr;

  auto lock_handle_android =
      std::make_unique<DisplayLockHandleAndroid>(std::move(lock_handle));

  // Intentionally release ownership to Java.
  // Callers are required to invoke DisplayLockHandleAndroid#release().
  return lock_handle_android.release()->GetJavaObject();
}

void TrackerImplAndroid::SetPriorityNotification(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  return tracker_->SetPriorityNotification(*features_[feature]);
}

base::android::ScopedJavaLocalRef<jstring>
TrackerImplAndroid::GetPendingPriorityNotification(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj) {
  auto notification = tracker_->GetPendingPriorityNotification();
  std::string pending_notification_string =
      notification.value_or(std::string());
  return base::android::ConvertUTF8ToJavaString(env,
                                                pending_notification_string);
}

void TrackerImplAndroid::RegisterPriorityNotificationHandler(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature,
    const base::android::JavaRef<jobject>& jrunnable) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  return tracker_->RegisterPriorityNotificationHandler(
      *features_[feature],
      base::BindOnce(&base::android::RunRunnableAndroid,
                     base::android::ScopedJavaGlobalRef<jobject>(jrunnable)));
}

void TrackerImplAndroid::UnregisterPriorityNotificationHandler(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jstring>& jfeature) {
  std::string feature = base::android::ConvertJavaStringToUTF8(env, jfeature);
  DCHECK(features_.find(feature) != features_.end());

  return tracker_->UnregisterPriorityNotificationHandler(*features_[feature]);
}

bool TrackerImplAndroid::IsInitialized(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj) {
  return tracker_->IsInitialized();
}

void TrackerImplAndroid::AddOnInitializedCallback(
    JNIEnv* env,
    const base::android::JavaRef<jobject>& jobj,
    const base::android::JavaParamRef<jobject>& j_callback_obj) {
  tracker_->AddOnInitializedCallback(base::BindOnce(
      &base::android::RunBooleanCallbackAndroid,
      base::android::ScopedJavaGlobalRef<jobject>(j_callback_obj)));
}

DisplayLockHandleAndroid::DisplayLockHandleAndroid(
    std::unique_ptr<DisplayLockHandle> display_lock_handle)
    : display_lock_handle_(std::move(display_lock_handle)) {
  java_obj_.Reset(
      base::android::AttachCurrentThread(),
      Java_DisplayLockHandleAndroid_create(base::android::AttachCurrentThread(),
                                           reinterpret_cast<intptr_t>(this))
          .obj());
}

DisplayLockHandleAndroid::~DisplayLockHandleAndroid() {
  Java_DisplayLockHandleAndroid_clearNativePtr(
      base::android::AttachCurrentThread(), java_obj_);
}

base::android::ScopedJavaLocalRef<jobject>
DisplayLockHandleAndroid::GetJavaObject() {
  return base::android::ScopedJavaLocalRef<jobject>(java_obj_);
}

void DisplayLockHandleAndroid::Release(JNIEnv* env) {
  delete this;
}

}  // namespace feature_engagement