Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4a552724 authored by Evan Laird's avatar Evan Laird
Browse files

[Sb refactor] Convert CastTile to use WifiRepository

CastTile was only using a single bit of information from the old data
pipeline. If the QS wifi icon was visible then it would consider "wifi
enabled". This CL sets up a new TileJavaAdapter which can be used to
collect on flows from QS, and wires up the wifi repository for CastTile.

Feature is gated behind the SIGNAL_CALLBACK_DEPRECATION flag. All tests
of the old behavior have been replicated with the flag set to `true`

Test: CastTileTest
Test: manual - check hotspot tile with wifi on/off while connected and
disconnected to hotspot. Verified the same behavior with the flag on and
off
Bug: 291321279
Change-Id: I67755215ed0222c79eaae7dc8ebaf41b4d65e8ad

Change-Id: I54b7af0c9f9c084305e7447051f37e6fd855bfb5
parent 56f6924d
Loading
Loading
Loading
Loading
+48 −17
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.systemui.qs.tiles;

import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;

import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION;

import android.annotation.NonNull;
import android.app.Dialog;
import android.content.Intent;
@@ -42,6 +44,7 @@ import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -54,6 +57,8 @@ import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -61,6 +66,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import javax.inject.Inject;

@@ -79,6 +85,9 @@ public class CastTile extends QSTileImpl<BooleanState> {
    private final NetworkController mNetworkController;
    private final DialogLaunchAnimator mDialogLaunchAnimator;
    private final Callback mCallback = new Callback();
    private final WifiInteractor mWifiInteractor;
    private final TileJavaAdapter mJavaAdapter;
    private final FeatureFlags mFeatureFlags;
    private boolean mWifiConnected;
    private boolean mHotspotConnected;

@@ -97,7 +106,10 @@ public class CastTile extends QSTileImpl<BooleanState> {
            KeyguardStateController keyguardStateController,
            NetworkController networkController,
            HotspotController hotspotController,
            DialogLaunchAnimator dialogLaunchAnimator
            DialogLaunchAnimator dialogLaunchAnimator,
            WifiInteractor wifiInteractor,
            TileJavaAdapter javaAdapter,
            FeatureFlags featureFlags
    ) {
        super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                statusBarStateController, activityStarter, qsLogger);
@@ -105,9 +117,16 @@ public class CastTile extends QSTileImpl<BooleanState> {
        mKeyguard = keyguardStateController;
        mNetworkController = networkController;
        mDialogLaunchAnimator = dialogLaunchAnimator;
        mWifiInteractor = wifiInteractor;
        mJavaAdapter = javaAdapter;
        mFeatureFlags = featureFlags;
        mController.observe(this, mCallback);
        mKeyguard.observe(this, mCallback);
        if (!mFeatureFlags.isEnabled(SIGNAL_CALLBACK_DEPRECATION)) {
            mNetworkController.observe(this, mSignalCallback);
        } else {
            mJavaAdapter.bind(this, mWifiInteractor.getWifiNetwork(), mNetworkModelConsumer);
        }
        hotspotController.observe(this, mHotspotCallback);
    }

@@ -293,20 +312,38 @@ public class CastTile extends QSTileImpl<BooleanState> {
        return mWifiConnected || mHotspotConnected;
    }

    private final SignalCallback mSignalCallback = new SignalCallback() {
                @Override
                public void setWifiIndicators(@NonNull WifiIndicators indicators) {
                    // statusIcon.visible has the connected status information
                    boolean enabledAndConnected = indicators.enabled
                            && (indicators.qsIcon == null ? false : indicators.qsIcon.visible);
                    if (enabledAndConnected != mWifiConnected) {
                        mWifiConnected = enabledAndConnected;
    private void setWifiConnected(boolean connected) {
        if (connected != mWifiConnected) {
            mWifiConnected = connected;
            // Hotspot is not connected, so changes here should update
            if (!mHotspotConnected) {
                refreshState();
            }
        }
    }

    private void setHotspotConnected(boolean connected) {
        if (connected != mHotspotConnected) {
            mHotspotConnected = connected;
            // Wifi is not connected, so changes here should update
            if (!mWifiConnected) {
                refreshState();
            }
        }
    }

    private final Consumer<WifiNetworkModel> mNetworkModelConsumer = (model) -> {
        setWifiConnected(model instanceof WifiNetworkModel.Active);
    };

    private final SignalCallback mSignalCallback = new SignalCallback() {
                @Override
                public void setWifiIndicators(@NonNull WifiIndicators indicators) {
                    // statusIcon.visible has the connected status information
                    boolean enabledAndConnected = indicators.enabled
                            && (indicators.qsIcon != null && indicators.qsIcon.visible);
                    setWifiConnected(enabledAndConnected);
                }
            };

    private final HotspotController.Callback mHotspotCallback =
@@ -314,13 +351,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
                @Override
                public void onHotspotChanged(boolean enabled, int numDevices) {
                    boolean enabledAndConnected = enabled && numDevices > 0;
                    if (enabledAndConnected != mHotspotConnected) {
                        mHotspotConnected = enabledAndConnected;
                        // Wifi is not connected, so changes here should update
                        if (!mWifiConnected) {
                            refreshState();
                        }
                    }
                    setHotspotConnected(enabledAndConnected);
                }
            };

+47 −0
Original line number Diff line number Diff line
/*
 *  Copyright (C) 2023 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.systemui.qs.tiles

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.dagger.SysUISingleton
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch

/**
 * Utility for binding tiles to kotlin flows. Similar to [JavaAdapter] and usable for QS tiles. We
 * use [Lifecycle.State.RESUMED] here to match the implementation of [CallbackController.observe]
 */
@SysUISingleton
class TileJavaAdapter @Inject constructor() {
    fun <T> bind(
        lifecycleOwner: LifecycleOwner,
        flow: Flow<T>,
        consumer: Consumer<T>,
    ) {
        lifecycleOwner.lifecycleScope.launch {
            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                flow.collect { consumer.accept(it) }
            }
        }
    }
}
+188 −36
Original line number Diff line number Diff line
@@ -14,6 +14,8 @@

package com.android.systemui.qs.tiles;

import static com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION;

import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;

@@ -40,6 +42,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
@@ -49,6 +52,12 @@ import com.android.systemui.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.connectivity.WifiIndicators;
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository;
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl;
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -65,7 +74,6 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;


@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -98,6 +106,11 @@ public class CastTileTest extends SysuiTestCase {
    @Mock
    private QsEventLogger mUiEventLogger;

    private WifiInteractor mWifiInteractor;
    private final TileJavaAdapter mJavaAdapter = new TileJavaAdapter();
    private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();

    private TestableLooper mTestableLooper;
    private CastTile mCastTile;

@@ -108,42 +121,10 @@ public class CastTileTest extends SysuiTestCase {

        when(mHost.getContext()).thenReturn(mContext);

        mCastTile = new CastTile(
                mHost,
                mUiEventLogger,
                mTestableLooper.getLooper(),
                new Handler(mTestableLooper.getLooper()),
                new FalsingManagerFake(),
                mMetricsLogger,
                mStatusBarStateController,
                mActivityStarter,
                mQSLogger,
                mController,
                mKeyguard,
                mNetworkController,
                mHotspotController,
                mDialogLaunchAnimator
        mWifiInteractor = new WifiInteractorImpl(
                new FakeConnectivityRepository(),
                mWifiRepository
        );
        mCastTile.initialize();

        // We are not setting the mocks to listening, so we trigger a first refresh state to
        // set the initial state
        mCastTile.refreshState();

        mTestableLooper.processAllMessages();

        mCastTile.handleSetListening(true);
        ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor =
                ArgumentCaptor.forClass(SignalCallback.class);
        verify(mNetworkController).observe(any(LifecycleOwner.class),
                signalCallbackArgumentCaptor.capture());
        mSignalCallback = signalCallbackArgumentCaptor.getValue();

        ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
                ArgumentCaptor.forClass(HotspotController.Callback.class);
        verify(mHotspotController).observe(any(LifecycleOwner.class),
                hotspotCallbackArgumentCaptor.capture());
        mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
    }

    @After
@@ -156,6 +137,7 @@ public class CastTileTest extends SysuiTestCase {
    // All these tests for enabled/disabled wifi have hotspot not enabled
    @Test
    public void testStateUnavailable_wifiDisabled() {
        createAndStartTileOldImpl();
        IconState qsIcon = new IconState(false, 0, "");
        WifiIndicators indicators = new WifiIndicators(
                false, mock(IconState.class),
@@ -169,6 +151,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testStateUnavailable_wifiNotConnected() {
        createAndStartTileOldImpl();
        IconState qsIcon = new IconState(false, 0, "");
        WifiIndicators indicators = new WifiIndicators(
                true, mock(IconState.class),
@@ -192,6 +175,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testStateActive_wifiEnabledAndCasting() {
        createAndStartTileOldImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastController.CastDevice.STATE_CONNECTED;
        List<CastDevice> devices = new ArrayList<>();
@@ -204,15 +188,87 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testStateInactive_wifiEnabledNotCasting() {
        createAndStartTileOldImpl();
        enableWifiAndProcessMessages();
        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
    }
    // -------------------------------------------------

    // -------------------------------------------------
    // All these tests for enabled/disabled wifi have hotspot not enabled, and have the
    // SIGNAL_CALLBACK_DEPRECATION flag set to true

    @Test
    public void stateUnavailable_wifiDisabled_newPipeline() {
        createAndStartTileNewImpl();
        mWifiRepository.setIsWifiEnabled(false);
        mTestableLooper.processAllMessages();

        assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
    }

    @Test
    public void stateUnavailable_wifiEnabled_notConnected_newPipeline() {
        createAndStartTileNewImpl();
        mWifiRepository.setIsWifiEnabled(true);
        mWifiRepository.setWifiNetwork(Inactive.INSTANCE);
        mTestableLooper.processAllMessages();

        assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
    }

    @Test
    public void stateActive_wifiConnectedAndCasting_newPipeline() {
        createAndStartTileNewImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastDevice.STATE_CONNECTED;
        List<CastDevice> devices = new ArrayList<>();
        devices.add(device);
        when(mController.getCastDevices()).thenReturn(devices);

        mWifiRepository.setWifiNetwork(
                new WifiNetworkModel.Active(
                        1 /* networkId */,
                        true /* isValidated */,
                        3 /* level */,
                        "test" /* ssid */,
                        WifiNetworkModel.HotspotDeviceType.NONE,
                        false /* isPasspointAccessPoint */,
                        false /* isOnlineSignUpforPasspointAccessPoint */,
                        null /* passpointProviderFriendlyName */
                ));
        mTestableLooper.processAllMessages();

        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
    }

    @Test
    public void stateInactive_wifiConnectedNotCasting_newPipeline() {
        createAndStartTileNewImpl();

        mWifiRepository.setWifiNetwork(
                new WifiNetworkModel.Active(
                        1 /* networkId */,
                        true /* isValidated */,
                        3 /* level */,
                        "test" /* ssid */,
                        WifiNetworkModel.HotspotDeviceType.NONE,
                        false /* isPasspointAccessPoint */,
                        false /* isOnlineSignUpforPasspointAccessPoint */,
                        null /* passpointProviderFriendlyName */
                ));
        mTestableLooper.processAllMessages();

        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
    }

    // -------------------------------------------------

    // -------------------------------------------------
    // All these tests for enabled/disabled hotspot have wifi not enabled
    @Test
    public void testStateUnavailable_hotspotDisabled() {
        createAndStartTileOldImpl();
        mHotspotCallback.onHotspotChanged(false, 0);
        mTestableLooper.processAllMessages();

@@ -221,6 +277,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testStateUnavailable_hotspotEnabledNotConnected() {
        createAndStartTileOldImpl();
        mHotspotCallback.onHotspotChanged(true, 0);
        mTestableLooper.processAllMessages();

@@ -229,6 +286,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testStateActive_hotspotEnabledAndConnectedAndCasting() {
        createAndStartTileOldImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastController.CastDevice.STATE_CONNECTED;
        List<CastDevice> devices = new ArrayList<>();
@@ -242,6 +300,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testStateInactive_hotspotEnabledAndConnectedAndNotCasting() {
        createAndStartTileOldImpl();
        mHotspotCallback.onHotspotChanged(true, 1);
        mTestableLooper.processAllMessages();
        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
@@ -250,6 +309,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testHandleClick_castDevicePresent() {
        createAndStartTileOldImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastDevice.STATE_CONNECTED;
        device.tag = mock(MediaRouter.RouteInfo.class);
@@ -267,6 +327,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testHandleClick_projectionOnly() {
        createAndStartTileOldImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastDevice.STATE_CONNECTED;
        device.tag = mock(MediaProjectionInfo.class);
@@ -283,6 +344,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testUpdateState_projectionOnly() {
        createAndStartTileOldImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastDevice.STATE_CONNECTED;
        device.tag = mock(MediaProjectionInfo.class);
@@ -298,6 +360,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testUpdateState_castingAndProjection() {
        createAndStartTileOldImpl();
        CastController.CastDevice casting = new CastController.CastDevice();
        casting.state = CastDevice.STATE_CONNECTED;
        casting.tag = mock(RouteInfo.class);
@@ -322,6 +385,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testUpdateState_connectedAndConnecting() {
        createAndStartTileOldImpl();
        CastController.CastDevice connecting = new CastController.CastDevice();
        connecting.state = CastDevice.STATE_CONNECTING;
        connecting.tag = mock(RouteInfo.class);
@@ -346,6 +410,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testExpandView_wifiNotConnected() {
        createAndStartTileOldImpl();
        mCastTile.refreshState();
        mTestableLooper.processAllMessages();

@@ -354,6 +419,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testExpandView_wifiEnabledNotCasting() {
        createAndStartTileOldImpl();
        enableWifiAndProcessMessages();

        assertTrue(mCastTile.getState().forceExpandIcon);
@@ -361,6 +427,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testExpandView_casting_projection() {
        createAndStartTileOldImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastController.CastDevice.STATE_CONNECTED;
        List<CastDevice> devices = new ArrayList<>();
@@ -374,6 +441,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testExpandView_connecting_projection() {
        createAndStartTileOldImpl();
        CastController.CastDevice connecting = new CastController.CastDevice();
        connecting.state = CastDevice.STATE_CONNECTING;
        connecting.name = "Test Casting Device";
@@ -389,6 +457,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testExpandView_casting_mediaRoute() {
        createAndStartTileOldImpl();
        CastController.CastDevice device = new CastController.CastDevice();
        device.state = CastDevice.STATE_CONNECTED;
        device.tag = mock(MediaRouter.RouteInfo.class);
@@ -403,6 +472,7 @@ public class CastTileTest extends SysuiTestCase {

    @Test
    public void testExpandView_connecting_mediaRoute() {
        createAndStartTileOldImpl();
        CastController.CastDevice connecting = new CastController.CastDevice();
        connecting.state = CastDevice.STATE_CONNECTING;
        connecting.tag = mock(RouteInfo.class);
@@ -416,4 +486,86 @@ public class CastTileTest extends SysuiTestCase {

        assertTrue(mCastTile.getState().forceExpandIcon);
    }

    /**
     * For simplicity, let this method still set the field even though that's kind of gross
     */
    private void createAndStartTileOldImpl() {
        mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, false);
        mCastTile = new CastTile(
                mHost,
                mUiEventLogger,
                mTestableLooper.getLooper(),
                new Handler(mTestableLooper.getLooper()),
                new FalsingManagerFake(),
                mMetricsLogger,
                mStatusBarStateController,
                mActivityStarter,
                mQSLogger,
                mController,
                mKeyguard,
                mNetworkController,
                mHotspotController,
                mDialogLaunchAnimator,
                mWifiInteractor,
                mJavaAdapter,
                mFeatureFlags
        );
        mCastTile.initialize();

        // We are not setting the mocks to listening, so we trigger a first refresh state to
        // set the initial state
        mCastTile.refreshState();

        mTestableLooper.processAllMessages();

        mCastTile.handleSetListening(true);
        ArgumentCaptor<SignalCallback> signalCallbackArgumentCaptor =
                ArgumentCaptor.forClass(SignalCallback.class);
        verify(mNetworkController).observe(any(LifecycleOwner.class),
                signalCallbackArgumentCaptor.capture());
        mSignalCallback = signalCallbackArgumentCaptor.getValue();

        ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
                ArgumentCaptor.forClass(HotspotController.Callback.class);
        verify(mHotspotController).observe(any(LifecycleOwner.class),
                hotspotCallbackArgumentCaptor.capture());
        mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
    }

    private void createAndStartTileNewImpl() {
        mFeatureFlags.set(SIGNAL_CALLBACK_DEPRECATION, true);
        mCastTile = new CastTile(
                mHost,
                mUiEventLogger,
                mTestableLooper.getLooper(),
                new Handler(mTestableLooper.getLooper()),
                new FalsingManagerFake(),
                mMetricsLogger,
                mStatusBarStateController,
                mActivityStarter,
                mQSLogger,
                mController,
                mKeyguard,
                mNetworkController,
                mHotspotController,
                mDialogLaunchAnimator,
                mWifiInteractor,
                mJavaAdapter,
                mFeatureFlags
        );
        mCastTile.initialize();

        // Since we do not capture the callbacks like in the old impl, set the state to RESUMED
        // So that TileJavaAdapter is collecting on flows
        mCastTile.setListening(new Object(), true);

        mTestableLooper.processAllMessages();

        ArgumentCaptor<HotspotController.Callback> hotspotCallbackArgumentCaptor =
                ArgumentCaptor.forClass(HotspotController.Callback.class);
        verify(mHotspotController).observe(any(LifecycleOwner.class),
                hotspotCallbackArgumentCaptor.capture());
        mHotspotCallback = hotspotCallbackArgumentCaptor.getValue();
    }
}