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

Commit 9607eb33 authored by tim peng's avatar tim peng Committed by Android (Google) Code Review
Browse files

Merge "Update rule to display output switcher Slice" into rvc-dev

parents 5d6c309d 9c2968ab
Loading
Loading
Loading
Loading
+7 −78
Original line number Diff line number Diff line
@@ -20,51 +20,33 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDIC

import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.session.MediaController;
import android.net.Uri;
import android.util.Log;

import androidx.core.graphics.drawable.IconCompat;
import androidx.slice.Slice;
import androidx.slice.builders.ListBuilder;
import androidx.slice.builders.SliceAction;

import com.android.internal.util.CollectionUtils;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.slices.CustomSliceable;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.slices.SliceBroadcastReceiver;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.media.MediaOutputSliceConstants;

import java.util.ArrayList;
import java.util.List;

public class MediaOutputIndicatorSlice implements CustomSliceable {

    private static final String TAG = "MediaOutputIndicatorSlice";
    private static final String TAG = "MediaOutputIndSlice";

    private Context mContext;
    private LocalBluetoothManager mLocalBluetoothManager;
    private LocalBluetoothProfileManager mProfileManager;
    private MediaOutputIndicatorWorker mWorker;

    public MediaOutputIndicatorSlice(Context context) {
        mContext = context;
        mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBtManager(context);
        if (mLocalBluetoothManager == null) {
            Log.e(TAG, "Bluetooth is not supported on this device");
            return;
        }
        mProfileManager = mLocalBluetoothManager.getProfileManager();
    }

    @Override
@@ -86,7 +68,7 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
                .addRow(new ListBuilder.RowBuilder()
                        .setTitle(title)
                        .setTitleItem(createEmptyIcon(), ListBuilder.ICON_IMAGE)
                        .setSubtitle(findActiveDeviceName())
                        .setSubtitle(getWorker().getCurrentConnectedMediaDevice().getName())
                        .setPrimaryAction(primarySliceAction));
        return listBuilder.build();
    }
@@ -146,63 +128,10 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
        // To decide Slice's visibility.
        // Return true if
        // 1. AudioMode is not in on-going call
        // 2. Bluetooth device is connected
        return (!CollectionUtils.isEmpty(getConnectedA2dpDevices())
                || !CollectionUtils.isEmpty(getConnectedHearingAidDevices()))
                && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext);
    }

    private List<BluetoothDevice> getConnectedA2dpDevices() {
        // Get A2dp devices on states
        // (STATE_CONNECTING, STATE_CONNECTED,  STATE_DISCONNECTING)
        final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
        if (a2dpProfile == null) {
            return new ArrayList<>();
        }
        return a2dpProfile.getConnectedDevices();
    }

    private List<BluetoothDevice> getConnectedHearingAidDevices() {
        // Get hearing aid profile devices on states
        // (STATE_CONNECTING, STATE_CONNECTED,  STATE_DISCONNECTING)
        final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile();
        if (hapProfile == null) {
            return new ArrayList<>();
        }

        return hapProfile.getConnectedDevices();
    }

    private CharSequence findActiveDeviceName() {
        // Return Hearing Aid device name if it is active
        BluetoothDevice activeDevice = findActiveHearingAidDevice();
        if (activeDevice != null) {
            return activeDevice.getAlias();
        }
        // Return A2DP device name if it is active
        final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
        if (a2dpProfile != null) {
            activeDevice = a2dpProfile.getActiveDevice();
            if (activeDevice != null) {
                return activeDevice.getAlias();
            }
        }
        // No active device, return default summary
        return mContext.getText(R.string.media_output_default_summary);
    }

    private BluetoothDevice findActiveHearingAidDevice() {
        final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile();
        if (hearingAidProfile == null) {
            return null;
        }

        final List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
        for (BluetoothDevice btDevice : activeDevices) {
            if (btDevice != null) {
                return btDevice;
            }
        }
        return null;
        // 2. worker is not null
        // 3. Available devices are more than 1
        return getWorker() != null
                && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)
                && getWorker().getMediaDevices().size() > 1;
    }
}
+62 −18
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.settings.media;

import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION;

import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -36,21 +35,33 @@ import androidx.annotation.Nullable;
import com.android.settings.bluetooth.Utils;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;

import com.google.common.annotations.VisibleForTesting;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Listener for background change from {@code BluetoothCallback} to update media output indicator.
 */
public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements BluetoothCallback {
public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements BluetoothCallback,
        LocalMediaManager.DeviceCallback {

    private static final String TAG = "MediaOutputIndicatorWorker";
    private static final String TAG = "MediaOutputIndWorker";

    private final DevicesChangedBroadcastReceiver mReceiver;
    private final Context mContext;
    private final Collection<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();

    private LocalBluetoothManager mLocalBluetoothManager;

    @VisibleForTesting
    LocalMediaManager mLocalMediaManager;

    public MediaOutputIndicatorWorker(Context context, Uri uri) {
        super(context, uri);
        mReceiver = new DevicesChangedBroadcastReceiver();
@@ -59,6 +70,7 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements

    @Override
    protected void onSlicePinned() {
        mMediaDevices.clear();
        mLocalBluetoothManager = Utils.getLocalBtManager(getContext());
        if (mLocalBluetoothManager == null) {
            Log.e(TAG, "Bluetooth is not supported on this device");
@@ -67,10 +79,25 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements
        final IntentFilter intentFilter = new IntentFilter(STREAM_DEVICES_CHANGED_ACTION);
        mContext.registerReceiver(mReceiver, intentFilter);
        mLocalBluetoothManager.getEventManager().registerCallback(this);

        if (mLocalMediaManager == null) {
            final MediaController controller = getActiveLocalMediaController();
            String packageName = null;
            if (controller != null) {
                packageName = controller.getPackageName();
            }
            mLocalMediaManager = new LocalMediaManager(mContext, packageName, null);
        }

        mLocalMediaManager.registerCallback(this);
        mLocalMediaManager.startScan();
    }

    @Override
    protected void onSliceUnpinned() {
        mLocalMediaManager.unregisterCallback(this);
        mLocalMediaManager.stopScan();

        if (mLocalBluetoothManager == null) {
            Log.e(TAG, "Bluetooth is not supported on this device");
            return;
@@ -82,20 +109,7 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements
    @Override
    public void close() {
        mLocalBluetoothManager = null;
    }

    @Override
    public void onBluetoothStateChanged(int bluetoothState) {
        // To handle the case that Bluetooth on and no connected devices
        notifySliceChange();
    }

    @Override
    public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) {
        if (bluetoothProfile == BluetoothProfile.A2DP ||
                bluetoothProfile == BluetoothProfile.HEARING_AID) {
            notifySliceChange();
        }
        mLocalMediaManager = null;
    }

    @Override
@@ -124,6 +138,36 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements
        }
        return null;
    }

    @Override
    public void onDeviceListUpdate(List<MediaDevice> devices) {
        buildMediaDevices(devices);
        notifySliceChange();
    }

    private void buildMediaDevices(List<MediaDevice> devices) {
        mMediaDevices.clear();
        mMediaDevices.addAll(devices);
    }

    @Override
    public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
        notifySliceChange();
    }

    @Override
    public void onDeviceAttributesChanged() {
        notifySliceChange();
    }

    Collection<MediaDevice> getMediaDevices() {
        return mMediaDevices;
    }

    MediaDevice getCurrentConnectedMediaDevice() {
        return mLocalMediaManager.getCurrentConnectedDevice();
    }

    private class DevicesChangedBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
+4 −6
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE_URI;

import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
@@ -246,11 +245,10 @@ public class MediaOutputSlice implements CustomSliceable {
        // Return true if
        // 1. AudioMode is not in on-going call
        // 2. worker is not null
        // 3. Bluetooth is enabled
        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();

        return adapter.isEnabled()
        // 3. Available devices are more than 1
        return getWorker() != null
                && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)
                && getWorker() != null;
                && getWorker().getMediaDevices().size() > 1;

    }
}
+50 −81
Original line number Diff line number Diff line
@@ -26,11 +26,9 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -46,14 +44,12 @@ import androidx.slice.widget.SliceLiveData;
import com.android.settings.R;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.A2dpProfile;
import com.android.settingslib.bluetooth.HearingAidProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -73,31 +69,28 @@ import java.util.List;
        MediaOutputIndicatorSliceTest.ShadowSliceBackgroundWorker.class})
public class MediaOutputIndicatorSliceTest {

    private static final String TEST_A2DP_DEVICE_NAME = "Test_A2DP_BT_Device_NAME";
    private static final String TEST_HAP_DEVICE_NAME = "Test_HAP_BT_Device_NAME";
    private static final String TEST_A2DP_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
    private static final String TEST_HAP_DEVICE_ADDRESS = "00:B2:B2:B2:B2:B2";
    private static final String TEST_DEVICE_1_NAME = "test_device_1_name";
    private static final String TEST_DEVICE_2_NAME = "test_device_2_name";
    private static final String TEST_PACKAGE_NAME = "com.test";

    private static MediaOutputIndicatorWorker sMediaOutputIndicatorWorker;

    private final List<MediaDevice> mDevices = new ArrayList<>();

    @Mock
    private A2dpProfile mA2dpProfile;
    private LocalBluetoothManager mLocalBluetoothManager;
    @Mock
    private HearingAidProfile mHearingAidProfile;
    private MediaController mMediaController;
    @Mock
    private LocalBluetoothManager mLocalBluetoothManager;
    private LocalMediaManager mLocalMediaManager;
    @Mock
    private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
    private MediaDevice mDevice1;
    @Mock
    private MediaController mMediaController;
    private MediaDevice mDevice2;
    @Mock
    private Drawable mTestDrawable;

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothDevice mA2dpDevice;
    private BluetoothDevice mHapDevice;
    private BluetoothManager mBluetoothManager;
    private Context mContext;
    private List<BluetoothDevice> mDevicesList;
    private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice;
    private AudioManager mAudioManager;
    private MediaSession.Token mToken;
@@ -107,7 +100,6 @@ public class MediaOutputIndicatorSliceTest {
        MockitoAnnotations.initMocks(this);
        mContext = spy(RuntimeEnvironment.application);
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
        mAudioManager.setMode(AudioManager.MODE_NORMAL);
        sMediaOutputIndicatorWorker = spy(new MediaOutputIndicatorWorker(mContext,
                MEDIA_OUTPUT_INDICATOR_SLICE_URI));
        mToken = new MediaSession.Token(Process.myUid(), null);
@@ -115,109 +107,86 @@ public class MediaOutputIndicatorSliceTest {
        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
        // Setup Bluetooth environment
        ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
        mBluetoothManager = new BluetoothManager(mContext);
        mBluetoothAdapter = mBluetoothManager.getAdapter();
        when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
        when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
        when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);

        // Setup A2dp device
        mA2dpDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_A2DP_DEVICE_ADDRESS));
        when(mA2dpDevice.getName()).thenReturn(TEST_A2DP_DEVICE_NAME);
        when(mA2dpDevice.isConnected()).thenReturn(true);
        // Setup HearingAid device
        mHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_HAP_DEVICE_ADDRESS));
        when(mHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME);
        when(mHapDevice.isConnected()).thenReturn(true);
        // Setup mock devices
        when(mDevice1.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(mDevice1.getIcon()).thenReturn(mTestDrawable);
        when(mDevice1.getMaxVolume()).thenReturn(100);
        when(mDevice1.isConnected()).thenReturn(true);
        when(mDevice2.getName()).thenReturn(TEST_DEVICE_2_NAME);
        when(mDevice2.getIcon()).thenReturn(mTestDrawable);
        when(mDevice2.getMaxVolume()).thenReturn(100);
        when(mDevice2.isConnected()).thenReturn(false);

        mMediaOutputIndicatorSlice = new MediaOutputIndicatorSlice(mContext);
        mDevicesList = new ArrayList<>();
    }

    @Test
    public void getSlice_noConnectedDevice_returnErrorSlice() {
        mDevicesList.clear();
        when(mA2dpProfile.getConnectedDevices()).thenReturn(mDevicesList);
    public void getSlice_withConnectedDevice_verifyMetadata() {
        mDevices.add(mDevice1);
        mDevices.add(mDevice2);
        when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
        doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
        mAudioManager.setMode(AudioManager.MODE_NORMAL);

        final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
        assertThat(metadata.isErrorSlice()).isTrue();
    }

    @Test
    public void getSlice_noActiveDevice_verifyDefaultName() {
        mDevicesList.add(mA2dpDevice);
        when(mA2dpProfile.getConnectedDevices()).thenReturn(mDevicesList);
        when(mA2dpProfile.getActiveDevice()).thenReturn(null);

        // Verify slice title and subtitle
        final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
        assertThat(metadata.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title));
        assertThat(metadata.getSubtitle()).isEqualTo(mContext.getText(
                R.string.media_output_default_summary));
        assertThat(metadata.getSubtitle()).isEqualTo(TEST_DEVICE_1_NAME);
        assertThat(metadata.isErrorSlice()).isFalse();
    }

    @Test
    @Ignore
    public void getSlice_A2dpDeviceActive_verifyName() {
        mDevicesList.add(mA2dpDevice);
        when(mA2dpProfile.getConnectedDevices()).thenReturn(mDevicesList);
        when(mA2dpProfile.getActiveDevice()).thenReturn(mA2dpDevice);
    public void getSlice_noConnectedDevice_returnErrorSlice() {
        mDevices.clear();
        when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
        mAudioManager.setMode(AudioManager.MODE_NORMAL);

        final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
        assertThat(metadata.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title));
        assertThat(metadata.getSubtitle()).isEqualTo(TEST_A2DP_DEVICE_NAME);
        assertThat(metadata.isErrorSlice()).isFalse();
    }

    @Test
    @Ignore
    public void getSlice_HADeviceActive_verifyName() {
        mDevicesList.add(mHapDevice);
        when(mHearingAidProfile.getConnectedDevices()).thenReturn(mDevicesList);
        when(mHearingAidProfile.getActiveDevices()).thenReturn(mDevicesList);

        // Verify slice title and subtitle
        final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
        assertThat(metadata.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title));
        assertThat(metadata.getSubtitle()).isEqualTo(TEST_HAP_DEVICE_NAME);
        assertThat(metadata.isErrorSlice()).isFalse();
        assertThat(metadata.isErrorSlice()).isTrue();
    }

    @Test
    public void getSlice_audioModeIsInCommunication_returnErrorSlice() {
        mDevicesList.add(mA2dpDevice);
        when(mA2dpProfile.getConnectedDevices()).thenReturn(mDevicesList);
        mDevices.add(mDevice1);
        mDevices.add(mDevice2);
        when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
        doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
        mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);

        final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);

        assertThat(metadata.isErrorSlice()).isTrue();
    }

    @Test
    public void getSlice_audioModeIsRingtone_returnErrorSlice() {
        mDevicesList.add(mA2dpDevice);
        when(mA2dpProfile.getConnectedDevices()).thenReturn(mDevicesList);
        mDevices.add(mDevice1);
        mDevices.add(mDevice2);
        when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
        doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
        mAudioManager.setMode(AudioManager.MODE_RINGTONE);

        final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);

        assertThat(metadata.isErrorSlice()).isTrue();
    }

    @Test
    public void getSlice_audioModeIsInCall_returnErrorSlice() {
        mDevicesList.add(mA2dpDevice);
        when(mA2dpProfile.getConnectedDevices()).thenReturn(mDevicesList);
        mDevices.add(mDevice1);
        mDevices.add(mDevice2);
        when(sMediaOutputIndicatorWorker.getMediaDevices()).thenReturn(mDevices);
        doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
        mAudioManager.setMode(AudioManager.MODE_IN_CALL);

        final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice();
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);

        assertThat(metadata.isErrorSlice()).isTrue();
    }

+19 −3
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -38,9 +39,11 @@ import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.net.Uri;

import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.LocalMediaManager;

import org.junit.Before;
import org.junit.Test;
@@ -56,7 +59,7 @@ import java.util.ArrayList;
import java.util.List;

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothUtils.class})
@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class})
public class MediaOutputIndicatorWorkerTest {
    private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");

@@ -68,6 +71,8 @@ public class MediaOutputIndicatorWorkerTest {
    private MediaSessionManager mMediaSessionManager;
    @Mock
    private MediaController mMediaController;
    @Mock
    private LocalMediaManager mLocalMediaManager;

    private Context mContext;
    private MediaOutputIndicatorWorker mMediaOutputIndicatorWorker;
@@ -95,29 +100,40 @@ public class MediaOutputIndicatorWorkerTest {

    @Test
    public void onSlicePinned_registerCallback() {
        mMediaOutputIndicatorWorker.mLocalMediaManager = mLocalMediaManager;
        mMediaOutputIndicatorWorker.onSlicePinned();

        verify(mBluetoothEventManager).registerCallback(mMediaOutputIndicatorWorker);
        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
        verify(mLocalMediaManager).registerCallback(mMediaOutputIndicatorWorker);
        verify(mLocalMediaManager).startScan();
    }

    @Test
    public void onSliceUnpinned_unRegisterCallback() {
        mMediaOutputIndicatorWorker.mLocalMediaManager = mLocalMediaManager;
        mMediaOutputIndicatorWorker.onSlicePinned();
        mMediaOutputIndicatorWorker.onSliceUnpinned();

        verify(mBluetoothEventManager).unregisterCallback(mMediaOutputIndicatorWorker);
        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
        verify(mLocalMediaManager).unregisterCallback(mMediaOutputIndicatorWorker);
        verify(mLocalMediaManager).stopScan();
    }

    @Test
    public void onReceive_shouldNotifyChange() {
        mMediaOutputIndicatorWorker.onSlicePinned();
        // onSlicePinned will registerCallback() and get first callback. Callback triggers this at
        // the first time.
        verify(mResolver, times(1)).notifyChange(URI, null);

        final Intent intent = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
        for (BroadcastReceiver receiver : mShadowApplication.getReceiversForIntent(intent)) {
            receiver.onReceive(mContext, intent);
        }

        verify(mResolver).notifyChange(URI, null);
        // Intent receiver triggers notifyChange() again
        verify(mResolver, times(2)).notifyChange(URI, null /* observer */);
    }

    @Test