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

Commit a8e4c9a7 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Launch output switcher with media package information" into rvc-dev am: b6c8fab8

Change-Id: Ic4244cf0d1a09ec727a0d4b320b7c314d933cc43
parents e58ea87a b6c8fab8
Loading
Loading
Loading
Loading
+34 −12
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ 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;

@@ -36,6 +37,8 @@ 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;
@@ -52,6 +55,7 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
    private Context mContext;
    private LocalBluetoothManager mLocalBluetoothManager;
    private LocalBluetoothProfileManager mProfileManager;
    private MediaOutputIndicatorWorker mWorker;

    public MediaOutputIndicatorSlice(Context context) {
        mContext = context;
@@ -66,22 +70,18 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
    @Override
    public Slice getSlice() {
        if (!isVisible()) {
            return new ListBuilder(mContext, MEDIA_OUTPUT_INDICATOR_SLICE_URI, ListBuilder.INFINITY)
            return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
                    .setIsError(true)
                    .build();
        }
        final IconCompat icon = IconCompat.createWithResource(mContext,
                com.android.internal.R.drawable.ic_settings_bluetooth);
        final CharSequence title = mContext.getText(R.string.media_output_title);
        final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext,
                0 /* requestCode */, getMediaOutputSliceIntent(), 0 /* flags */);
        final SliceAction primarySliceAction = SliceAction.createDeeplink(
                primaryActionIntent, icon, ListBuilder.ICON_IMAGE, title);
                getBroadcastIntent(), icon, ListBuilder.ICON_IMAGE, title);
        @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
        // To set an empty icon to indent the row
        final ListBuilder listBuilder = new ListBuilder(mContext,
                MEDIA_OUTPUT_INDICATOR_SLICE_URI,
                ListBuilder.INFINITY)
        final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
                .setAccentColor(color)
                .addRow(new ListBuilder.RowBuilder()
                        .setTitle(title)
@@ -96,11 +96,11 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
        return IconCompat.createWithBitmap(bitmap);
    }

    private Intent getMediaOutputSliceIntent() {
        final Intent intent = new Intent()
                .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        return intent;
    private PendingIntent getBroadcastIntent() {
        final Intent intent = new Intent(getUri().toString());
        intent.setClass(mContext, SliceBroadcastReceiver.class);
        return PendingIntent.getBroadcast(mContext, 0, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
    }

    @Override
@@ -120,6 +120,28 @@ public class MediaOutputIndicatorSlice implements CustomSliceable {
        return MediaOutputIndicatorWorker.class;
    }

    @Override
    public void onNotifyChange(Intent i) {
        final MediaController mediaController = getWorker().getActiveLocalMediaController();
        final Intent intent = new Intent()
                .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (mediaController != null) {
            intent.putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN,
                    mediaController.getSessionToken());
            intent.putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
                    mediaController.getPackageName());
        }
        mContext.startActivity(intent);
    }

    private MediaOutputIndicatorWorker getWorker() {
        if (mWorker == null) {
            mWorker = SliceBackgroundWorker.getInstance(getUri());
        }
        return mWorker;
    }

    private boolean isVisible() {
        // To decide Slice's visibility.
        // Return true if
+26 −2
Original line number Diff line number Diff line
@@ -24,18 +24,21 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

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 java.io.IOException;

/**
 * Listener for background change from {@code BluetoothCallback} to update media output indicator.
 */
@@ -100,6 +103,27 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements
        notifySliceChange();
    }

    @Nullable
    MediaController getActiveLocalMediaController() {
        final MediaSessionManager mMediaSessionManager = mContext.getSystemService(
                MediaSessionManager.class);

        for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) {
            final MediaController.PlaybackInfo pi = controller.getPlaybackInfo();
            if (pi == null) {
                return null;
            }
            final PlaybackState playbackState = controller.getPlaybackState();
            if (playbackState == null) {
                return null;
            }
            if (pi.getPlaybackType() == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL
                    && playbackState.getState() == PlaybackState.STATE_PLAYING) {
                return controller;
            }
        }
        return null;
    }
    private class DevicesChangedBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
+66 −2
Original line number Diff line number Diff line
@@ -17,16 +17,25 @@

package com.android.settings.media;

import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.doReturn;
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.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.net.Uri;
import android.text.TextUtils;

import androidx.slice.Slice;
import androidx.slice.SliceMetadata;
@@ -34,33 +43,42 @@ import androidx.slice.SliceProvider;
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.MediaOutputSliceConstants;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;

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

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothUtils.class})
@Config(shadows = {ShadowBluetoothUtils.class,
        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_PACKAGE_NAME = "com.test";

    private static MediaOutputIndicatorWorker sMediaOutputIndicatorWorker;

    @Mock
    private A2dpProfile mA2dpProfile;
@@ -70,6 +88,8 @@ public class MediaOutputIndicatorSliceTest {
    private LocalBluetoothManager mLocalBluetoothManager;
    @Mock
    private LocalBluetoothProfileManager mLocalBluetoothProfileManager;
    @Mock
    private MediaController mMediaController;

    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothDevice mA2dpDevice;
@@ -79,6 +99,7 @@ public class MediaOutputIndicatorSliceTest {
    private List<BluetoothDevice> mDevicesList;
    private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice;
    private AudioManager mAudioManager;
    private MediaSession.Token mToken;

    @Before
    public void setUp() throws Exception {
@@ -86,9 +107,11 @@ public class MediaOutputIndicatorSliceTest {
        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(null);
        // Set-up specs for SliceMetadata.
        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);

        // Setup Bluetooth environment
        ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
        mBluetoothManager = new BluetoothManager(mContext);
@@ -196,4 +219,45 @@ public class MediaOutputIndicatorSliceTest {
        final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice);
        assertThat(metadata.isErrorSlice()).isTrue();
    }

    @Test
    public void onNotifyChange_withActiveLocalMedia_verifyIntentExtra() {
        when(mMediaController.getSessionToken()).thenReturn(mToken);
        when(mMediaController.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
        doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
                .getActiveLocalMediaController();

        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
        mMediaOutputIndicatorSlice.onNotifyChange(new Intent());
        verify(mContext).startActivity(intentCaptor.capture());

        assertThat(TextUtils.equals(TEST_PACKAGE_NAME, intentCaptor.getValue().getStringExtra(
                MediaOutputSliceConstants.EXTRA_PACKAGE_NAME))).isTrue();
        assertThat(mToken == intentCaptor.getValue().getExtras().getParcelable(
                MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN)).isTrue();
    }

    @Test
    public void onNotifyChange_withoutActiveLocalMedia_verifyIntentExtra() {
        doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
                .getActiveLocalMediaController();

        final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
        mMediaOutputIndicatorSlice.onNotifyChange(new Intent());
        verify(mContext).startActivity(intentCaptor.capture());

        assertThat(TextUtils.isEmpty(intentCaptor.getValue().getStringExtra(
                MediaOutputSliceConstants.EXTRA_PACKAGE_NAME))).isTrue();
        assertThat(intentCaptor.getValue().getExtras().getParcelable(
                MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN) == null).isTrue();
    }

    @Implements(SliceBackgroundWorker.class)
    public static class ShadowSliceBackgroundWorker {

        @Implementation
        public static SliceBackgroundWorker getInstance(Uri uri) {
            return sMediaOutputIndicatorWorker;
        }
    }
}
+87 −8
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.settings.media;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -28,7 +30,12 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.VolumeProvider;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.net.Uri;

import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
@@ -45,6 +52,9 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;

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

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothUtils.class})
public class MediaOutputIndicatorWorkerTest {
@@ -54,10 +64,18 @@ public class MediaOutputIndicatorWorkerTest {
    private BluetoothEventManager mBluetoothEventManager;
    @Mock
    private LocalBluetoothManager mLocalBluetoothManager;
    @Mock
    private MediaSessionManager mMediaSessionManager;
    @Mock
    private MediaController mMediaController;

    private Context mContext;
    private MediaOutputIndicatorWorker mMediaDeviceUpdateWorker;
    private MediaOutputIndicatorWorker mMediaOutputIndicatorWorker;
    private ShadowApplication mShadowApplication;
    private ContentResolver mResolver;
    private List<MediaController> mMediaControllers = new ArrayList<>();
    private PlaybackState mPlaybackState;
    private MediaController.PlaybackInfo mPlaybackInfo;

    @Before
    public void setUp() {
@@ -66,7 +84,10 @@ public class MediaOutputIndicatorWorkerTest {
        mContext = spy(RuntimeEnvironment.application);
        ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager;
        when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
        mMediaDeviceUpdateWorker = new MediaOutputIndicatorWorker(mContext, URI);
        mMediaOutputIndicatorWorker = new MediaOutputIndicatorWorker(mContext, URI);
        when(mContext.getSystemService(MediaSessionManager.class)).thenReturn(mMediaSessionManager);
        mMediaControllers.add(mMediaController);
        when(mMediaSessionManager.getActiveSessions(any())).thenReturn(mMediaControllers);

        mResolver = mock(ContentResolver.class);
        doReturn(mResolver).when(mContext).getContentResolver();
@@ -74,22 +95,22 @@ public class MediaOutputIndicatorWorkerTest {

    @Test
    public void onSlicePinned_registerCallback() {
        mMediaDeviceUpdateWorker.onSlicePinned();
        verify(mBluetoothEventManager).registerCallback(mMediaDeviceUpdateWorker);
        mMediaOutputIndicatorWorker.onSlicePinned();
        verify(mBluetoothEventManager).registerCallback(mMediaOutputIndicatorWorker);
        verify(mContext).registerReceiver(any(BroadcastReceiver.class), any(IntentFilter.class));
    }

    @Test
    public void onSliceUnpinned_unRegisterCallback() {
        mMediaDeviceUpdateWorker.onSlicePinned();
        mMediaDeviceUpdateWorker.onSliceUnpinned();
        verify(mBluetoothEventManager).unregisterCallback(mMediaDeviceUpdateWorker);
        mMediaOutputIndicatorWorker.onSlicePinned();
        mMediaOutputIndicatorWorker.onSliceUnpinned();
        verify(mBluetoothEventManager).unregisterCallback(mMediaOutputIndicatorWorker);
        verify(mContext).unregisterReceiver(any(BroadcastReceiver.class));
    }

    @Test
    public void onReceive_shouldNotifyChange() {
        mMediaDeviceUpdateWorker.onSlicePinned();
        mMediaOutputIndicatorWorker.onSlicePinned();

        final Intent intent = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
        for (BroadcastReceiver receiver : mShadowApplication.getReceiversForIntent(intent)) {
@@ -98,4 +119,62 @@ public class MediaOutputIndicatorWorkerTest {

        verify(mResolver).notifyChange(URI, null);
    }

    @Test
    public void getActiveLocalMediaController_localMediaPlaying_returnController() {
        mPlaybackInfo = new MediaController.PlaybackInfo(
                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
                100,
                10,
                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
                null);
        mPlaybackState = new PlaybackState.Builder()
                .setState(PlaybackState.STATE_PLAYING, 0, 1)
                .build();

        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);

        assertThat(mMediaOutputIndicatorWorker.getActiveLocalMediaController()).isEqualTo(
                mMediaController);
    }

    @Test
    public void getActiveLocalMediaController_remoteMediaPlaying_returnNull() {
        mPlaybackInfo = new MediaController.PlaybackInfo(
                MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
                100,
                10,
                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
                null);
        mPlaybackState = new PlaybackState.Builder()
                .setState(PlaybackState.STATE_PLAYING, 0, 1)
                .build();

        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);

        assertThat(mMediaOutputIndicatorWorker.getActiveLocalMediaController()).isNull();
    }

    @Test
    public void getActiveLocalMediaController_localMediaStopped_returnNull() {
        mPlaybackInfo = new MediaController.PlaybackInfo(
                MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
                VolumeProvider.VOLUME_CONTROL_ABSOLUTE,
                100,
                10,
                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(),
                null);
        mPlaybackState = new PlaybackState.Builder()
                .setState(PlaybackState.STATE_STOPPED, 0, 1)
                .build();

        when(mMediaController.getPlaybackInfo()).thenReturn(mPlaybackInfo);
        when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);

        assertThat(mMediaOutputIndicatorWorker.getActiveLocalMediaController()).isNull();
    }
}