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

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

Merge "Add entry point at output switcher to do group operation" into rvc-dev

parents b6586696 b4c4c362
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -3202,6 +3202,10 @@
                    <action android:name="com.android.settings.panel.action.MEDIA_OUTPUT" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
                <intent-filter>
                    <action android:name="com.android.settings.panel.action.MEDIA_OUTPUT_GROUP" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
        </activity-alias>

        <provider android:name=".slices.SettingsSliceProvider"
+1 −2
Original line number Diff line number Diff line
@@ -118,8 +118,7 @@ public class MediaOutputGroupSlice implements CustomSliceable {
        return listBuilder.build();
    }

    private void addRow(ListBuilder listBuilder, List<MediaDevice> mediaDevices,
            boolean selected) {
    private void addRow(ListBuilder listBuilder, List<MediaDevice> mediaDevices, boolean selected) {
        for (MediaDevice device : mediaDevices) {
            final int maxVolume = device.getMaxVolume();
            final IconCompat titleIcon = Utils.createIconWithDrawable(device.getIcon());
+111 −46
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ import com.android.settings.slices.CustomSliceable;
import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.slices.SliceBroadcastReceiver;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;

import java.util.Collection;

@@ -54,6 +55,8 @@ public class MediaOutputSlice implements CustomSliceable {

    private static final String TAG = "MediaOutputSlice";
    private static final String MEDIA_DEVICE_ID = "media_device_id";
    private static final String MEDIA_GROUP_DEVICE = "media_group_device";
    private static final String MEDIA_GROUP_REQUEST = "media_group_request";
    private static final int NON_SLIDER_VALUE = -1;

    public static final String MEDIA_PACKAGE_NAME = "media_package_name";
@@ -86,6 +89,15 @@ public class MediaOutputSlice implements CustomSliceable {

        final Collection<MediaDevice> devices = getMediaDevices();
        final MediaDeviceUpdateWorker worker = getWorker();

        if (worker.getSelectedMediaDevice().size() > 1) {
            // Insert group item to the first when it is available
            listBuilder.addInputRange(getGroupRow());
            // Add all other devices
            for (MediaDevice device : devices) {
                addRow(device, null /* connectedDevice */, listBuilder);
            }
        } else {
            final MediaDevice connectedDevice = worker.getCurrentConnectedMediaDevice();
            final boolean isTouched = worker.getIsTouched();
            // Fix the last top device when user press device to transfer.
@@ -97,24 +109,38 @@ public class MediaOutputSlice implements CustomSliceable {
            }

            for (MediaDevice device : devices) {
            if (topDevice == null
                    || !TextUtils.equals(topDevice.getId(), device.getId())) {
                if (topDevice == null || !TextUtils.equals(topDevice.getId(), device.getId())) {
                    addRow(device, connectedDevice, listBuilder);
                }
            }

        }
        return listBuilder.build();
    }

    private void addRow(MediaDevice device, MediaDevice connectedDevice, ListBuilder listBuilder) {
        if (connectedDevice != null && TextUtils.equals(device.getId(), connectedDevice.getId())) {
            listBuilder.addInputRange(getActiveDeviceHeaderRow(device));
        } else {
            listBuilder.addRow(getMediaDeviceRow(device));
        }
    private ListBuilder.InputRangeBuilder getGroupRow() {
        final IconCompat icon = IconCompat.createWithResource(mContext,
                R.drawable.ic_speaker_group_black_24dp);
        final CharSequence sessionName = getWorker().getSessionName();
        final CharSequence title = TextUtils.isEmpty(sessionName)
                ? mContext.getString(R.string.media_output_group) : sessionName;
        final PendingIntent broadcastAction =
                getBroadcastIntent(mContext, MEDIA_GROUP_DEVICE, MEDIA_GROUP_DEVICE.hashCode());
        final SliceAction primarySliceAction = SliceAction.createDeeplink(broadcastAction, icon,
                ListBuilder.ICON_IMAGE, title);
        final ListBuilder.InputRangeBuilder builder = new ListBuilder.InputRangeBuilder()
                .setTitleItem(icon, ListBuilder.ICON_IMAGE)
                .setTitle(title)
                .setPrimaryAction(primarySliceAction)
                .setInputAction(getSliderInputAction(MEDIA_GROUP_DEVICE.hashCode(),
                        MEDIA_GROUP_DEVICE))
                .setMax(getWorker().getSessionVolumeMax())
                .setValue(getWorker().getSessionVolume())
                .addEndItem(getEndItemSliceAction());
        return builder;
    }

    private ListBuilder.InputRangeBuilder getActiveDeviceHeaderRow(MediaDevice device) {
    private void addRow(MediaDevice device, MediaDevice connectedDevice, ListBuilder listBuilder) {
        if (connectedDevice != null && TextUtils.equals(device.getId(), connectedDevice.getId())) {
            final String title = device.getName();
            final IconCompat icon = getDeviceIconCompat(device);

@@ -122,6 +148,8 @@ public class MediaOutputSlice implements CustomSliceable {
                    getBroadcastIntent(mContext, device.getId(), device.hashCode());
            final SliceAction primarySliceAction = SliceAction.createDeeplink(broadcastAction, icon,
                    ListBuilder.ICON_IMAGE, title);

            if (device.getMaxVolume() > 0) {
                final ListBuilder.InputRangeBuilder builder = new ListBuilder.InputRangeBuilder()
                        .setTitleItem(icon, ListBuilder.ICON_IMAGE)
                        .setTitle(title)
@@ -129,7 +157,24 @@ public class MediaOutputSlice implements CustomSliceable {
                        .setInputAction(getSliderInputAction(device.hashCode(), device.getId()))
                        .setMax(device.getMaxVolume())
                        .setValue(device.getCurrentVolume());
        return builder;
                // Check end item visibility
                if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE
                        && !getWorker().getSelectableMediaDevice().isEmpty()) {
                    builder.addEndItem(getEndItemSliceAction());
                }
                listBuilder.addInputRange(builder);
            } else {
                final ListBuilder.RowBuilder builder = getMediaDeviceRow(device);
                // Check end item visibility
                if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE
                        && !getWorker().getSelectableMediaDevice().isEmpty()) {
                    builder.addEndItem(getEndItemSliceAction());
                }
                listBuilder.addRow(builder);
            }
        } else {
            listBuilder.addRow(getMediaDeviceRow(device));
        }
    }

    private PendingIntent getSliderInputAction(int requestCode, String id) {
@@ -141,6 +186,20 @@ public class MediaOutputSlice implements CustomSliceable {
        return PendingIntent.getBroadcast(mContext, requestCode, intent, 0);
    }

    private SliceAction getEndItemSliceAction() {
        final Intent intent = new Intent()
                .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT_GROUP)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
                        getWorker().getPackageName());

        return SliceAction.createDeeplink(
                PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */),
                IconCompat.createWithResource(mContext, R.drawable.ic_add_blue_24dp),
                ListBuilder.ICON_IMAGE,
                mContext.getText(R.string.add));
    }

    private IconCompat getDeviceIconCompat(MediaDevice device) {
        Drawable drawable = device.getIcon();
        if (drawable == null) {
@@ -169,14 +228,12 @@ public class MediaOutputSlice implements CustomSliceable {
        final PendingIntent broadcastAction =
                getBroadcastIntent(mContext, device.getId(), device.hashCode());
        final IconCompat deviceIcon = getDeviceIconCompat(device);

        final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
                .setTitleItem(deviceIcon, ListBuilder.ICON_IMAGE)
                .setPrimaryAction(SliceAction.create(broadcastAction, deviceIcon,
                        ListBuilder.ICON_IMAGE, deviceName));
        // Append status to tile only for the disconnected Bluetooth device.
                .setTitleItem(deviceIcon, ListBuilder.ICON_IMAGE);

        if (device.getDeviceType() == MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE
                && !device.isConnected()) {
            // Append status to title only for the disconnected Bluetooth device.
            final SpannableString spannableTitle = new SpannableString(
                    mContext.getString(R.string.media_output_disconnected_status, deviceName));
            spannableTitle.setSpan(new ForegroundColorSpan(Color.GRAY), deviceName.length(),
@@ -214,21 +271,29 @@ public class MediaOutputSlice implements CustomSliceable {
        if (TextUtils.isEmpty(id)) {
            return;
        }

        final int newPosition = intent.getIntExtra(EXTRA_RANGE_VALUE, NON_SLIDER_VALUE);
        if (TextUtils.equals(id, MEDIA_GROUP_DEVICE)) {
            // Session volume adjustment
            worker.adjustSessionVolume(newPosition);
        } else {
            final MediaDevice device = worker.getMediaDeviceById(id);
            if (device == null) {
                Log.d(TAG, "onNotifyChange: Unable to get device " + id);
                return;
            }
        final int newPosition = intent.getIntExtra(EXTRA_RANGE_VALUE, NON_SLIDER_VALUE);

            if (newPosition == NON_SLIDER_VALUE) {
                // Intent for device connection
            Log.d(TAG, "onNotifyChange() device name : " + device.getName());
                Log.d(TAG, "onNotifyChange: Switch to " + device.getName());
                worker.setIsTouched(true);
                worker.connectDevice(device);
            } else {
            // Intent for volume adjustment
                // Single device volume adjustment
                worker.adjustVolume(device, newPosition);
            }
        }
    }

    @Override
    public Intent getIntent() {
+3 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.settings.panel;

import static com.android.settingslib.media.MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT;
import static com.android.settingslib.media.MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT_GROUP;

import android.content.Context;
import android.os.Bundle;
@@ -46,6 +47,8 @@ public class PanelFeatureProviderImpl implements PanelFeatureProvider {
                return WifiPanel.create(context);
            case Settings.Panel.ACTION_VOLUME:
                return VolumePanel.create(context);
            case ACTION_MEDIA_OUTPUT_GROUP:
                return MediaOutputGroupPanel.create(context, mediaPackageName);
        }

        throw new IllegalStateException("No matching panel for: "  + panelType);
+160 −2
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.text.TextUtils;

import androidx.slice.Slice;
import androidx.slice.SliceMetadata;
@@ -67,7 +68,9 @@ import java.util.List;
public class MediaOutputSliceTest {

    private static final String TEST_DEVICE_1_ID = "test_device_1_id";
    private static final String TEST_DEVICE_2_ID = "test_device_2_id";
    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 int TEST_DEVICE_1_ICON =
            com.android.internal.R.drawable.ic_bt_headphones_a2dp;

@@ -98,7 +101,8 @@ public class MediaOutputSliceTest {
        mShadowBluetoothAdapter.setEnabled(true);

        mMediaOutputSlice = new MediaOutputSlice(mContext);
        mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, MEDIA_OUTPUT_SLICE_URI);
        mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext,
                MEDIA_OUTPUT_SLICE_URI);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);
        mMediaDeviceUpdateWorker.mLocalMediaManager = mLocalMediaManager;
        mMediaOutputSlice.init(mMediaDeviceUpdateWorker);
@@ -147,6 +151,19 @@ public class MediaOutputSliceTest {
        when(device.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(device.getIcon()).thenReturn(mTestDrawable);
        when(device.getMaxVolume()).thenReturn(100);
        when(device.isConnected()).thenReturn(true);
        when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device.getId()).thenReturn(TEST_DEVICE_1_ID);
        final MediaDevice device2 = mock(MediaDevice.class);
        when(device2.getName()).thenReturn(TEST_DEVICE_2_NAME);
        when(device2.getIcon()).thenReturn(mTestDrawable);
        when(device2.getMaxVolume()).thenReturn(100);
        when(device2.isConnected()).thenReturn(false);
        when(device2.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device2.getId()).thenReturn(TEST_DEVICE_2_ID);
        mDevices.add(device);
        mDevices.add(device2);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);
        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device);

        final Slice mediaSlice = mMediaOutputSlice.getSlice();
@@ -165,8 +182,16 @@ public class MediaOutputSliceTest {
        when(device.getMaxVolume()).thenReturn(100);
        when(device.isConnected()).thenReturn(false);
        when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);

        when(device.getId()).thenReturn(TEST_DEVICE_1_ID);
        final MediaDevice device2 = mock(MediaDevice.class);
        when(device2.getName()).thenReturn(TEST_DEVICE_2_NAME);
        when(device2.getIcon()).thenReturn(mTestDrawable);
        when(device2.getMaxVolume()).thenReturn(100);
        when(device2.isConnected()).thenReturn(false);
        when(device2.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
        when(device2.getId()).thenReturn(TEST_DEVICE_2_ID);
        mDevices.add(device);
        mDevices.add(device2);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);

        final Slice mediaSlice = mMediaOutputSlice.getSlice();
@@ -177,6 +202,139 @@ public class MediaOutputSliceTest {
                R.string.media_output_disconnected_status, TEST_DEVICE_1_NAME));
    }

    @Test
    public void getSlice_inGroupState_checkSliceSize() {
        final List<MediaDevice> mSelectedDevices = new ArrayList<>();
        final List<MediaDevice> mSelectableDevices = new ArrayList<>();
        mDevices.clear();
        final MediaDevice device = mock(MediaDevice.class);
        when(device.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(device.getIcon()).thenReturn(mTestDrawable);
        when(device.getMaxVolume()).thenReturn(100);
        when(device.isConnected()).thenReturn(true);
        when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device.getId()).thenReturn(TEST_DEVICE_1_ID);
        final MediaDevice device2 = mock(MediaDevice.class);
        when(device2.getName()).thenReturn(TEST_DEVICE_2_NAME);
        when(device2.getIcon()).thenReturn(mTestDrawable);
        when(device2.getMaxVolume()).thenReturn(100);
        when(device2.isConnected()).thenReturn(true);
        when(device2.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device2.getId()).thenReturn(TEST_DEVICE_2_ID);
        mSelectedDevices.add(device);
        mSelectedDevices.add(device2);
        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device);
        mDevices.add(device);
        mDevices.add(device2);
        when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mSelectedDevices);
        when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(mSelectableDevices);
        when(mMediaDeviceUpdateWorker.getSessionVolumeMax()).thenReturn(100);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);

        final Slice mediaSlice = mMediaOutputSlice.getSlice();

        assertThat(SliceQuery.findAll(mediaSlice, FORMAT_SLICE, HINT_LIST_ITEM, null).size())
                .isEqualTo(mDevices.size() + 1);
    }

    @Test
    public void getSlice_notInGroupState_checkSliceSize() {
        final List<MediaDevice> mSelectedDevices = new ArrayList<>();
        final List<MediaDevice> mSelectableDevices = new ArrayList<>();
        mDevices.clear();
        final MediaDevice device = mock(MediaDevice.class);
        when(device.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(device.getIcon()).thenReturn(mTestDrawable);
        when(device.getMaxVolume()).thenReturn(100);
        when(device.isConnected()).thenReturn(true);
        when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device.getId()).thenReturn(TEST_DEVICE_1_ID);
        final MediaDevice device2 = mock(MediaDevice.class);
        when(device2.getName()).thenReturn(TEST_DEVICE_2_NAME);
        when(device2.getIcon()).thenReturn(mTestDrawable);
        when(device2.getMaxVolume()).thenReturn(100);
        when(device2.isConnected()).thenReturn(true);
        when(device2.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device2.getId()).thenReturn(TEST_DEVICE_2_ID);
        mSelectedDevices.add(device);
        mSelectableDevices.add(device2);
        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device);
        mDevices.add(device);
        mDevices.add(device2);
        when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mSelectedDevices);
        when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(mSelectableDevices);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);

        final Slice mediaSlice = mMediaOutputSlice.getSlice();

        assertThat(SliceQuery.findAll(mediaSlice, FORMAT_SLICE, HINT_LIST_ITEM, null).size())
                .isEqualTo(mDevices.size());
    }

    @Test
    public void getSlice_singleCastDevice_notContainGroupIconText() {
        final List<MediaDevice> mSelectedDevices = new ArrayList<>();
        final List<MediaDevice> mSelectableDevices = new ArrayList<>();
        mDevices.clear();
        final MediaDevice device = mock(MediaDevice.class);
        when(device.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(device.getIcon()).thenReturn(mTestDrawable);
        when(device.getMaxVolume()).thenReturn(100);
        when(device.isConnected()).thenReturn(true);
        when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device.getId()).thenReturn(TEST_DEVICE_1_ID);
        when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mDevices);
        when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(null);
        mSelectedDevices.add(device);
        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device);
        mDevices.add(device);
        when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mSelectedDevices);
        when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(mSelectableDevices);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);

        final Slice mediaSlice = mMediaOutputSlice.getSlice();

        final String sliceInfo = SliceQuery.findAll(mediaSlice, FORMAT_SLICE, HINT_LIST_ITEM,
                null).toString();

        assertThat(TextUtils.indexOf(sliceInfo, mContext.getText(R.string.add))).isEqualTo(-1);
    }

    @Test
    public void getSlice_multipleCastDevices_containGroupIconText() {
        final List<MediaDevice> mSelectedDevices = new ArrayList<>();
        final List<MediaDevice> mSelectableDevices = new ArrayList<>();
        mDevices.clear();
        final MediaDevice device = mock(MediaDevice.class);
        when(device.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(device.getIcon()).thenReturn(mTestDrawable);
        when(device.getMaxVolume()).thenReturn(100);
        when(device.isConnected()).thenReturn(true);
        when(device.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device.getId()).thenReturn(TEST_DEVICE_1_ID);
        final MediaDevice device2 = mock(MediaDevice.class);
        when(device2.getName()).thenReturn(TEST_DEVICE_2_NAME);
        when(device2.getIcon()).thenReturn(mTestDrawable);
        when(device2.getMaxVolume()).thenReturn(100);
        when(device2.isConnected()).thenReturn(true);
        when(device2.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
        when(device2.getId()).thenReturn(TEST_DEVICE_2_ID);
        mSelectedDevices.add(device);
        mSelectableDevices.add(device2);
        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device);
        mDevices.add(device);
        mDevices.add(device2);
        when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mSelectedDevices);
        when(mLocalMediaManager.getSelectableMediaDevice()).thenReturn(mSelectableDevices);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);

        final Slice mediaSlice = mMediaOutputSlice.getSlice();
        String sliceInfo = SliceQuery.findAll(mediaSlice, FORMAT_SLICE, HINT_LIST_ITEM,
                null).toString();

        assertThat(TextUtils.indexOf(sliceInfo, mContext.getText(R.string.add))).isNotEqualTo(-1);
    }

    @Test
    public void onNotifyChange_foundMediaDevice_connect() {
        mDevices.clear();