From e13e09d1b4f7ae618175c8819b63d416b0cbe7f3 Mon Sep 17 00:00:00 2001 From: hughchen Date: Wed, 6 Mar 2019 16:55:21 +0800 Subject: [PATCH] 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 --- .../media/MediaDeviceUpdateWorker.java | 2 +- .../settings/media/MediaOutputSlice.java | 70 +++++++++---------- .../settings/panel/SettingsPanelActivity.java | 13 +--- .../settings/media/MediaOutputSliceTest.java | 53 ++++++-------- .../panel/SettingsPanelActivityTest.java | 7 +- 5 files changed, 60 insertions(+), 85 deletions(-) diff --git a/src/com/android/settings/media/MediaDeviceUpdateWorker.java b/src/com/android/settings/media/MediaDeviceUpdateWorker.java index 7416018f6cd..d1e55e413d0 100644 --- a/src/com/android/settings/media/MediaDeviceUpdateWorker.java +++ b/src/com/android/settings/media/MediaDeviceUpdateWorker.java @@ -70,7 +70,7 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker @Override public void close() { - + mLocalMediaManager = null; } @Override diff --git a/src/com/android/settings/media/MediaOutputSlice.java b/src/com/android/settings/media/MediaOutputSlice.java index d52b441f9e0..232986c45e0 100644 --- a/src/com/android/settings/media/MediaOutputSlice.java +++ b/src/com/android/settings/media/MediaOutputSlice.java @@ -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 devices = getMediaDevices(); - final CharSequence title = Utils.getApplicationLabel(mContext, mPackageName); - final CharSequence summary = - mContext.getString(R.string.media_output_panel_summary_of_playing_device, - getConnectedDeviceName()); - - final Drawable drawable = - Utils.getBadgedIcon(mIconDrawableFactory, pm, mPackageName, UserHandle.myUserId()); - final IconCompat icon = Utils.createIconWithDrawable(drawable); - @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext); - final SliceAction primarySliceAction = SliceAction.createDeeplink(getPrimaryAction(), icon, + + final MediaDevice connectedDevice = getWorker().getCurrentConnectedMediaDevice(); + final ListBuilder listBuilder = buildActiveDeviceHeader(color, connectedDevice); + + 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; } diff --git a/src/com/android/settings/panel/SettingsPanelActivity.java b/src/com/android/settings/panel/SettingsPanelActivity.java index 3819c80a64c..8aee38238d8 100644 --- a/src/com/android/settings/panel/SettingsPanelActivity.java +++ b/src/com/android/settings/panel/SettingsPanelActivity.java @@ -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); diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java index daaba90bfa6..da0d85b5472 100644 --- a/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java +++ b/tests/robotests/src/com/android/settings/media/MediaOutputSliceTest.java @@ -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 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 diff --git a/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java index a51b7b0c7ad..1d5c3c28cee 100644 --- a/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java +++ b/tests/robotests/src/com/android/settings/panel/SettingsPanelActivityTest.java @@ -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