Commit b88ea17c authored by Ella Ge's avatar Ella Ge Committed by Commit Bot

[LocationDelegation] TWA start/stop location updates

This CL adds the java part of InstalledWebappGeolocationBridge that
pass start/stop location updates to the TWAClient, and also pass the
location data that received from TrustedWebActivityCallback, to the
native part.

This also adds the InstalledWebappGeolocationBridge::SetOverride,
which is the same as GeolocationImpl::SetOverride.

Bug: 1069506
Change-Id: I4d561227a0ecadb52a41c19341044fe8488ab4b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2212455
Commit-Queue: Ella Ge <eirage@chromium.org>
Reviewed-by: default avatarMichael Thiessen <mthiesse@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#776524}
parent af925f06
......@@ -2824,6 +2824,7 @@ generate_jni("chrome_jni_headers") {
"java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java",
"java/src/org/chromium/chrome/browser/browserservices/OriginVerifier.java",
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java",
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappGeolocationBridge.java",
"java/src/org/chromium/chrome/browser/browsing_data/BrowsingDataBridge.java",
"java/src/org/chromium/chrome/browser/browsing_data/BrowsingDataCounterBridge.java",
"java/src/org/chromium/chrome/browser/browsing_data/UrlFilterBridge.java",
......
......@@ -175,6 +175,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityUmaRecorder.java",
"java/src/org/chromium/chrome/browser/browserservices/VerificationResultStore.java",
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappBridge.java",
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappGeolocationBridge.java",
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/LocationPermissionUpdater.java",
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationChannelPreserver.java",
"java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationPermissionUpdater.java",
......
......@@ -23,6 +23,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/browserservices/ClientAppDataRegisterTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/SessionDataHolderTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappGeolocationBridgeTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/permissiondelegation/LocationPermissionUpdaterTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationChannelPreserverTest.java",
"junit/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationPermissionUpdaterTest.java",
......
......@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.browserservices;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityUmaRecorder.DelegatedNotificationSmallIconFallback.FALLBACK_ICON_NOT_PROVIDED;
import static org.chromium.chrome.browser.browserservices.TrustedWebActivityUmaRecorder.DelegatedNotificationSmallIconFallback.NO_FALLBACK;
import static org.chromium.chrome.browser.browserservices.permissiondelegation.InstalledWebappGeolocationBridge.EXTRA_NEW_LOCATION_ERROR_CALLBACK;
import android.app.Notification;
import android.content.ComponentName;
......@@ -66,6 +67,9 @@ public class TrustedWebActivityClient {
private static final String EXTRA_COMMAND_EXECUTION_RESULT = "executionResult";
private static final String CHECK_LOCATION_PERMISSION_COMMAND_NAME =
"checkAndroidLocationPermission";
private static final String START_LOCATION_COMMAND_NAME = "startLocation";
private static final String STOP_LOCATION_COMMAND_NAME = "stopLocation";
private static final String LOCATION_ARG_ENABLE_HIGH_ACCURACY = "enableHighAccuracy";
private final TrustedWebActivityServiceConnectionPool mConnection;
private final TrustedWebActivityPermissionManager mDelegatesManager;
......@@ -188,6 +192,41 @@ public class TrustedWebActivityClient {
});
}
public void startListeningLocationUpdates(
Origin origin, boolean highAccuracy, TrustedWebActivityCallback locationCallback) {
connectAndExecute(origin.uri(), new ExecutionCallback() {
@Override
public void onConnected(Origin origin, TrustedWebActivityServiceConnection service)
throws RemoteException {
Bundle args = new Bundle();
args.putBoolean(LOCATION_ARG_ENABLE_HIGH_ACCURACY, highAccuracy);
Bundle executionResult = service.sendExtraCommand(
START_LOCATION_COMMAND_NAME, args, locationCallback);
// Notify an error if the service does not know how to handle the extraCommand.
if (executionResult == null) {
notifyLocationUpdateError(
locationCallback, "Failed to request location updates");
}
}
@Override
public void onNoTwaFound() {
notifyLocationUpdateError(locationCallback, "NoTwaFound");
}
});
}
public void stopLocationUpdates(Origin origin) {
connectAndExecute(origin.uri(), new ExecutionCallback() {
@Override
public void onConnected(Origin origin, TrustedWebActivityServiceConnection service)
throws RemoteException {
service.sendExtraCommand(STOP_LOCATION_COMMAND_NAME, Bundle.EMPTY, null);
}
});
}
/**
* Displays a notification through a Trusted Web Activity client.
* @param scope The scope of the Service Worker that triggered the notification.
......@@ -356,4 +395,10 @@ public class TrustedWebActivityClient {
return null;
}
private void notifyLocationUpdateError(TrustedWebActivityCallback callback, String message) {
Bundle error = new Bundle();
error.putString("message", message);
callback.onExtraCallback(EXTRA_NEW_LOCATION_ERROR_CALLBACK, error);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.browserservices.permissiondelegation;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.browser.trusted.TrustedWebActivityCallback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.browser.ChromeApplication;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.content_public.browser.UiThreadTaskTraits;
/**
* Provides Trusted Web Activity Client App location for native. The C++ counterpart is the
* {@code installed_webapp_geolocation_bridge.h}. The InstalledWebappGeolocationBridge is
* responsible for converting start and stop location command from the native to the corresponding
* TWA client app via {@link TrustedWebActivityClient}, and notify the native part when there is
* a location result.
* Lifecycle: The native part is responsible for controlling its lifecycle. A new instance will be
* created for each new geolocation request. This class should not be used after "stopAndDestroy" is
* called.
*/
public class InstalledWebappGeolocationBridge {
static final String EXTRA_NEW_LOCATION_AVAILABLE_CALLBACK = "onNewLocationAvailable";
public static final String EXTRA_NEW_LOCATION_ERROR_CALLBACK = "onNewLocationError";
private long mNativePointer;
private final Origin mOrigin;
private final TrustedWebActivityClient mTwaClient;
private final TrustedWebActivityCallback mLocationUpdateCallback =
new TrustedWebActivityCallback() {
@Override
public void onExtraCallback(String callbackName, @Nullable Bundle bundle) {
// Hop back over to the UI thread to deal with the result.
PostTask.postTask(UiThreadTaskTraits.USER_VISIBLE, () -> {
if (TextUtils.equals(callbackName, EXTRA_NEW_LOCATION_AVAILABLE_CALLBACK)) {
notifyNewGeoposition(bundle);
} else if (TextUtils.equals(
callbackName, EXTRA_NEW_LOCATION_ERROR_CALLBACK)) {
String message = bundle != null ? bundle.getString("message", "") : "";
notifyNewGeopositionError(message);
}
});
}
};
InstalledWebappGeolocationBridge(
long nativePtr, Origin origin, TrustedWebActivityClient client) {
mNativePointer = nativePtr;
mOrigin = origin;
mTwaClient = client;
}
@CalledByNative
@Nullable
public static InstalledWebappGeolocationBridge create(long nativePtr, String url) {
Origin origin = Origin.create(Uri.parse(url));
if (origin == null) return null;
return new InstalledWebappGeolocationBridge(nativePtr, origin,
ChromeApplication.getComponent().resolveTrustedWebActivityClient());
}
@CalledByNative
public void start(boolean highAccuracy) {
mTwaClient.startListeningLocationUpdates(mOrigin, highAccuracy, mLocationUpdateCallback);
}
@CalledByNative
public void stopAndDestroy() {
mNativePointer = 0;
mTwaClient.stopLocationUpdates(mOrigin);
}
private void notifyNewGeoposition(@Nullable Bundle bundle) {
if (bundle == null || mNativePointer == 0) return;
double latitude = bundle.getDouble("latitude");
double longitude = bundle.getDouble("longitude");
// Android Location.getTime() is in milliseconds, convert to seconds before passing the
// value to native.
double timeStamp = bundle.getLong("timeStamp") / 1000.0;
boolean hasAltitude = bundle.containsKey("altitude");
double altitude = bundle.getDouble("altitude");
boolean hasAccuracy = bundle.containsKey("accuracy");
double accuracy = bundle.getDouble("accuracy");
boolean hasBearing = bundle.containsKey("bearing");
double bearing = bundle.getDouble("bearing");
boolean hasSpeed = bundle.containsKey("speed");
double speed = bundle.getDouble("speed");
InstalledWebappGeolocationBridgeJni.get().onNewLocationAvailable(mNativePointer, latitude,
longitude, timeStamp, hasAltitude, altitude, hasAccuracy, accuracy, hasBearing,
bearing, hasSpeed, speed);
}
private void notifyNewGeopositionError(String message) {
if (mNativePointer == 0) return;
InstalledWebappGeolocationBridgeJni.get().onNewErrorAvailable(mNativePointer, message);
}
@NativeMethods
interface Natives {
void onNewLocationAvailable(long nativeInstalledWebappGeolocationBridge, double latitude,
double longitude, double timeStamp, boolean hasAltitude, double altitude,
boolean hasAccuracy, double accuracy, boolean hasHeading, double heading,
boolean hasSpeed, double speed);
void onNewErrorAvailable(long nativeInstalledWebappGeolocationBridge, String message);
}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.browserservices.permissiondelegation;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.os.Bundle;
import androidx.browser.trusted.TrustedWebActivityCallback;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.embedder_support.util.Origin;
/**
* Tests for {@link InstalledWebappGeolocationBridge}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@EnableFeatures(ChromeFeatureList.TRUSTED_WEB_ACTIVITY_LOCATION_DELEGATION)
public class InstalledWebappGeolocationBridgeTest {
private static final Origin ORIGIN = Origin.create("https://www.website.com");
private static final Origin OTHER_ORIGIN = Origin.create("https://www.other.website.com");
private static final String EXTRA_CALLBACK = "extraCallback";
private static final long NATIVE_POINTER = 12;
@Rule
public JniMocker mocker = new JniMocker();
@Mock
private TrustedWebActivityClient mTrustedWebActivityClient;
@Mock
private InstalledWebappGeolocationBridge.Natives mNativeMock;
private InstalledWebappGeolocationBridge mGeolocation;
private boolean mIsHighAccuracy = false;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mocker.mock(InstalledWebappGeolocationBridgeJni.TEST_HOOKS, mNativeMock);
mGeolocation = new InstalledWebappGeolocationBridge(
NATIVE_POINTER, ORIGIN, mTrustedWebActivityClient);
}
@Test
@Feature("TrustedWebActivities")
public void getLocationError_whenClientDoesntHaveService() {
uninstallTrustedWebActivityService(ORIGIN);
mGeolocation.start(false /* HighAccuracy */);
verifyGetLocationError();
}
@Test
@Feature("TrustedWebActivities")
public void getLocationUpdate_afterStartListening() {
installTrustedWebActivityService(ORIGIN);
mGeolocation.start(false /* HighAccuracy */);
verifyGetLocationUpdate();
}
@Test
@Feature("TrustedWebActivities")
public void noLocationUpdate_stopBeforeStart() {
installTrustedWebActivityService(ORIGIN);
mGeolocation.stopAndDestroy();
mGeolocation.start(false /* HighAccuracy */);
verifyNoLocationUpdate();
}
@Test
@Feature("TrustedWebActivities")
public void getLocationError_whenOnlytherClientHasService() {
installTrustedWebActivityService(OTHER_ORIGIN);
uninstallTrustedWebActivityService(ORIGIN);
mGeolocation.start(false /* HighAccuracy */);
verifyGetLocationError();
verifyNoLocationUpdate();
}
@Test
@Feature("TrustedWebActivities")
public void changeHighAccuracyAfterStart() {
installTrustedWebActivityService(ORIGIN);
mGeolocation.start(false /* HighAccuracy */);
assertFalse(mIsHighAccuracy);
mGeolocation.start(true /* HighAccuracy */);
assertTrue(mIsHighAccuracy);
}
/** "Installs" a Trusted Web Activity Service for the origin. */
@SuppressWarnings("unchecked")
private void installTrustedWebActivityService(Origin origin) {
doAnswer(invocation -> {
TrustedWebActivityCallback callback = invocation.getArgument(2);
mIsHighAccuracy = invocation.getArgument(1);
Bundle result = new Bundle();
// Put arbitrary value to test the result bundle is converted correctly.
// These value may not be valid geolocation data.
result.putDouble("latitude", 1.0d);
result.putDouble("longitude", -2.1d);
result.putLong("timeStamp", 30);
result.putDouble("altitude", 4.0d);
result.putDouble("accuracy", 5.3d);
result.putDouble("bearing", -6.4d);
result.putDouble("speed", 7.5d);
callback.onExtraCallback(
InstalledWebappGeolocationBridge.EXTRA_NEW_LOCATION_AVAILABLE_CALLBACK, result);
return true;
})
.when(mTrustedWebActivityClient)
.startListeningLocationUpdates(eq(origin), anyBoolean(), any());
}
private void uninstallTrustedWebActivityService(Origin origin) {
doAnswer(invocation -> {
TrustedWebActivityCallback callback = invocation.getArgument(2);
Bundle error = new Bundle();
error.putString("message", "any errro message");
callback.onExtraCallback(
InstalledWebappGeolocationBridge.EXTRA_NEW_LOCATION_ERROR_CALLBACK, error);
return true;
})
.when(mTrustedWebActivityClient)
.startListeningLocationUpdates(eq(origin), anyBoolean(), any());
}
// Verify native gets location update with correct value.
private void verifyGetLocationUpdate() {
verify(mNativeMock)
.onNewLocationAvailable(eq(NATIVE_POINTER), eq(1.0d), eq(-2.1d), eq(0.03d),
eq(true), eq(4.0d), eq(true), eq(5.3d), eq(true), eq(-6.4d), eq(true),
eq(7.5d));
}
private void verifyGetLocationError() {
verify(mNativeMock).onNewErrorAvailable(eq(NATIVE_POINTER), anyString());
}
private void verifyNoLocationUpdate() {
verify(mNativeMock, never())
.onNewLocationAvailable(anyInt(), anyDouble(), anyDouble(), anyDouble(),
anyBoolean(), anyDouble(), anyBoolean(), anyDouble(), anyBoolean(),
anyDouble(), anyBoolean(), anyDouble());
}
}
......@@ -6,8 +6,10 @@
#include <utility>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/android/chrome_jni_headers/InstalledWebappGeolocationBridge_jni.h"
#include "chrome/browser/installable/installed_webapp_geolocation_context.h"
#include "services/device/public/cpp/geolocation/geoposition.h"
......@@ -31,11 +33,22 @@ InstalledWebappGeolocationBridge::~InstalledWebappGeolocationBridge() {
}
void InstalledWebappGeolocationBridge::StartListeningForUpdates() {
// TODO(crbug.com/1069506) Add implementation.
JNIEnv* env = base::android::AttachCurrentThread();
if (java_ref_.is_null()) {
base::android::ScopedJavaLocalRef<jstring> j_origin =
base::android::ConvertUTF8ToJavaString(env, origin_.GetOrigin().spec());
java_ref_.Reset(Java_InstalledWebappGeolocationBridge_create(
env, reinterpret_cast<intptr_t>(this), j_origin));
}
Java_InstalledWebappGeolocationBridge_start(env, java_ref_, high_accuracy_);
}
void InstalledWebappGeolocationBridge::StopUpdates() {
// TODO(crbug.com/1069506) Add implementation.
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) {
......@@ -65,11 +78,18 @@ void InstalledWebappGeolocationBridge::QueryNextPosition(
void InstalledWebappGeolocationBridge::SetOverride(
const device::mojom::Geoposition& position) {
// TODO(crbug.com/1069506) Add implementation.
if (!position_callback_.is_null())
ReportCurrentPosition();
position_override_ = position;
StopUpdates();
OnLocationUpdate(position_override_);
}
void InstalledWebappGeolocationBridge::ClearOverride() {
// TODO(crbug.com/1069506) Add implementation.
position_override_ = device::mojom::Geoposition();
StartListeningForUpdates();
}
void InstalledWebappGeolocationBridge::OnConnectionError() {
......@@ -96,3 +116,43 @@ void InstalledWebappGeolocationBridge::ReportCurrentPosition() {
std::move(position_callback_).Run(current_position_.Clone());
has_position_to_report_ = false;
}
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) {
device::mojom::Geoposition position;
position.latitude = latitude;
position.longitude = longitude;
position.timestamp = base::Time::FromDoubleT(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;
OnLocationUpdate(position);
}
void InstalledWebappGeolocationBridge::OnNewErrorAvailable(JNIEnv* env,
jstring message) {
device::mojom::Geoposition position_error;
position_error.error_code =
device::mojom::Geoposition::ErrorCode::POSITION_UNAVAILABLE;
position_error.error_message =
base::android::ConvertJavaStringToUTF8(env, message);
OnLocationUpdate(position_error);
}
......@@ -5,6 +5,9 @@
#ifndef CHROME_BROWSER_INSTALLABLE_INSTALLED_WEBAPP_GEOLOCATION_BRIDGE_H_
#define CHROME_BROWSER_INSTALLABLE_INSTALLED_WEBAPP_GEOLOCATION_BRIDGE_H_
#include <jni.h>
#include "base/android/scoped_java_ref.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "services/device/public/mojom/geolocation.mojom.h"
#include "services/device/public/mojom/geoposition.mojom.h"
......@@ -34,6 +37,21 @@ class InstalledWebappGeolocationBridge : public device::mojom::Geolocation {
void SetOverride(const device::mojom::Geoposition& position);
void ClearOverride();
// Called by JNI on its thread looper.
void 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);
void OnNewErrorAvailable(JNIEnv* env, jstring message);
private:
// device::mojom::Geolocation:
void SetHighAccuracy(bool high_accuracy) override;
......@@ -64,6 +82,8 @@ class InstalledWebappGeolocationBridge : public device::mojom::Geolocation {
bool has_position_to_report_;
base::android::ScopedJavaGlobalRef<jobject> java_ref_;
// The binding between this object and the other end of the pipe.
mojo::Receiver<device::mojom::Geolocation> receiver_;
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment