Loading src/com/android/settings/media/MediaDeviceUpdateWorker.java +11 −0 Original line number Original line Diff line number Diff line Loading @@ -146,6 +146,17 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker return mTopDevice; return mTopDevice; } } /** * Find the active MediaDevice. * * @param type the media device type. * @return MediaDevice list * */ public List<MediaDevice> getActiveMediaDevice(@MediaDevice.MediaDeviceType int type) { return mLocalMediaManager.getActiveMediaDevice(type); } /** /** * Request to set volume. * Request to set volume. * * Loading src/com/android/settings/media/RemoteMediaSlice.java 0 → 100644 +182 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.media; import static android.app.slice.Slice.EXTRA_RANGE_VALUE; import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; import androidx.slice.builders.ListBuilder.InputRangeBuilder; import androidx.slice.builders.SliceAction; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.notification.SoundSettings; import com.android.settings.slices.CustomSliceable; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBroadcastReceiver; import com.android.settings.slices.SliceBuilderUtils; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaOutputSliceConstants; import java.util.List; /** * Display the Remote Media device information. */ public class RemoteMediaSlice implements CustomSliceable { private static final String TAG = "RemoteMediaSlice"; private static final String MEDIA_ID = "media_id"; private final Context mContext; private MediaDeviceUpdateWorker mWorker; public RemoteMediaSlice(Context context) { mContext = context; } @Override public void onNotifyChange(Intent intent) { final int newPosition = intent.getIntExtra(EXTRA_RANGE_VALUE, -1); final String id = intent.getStringExtra(MEDIA_ID); if (!TextUtils.isEmpty(id)) { getWorker().adjustVolume(getWorker().getMediaDeviceById(id), newPosition); } } @Override public Slice getSlice() { final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY) .setAccentColor(COLOR_NOT_TINTED); if (getWorker() == null) { Log.e(TAG, "Unable to get the slice worker."); return listBuilder.build(); } // Only displaying remote devices final List<MediaDevice> mediaDevices = getWorker().getActiveMediaDevice( MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); if (mediaDevices.isEmpty()) { Log.d(TAG, "No active remote media device"); return listBuilder.build(); } final CharSequence castVolume = mContext.getText(R.string.remote_media_volume_option_title); final CharSequence outputTitle = mContext.getText(R.string.media_output_title); final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_volume_remote); // To create an empty icon to indent the row final IconCompat emptyIcon = createEmptyIcon(); int requestCode = 0; for (MediaDevice mediaDevice : mediaDevices) { final int maxVolume = mediaDevice.getMaxVolume(); if (maxVolume <= 0) { Log.d(TAG, "Unable to add Slice. " + mediaDevice.getName() + ": max volume is " + maxVolume); continue; } final String title = castVolume + " (" + mediaDevice.getClientAppLabel() + ")"; listBuilder.addInputRange(new InputRangeBuilder() .setTitleItem(icon, ListBuilder.ICON_IMAGE) .setTitle(title) .setInputAction(getSliderInputAction(requestCode++, mediaDevice.getId())) .setPrimaryAction(getSoundSettingAction(title, icon, mediaDevice.getId())) .setMax(maxVolume) .setValue(mediaDevice.getCurrentVolume())); listBuilder.addRow(new ListBuilder.RowBuilder() .setTitle(outputTitle) .setSubtitle(mediaDevice.getName()) .setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE) .setPrimaryAction(getMediaOutputSliceAction())); } return listBuilder.build(); } private IconCompat createEmptyIcon() { final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); return IconCompat.createWithBitmap(bitmap); } private PendingIntent getSliderInputAction(int requestCode, String id) { final Intent intent = new Intent(getUri().toString()) .setData(getUri()) .putExtra(MEDIA_ID, id) .setClass(mContext, SliceBroadcastReceiver.class); return PendingIntent.getBroadcast(mContext, requestCode, intent, 0); } private SliceAction getSoundSettingAction(String actionTitle, IconCompat icon, String id) { final Uri contentUri = new Uri.Builder().appendPath(id).build(); final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, SoundSettings.class.getName(), id, mContext.getText(R.string.sound_settings).toString(), 0); intent.setClassName(mContext.getPackageName(), SubSettings.class.getName()); intent.setData(contentUri); final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); final SliceAction primarySliceAction = SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.ICON_IMAGE, actionTitle); return primarySliceAction; } private SliceAction getMediaOutputSliceAction() { final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_volume_remote); final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); final SliceAction primarySliceAction = SliceAction.createDeeplink( primaryActionIntent, icon, ListBuilder.ICON_IMAGE, mContext.getText(R.string.media_output_title)); return primarySliceAction; } @Override public Uri getUri() { return REMOTE_MEDIA_SLICE_URI; } @Override public Intent getIntent() { return null; } @Override public Class getBackgroundWorkerClass() { return MediaDeviceUpdateWorker.class; } private MediaDeviceUpdateWorker getWorker() { if (mWorker == null) { mWorker = SliceBackgroundWorker.getInstance(getUri()); } return mWorker; } } src/com/android/settings/panel/VolumePanel.java +3 −5 Original line number Original line Diff line number Diff line Loading @@ -17,10 +17,10 @@ package com.android.settings.panel; package com.android.settings.panel; import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_REMOTE_MEDIA_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI; import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums; Loading @@ -30,7 +30,6 @@ import android.net.Uri; import android.provider.Settings; import android.provider.Settings; import com.android.settings.R; import com.android.settings.R; import com.android.settings.notification.RemoteVolumePreferenceController; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; Loading @@ -55,9 +54,8 @@ public class VolumePanel implements PanelContent { @Override @Override public List<Uri> getSlices() { public List<Uri> getSlices() { final List<Uri> uris = new ArrayList<>(); final List<Uri> uris = new ArrayList<>(); if (RemoteVolumePreferenceController.getActiveRemoteToken(mContext) != null) { uris.add(VOLUME_REMOTE_MEDIA_URI); uris.add(REMOTE_MEDIA_SLICE_URI); } uris.add(VOLUME_MEDIA_URI); uris.add(VOLUME_MEDIA_URI); uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI); uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI); uris.add(VOLUME_CALL_URI); uris.add(VOLUME_CALL_URI); Loading src/com/android/settings/slices/CustomSliceRegistry.java +12 −10 Original line number Original line Diff line number Diff line Loading @@ -41,6 +41,7 @@ import com.android.settings.homepage.contextualcards.slices.NotificationChannelS import com.android.settings.location.LocationSlice; import com.android.settings.location.LocationSlice; import com.android.settings.media.MediaOutputIndicatorSlice; import com.android.settings.media.MediaOutputIndicatorSlice; import com.android.settings.media.MediaOutputSlice; import com.android.settings.media.MediaOutputSlice; import com.android.settings.media.RemoteMediaSlice; import com.android.settings.network.telephony.MobileDataSlice; import com.android.settings.network.telephony.MobileDataSlice; import com.android.settings.notification.zen.ZenModeButtonPreferenceController; import com.android.settings.notification.zen.ZenModeButtonPreferenceController; import com.android.settings.wifi.calling.WifiCallingSliceHelper; import com.android.settings.wifi.calling.WifiCallingSliceHelper; Loading Loading @@ -224,16 +225,6 @@ public class CustomSliceRegistry { .appendPath("media_volume") .appendPath("media_volume") .build(); .build(); /** * Full {@link Uri} for the Remote Media Volume Slice. */ public static final Uri VOLUME_REMOTE_MEDIA_URI = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath("remote_volume") .build(); /** /** * Full {@link Uri} for the Ringer volume Slice. * Full {@link Uri} for the Ringer volume Slice. */ */ Loading Loading @@ -312,6 +303,16 @@ public class CustomSliceRegistry { .appendPath("dark_theme") .appendPath("dark_theme") .build(); .build(); /** * Backing Uri for the Remote Media Slice. */ public static Uri REMOTE_MEDIA_SLICE_URI = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath(MediaOutputSliceConstants.KEY_REMOTE_MEDIA) .build(); @VisibleForTesting @VisibleForTesting static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice; static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice; Loading @@ -335,6 +336,7 @@ public class CustomSliceRegistry { sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class); sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class); sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class); sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class); sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class); sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class); sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class); } } public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) { public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) { Loading tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java 0 → 100644 +170 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.media; import static android.app.slice.Slice.EXTRA_RANGE_VALUE; import static android.app.slice.Slice.HINT_LIST_ITEM; import static android.app.slice.SliceItem.FORMAT_SLICE; import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; 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.content.Context; import android.content.Intent; import android.net.Uri; import androidx.slice.Slice; import androidx.slice.SliceMetadata; import androidx.slice.SliceProvider; import androidx.slice.core.SliceAction; import androidx.slice.core.SliceQuery; import androidx.slice.widget.SliceLiveData; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; 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 = RemoteMediaSliceTest.ShadowSliceBackgroundWorker.class) public class RemoteMediaSliceTest { private static final String MEDIA_ID = "media_id"; private static final String TEST_PACKAGE_LABEL = "music"; 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_VOLUME = 3; private static MediaDeviceUpdateWorker sMediaDeviceUpdateWorker; @Mock private LocalMediaManager mLocalMediaManager; @Mock private MediaDevice mDevice; private final List<MediaDevice> mDevices = new ArrayList<>(); private Context mContext; private RemoteMediaSlice mRemoteMediaSlice; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); mRemoteMediaSlice = new RemoteMediaSlice(mContext); sMediaDeviceUpdateWorker = spy(new MediaDeviceUpdateWorker(mContext, REMOTE_MEDIA_SLICE_URI)); sMediaDeviceUpdateWorker.mLocalMediaManager = mLocalMediaManager; when(sMediaDeviceUpdateWorker.getActiveMediaDevice( MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE)).thenReturn(mDevices); when(mDevice.getId()).thenReturn(TEST_DEVICE_1_ID); when(mDevice.getName()).thenReturn(TEST_DEVICE_1_NAME); when(mDevice.getMaxVolume()).thenReturn(100); when(mDevice.getCurrentVolume()).thenReturn(10); when(mDevice.getClientAppLabel()).thenReturn(TEST_PACKAGE_LABEL); } @Test public void onNotifyChange_noId_doNothing() { mDevices.add(mDevice); when(mLocalMediaManager.getMediaDeviceById(mDevices, TEST_DEVICE_1_ID)).thenReturn(mDevice); sMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices); final Intent intent = new Intent(); intent.putExtra(EXTRA_RANGE_VALUE, TEST_VOLUME); mRemoteMediaSlice.onNotifyChange(intent); verify(mDevice, never()).requestSetVolume(anyInt()); } @Test public void onNotifyChange_verifyAdjustVolume() { mDevices.add(mDevice); when(mLocalMediaManager.getMediaDeviceById(mDevices, TEST_DEVICE_1_ID)).thenReturn(mDevice); sMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices); final Intent intent = new Intent(); intent.putExtra(MEDIA_ID, TEST_DEVICE_1_ID); intent.putExtra(EXTRA_RANGE_VALUE, TEST_VOLUME); mRemoteMediaSlice.onNotifyChange(intent); verify(mDevice).requestSetVolume(TEST_VOLUME); } @Test public void getSlice_noActiveDevice_checkRowNumber() { final Slice slice = mRemoteMediaSlice.getSlice(); final int rows = SliceQuery.findAll(slice, FORMAT_SLICE, HINT_LIST_ITEM, null).size(); assertThat(rows).isEqualTo(0); } @Test public void getSlice_withActiveDevice_checkRowNumber() { mDevices.add(mDevice); final Slice slice = mRemoteMediaSlice.getSlice(); final int rows = SliceQuery.findAll(slice, FORMAT_SLICE, HINT_LIST_ITEM, null).size(); // InputRange and Row assertThat(rows).isEqualTo(2); } @Test public void getSlice_withActiveDevice_checkTitle() { mDevices.add(mDevice); final Slice slice = mRemoteMediaSlice.getSlice(); final SliceMetadata metadata = SliceMetadata.from(mContext, slice); final SliceAction primaryAction = metadata.getPrimaryAction(); assertThat(primaryAction.getTitle().toString()).isEqualTo(mContext.getText( com.android.settings.R.string.remote_media_volume_option_title) + " (" + TEST_PACKAGE_LABEL + ")"); } @Implements(SliceBackgroundWorker.class) public static class ShadowSliceBackgroundWorker { @Implementation public static SliceBackgroundWorker getInstance(Uri uri) { return sMediaDeviceUpdateWorker; } } } Loading
src/com/android/settings/media/MediaDeviceUpdateWorker.java +11 −0 Original line number Original line Diff line number Diff line Loading @@ -146,6 +146,17 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker return mTopDevice; return mTopDevice; } } /** * Find the active MediaDevice. * * @param type the media device type. * @return MediaDevice list * */ public List<MediaDevice> getActiveMediaDevice(@MediaDevice.MediaDeviceType int type) { return mLocalMediaManager.getActiveMediaDevice(type); } /** /** * Request to set volume. * Request to set volume. * * Loading
src/com/android/settings/media/RemoteMediaSlice.java 0 → 100644 +182 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.media; import static android.app.slice.Slice.EXTRA_RANGE_VALUE; import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; import androidx.slice.builders.ListBuilder.InputRangeBuilder; import androidx.slice.builders.SliceAction; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.notification.SoundSettings; import com.android.settings.slices.CustomSliceable; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBroadcastReceiver; import com.android.settings.slices.SliceBuilderUtils; import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaOutputSliceConstants; import java.util.List; /** * Display the Remote Media device information. */ public class RemoteMediaSlice implements CustomSliceable { private static final String TAG = "RemoteMediaSlice"; private static final String MEDIA_ID = "media_id"; private final Context mContext; private MediaDeviceUpdateWorker mWorker; public RemoteMediaSlice(Context context) { mContext = context; } @Override public void onNotifyChange(Intent intent) { final int newPosition = intent.getIntExtra(EXTRA_RANGE_VALUE, -1); final String id = intent.getStringExtra(MEDIA_ID); if (!TextUtils.isEmpty(id)) { getWorker().adjustVolume(getWorker().getMediaDeviceById(id), newPosition); } } @Override public Slice getSlice() { final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY) .setAccentColor(COLOR_NOT_TINTED); if (getWorker() == null) { Log.e(TAG, "Unable to get the slice worker."); return listBuilder.build(); } // Only displaying remote devices final List<MediaDevice> mediaDevices = getWorker().getActiveMediaDevice( MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE); if (mediaDevices.isEmpty()) { Log.d(TAG, "No active remote media device"); return listBuilder.build(); } final CharSequence castVolume = mContext.getText(R.string.remote_media_volume_option_title); final CharSequence outputTitle = mContext.getText(R.string.media_output_title); final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_volume_remote); // To create an empty icon to indent the row final IconCompat emptyIcon = createEmptyIcon(); int requestCode = 0; for (MediaDevice mediaDevice : mediaDevices) { final int maxVolume = mediaDevice.getMaxVolume(); if (maxVolume <= 0) { Log.d(TAG, "Unable to add Slice. " + mediaDevice.getName() + ": max volume is " + maxVolume); continue; } final String title = castVolume + " (" + mediaDevice.getClientAppLabel() + ")"; listBuilder.addInputRange(new InputRangeBuilder() .setTitleItem(icon, ListBuilder.ICON_IMAGE) .setTitle(title) .setInputAction(getSliderInputAction(requestCode++, mediaDevice.getId())) .setPrimaryAction(getSoundSettingAction(title, icon, mediaDevice.getId())) .setMax(maxVolume) .setValue(mediaDevice.getCurrentVolume())); listBuilder.addRow(new ListBuilder.RowBuilder() .setTitle(outputTitle) .setSubtitle(mediaDevice.getName()) .setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE) .setPrimaryAction(getMediaOutputSliceAction())); } return listBuilder.build(); } private IconCompat createEmptyIcon() { final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); return IconCompat.createWithBitmap(bitmap); } private PendingIntent getSliderInputAction(int requestCode, String id) { final Intent intent = new Intent(getUri().toString()) .setData(getUri()) .putExtra(MEDIA_ID, id) .setClass(mContext, SliceBroadcastReceiver.class); return PendingIntent.getBroadcast(mContext, requestCode, intent, 0); } private SliceAction getSoundSettingAction(String actionTitle, IconCompat icon, String id) { final Uri contentUri = new Uri.Builder().appendPath(id).build(); final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, SoundSettings.class.getName(), id, mContext.getText(R.string.sound_settings).toString(), 0); intent.setClassName(mContext.getPackageName(), SubSettings.class.getName()); intent.setData(contentUri); final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); final SliceAction primarySliceAction = SliceAction.createDeeplink(pendingIntent, icon, ListBuilder.ICON_IMAGE, actionTitle); return primarySliceAction; } private SliceAction getMediaOutputSliceAction() { final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_volume_remote); final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */); final SliceAction primarySliceAction = SliceAction.createDeeplink( primaryActionIntent, icon, ListBuilder.ICON_IMAGE, mContext.getText(R.string.media_output_title)); return primarySliceAction; } @Override public Uri getUri() { return REMOTE_MEDIA_SLICE_URI; } @Override public Intent getIntent() { return null; } @Override public Class getBackgroundWorkerClass() { return MediaDeviceUpdateWorker.class; } private MediaDeviceUpdateWorker getWorker() { if (mWorker == null) { mWorker = SliceBackgroundWorker.getInstance(getUri()); } return mWorker; } }
src/com/android/settings/panel/VolumePanel.java +3 −5 Original line number Original line Diff line number Diff line Loading @@ -17,10 +17,10 @@ package com.android.settings.panel; package com.android.settings.panel; import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_REMOTE_MEDIA_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI; import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI; import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums; Loading @@ -30,7 +30,6 @@ import android.net.Uri; import android.provider.Settings; import android.provider.Settings; import com.android.settings.R; import com.android.settings.R; import com.android.settings.notification.RemoteVolumePreferenceController; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; Loading @@ -55,9 +54,8 @@ public class VolumePanel implements PanelContent { @Override @Override public List<Uri> getSlices() { public List<Uri> getSlices() { final List<Uri> uris = new ArrayList<>(); final List<Uri> uris = new ArrayList<>(); if (RemoteVolumePreferenceController.getActiveRemoteToken(mContext) != null) { uris.add(VOLUME_REMOTE_MEDIA_URI); uris.add(REMOTE_MEDIA_SLICE_URI); } uris.add(VOLUME_MEDIA_URI); uris.add(VOLUME_MEDIA_URI); uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI); uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI); uris.add(VOLUME_CALL_URI); uris.add(VOLUME_CALL_URI); Loading
src/com/android/settings/slices/CustomSliceRegistry.java +12 −10 Original line number Original line Diff line number Diff line Loading @@ -41,6 +41,7 @@ import com.android.settings.homepage.contextualcards.slices.NotificationChannelS import com.android.settings.location.LocationSlice; import com.android.settings.location.LocationSlice; import com.android.settings.media.MediaOutputIndicatorSlice; import com.android.settings.media.MediaOutputIndicatorSlice; import com.android.settings.media.MediaOutputSlice; import com.android.settings.media.MediaOutputSlice; import com.android.settings.media.RemoteMediaSlice; import com.android.settings.network.telephony.MobileDataSlice; import com.android.settings.network.telephony.MobileDataSlice; import com.android.settings.notification.zen.ZenModeButtonPreferenceController; import com.android.settings.notification.zen.ZenModeButtonPreferenceController; import com.android.settings.wifi.calling.WifiCallingSliceHelper; import com.android.settings.wifi.calling.WifiCallingSliceHelper; Loading Loading @@ -224,16 +225,6 @@ public class CustomSliceRegistry { .appendPath("media_volume") .appendPath("media_volume") .build(); .build(); /** * Full {@link Uri} for the Remote Media Volume Slice. */ public static final Uri VOLUME_REMOTE_MEDIA_URI = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath("remote_volume") .build(); /** /** * Full {@link Uri} for the Ringer volume Slice. * Full {@link Uri} for the Ringer volume Slice. */ */ Loading Loading @@ -312,6 +303,16 @@ public class CustomSliceRegistry { .appendPath("dark_theme") .appendPath("dark_theme") .build(); .build(); /** * Backing Uri for the Remote Media Slice. */ public static Uri REMOTE_MEDIA_SLICE_URI = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) .authority(SettingsSliceProvider.SLICE_AUTHORITY) .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) .appendPath(MediaOutputSliceConstants.KEY_REMOTE_MEDIA) .build(); @VisibleForTesting @VisibleForTesting static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice; static final Map<Uri, Class<? extends CustomSliceable>> sUriToSlice; Loading @@ -335,6 +336,7 @@ public class CustomSliceRegistry { sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class); sUriToSlice.put(STORAGE_SLICE_URI, StorageSlice.class); sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class); sUriToSlice.put(WIFI_SLICE_URI, WifiSlice.class); sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class); sUriToSlice.put(DARK_THEME_SLICE_URI, DarkThemeSlice.class); sUriToSlice.put(REMOTE_MEDIA_SLICE_URI, RemoteMediaSlice.class); } } public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) { public static Class<? extends CustomSliceable> getSliceClassByUri(Uri uri) { Loading
tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java 0 → 100644 +170 −0 Original line number Original line Diff line number Diff line /* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.media; import static android.app.slice.Slice.EXTRA_RANGE_VALUE; import static android.app.slice.Slice.HINT_LIST_ITEM; import static android.app.slice.SliceItem.FORMAT_SLICE; import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; 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.content.Context; import android.content.Intent; import android.net.Uri; import androidx.slice.Slice; import androidx.slice.SliceMetadata; import androidx.slice.SliceProvider; import androidx.slice.core.SliceAction; import androidx.slice.core.SliceQuery; import androidx.slice.widget.SliceLiveData; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; 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 = RemoteMediaSliceTest.ShadowSliceBackgroundWorker.class) public class RemoteMediaSliceTest { private static final String MEDIA_ID = "media_id"; private static final String TEST_PACKAGE_LABEL = "music"; 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_VOLUME = 3; private static MediaDeviceUpdateWorker sMediaDeviceUpdateWorker; @Mock private LocalMediaManager mLocalMediaManager; @Mock private MediaDevice mDevice; private final List<MediaDevice> mDevices = new ArrayList<>(); private Context mContext; private RemoteMediaSlice mRemoteMediaSlice; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); mRemoteMediaSlice = new RemoteMediaSlice(mContext); sMediaDeviceUpdateWorker = spy(new MediaDeviceUpdateWorker(mContext, REMOTE_MEDIA_SLICE_URI)); sMediaDeviceUpdateWorker.mLocalMediaManager = mLocalMediaManager; when(sMediaDeviceUpdateWorker.getActiveMediaDevice( MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE)).thenReturn(mDevices); when(mDevice.getId()).thenReturn(TEST_DEVICE_1_ID); when(mDevice.getName()).thenReturn(TEST_DEVICE_1_NAME); when(mDevice.getMaxVolume()).thenReturn(100); when(mDevice.getCurrentVolume()).thenReturn(10); when(mDevice.getClientAppLabel()).thenReturn(TEST_PACKAGE_LABEL); } @Test public void onNotifyChange_noId_doNothing() { mDevices.add(mDevice); when(mLocalMediaManager.getMediaDeviceById(mDevices, TEST_DEVICE_1_ID)).thenReturn(mDevice); sMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices); final Intent intent = new Intent(); intent.putExtra(EXTRA_RANGE_VALUE, TEST_VOLUME); mRemoteMediaSlice.onNotifyChange(intent); verify(mDevice, never()).requestSetVolume(anyInt()); } @Test public void onNotifyChange_verifyAdjustVolume() { mDevices.add(mDevice); when(mLocalMediaManager.getMediaDeviceById(mDevices, TEST_DEVICE_1_ID)).thenReturn(mDevice); sMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices); final Intent intent = new Intent(); intent.putExtra(MEDIA_ID, TEST_DEVICE_1_ID); intent.putExtra(EXTRA_RANGE_VALUE, TEST_VOLUME); mRemoteMediaSlice.onNotifyChange(intent); verify(mDevice).requestSetVolume(TEST_VOLUME); } @Test public void getSlice_noActiveDevice_checkRowNumber() { final Slice slice = mRemoteMediaSlice.getSlice(); final int rows = SliceQuery.findAll(slice, FORMAT_SLICE, HINT_LIST_ITEM, null).size(); assertThat(rows).isEqualTo(0); } @Test public void getSlice_withActiveDevice_checkRowNumber() { mDevices.add(mDevice); final Slice slice = mRemoteMediaSlice.getSlice(); final int rows = SliceQuery.findAll(slice, FORMAT_SLICE, HINT_LIST_ITEM, null).size(); // InputRange and Row assertThat(rows).isEqualTo(2); } @Test public void getSlice_withActiveDevice_checkTitle() { mDevices.add(mDevice); final Slice slice = mRemoteMediaSlice.getSlice(); final SliceMetadata metadata = SliceMetadata.from(mContext, slice); final SliceAction primaryAction = metadata.getPrimaryAction(); assertThat(primaryAction.getTitle().toString()).isEqualTo(mContext.getText( com.android.settings.R.string.remote_media_volume_option_title) + " (" + TEST_PACKAGE_LABEL + ")"); } @Implements(SliceBackgroundWorker.class) public static class ShadowSliceBackgroundWorker { @Implementation public static SliceBackgroundWorker getInstance(Uri uri) { return sMediaDeviceUpdateWorker; } } }