chromium/chrome/android/junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java

// Copyright 2015 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.gcore;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLooper;

import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;

import java.util.concurrent.TimeUnit;

/** Tests for {@link GoogleApiClientHelper} */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class GoogleApiClientHelperTest {
    private GoogleApiClient mMockClient;

    @Before
    public void setUp() {
        LifecycleHook.destroyInstanceForJUnitTests();
        mMockClient = mock(GoogleApiClient.class);
    }

    @After
    public void tearDown() {
        LifecycleHook.destroyInstanceForJUnitTests();
    }

    /** Tests that connection attempts are delayed. */
    @Test
    @Feature({"GCore"})
    // TODO(crbug.com/40182398): Change to use paused loop. See crbug for details.
    @LooperMode(LooperMode.Mode.LEGACY)
    public void connectionAttemptDelayTest() {
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);

        ShadowLooper.pauseMainLooper();
        helper.onConnectionFailed(new ConnectionResult(ConnectionResult.SERVICE_UPDATING));
        verify(mMockClient, times(0)).connect();
        ShadowLooper.unPauseMainLooper();

        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        verify(mMockClient, times(1)).connect();
    }

    /** Tests that the connection handler gives up after a number of connection attempts. */
    @Test
    @Feature({"GCore"})
    public void connectionFailureTest() {
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);

        helper.onConnectionFailed(new ConnectionResult(ConnectionResult.DEVELOPER_ERROR));
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

        // Should not retry on unrecoverable errors
        verify(mMockClient, never()).connect();

        // Connection attempts
        for (int i = 0; i < ConnectedTask.RETRY_NUMBER_LIMIT; i++) {
            helper.onConnectionFailed(new ConnectionResult(ConnectionResult.SERVICE_UPDATING));
            ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        }

        // Should have tried to connect every time.
        verify(mMockClient, times(ConnectedTask.RETRY_NUMBER_LIMIT)).connect();

        // Try again
        helper.onConnectionFailed(new ConnectionResult(ConnectionResult.SERVICE_UPDATING));
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

        // The connection handler should have given up, no new call.
        verify(mMockClient, times(ConnectedTask.RETRY_NUMBER_LIMIT)).connect();
    }

    /** Tests that when a connection succeeds, the retry limit is reset. */
    @Test
    @Feature({"GCore"})
    public void connectionAttemptsResetTest() {
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);

        // Connection attempts
        for (int i = 0; i < ConnectedTask.RETRY_NUMBER_LIMIT - 1; i++) {
            helper.onConnectionFailed(new ConnectionResult(ConnectionResult.SERVICE_UPDATING));
            ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        }

        // Should have tried to connect every time.
        verify(mMockClient, times(ConnectedTask.RETRY_NUMBER_LIMIT - 1)).connect();

        // Connection successful now
        helper.onConnected(null);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

        for (int i = 0; i < ConnectedTask.RETRY_NUMBER_LIMIT; i++) {
            helper.onConnectionFailed(new ConnectionResult(ConnectionResult.SERVICE_UPDATING));
            ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        }

        // A success should allow for more connection attempts.
        verify(mMockClient, times(ConnectedTask.RETRY_NUMBER_LIMIT * 2 - 1)).connect();

        // This should not result in a connection attempt, the limit is still there.
        helper.onConnectionFailed(new ConnectionResult(ConnectionResult.SERVICE_UPDATING));
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

        // The connection handler should have given up, no new call.
        verify(mMockClient, times(ConnectedTask.RETRY_NUMBER_LIMIT * 2 - 1)).connect();
    }

    @Test
    @Feature({"GCore"})
    public void lifecycleManagementTest() {
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        Activity mockActivity = mock(Activity.class);
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.CREATED);

        // The helper should have been registered to handle connectivity issues.
        verify(mMockClient).registerConnectionCallbacks(helper);
        verify(mMockClient).registerConnectionFailedListener(helper);

        // Client was not connected. Coming in the foreground should not change that.
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STARTED);
        verify(mMockClient, never()).connect();

        // We now say we are connected
        when(mMockClient.isConnected()).thenReturn(true);

        // Should be disconnected when we go in the background
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STOPPED);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        verify(mMockClient, times(1)).disconnect();

        // Should be reconnected when we come in the foreground
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STARTED);
        verify(mMockClient).connect();

        helper.disable();

        // The helper should have been unregistered from handling connectivity issues.
        verify(mMockClient).unregisterConnectionCallbacks(helper);
        verify(mMockClient).unregisterConnectionFailedListener(helper);

        // Should not be interacted with anymore when we stop managing it.
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STOPPED);
        verify(mMockClient).disconnect();
    }

    @Test
    @Feature({"GCore"})
    public void lifecycleManagementDelayTest() {
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        Activity mockActivity = mock(Activity.class);
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.CREATED);
        helper.setDisconnectionDelay(5000);

        // We have a connected client
        when(mMockClient.isConnected()).thenReturn(true);

        // Should not be disconnected when we go in the background
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STOPPED);
        ShadowLooper.runUiThreadTasks();
        verify(mMockClient, times(0)).disconnect();

        // Should be disconnected when we wait.
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        verify(mMockClient, times(1)).disconnect();

        // Should be reconnected when we come in the foreground
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STARTED);
        verify(mMockClient).connect();

        // Should not disconnect when we became visible during the delay
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STOPPED);
        ShadowLooper.runUiThreadTasks();
        verify(mMockClient, times(1)).disconnect();
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STARTED);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        verify(mMockClient, times(1)).disconnect();
    }

    @Test
    @Feature({"GCore"})
    public void disconnectionCancellingTest() {
        int disconnectionTimeout = 5000;
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        Activity mockActivity = mock(Activity.class);
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.CREATED);
        helper.setDisconnectionDelay(disconnectionTimeout);

        // We have a connected client
        when(mMockClient.isConnected()).thenReturn(true);

        // We go in the background and come back before the end of the timeout.
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STOPPED);
        ShadowLooper.idleMainLooper(disconnectionTimeout - 42, TimeUnit.MILLISECONDS);
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STARTED);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

        // The client should not have been disconnected, which would drop requests otherwise.
        verify(mMockClient, never()).disconnect();
    }

    @Test
    @Feature({"GCore"})
    public void willUseConnectionBackgroundTest() {
        int disconnectionTimeout = 5000;
        int arbitraryNumberOfSeconds = 42;
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        Activity mockActivity = mock(Activity.class);
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.CREATED);
        helper.setDisconnectionDelay(disconnectionTimeout);

        // We have a connected client
        when(mMockClient.isConnected()).thenReturn(true);

        // We go in the background and extend the delay
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STOPPED);
        ShadowLooper.idleMainLooper(
                disconnectionTimeout - arbitraryNumberOfSeconds, TimeUnit.MILLISECONDS);
        helper.willUseConnection();

        // The client should not have been disconnected.
        ShadowLooper.idleMainLooper(
                disconnectionTimeout - arbitraryNumberOfSeconds, TimeUnit.MILLISECONDS);
        verify(mMockClient, never()).disconnect();

        // After the full timeout it should still disconnect though
        ShadowLooper.idleMainLooper(arbitraryNumberOfSeconds, TimeUnit.MILLISECONDS);
        verify(mMockClient).disconnect();

        // The client is now disconnected then
        when(mMockClient.isConnected()).thenReturn(false);

        // The call should reconnect a disconnected client
        helper.willUseConnection();
        verify(mMockClient).connect();
    }

    @Test
    @Feature({"GCore"})
    public void willUseConnectionForegroundTest() {
        GoogleApiClientHelper helper = new GoogleApiClientHelper(mMockClient);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        Activity mockActivity = mock(Activity.class);
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.CREATED);
        helper.setDisconnectionDelay(5000);

        // We have a connected client
        when(mMockClient.isConnected()).thenReturn(true);

        // We are in the foreground
        ApplicationStatus.onStateChangeForTesting(mockActivity, ActivityState.STARTED);
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();

        // Disconnections should not be scheduled when in the foreground.
        helper.willUseConnection();
        ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
        verify(mMockClient, never()).disconnect();
    }
}