/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.managedprovisioning.common;

import static android.app.admin.DevicePolicyManager.ACTION_ADMIN_POLICY_COMPLIANCE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;

import static com.android.managedprovisioning.TestUtils.createTestAdminExtras;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.test.AndroidTestCase;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import com.android.managedprovisioning.TestUtils;
import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
import com.android.managedprovisioning.model.ProvisioningParams;
import com.android.managedprovisioning.provisioning.Constants;

import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

public class StartDpcInsideSuwServiceConnectionTest extends AndroidTestCase {
    private static final String TEST_MDM_PACKAGE_NAME = "mdm.package.name";
    private static final String TEST_MDM_ADMIN_RECEIVER = TEST_MDM_PACKAGE_NAME + ".AdminReceiver";
    private static final ComponentName TEST_MDM_ADMIN = new ComponentName(TEST_MDM_PACKAGE_NAME,
            TEST_MDM_ADMIN_RECEIVER);
    private static final PersistableBundle TEST_MDM_EXTRA_BUNDLE = createTestAdminExtras();
    private static final int TEST_REQUEST_CODE = 3;
    private static final String TEST_SUW_PACKAGE_NAME = "suw.package.name";
    private static final String TEST_SUW_SERVICE_CLASS = TEST_SUW_PACKAGE_NAME + ".TestService";
    private static final ComponentName TEST_SUW_COMPONENT_NAME = new ComponentName(
            TEST_SUW_PACKAGE_NAME, TEST_SUW_SERVICE_CLASS);

    @Mock private Activity mActivity;
    @Mock private Activity mRestoredActivity;
    @Mock private Utils mUtils;
    @Mock private ProvisioningAnalyticsTracker mProvisioningAnalyticsTracker;
    @Mock private TransitionHelper mTransitionHelper;
    @Mock private SharedPreferences mSharedPreferences;

    private StartDpcInsideSuwServiceConnection mStartDpcInsideSuwServiceConnection;
    private Runnable mDpcIntentSender;
    private ProvisioningParams mParams;
    private final Context mTargetContext = InstrumentationRegistry.getTargetContext();

    @Override
    public void setUp() throws Exception {
        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
        MockitoAnnotations.initMocks(this);
        when(mUtils.canResolveIntentAsUser(any(Context.class), any(Intent.class), anyInt()))
                .thenReturn(true);

        final PolicyComplianceUtils policyComplianceUtils = new PolicyComplianceUtils();
        mParams = new ProvisioningParams.Builder()
                .setDeviceAdminComponentName(TEST_MDM_ADMIN)
                .setProvisioningAction(ACTION_PROVISION_MANAGED_DEVICE)
                .setAdminExtrasBundle(TEST_MDM_EXTRA_BUNDLE)
                .build();

        when(mActivity.getSharedPreferences(anyString(), anyInt())).thenReturn(mSharedPreferences);
        when(mActivity.getResources()).thenReturn(mTargetContext.getResources());
        when(mRestoredActivity.getSharedPreferences(anyString(), anyInt()))
                .thenReturn(mSharedPreferences);
        when(mRestoredActivity.getResources()).thenReturn(mTargetContext.getResources());

        mStartDpcInsideSuwServiceConnection = new StartDpcInsideSuwServiceConnection();
        Constants.ENABLE_CUSTOM_TRANSITIONS = true;
        mDpcIntentSender = () ->
                policyComplianceUtils.startPolicyComplianceActivityForResultIfResolved(
                        mActivity, mParams, TEST_REQUEST_CODE, mUtils,
                        mProvisioningAnalyticsTracker, mTransitionHelper);
    }

    @SmallTest
    public void testBindingSucceeds_serviceConnects() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN connection to the NetworkInterceptService is established
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);

        // THEN an intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN calling dpcFinished and unbind
        mStartDpcInsideSuwServiceConnection.dpcFinished();
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_instanceStateSavedAndRestoredBeforeServiceConnected() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN saving state and calling unbind before a service connection was established
        final Bundle savedInstanceState = new Bundle();
        mStartDpcInsideSuwServiceConnection.saveInstanceState(savedInstanceState);
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));

        // GIVEN that a restored activity can also bind to the SUW NetworkInterceptService
        when(mRestoredActivity.bindService(
                any(Intent.class), any(ServiceConnection.class), anyInt())).thenReturn(true);

        // WHEN we restore the service connection from the saved state
        final StartDpcInsideSuwServiceConnection restoredServiceConnection =
                getRestoredServiceConnection(savedInstanceState);

        // THEN we bind to the SUW NetworkInterceptService again
        verifyBindServiceCalled(mRestoredActivity, restoredServiceConnection);

        // WHEN connection to the NetworkInterceptService is now established
        restoredServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);

        // THEN only one intent is sent to the DPC
        verifyDpcLaunched(mRestoredActivity);

        // WHEN calling dpcFinished and unbind
        restoredServiceConnection.dpcFinished();
        restoredServiceConnection.unbind(mRestoredActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mRestoredActivity).unbindService(eq(restoredServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_instanceStateSavedAndRestoredAfterServiceConnected() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN connection to the NetworkInterceptService is now established
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);

        // THEN only one intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN saving state and calling unbind after a service connection was established
        final Bundle savedInstanceState = new Bundle();
        mStartDpcInsideSuwServiceConnection.saveInstanceState(savedInstanceState);
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));

        // GIVEN that a restored activity can also bind to the SUW NetworkInterceptService
        when(mRestoredActivity.bindService(
                any(Intent.class), any(ServiceConnection.class), anyInt())).thenReturn(true);

        // WHEN we restore the service connection from the saved state
        final StartDpcInsideSuwServiceConnection restoredServiceConnection =
                getRestoredServiceConnection(savedInstanceState);

        // THEN we bind to the SUW NetworkInterceptService again
        verifyBindServiceCalled(mRestoredActivity, restoredServiceConnection);

        // WHEN connection to the NetworkInterceptService is now established
        restoredServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);

        // THEN no new intent is sent to the DPC
        verify(mRestoredActivity, never()).startActivityForResultAsUser(any(Intent.class), anyInt(),
                any(UserHandle.class));

        // WHEN calling dpcFinished and unbind
        restoredServiceConnection.dpcFinished();
        restoredServiceConnection.unbind(mRestoredActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mRestoredActivity).unbindService(eq(restoredServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_instanceStateSavedAndRestoredAfterDpcFinished() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN connection to the NetworkInterceptService is now established
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);

        // THEN only one intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN calling dpcFinished and unbind
        mStartDpcInsideSuwServiceConnection.dpcFinished();
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));

        // WHEN calling saveInstanceState() after we've unbound from the service
        final Bundle savedInstanceState = new Bundle();
        mStartDpcInsideSuwServiceConnection.saveInstanceState(savedInstanceState);

        // GIVEN that a restored activity can also bind to the SUW NetworkInterceptService
        when(mRestoredActivity.bindService(
                any(Intent.class), any(ServiceConnection.class), anyInt())).thenReturn(true);

        // WHEN we restore the service connection from the saved state
        final StartDpcInsideSuwServiceConnection restoredServiceConnection =
                getRestoredServiceConnection(savedInstanceState);

        // THEN we do not bind to the SUW NetworkInterceptService again
        verify(mRestoredActivity, never()).bindService(any(Intent.class),
                eq(restoredServiceConnection), anyInt());

        // THEN no new intent is sent to the DPC
        verify(mRestoredActivity, never()).startActivityForResultAsUser(any(Intent.class), anyInt(),
                any(UserHandle.class));

        // WHEN calling dpcFinished and unbind
        restoredServiceConnection.dpcFinished();
        restoredServiceConnection.unbind(mRestoredActivity);

        // THEN we do not unbind from the NetworkInterceptService
        verify(mRestoredActivity, never()).unbindService(eq(restoredServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_serviceConnectsAndDisconnects() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN connection to the NetworkInterceptService is established and then lost
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);
        mStartDpcInsideSuwServiceConnection.onServiceDisconnected(TEST_SUW_COMPONENT_NAME);

        // THEN only one intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN calling dpcFinished and unbind
        mStartDpcInsideSuwServiceConnection.dpcFinished();
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_serviceConnectsAndDisconnects_instanceStateSavedAndRestored() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN connection to the NetworkInterceptService is established and then lost
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);
        mStartDpcInsideSuwServiceConnection.onServiceDisconnected(TEST_SUW_COMPONENT_NAME);

        // THEN only one intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN saving state and calling unbind after a service connection was established
        final Bundle savedInstanceState = new Bundle();
        mStartDpcInsideSuwServiceConnection.saveInstanceState(savedInstanceState);
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));

        // GIVEN that a restored activity can also bind to the SUW NetworkInterceptService
        when(mRestoredActivity.bindService(
                any(Intent.class), any(ServiceConnection.class), anyInt())).thenReturn(true);

        // WHEN we restore the service connection from the saved state
        final StartDpcInsideSuwServiceConnection restoredServiceConnection =
                getRestoredServiceConnection(savedInstanceState);

        // THEN we bind to the SUW NetworkInterceptService again
        verifyBindServiceCalled(mRestoredActivity, restoredServiceConnection);

        // WHEN connection to the NetworkInterceptService is now established
        restoredServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);

        // THEN no new intent is sent to the DPC
        verify(mRestoredActivity, never()).startActivityForResultAsUser(any(Intent.class), anyInt(),
                any(UserHandle.class));

        // WHEN calling dpcFinished and unbind
        restoredServiceConnection.dpcFinished();
        restoredServiceConnection.unbind(mRestoredActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mRestoredActivity).unbindService(eq(restoredServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_serviceConnectsTwice() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN connection to the NetworkInterceptService is established, lost, and then
        // re-established
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);
        mStartDpcInsideSuwServiceConnection.onServiceDisconnected(TEST_SUW_COMPONENT_NAME);
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);

        // THEN only one intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN calling dpcFinished and unbind
        mStartDpcInsideSuwServiceConnection.dpcFinished();
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_serviceDies() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN connection to the NetworkInterceptService is established, but then dies
        mStartDpcInsideSuwServiceConnection.onServiceConnected(TEST_SUW_COMPONENT_NAME, null);
        mStartDpcInsideSuwServiceConnection.onBindingDied(TEST_SUW_COMPONENT_NAME);

        // THEN only one intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN calling dpcFinished and unbind
        mStartDpcInsideSuwServiceConnection.dpcFinished();
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_nullBinding() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN the NetworkInterceptService returns a null binding
        mStartDpcInsideSuwServiceConnection.onNullBinding(TEST_SUW_COMPONENT_NAME);

        // THEN an intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN calling dpcFinished and unbind
        mStartDpcInsideSuwServiceConnection.dpcFinished();
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));
    }

    @SmallTest
    public void testBindingSucceeds_nullBinding_instanceStateSavedAndRestored() {
        // GIVEN that we can bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(true);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // WHEN the NetworkInterceptService returns a null binding
        mStartDpcInsideSuwServiceConnection.onNullBinding(TEST_SUW_COMPONENT_NAME);

        // THEN an intent is sent to the DPC
        verifyDpcLaunched(mActivity);

        // WHEN saving state and calling unbind after the null binding
        final Bundle savedInstanceState = new Bundle();
        mStartDpcInsideSuwServiceConnection.saveInstanceState(savedInstanceState);
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));

        // GIVEN that a restored activity can bind to the SUW NetworkInterceptService
        when(mRestoredActivity.bindService(
                any(Intent.class), any(ServiceConnection.class), anyInt())).thenReturn(true);

        // WHEN we restore the service connection from the saved state
        final StartDpcInsideSuwServiceConnection restoredServiceConnection =
                getRestoredServiceConnection(savedInstanceState);

        // THEN we bind to the SUW NetworkInterceptService again
        verifyBindServiceCalled(mRestoredActivity, restoredServiceConnection);

        // WHEN the NetworkInterceptService returns a null binding again
        restoredServiceConnection.onNullBinding(TEST_SUW_COMPONENT_NAME);

        // THEN no new intent is sent to the DPC
        verify(mRestoredActivity, never()).startActivityForResultAsUser(any(Intent.class), anyInt(),
                any(UserHandle.class));

        // WHEN calling dpcFinished and unbind
        restoredServiceConnection.dpcFinished();
        restoredServiceConnection.unbind(mRestoredActivity);

        // THEN we unbind from the NetworkInterceptService
        verify(mRestoredActivity).unbindService(eq(restoredServiceConnection));
    }

    @SmallTest
    public void testBindingFails() {
        // GIVEN that we can't bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(false);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we attempt to bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // THEN an intent is sent to the DPC, even though the service binding failed
        verifyDpcLaunched(mActivity);

        // WHEN calling dpcFinished and unbind
        mStartDpcInsideSuwServiceConnection.dpcFinished();
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService even if binding failed
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));
    }

    @SmallTest
    public void testBindingFails_instanceStateSavedAndRestored() {
        // GIVEN that we can't bind to the SUW NetworkInterceptService
        when(mActivity.bindService(any(Intent.class), any(ServiceConnection.class), anyInt()))
                .thenReturn(false);

        // WHEN calling triggerDpcStart()
        mStartDpcInsideSuwServiceConnection.triggerDpcStart(mActivity, mDpcIntentSender);

        // THEN we attempt to bind to the SUW NetworkInterceptService
        verifyBindServiceCalled(mActivity, mStartDpcInsideSuwServiceConnection);

        // THEN an intent is sent to the DPC, even though the service binding failed
        verifyDpcLaunched(mActivity);

        // WHEN saving state and calling unbind after the binding failed
        final Bundle savedInstanceState = new Bundle();
        mStartDpcInsideSuwServiceConnection.saveInstanceState(savedInstanceState);
        mStartDpcInsideSuwServiceConnection.unbind(mActivity);

        // THEN we unbind from the NetworkInterceptService even if binding failed
        verify(mActivity).unbindService(eq(mStartDpcInsideSuwServiceConnection));

        // GIVEN that a restored activity could now bind to the SUW NetworkInterceptService
        when(mRestoredActivity.bindService(
                any(Intent.class), any(ServiceConnection.class), anyInt())).thenReturn(true);

        // WHEN we restore the service connection from the saved state
        final StartDpcInsideSuwServiceConnection restoredServiceConnection =
                getRestoredServiceConnection(savedInstanceState);

        // THEN we still do not bind to the SUW NetworkInterceptService
        verify(mRestoredActivity, never()).bindService(any(Intent.class),
                eq(restoredServiceConnection), anyInt());

        // THEN no new intent is sent to the DPC
        verify(mRestoredActivity, never()).startActivityForResultAsUser(any(Intent.class), anyInt(),
                any(UserHandle.class));

        // WHEN calling dpcFinished and unbind
        restoredServiceConnection.dpcFinished();
        restoredServiceConnection.unbind(mRestoredActivity);

        // THEN we do not unbind from the NetworkInterceptService
        verify(mRestoredActivity, never()).unbindService(eq(restoredServiceConnection));
    }

    private void verifyDpcLaunched(Activity activity) {
        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
        verify(mTransitionHelper).startActivityForResultAsUserWithTransition(
                eq(activity), intentCaptor.capture(), anyInt(), any(UserHandle.class));
        final String intentAction = intentCaptor.getValue().getAction();
        // THEN the intent should be ACTION_PROVISIONING_SUCCESSFUL
        assertEquals(ACTION_ADMIN_POLICY_COMPLIANCE, intentAction);
        // THEN the intent should only be sent to the dpc
        assertEquals(TEST_MDM_PACKAGE_NAME, intentCaptor.getValue().getPackage());
        // THEN the admin extras bundle should contain mdm extras
        assertExtras(intentCaptor.getValue());
        // THEN a metric should be logged
        verify(mProvisioningAnalyticsTracker).logDpcSetupStarted(eq(activity), eq(intentAction));
    }

    private void verifyBindServiceCalled(Activity activity,
            StartDpcInsideSuwServiceConnection serviceConnection) {
        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
        verify(activity).bindService(intentCaptor.capture(), eq(serviceConnection), anyInt());
        // THEN the intent should be NETWORK_INTERCEPT_SERVICE_ACTION
        assertEquals(StartDpcInsideSuwServiceConnection.NETWORK_INTERCEPT_SERVICE_ACTION,
                intentCaptor.getValue().getAction());
        // THEN the intent should be sent to Setup Wizard
        assertEquals(StartDpcInsideSuwServiceConnection.SETUP_WIZARD_PACKAGE_NAME,
                intentCaptor.getValue().getPackage());
    }

    private void assertExtras(Intent intent) {
        assertTrue(TestUtils.bundleEquals(TEST_MDM_EXTRA_BUNDLE,
                (PersistableBundle) intent.getExtra(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE)));
    }

    private StartDpcInsideSuwServiceConnection getRestoredServiceConnection(
            Bundle savedInstanceState) {
        final PolicyComplianceUtils policyComplianceUtils = new PolicyComplianceUtils();
        final Runnable dpcIntentSenderForRestoredActivity = () ->
                policyComplianceUtils.startPolicyComplianceActivityForResultIfResolved(
                        mRestoredActivity, mParams, TEST_REQUEST_CODE,
                        mUtils, mProvisioningAnalyticsTracker, mTransitionHelper);

        return new StartDpcInsideSuwServiceConnection(mRestoredActivity, savedInstanceState,
                dpcIntentSenderForRestoredActivity);
    }
}