chromium/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/permissiondelegation/InstalledWebappGeolocationBridgeTest.java

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

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.robolectric.annotation.LooperMode;

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.url.GURL;
import org.chromium.url.JUnitTestGURLs;

/** Tests for {@link InstalledWebappGeolocationBridge}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@LooperMode(LooperMode.Mode.LEGACY)
public class InstalledWebappGeolocationBridgeTest {
    private static final long NATIVE_POINTER = 12;

    private GURL mScope;
    private GURL mOtherScope;

    @Rule public JniMocker mocker = new JniMocker();

    @Mock private TrustedWebActivityClient mTrustedWebActivityClient;
    @Mock private InstalledWebappGeolocationBridge.Natives mNativeMock;

    private InstalledWebappGeolocationBridge mGeolocation;

    private boolean mIsHighAccuracy;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mocker.mock(InstalledWebappGeolocationBridgeJni.TEST_HOOKS, mNativeMock);

        mScope = JUnitTestGURLs.URL_1;
        mOtherScope = JUnitTestGURLs.URL_2;

        mGeolocation =
                new InstalledWebappGeolocationBridge(
                        NATIVE_POINTER, mScope, mTrustedWebActivityClient);
    }

    @Test
    @Feature("TrustedWebActivities")
    public void getLocationError_whenClientDoesntHaveService() {
        uninstallTrustedWebActivityService(mScope);
        mGeolocation.start(false /* HighAccuracy */);
        verifyGetLocationError();
    }

    @Test
    @Feature("TrustedWebActivities")
    public void getLocationUpdate_afterStartListening() {
        installTrustedWebActivityService(mScope);
        mGeolocation.start(false /* HighAccuracy */);
        verifyGetLocationUpdate();
    }

    @Test
    @Feature("TrustedWebActivities")
    public void noLocationUpdate_stopBeforeStart() {
        installTrustedWebActivityService(mScope);
        mGeolocation.stopAndDestroy();
        mGeolocation.start(false /* HighAccuracy */);
        verifyNoLocationUpdate();
    }

    @Test
    @Feature("TrustedWebActivities")
    public void getLocationError_whenOnlytherClientHasService() {
        installTrustedWebActivityService(mOtherScope);
        uninstallTrustedWebActivityService(mScope);
        mGeolocation.start(false /* HighAccuracy */);
        verifyGetLocationError();
        verifyNoLocationUpdate();
    }

    @Test
    @Feature("TrustedWebActivities")
    public void changeHighAccuracyAfterStart() {
        installTrustedWebActivityService(mScope);
        mGeolocation.start(false /* HighAccuracy */);
        assertFalse(mIsHighAccuracy);
        mGeolocation.start(true /* HighAccuracy */);
        assertTrue(mIsHighAccuracy);
    }

    /** "Installs" a Trusted Web Activity Service for the scope. */
    @SuppressWarnings("unchecked")
    private void installTrustedWebActivityService(GURL scope) {
        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(scope.getSpec()), anyBoolean(), any());
    }

    private void uninstallTrustedWebActivityService(GURL scope) {
        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(scope.getSpec()), 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());
    }
}