chromium/chrome/browser/webapps/installable/installed_webapp_geolocation_bridge.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 "chrome/browser/webapps/installable/installed_webapp_geolocation_bridge.h"

#include <utility>

#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/webapps/installable/installed_webapp_geolocation_context.h"
#include "services/device/public/cpp/geolocation/geoposition.h"
#include "url/android/gurl_android.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "chrome/android/chrome_jni_headers/InstalledWebappGeolocationBridge_jni.h"

InstalledWebappGeolocationBridge::InstalledWebappGeolocationBridge(
    mojo::PendingReceiver<Geolocation> receiver,
    const GURL& url,
    InstalledWebappGeolocationContext* context)
    : context_(context),
      url_(url),
      high_accuracy_(false),
      receiver_(this, std::move(receiver)) {
  DCHECK(context_);
  receiver_.set_disconnect_handler(
      base::BindOnce(&InstalledWebappGeolocationBridge::OnConnectionError,
                     base::Unretained(this)));
}

InstalledWebappGeolocationBridge::~InstalledWebappGeolocationBridge() {
  StopUpdates();
}

void InstalledWebappGeolocationBridge::StartListeningForUpdates() {
  JNIEnv* env = base::android::AttachCurrentThread();
  if (java_ref_.is_null()) {
    java_ref_.Reset(Java_InstalledWebappGeolocationBridge_create(
        env, reinterpret_cast<intptr_t>(this),
        url::GURLAndroid::FromNativeGURL(env, url_)));
  }
  Java_InstalledWebappGeolocationBridge_start(env, java_ref_, high_accuracy_);
}

void InstalledWebappGeolocationBridge::StopUpdates() {
  if (!java_ref_.is_null()) {
    JNIEnv* env = base::android::AttachCurrentThread();
    Java_InstalledWebappGeolocationBridge_stopAndDestroy(env, java_ref_);
    java_ref_.Reset();
  }
}

void InstalledWebappGeolocationBridge::SetHighAccuracy(bool high_accuracy) {
  high_accuracy_ = high_accuracy;

  if (position_override_ && position_override_->is_position() &&
      device::ValidateGeoposition(*position_override_->get_position())) {
    OnLocationUpdate(position_override_.Clone());
    return;
  }

  StartListeningForUpdates();
}

void InstalledWebappGeolocationBridge::QueryNextPosition(
    QueryNextPositionCallback callback) {
  if (!position_callback_.is_null()) {
    DVLOG(1) << "Overlapped call to QueryNextPosition!";
    OnConnectionError();  // Simulate a connection error.
    return;
  }

  position_callback_ = std::move(callback);

  if (current_position_) {
    ReportCurrentPosition();
  }
}

void InstalledWebappGeolocationBridge::SetOverride(
    device::mojom::GeopositionResultPtr result) {
  CHECK(result);
  if (current_position_ && !position_callback_.is_null()) {
    ReportCurrentPosition();
  }

  position_override_ = std::move(result);
  StopUpdates();

  OnLocationUpdate(position_override_.Clone());
}

void InstalledWebappGeolocationBridge::ClearOverride() {
  position_override_.reset();
  StartListeningForUpdates();
}

void InstalledWebappGeolocationBridge::OnPermissionRevoked() {
  if (!position_callback_.is_null()) {
    std::move(position_callback_)
        .Run(device::mojom::GeopositionResult::NewError(
            device::mojom::GeopositionError::New(
                device::mojom::GeopositionErrorCode::kPermissionDenied,
                /*error_message=*/"User denied Geolocation",
                /*error_technical=*/"")));
  }
  position_callback_.Reset();
}

void InstalledWebappGeolocationBridge::OnConnectionError() {
  context_->OnConnectionError(this);

  // The above call deleted this instance, so the only safe thing to do is
  // return.
}

void InstalledWebappGeolocationBridge::OnLocationUpdate(
    device::mojom::GeopositionResultPtr result) {
  DCHECK(context_);
  CHECK(result);

  current_position_ = std::move(result);

  if (!position_callback_.is_null())
    ReportCurrentPosition();
}

void InstalledWebappGeolocationBridge::ReportCurrentPosition() {
  DCHECK(position_callback_);
  CHECK(current_position_);
  std::move(position_callback_).Run(std::move(current_position_));
}

void InstalledWebappGeolocationBridge::OnNewLocationAvailable(
    JNIEnv* env,
    jdouble latitude,
    jdouble longitude,
    jdouble time_stamp,
    jboolean has_altitude,
    jdouble altitude,
    jboolean has_accuracy,
    jdouble accuracy,
    jboolean has_heading,
    jdouble heading,
    jboolean has_speed,
    jdouble speed) {
  auto position = device::mojom::Geoposition::New();
  position->latitude = latitude;
  position->longitude = longitude;
  position->timestamp = base::Time::FromSecondsSinceUnixEpoch(time_stamp);
  if (has_altitude)
    position->altitude = altitude;
  if (has_accuracy)
    position->accuracy = accuracy;
  if (has_heading)
    position->heading = heading;
  if (has_speed)
    position->speed = speed;

  // If position is invalid, mark it as unavailable.
  device::mojom::GeopositionResultPtr result;
  if (device::ValidateGeoposition(*position)) {
    result = device::mojom::GeopositionResult::NewPosition(std::move(position));
  } else {
    result = device::mojom::GeopositionResult::NewError(
        device::mojom::GeopositionError::New(
            device::mojom::GeopositionErrorCode::kPositionUnavailable,
            /*error_message=*/"", /*error_technical=*/""));
  }

  OnLocationUpdate(std::move(result));
}

void InstalledWebappGeolocationBridge::OnNewErrorAvailable(
    JNIEnv* env,
    std::string& message) {
  OnLocationUpdate(device::mojom::GeopositionResult::NewError(
      device::mojom::GeopositionError::New(
          device::mojom::GeopositionErrorCode::kPositionUnavailable, message,
          /*error_technical=*/"")));
}