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

Commit e13e09d1 authored by hughchen's avatar hughchen
Browse files

Update output switcher behavior and UI design

- Remove media stream stuff, use active device info as header.
- Show the sub title of media device.
- Add new requirement, when user click disconnected bluetooth device,
  will auto connceted it.
- When bluetooth state is off, do not showing content of slice.

Bug: 127201385
Test: make -j42 RunSettingsRoboTests
Change-Id: Ie1880ba3ef9910a42caf0cca2b6502e54787f5a0
parent cf6757a7
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -70,7 +70,7 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker

    @Override
    public void close() {

        mLocalMediaManager = null;
    }

    @Override
+31 −37
Original line number Diff line number Diff line
@@ -20,13 +20,11 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE

import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.UserHandle;
import android.util.IconDrawableFactory;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.VisibleForTesting;
@@ -58,37 +56,48 @@ public class MediaOutputSlice implements CustomSliceable {

    private MediaDeviceUpdateWorker mWorker;
    private String mPackageName;
    private IconDrawableFactory mIconDrawableFactory;

    public MediaOutputSlice(Context context) {
        mContext = context;
        mPackageName = getUri().getQueryParameter(MEDIA_PACKAGE_NAME);
        mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
    }

    @VisibleForTesting
    void init(String packageName, MediaDeviceUpdateWorker worker, IconDrawableFactory factory) {
    void init(String packageName, MediaDeviceUpdateWorker worker) {
        mPackageName = packageName;
        mWorker = worker;
        mIconDrawableFactory = factory;
    }

    @Override
    public Slice getSlice() {
        final PackageManager pm = mContext.getPackageManager();
        final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (!adapter.isEnabled()) {
            Log.d(TAG, "getSlice() Bluetooth is off");
            return null;
        }

        final List<MediaDevice> devices = getMediaDevices();
        final CharSequence title = Utils.getApplicationLabel(mContext, mPackageName);
        final CharSequence summary =
                mContext.getString(R.string.media_output_panel_summary_of_playing_device,
                        getConnectedDeviceName());
        @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);

        final Drawable drawable =
                Utils.getBadgedIcon(mIconDrawableFactory, pm, mPackageName, UserHandle.myUserId());
        final IconCompat icon = Utils.createIconWithDrawable(drawable);
        final MediaDevice connectedDevice = getWorker().getCurrentConnectedMediaDevice();
        final ListBuilder listBuilder = buildActiveDeviceHeader(color, connectedDevice);

        @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
        final SliceAction primarySliceAction = SliceAction.createDeeplink(getPrimaryAction(), icon,
        for (MediaDevice device : devices) {
            if (!TextUtils.equals(connectedDevice.getId(), device.getId())) {
                listBuilder.addRow(getMediaDeviceRow(device));
            }
        }

        return listBuilder.build();
    }

    private ListBuilder buildActiveDeviceHeader(@ColorInt int color, MediaDevice device) {
        final String title = device.getName();
        final IconCompat icon = IconCompat.createWithResource(mContext, device.getIcon());

        final PendingIntent broadcastAction =
                getBroadcastIntent(mContext, device.getId(), device.hashCode());
        final SliceAction primarySliceAction = SliceAction.createDeeplink(broadcastAction, icon,
                ListBuilder.ICON_IMAGE, title);

        final ListBuilder listBuilder = new ListBuilder(mContext, MEDIA_OUTPUT_SLICE_URI,
@@ -97,14 +106,10 @@ public class MediaOutputSlice implements CustomSliceable {
                .addRow(new ListBuilder.RowBuilder()
                        .setTitleItem(icon, ListBuilder.ICON_IMAGE)
                        .setTitle(title)
                        .setSubtitle(summary)
                        .setSubtitle(device.getSummary())
                        .setPrimaryAction(primarySliceAction));

        for (MediaDevice device : devices) {
            listBuilder.addRow(getMediaDeviceRow(device));
        }

        return listBuilder.build();
        return listBuilder;
    }

    private MediaDeviceUpdateWorker getWorker() {
@@ -120,18 +125,6 @@ public class MediaOutputSlice implements CustomSliceable {
        return devices;
    }

    private String getConnectedDeviceName() {
        final MediaDevice device = getWorker().getCurrentConnectedMediaDevice();
        return device != null ? device.getName() : "";
    }

    private PendingIntent getPrimaryAction() {
        final PackageManager pm = mContext.getPackageManager();
        final Intent launchIntent = pm.getLaunchIntentForPackage(mPackageName);
        final Intent intent = launchIntent;
        return PendingIntent.getActivity(mContext, 0  /* requestCode */, intent, 0  /* flags */);
    }

    private ListBuilder.RowBuilder getMediaDeviceRow(MediaDevice device) {
        final String title = device.getName();
        final PendingIntent broadcastAction =
@@ -141,7 +134,8 @@ public class MediaOutputSlice implements CustomSliceable {
                .setTitleItem(deviceIcon, ListBuilder.ICON_IMAGE)
                .setPrimaryAction(SliceAction.create(broadcastAction, deviceIcon,
                        ListBuilder.ICON_IMAGE, title))
                .setTitle(title);
                .setTitle(title)
                .setSubtitle(device.getSummary());

        return rowBuilder;
    }
+2 −11
Original line number Diff line number Diff line
@@ -16,13 +16,11 @@

package com.android.settings.panel;

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

import android.app.settings.SettingsEnums;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -75,15 +73,8 @@ public class SettingsPanelActivity extends FragmentActivity {
            return;
        }

        final String mediaPackageName =
                callingIntent.getStringExtra(EXTRA_PACKAGE_NAME);

        if (TextUtils.equals(ACTION_MEDIA_OUTPUT, callingIntent.getAction())
                && TextUtils.isEmpty(mediaPackageName)) {
            Log.e(TAG, "Missing EXTRA_PACKAGE_NAME, closing Panel Activity");
            finish();
            return;
        }
        // We will use it once media output switch panel support remote device.
        final String mediaPackageName = callingIntent.getStringExtra(EXTRA_PACKAGE_NAME);

        setContentView(R.layout.settings_panel);

+21 −32
Original line number Diff line number Diff line
@@ -21,21 +21,15 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE

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

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.IconDrawableFactory;

import androidx.slice.Slice;
import androidx.slice.SliceMetadata;
@@ -43,6 +37,7 @@ import androidx.slice.SliceProvider;
import androidx.slice.core.SliceAction;
import androidx.slice.widget.SliceLiveData;

import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;

@@ -53,70 +48,64 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;

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

@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothAdapter.class})
public class MediaOutputSliceTest {

    private static final String TEST_PACKAGE_NAME = "com.fake.android.music";
    private static final String TEST_LABEL = "Test app";
    private static final String TEST_DEVICE_1_ID = "test_device_1_id";
    private static final String TEST_DEVICE_1_NAME = "test_device_1_name";
    private static final int TEST_DEVICE_1_ICON =
            com.android.internal.R.drawable.ic_bt_headphones_a2dp;

    @Mock
    private PackageManager mPackageManager;
    @Mock
    private ApplicationInfo mApplicationInfo;
    @Mock
    private ApplicationInfo mApplicationInfo2;
    @Mock
    private LocalMediaManager mLocalMediaManager;
    @Mock
    private IconDrawableFactory mIconDrawableFactory;
    @Mock
    private Drawable mTestDrawable;

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

    private Context mContext;
    private MediaOutputSlice mMediaOutputSlice;
    private MediaDeviceUpdateWorker mMediaDeviceUpdateWorker;
    private ShadowBluetoothAdapter mShadowBluetoothAdapter;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        mContext = spy(RuntimeEnvironment.application);

        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt()))
                .thenReturn(mApplicationInfo);
        when(mPackageManager.getApplicationInfoAsUser(eq(TEST_PACKAGE_NAME), anyInt(), anyInt()))
                .thenReturn(mApplicationInfo2);
        when(mApplicationInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL);
        when(mIconDrawableFactory.getBadgedIcon(mApplicationInfo2, UserHandle.myUserId()))
                .thenReturn(mTestDrawable);
        when(mTestDrawable.getIntrinsicWidth()).thenReturn(100);
        when(mTestDrawable.getIntrinsicHeight()).thenReturn(100);

        // Set-up specs for SliceMetadata.
        SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS);
        // Setup BluetoothAdapter
        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
        mShadowBluetoothAdapter.setEnabled(true);

        mMediaOutputSlice = new MediaOutputSlice(mContext);
        mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, MEDIA_OUTPUT_SLICE_URI);
        mMediaDeviceUpdateWorker.setPackageName(TEST_PACKAGE_NAME);
        mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);
        mMediaDeviceUpdateWorker.mLocalMediaManager = mLocalMediaManager;
        mMediaOutputSlice.init(TEST_PACKAGE_NAME, mMediaDeviceUpdateWorker, mIconDrawableFactory);
        mMediaOutputSlice.init(TEST_PACKAGE_NAME, mMediaDeviceUpdateWorker);
    }

    @Test
    public void getSlice_shouldHaveAppTitle() {
    public void getSlice_shouldHaveActiveDeviceName() {
        mDevices.clear();
        final MediaDevice device = mock(MediaDevice.class);
        when(device.getName()).thenReturn(TEST_DEVICE_1_NAME);
        when(device.getIcon()).thenReturn(TEST_DEVICE_1_ICON);
        when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(device);

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

        final SliceAction primaryAction = metadata.getPrimaryAction();
        assertThat(primaryAction.getTitle().toString()).isEqualTo(TEST_LABEL);
        assertThat(primaryAction.getTitle().toString()).isEqualTo(TEST_DEVICE_1_NAME);
    }

    @Test
+4 −3
Original line number Diff line number Diff line
@@ -76,15 +76,16 @@ public class SettingsPanelActivityTest {
    }

    @Test
    public void startMediaOutputSlice_withoutPackageName_bundleShouldNotHaveValue() {
    public void startMediaOutputSlice_withoutPackageName_bundleShouldHaveValue() {
        final Intent intent = new Intent()
                .setAction("com.android.settings.panel.action.MEDIA_OUTPUT");

        final SettingsPanelActivity activity =
                Robolectric.buildActivity(SettingsPanelActivity.class, intent).create().get();

        assertThat(activity.mBundle.containsKey(KEY_MEDIA_PACKAGE_NAME)).isFalse();
        assertThat(activity.mBundle.containsKey(KEY_PANEL_TYPE_ARGUMENT)).isFalse();
        assertThat(activity.mBundle.containsKey(KEY_MEDIA_PACKAGE_NAME)).isTrue();
        assertThat(activity.mBundle.getString(KEY_PANEL_TYPE_ARGUMENT))
                .isEqualTo("com.android.settings.panel.action.MEDIA_OUTPUT");
    }

    @Test