Loading media/java/android/media/flags/media_better_together.aconfig +0 −10 Original line number Diff line number Diff line Loading @@ -270,16 +270,6 @@ flag { bug: "293743975" } flag { name: "fix_output_media_item_list_index_out_of_bounds_exception" namespace: "media_better_together" description: "Fixes a bug of causing IndexOutOfBoundsException when building media item list." bug: "398246089" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "avoid_binder_calls_during_render" namespace: "media_better_together" Loading packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +9 −155 Original line number Diff line number Diff line Loading @@ -68,7 +68,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import com.android.internal.annotations.GuardedBy; import com.android.media.flags.Flags; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.Utils; Loading @@ -89,7 +88,6 @@ import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaItem.MediaItemType; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; Loading @@ -108,14 +106,12 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; Loading Loading @@ -626,12 +622,13 @@ public class MediaSwitchingController Collections.sort(devices, Comparator.naturalOrder()); } } if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { // For the first time building list, to make sure the top device is the connected // device. boolean hasMutingExpectedDevice = avoidBinderCallsForMutingExpectedDevice() ? containsMutingExpectedDevice( devices) : hasMutingExpectedDevice(); avoidBinderCallsForMutingExpectedDevice() ? containsMutingExpectedDevice(devices) : hasMutingExpectedDevice(); boolean needToHandleMutingExpectedDevice = hasMutingExpectedDevice && !isCurrentConnectedDeviceRemote(); final MediaDevice connectedMediaDevice = Loading @@ -641,100 +638,6 @@ public class MediaSwitchingController getSelectedMediaDevice(), connectedMediaDevice, needToHandleMutingExpectedDevice); } else { List<MediaItem> updatedMediaItems = buildMediaItems( mOutputMediaItemListProxy.getOutputMediaItemList(), devices); mOutputMediaItemListProxy.clearAndAddAll(updatedMediaItems); } } } protected List<MediaItem> buildMediaItems( List<MediaItem> oldMediaItems, List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { // For the first time building list, to make sure the top device is the connected // device. boolean hasMutingExpectedDevice = avoidBinderCallsForMutingExpectedDevice() ? containsMutingExpectedDevice( devices) : hasMutingExpectedDevice(); boolean needToHandleMutingExpectedDevice = hasMutingExpectedDevice && !isCurrentConnectedDeviceRemote(); final MediaDevice connectedMediaDevice = needToHandleMutingExpectedDevice ? null : getCurrentConnectedMediaDevice(); if (oldMediaItems.isEmpty()) { if (connectedMediaDevice == null) { if (DEBUG) { Log.d(TAG, "No connected media device or muting expected device exist."); } return categorizeMediaItemsLocked( /* connectedMediaDevice */ null, devices, needToHandleMutingExpectedDevice); } else { // selected device exist return categorizeMediaItemsLocked( connectedMediaDevice, devices, /* needToHandleMutingExpectedDevice */ false); } } // To keep the same list order final List<MediaDevice> targetMediaDevices = new ArrayList<>(); final Map<Integer, MediaItem> dividerItems = new HashMap<>(); Map<String, MediaDevice> idToMediaDeviceMap = devices.stream() .collect(Collectors.toMap(MediaDevice::getId, Function.identity())); for (MediaItem originalMediaItem : oldMediaItems) { switch (originalMediaItem.getMediaItemType()) { case MediaItemType.TYPE_GROUP_DIVIDER -> { dividerItems.put( oldMediaItems.indexOf(originalMediaItem), originalMediaItem); } case MediaItemType.TYPE_DEVICE -> { String originalMediaItemId = originalMediaItem.getMediaDevice().orElseThrow().getId(); if (idToMediaDeviceMap.containsKey(originalMediaItemId)) { targetMediaDevices.add(idToMediaDeviceMap.get(originalMediaItemId)); } } case MediaItemType.TYPE_PAIR_NEW_DEVICE -> { // Do nothing. } } } if (targetMediaDevices.size() != devices.size()) { devices.removeAll(targetMediaDevices); targetMediaDevices.addAll(devices); } List<MediaItem> finalMediaItems = targetMediaDevices.stream() .map(MediaItem::createDeviceMediaItem) .collect(Collectors.toList()); boolean shouldAddFirstSeenSelectedDevice = Flags.enableOutputSwitcherDeviceGrouping(); if (shouldAddFirstSeenSelectedDevice) { finalMediaItems.clear(); Set<String> selectedDevicesIds = getSelectedMediaDevice().stream() .map(MediaDevice::getId) .collect(Collectors.toSet()); for (MediaDevice targetMediaDevice : targetMediaDevices) { if (shouldAddFirstSeenSelectedDevice && selectedDevicesIds.contains(targetMediaDevice.getId())) { finalMediaItems.add(MediaItem.createDeviceMediaItem( targetMediaDevice, /* isFirstDeviceInGroup */ true)); shouldAddFirstSeenSelectedDevice = false; } else { finalMediaItems.add(MediaItem.createDeviceMediaItem( targetMediaDevice, /* isFirstDeviceInGroup */ false)); } } } dividerItems.forEach(finalMediaItems::add); return finalMediaItems; } } Loading @@ -751,55 +654,6 @@ public class MediaSwitchingController } } /** * Initial categorization of current devices, will not be called for updates to the devices * list. */ @GuardedBy("mMediaDevicesLock") private List<MediaItem> categorizeMediaItemsLocked( MediaDevice connectedMediaDevice, List<MediaDevice> devices, boolean needToHandleMutingExpectedDevice) { List<MediaItem> finalMediaItems = new ArrayList<>(); Set<String> selectedDevicesIds = getSelectedMediaDevice().stream() .map(MediaDevice::getId) .collect(Collectors.toSet()); if (connectedMediaDevice != null) { selectedDevicesIds.add(connectedMediaDevice.getId()); } boolean groupSelectedDevices = Flags.enableOutputSwitcherDeviceGrouping(); int nextSelectedItemIndex = 0; boolean suggestedDeviceAdded = false; boolean displayGroupAdded = false; boolean selectedDeviceAdded = false; for (MediaDevice device : devices) { if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) { finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device)); nextSelectedItemIndex++; } else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains( device.getId())) { if (groupSelectedDevices) { finalMediaItems.add( nextSelectedItemIndex++, MediaItem.createDeviceMediaItem(device, !selectedDeviceAdded)); selectedDeviceAdded = true; } else { finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device)); } } else { if (device.isSuggestedDevice() && !suggestedDeviceAdded) { addSuggestedDeviceGroupDivider(finalMediaItems); suggestedDeviceAdded = true; } else if (!device.isSuggestedDevice() && !displayGroupAdded) { addSpeakersAndDisplaysGroupDivider(finalMediaItems); displayGroupAdded = true; } finalMediaItems.add(MediaItem.createDeviceMediaItem(device)); } } return finalMediaItems; } private void addSuggestedDeviceGroupDivider(List<MediaItem> mediaItems) { mediaItems.add( MediaItem.createGroupDividerMediaItem( Loading packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java +18 −34 Original line number Diff line number Diff line Loading @@ -57,18 +57,16 @@ public class OutputMediaItemListProxy { /** Returns the list of output media items. */ public List<MediaItem> getOutputMediaItemList() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { if (isEmpty() && !mOutputMediaItemList.isEmpty()) { // Ensures mOutputMediaItemList is empty when all individual media item lists are // empty, preventing unexpected state issues. // Ensures mOutputMediaItemList is empty when all individual media item lists are empty, // preventing unexpected state issues. mOutputMediaItemList.clear(); } else if (!isEmpty() && mOutputMediaItemList.isEmpty()) { // When any individual media item list is modified, the cached mOutputMediaItemList // is emptied. On the next request for the output media item list, a fresh list is // created and stored in the cache. // When any individual media item list is modified, the cached mOutputMediaItemList is // emptied. On the next request for the output media item list, a fresh list is created // and stored in the cache. mOutputMediaItemList.addAll(createOutputMediaItemList()); } } return mOutputMediaItemList; } Loading Loading @@ -180,41 +178,27 @@ public class OutputMediaItemListProxy { mOutputMediaItemList.clear(); } /** Updates the list of output media items with the given list. */ public void clearAndAddAll(List<MediaItem> updatedMediaItems) { mOutputMediaItemList.clear(); mOutputMediaItemList.addAll(updatedMediaItems); } /** Removes the media items with muting expected devices. */ public void removeMutingExpectedDevices() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { mSelectedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); mSuggestedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); mSpeakersAndDisplaysMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); } mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); } /** Clears the output media item list. */ public void clear() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { mSelectedMediaItems.clear(); mSuggestedMediaItems.clear(); mSpeakersAndDisplaysMediaItems.clear(); } mOutputMediaItemList.clear(); } /** Returns whether the output media item list is empty. */ public boolean isEmpty() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { return mSelectedMediaItems.isEmpty() && mSuggestedMediaItems.isEmpty() && mSpeakersAndDisplaysMediaItems.isEmpty(); } else { return mOutputMediaItemList.isEmpty(); } } private void buildMediaItems( Loading packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +0 −1 Original line number Diff line number Diff line Loading @@ -211,7 +211,6 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return FlagsParameterization.allCombinationsOf( Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION, Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING); } Loading packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java +99 −121 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
media/java/android/media/flags/media_better_together.aconfig +0 −10 Original line number Diff line number Diff line Loading @@ -270,16 +270,6 @@ flag { bug: "293743975" } flag { name: "fix_output_media_item_list_index_out_of_bounds_exception" namespace: "media_better_together" description: "Fixes a bug of causing IndexOutOfBoundsException when building media item list." bug: "398246089" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "avoid_binder_calls_during_render" namespace: "media_better_together" Loading
packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java +9 −155 Original line number Diff line number Diff line Loading @@ -68,7 +68,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import com.android.internal.annotations.GuardedBy; import com.android.media.flags.Flags; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.Utils; Loading @@ -89,7 +88,6 @@ import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.dialog.MediaItem.MediaItemType; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; import com.android.systemui.monet.ColorScheme; import com.android.systemui.plugins.ActivityStarter; Loading @@ -108,14 +106,12 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.function.Function; import java.util.stream.Collectors; import javax.inject.Inject; Loading Loading @@ -626,12 +622,13 @@ public class MediaSwitchingController Collections.sort(devices, Comparator.naturalOrder()); } } if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { // For the first time building list, to make sure the top device is the connected // device. boolean hasMutingExpectedDevice = avoidBinderCallsForMutingExpectedDevice() ? containsMutingExpectedDevice( devices) : hasMutingExpectedDevice(); avoidBinderCallsForMutingExpectedDevice() ? containsMutingExpectedDevice(devices) : hasMutingExpectedDevice(); boolean needToHandleMutingExpectedDevice = hasMutingExpectedDevice && !isCurrentConnectedDeviceRemote(); final MediaDevice connectedMediaDevice = Loading @@ -641,100 +638,6 @@ public class MediaSwitchingController getSelectedMediaDevice(), connectedMediaDevice, needToHandleMutingExpectedDevice); } else { List<MediaItem> updatedMediaItems = buildMediaItems( mOutputMediaItemListProxy.getOutputMediaItemList(), devices); mOutputMediaItemListProxy.clearAndAddAll(updatedMediaItems); } } } protected List<MediaItem> buildMediaItems( List<MediaItem> oldMediaItems, List<MediaDevice> devices) { synchronized (mMediaDevicesLock) { // For the first time building list, to make sure the top device is the connected // device. boolean hasMutingExpectedDevice = avoidBinderCallsForMutingExpectedDevice() ? containsMutingExpectedDevice( devices) : hasMutingExpectedDevice(); boolean needToHandleMutingExpectedDevice = hasMutingExpectedDevice && !isCurrentConnectedDeviceRemote(); final MediaDevice connectedMediaDevice = needToHandleMutingExpectedDevice ? null : getCurrentConnectedMediaDevice(); if (oldMediaItems.isEmpty()) { if (connectedMediaDevice == null) { if (DEBUG) { Log.d(TAG, "No connected media device or muting expected device exist."); } return categorizeMediaItemsLocked( /* connectedMediaDevice */ null, devices, needToHandleMutingExpectedDevice); } else { // selected device exist return categorizeMediaItemsLocked( connectedMediaDevice, devices, /* needToHandleMutingExpectedDevice */ false); } } // To keep the same list order final List<MediaDevice> targetMediaDevices = new ArrayList<>(); final Map<Integer, MediaItem> dividerItems = new HashMap<>(); Map<String, MediaDevice> idToMediaDeviceMap = devices.stream() .collect(Collectors.toMap(MediaDevice::getId, Function.identity())); for (MediaItem originalMediaItem : oldMediaItems) { switch (originalMediaItem.getMediaItemType()) { case MediaItemType.TYPE_GROUP_DIVIDER -> { dividerItems.put( oldMediaItems.indexOf(originalMediaItem), originalMediaItem); } case MediaItemType.TYPE_DEVICE -> { String originalMediaItemId = originalMediaItem.getMediaDevice().orElseThrow().getId(); if (idToMediaDeviceMap.containsKey(originalMediaItemId)) { targetMediaDevices.add(idToMediaDeviceMap.get(originalMediaItemId)); } } case MediaItemType.TYPE_PAIR_NEW_DEVICE -> { // Do nothing. } } } if (targetMediaDevices.size() != devices.size()) { devices.removeAll(targetMediaDevices); targetMediaDevices.addAll(devices); } List<MediaItem> finalMediaItems = targetMediaDevices.stream() .map(MediaItem::createDeviceMediaItem) .collect(Collectors.toList()); boolean shouldAddFirstSeenSelectedDevice = Flags.enableOutputSwitcherDeviceGrouping(); if (shouldAddFirstSeenSelectedDevice) { finalMediaItems.clear(); Set<String> selectedDevicesIds = getSelectedMediaDevice().stream() .map(MediaDevice::getId) .collect(Collectors.toSet()); for (MediaDevice targetMediaDevice : targetMediaDevices) { if (shouldAddFirstSeenSelectedDevice && selectedDevicesIds.contains(targetMediaDevice.getId())) { finalMediaItems.add(MediaItem.createDeviceMediaItem( targetMediaDevice, /* isFirstDeviceInGroup */ true)); shouldAddFirstSeenSelectedDevice = false; } else { finalMediaItems.add(MediaItem.createDeviceMediaItem( targetMediaDevice, /* isFirstDeviceInGroup */ false)); } } } dividerItems.forEach(finalMediaItems::add); return finalMediaItems; } } Loading @@ -751,55 +654,6 @@ public class MediaSwitchingController } } /** * Initial categorization of current devices, will not be called for updates to the devices * list. */ @GuardedBy("mMediaDevicesLock") private List<MediaItem> categorizeMediaItemsLocked( MediaDevice connectedMediaDevice, List<MediaDevice> devices, boolean needToHandleMutingExpectedDevice) { List<MediaItem> finalMediaItems = new ArrayList<>(); Set<String> selectedDevicesIds = getSelectedMediaDevice().stream() .map(MediaDevice::getId) .collect(Collectors.toSet()); if (connectedMediaDevice != null) { selectedDevicesIds.add(connectedMediaDevice.getId()); } boolean groupSelectedDevices = Flags.enableOutputSwitcherDeviceGrouping(); int nextSelectedItemIndex = 0; boolean suggestedDeviceAdded = false; boolean displayGroupAdded = false; boolean selectedDeviceAdded = false; for (MediaDevice device : devices) { if (needToHandleMutingExpectedDevice && device.isMutingExpectedDevice()) { finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device)); nextSelectedItemIndex++; } else if (!needToHandleMutingExpectedDevice && selectedDevicesIds.contains( device.getId())) { if (groupSelectedDevices) { finalMediaItems.add( nextSelectedItemIndex++, MediaItem.createDeviceMediaItem(device, !selectedDeviceAdded)); selectedDeviceAdded = true; } else { finalMediaItems.add(0, MediaItem.createDeviceMediaItem(device)); } } else { if (device.isSuggestedDevice() && !suggestedDeviceAdded) { addSuggestedDeviceGroupDivider(finalMediaItems); suggestedDeviceAdded = true; } else if (!device.isSuggestedDevice() && !displayGroupAdded) { addSpeakersAndDisplaysGroupDivider(finalMediaItems); displayGroupAdded = true; } finalMediaItems.add(MediaItem.createDeviceMediaItem(device)); } } return finalMediaItems; } private void addSuggestedDeviceGroupDivider(List<MediaItem> mediaItems) { mediaItems.add( MediaItem.createGroupDividerMediaItem( Loading
packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java +18 −34 Original line number Diff line number Diff line Loading @@ -57,18 +57,16 @@ public class OutputMediaItemListProxy { /** Returns the list of output media items. */ public List<MediaItem> getOutputMediaItemList() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { if (isEmpty() && !mOutputMediaItemList.isEmpty()) { // Ensures mOutputMediaItemList is empty when all individual media item lists are // empty, preventing unexpected state issues. // Ensures mOutputMediaItemList is empty when all individual media item lists are empty, // preventing unexpected state issues. mOutputMediaItemList.clear(); } else if (!isEmpty() && mOutputMediaItemList.isEmpty()) { // When any individual media item list is modified, the cached mOutputMediaItemList // is emptied. On the next request for the output media item list, a fresh list is // created and stored in the cache. // When any individual media item list is modified, the cached mOutputMediaItemList is // emptied. On the next request for the output media item list, a fresh list is created // and stored in the cache. mOutputMediaItemList.addAll(createOutputMediaItemList()); } } return mOutputMediaItemList; } Loading Loading @@ -180,41 +178,27 @@ public class OutputMediaItemListProxy { mOutputMediaItemList.clear(); } /** Updates the list of output media items with the given list. */ public void clearAndAddAll(List<MediaItem> updatedMediaItems) { mOutputMediaItemList.clear(); mOutputMediaItemList.addAll(updatedMediaItems); } /** Removes the media items with muting expected devices. */ public void removeMutingExpectedDevices() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { mSelectedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); mSuggestedMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); mSpeakersAndDisplaysMediaItems.removeIf((MediaItem::isMutingExpectedDevice)); } mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice)); } /** Clears the output media item list. */ public void clear() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { mSelectedMediaItems.clear(); mSuggestedMediaItems.clear(); mSpeakersAndDisplaysMediaItems.clear(); } mOutputMediaItemList.clear(); } /** Returns whether the output media item list is empty. */ public boolean isEmpty() { if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) { return mSelectedMediaItems.isEmpty() && mSuggestedMediaItems.isEmpty() && mSpeakersAndDisplaysMediaItems.isEmpty(); } else { return mOutputMediaItemList.isEmpty(); } } private void buildMediaItems( Loading
packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java +0 −1 Original line number Diff line number Diff line Loading @@ -211,7 +211,6 @@ public class MediaSwitchingControllerTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { return FlagsParameterization.allCombinationsOf( Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION, Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING); } Loading
packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java +99 −121 File changed.Preview size limit exceeded, changes collapsed. Show changes