Merge changes I669f379d,Idd5fb950

* changes:
  Fix ConcurrentModificationException
  Update output switch UI
This commit is contained in:
TreeHugger Robot
2020-01-15 06:26:17 +00:00
committed by Android (Google) Code Review
4 changed files with 87 additions and 22 deletions

View File

@@ -31,12 +31,15 @@ import android.text.TextUtils;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settingslib.Utils;
import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.utils.ThreadUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/** /**
* SliceBackgroundWorker for get MediaDevice list and handle MediaDevice state change event. * SliceBackgroundWorker for get MediaDevice list and handle MediaDevice state change event.
@@ -45,10 +48,13 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker
implements LocalMediaManager.DeviceCallback { implements LocalMediaManager.DeviceCallback {
private final Context mContext; private final Context mContext;
private final List<MediaDevice> mMediaDevices = new ArrayList<>(); private final Collection<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
private final DevicesChangedBroadcastReceiver mReceiver; private final DevicesChangedBroadcastReceiver mReceiver;
private final String mPackageName; private final String mPackageName;
private boolean mIsTouched;
private MediaDevice mTopDevice;
@VisibleForTesting @VisibleForTesting
LocalMediaManager mLocalMediaManager; LocalMediaManager mLocalMediaManager;
@@ -62,6 +68,7 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker
@Override @Override
protected void onSlicePinned() { protected void onSlicePinned() {
mMediaDevices.clear(); mMediaDevices.clear();
mIsTouched = false;
if (mLocalMediaManager == null) { if (mLocalMediaManager == null) {
mLocalMediaManager = new LocalMediaManager(mContext, mPackageName, null); mLocalMediaManager = new LocalMediaManager(mContext, mPackageName, null);
} }
@@ -100,8 +107,13 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker
notifySliceChange(); notifySliceChange();
} }
public List<MediaDevice> getMediaDevices() { @Override
return new ArrayList<>(mMediaDevices); public void onDeviceAttributesChanged() {
notifySliceChange();
}
public Collection<MediaDevice> getMediaDevices() {
return mMediaDevices;
} }
public void connectDevice(MediaDevice device) { public void connectDevice(MediaDevice device) {
@@ -111,18 +123,35 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker
} }
public MediaDevice getMediaDeviceById(String id) { public MediaDevice getMediaDeviceById(String id) {
return mLocalMediaManager.getMediaDeviceById(mMediaDevices, id); return mLocalMediaManager.getMediaDeviceById(new ArrayList<>(mMediaDevices), id);
} }
public MediaDevice getCurrentConnectedMediaDevice() { public MediaDevice getCurrentConnectedMediaDevice() {
return mLocalMediaManager.getCurrentConnectedDevice(); return mLocalMediaManager.getCurrentConnectedDevice();
} }
void setIsTouched(boolean isTouched) {
mIsTouched = isTouched;
}
boolean getIsTouched() {
return mIsTouched;
}
void setTopDevice(MediaDevice device) {
mTopDevice = device;
}
MediaDevice getTopDevice() {
return mTopDevice;
}
private class DevicesChangedBroadcastReceiver extends BroadcastReceiver { private class DevicesChangedBroadcastReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
final String action = intent.getAction(); final String action = intent.getAction();
if (TextUtils.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION, action)) { if (TextUtils.equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION, action)
&& Utils.isAudioModeOngoingCall(mContext)) {
notifySliceChange(); notifySliceChange();
} }
} }

View File

@@ -40,7 +40,7 @@ import com.android.settings.slices.SliceBackgroundWorker;
import com.android.settings.slices.SliceBroadcastReceiver; import com.android.settings.slices.SliceBroadcastReceiver;
import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaDevice;
import java.util.List; import java.util.Collection;
/** /**
* Show the Media device that can be transfer the media. * Show the Media device that can be transfer the media.
@@ -61,7 +61,7 @@ public class MediaOutputSlice implements CustomSliceable {
} }
@VisibleForTesting @VisibleForTesting
void init(String packageName, MediaDeviceUpdateWorker worker) { void init(MediaDeviceUpdateWorker worker) {
mWorker = worker; mWorker = worker;
} }
@@ -78,16 +78,21 @@ public class MediaOutputSlice implements CustomSliceable {
return listBuilder.build(); return listBuilder.build();
} }
final List<MediaDevice> devices = getMediaDevices(); final Collection<MediaDevice> devices = getMediaDevices();
final MediaDeviceUpdateWorker worker = getWorker();
final MediaDevice connectedDevice = worker.getCurrentConnectedMediaDevice();
final boolean isTouched = worker.getIsTouched();
// Fix the last top device when user press device to transfer.
final MediaDevice topDevice = isTouched ? worker.getTopDevice() : connectedDevice;
final MediaDevice connectedDevice = getWorker().getCurrentConnectedMediaDevice(); if (topDevice != null) {
if (connectedDevice != null) { listBuilder.addRow(getActiveDeviceHeaderRow(topDevice));
listBuilder.addRow(getActiveDeviceHeaderRow(connectedDevice)); worker.setTopDevice(topDevice);
} }
for (MediaDevice device : devices) { for (MediaDevice device : devices) {
if (connectedDevice == null if (topDevice == null
|| !TextUtils.equals(connectedDevice.getId(), device.getId())) { || !TextUtils.equals(topDevice.getId(), device.getId())) {
listBuilder.addRow(getMediaDeviceRow(device)); listBuilder.addRow(getMediaDeviceRow(device));
} }
} }
@@ -131,8 +136,8 @@ public class MediaOutputSlice implements CustomSliceable {
return mWorker; return mWorker;
} }
private List<MediaDevice> getMediaDevices() { private Collection<MediaDevice> getMediaDevices() {
final List<MediaDevice> devices = getWorker().getMediaDevices(); final Collection<MediaDevice> devices = getWorker().getMediaDevices();
return devices; return devices;
} }
@@ -173,6 +178,7 @@ public class MediaOutputSlice implements CustomSliceable {
final MediaDevice device = worker.getMediaDeviceById(id); final MediaDevice device = worker.getMediaDeviceById(id);
if (device != null) { if (device != null) {
Log.d(TAG, "onNotifyChange() device name : " + device.getName()); Log.d(TAG, "onNotifyChange() device name : " + device.getName());
worker.setIsTouched(true);
worker.connectDevice(device); worker.connectDevice(device);
} }
} }

View File

@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -31,6 +32,7 @@ import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri; import android.net.Uri;
import com.android.settings.testutils.shadow.ShadowAudioManager;
import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice; import com.android.settingslib.media.MediaDevice;
@@ -40,12 +42,14 @@ import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication; import org.robolectric.shadows.ShadowApplication;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = ShadowAudioManager.class)
public class MediaDeviceUpdateWorkerTest { public class MediaDeviceUpdateWorkerTest {
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test"); private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
@@ -61,6 +65,7 @@ public class MediaDeviceUpdateWorkerTest {
private MediaDevice mMediaDevice1; private MediaDevice mMediaDevice1;
private MediaDevice mMediaDevice2; private MediaDevice mMediaDevice2;
private ShadowApplication mShadowApplication; private ShadowApplication mShadowApplication;
private AudioManager mAudioManager;
@Before @Before
public void setUp() { public void setUp() {
@@ -69,6 +74,7 @@ public class MediaDeviceUpdateWorkerTest {
mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, URI); mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, URI);
mResolver = mock(ContentResolver.class); mResolver = mock(ContentResolver.class);
mShadowApplication = ShadowApplication.getInstance(); mShadowApplication = ShadowApplication.getInstance();
mAudioManager = mContext.getSystemService(AudioManager.class);
mMediaDevice1 = mock(MediaDevice.class); mMediaDevice1 = mock(MediaDevice.class);
when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID); when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_1_ID);
@@ -94,6 +100,13 @@ public class MediaDeviceUpdateWorkerTest {
verify(mResolver).notifyChange(URI, null); verify(mResolver).notifyChange(URI, null);
} }
@Test
public void onDeviceAttributesChanged_shouldNotifyChange() {
mMediaDeviceUpdateWorker.onDeviceAttributesChanged();
verify(mResolver).notifyChange(URI, null);
}
@Test @Test
public void onDeviceListUpdate_sameDeviceList_shouldBeEqual() { public void onDeviceListUpdate_sameDeviceList_shouldBeEqual() {
mMediaDeviceUpdateWorker.onDeviceListUpdate(mMediaDevices); mMediaDeviceUpdateWorker.onDeviceListUpdate(mMediaDevices);
@@ -103,7 +116,8 @@ public class MediaDeviceUpdateWorkerTest {
newDevices.add(mMediaDevice2); newDevices.add(mMediaDevice2);
mMediaDeviceUpdateWorker.onDeviceListUpdate(newDevices); mMediaDeviceUpdateWorker.onDeviceListUpdate(newDevices);
final List<MediaDevice> devices = mMediaDeviceUpdateWorker.getMediaDevices(); final List<MediaDevice> devices =
new ArrayList<>(mMediaDeviceUpdateWorker.getMediaDevices());
assertThat(devices.get(0).getId()).isEqualTo(newDevices.get(0).getId()); assertThat(devices.get(0).getId()).isEqualTo(newDevices.get(0).getId());
assertThat(devices.get(1).getId()).isEqualTo(newDevices.get(1).getId()); assertThat(devices.get(1).getId()).isEqualTo(newDevices.get(1).getId());
@@ -121,7 +135,8 @@ public class MediaDeviceUpdateWorkerTest {
newDevices.add(device3); newDevices.add(device3);
mMediaDeviceUpdateWorker.onDeviceListUpdate(newDevices); mMediaDeviceUpdateWorker.onDeviceListUpdate(newDevices);
final List<MediaDevice> devices = mMediaDeviceUpdateWorker.getMediaDevices(); final List<MediaDevice> devices =
new ArrayList<>(mMediaDeviceUpdateWorker.getMediaDevices());
assertThat(devices.size()).isEqualTo(newDevices.size()); assertThat(devices.size()).isEqualTo(newDevices.size());
} }
@@ -134,14 +149,16 @@ public class MediaDeviceUpdateWorkerTest {
newDevices.add(mMediaDevice1); newDevices.add(mMediaDevice1);
mMediaDeviceUpdateWorker.onDeviceListUpdate(newDevices); mMediaDeviceUpdateWorker.onDeviceListUpdate(newDevices);
final List<MediaDevice> devices = mMediaDeviceUpdateWorker.getMediaDevices(); final List<MediaDevice> devices =
new ArrayList<>(mMediaDeviceUpdateWorker.getMediaDevices());
assertThat(devices.size()).isEqualTo(newDevices.size()); assertThat(devices.size()).isEqualTo(newDevices.size());
} }
@Test @Test
public void onReceive_shouldNotifyChange() { public void onReceive_inCallState_shouldNotifyChange() {
mMediaDeviceUpdateWorker.mLocalMediaManager = mock(LocalMediaManager.class); mMediaDeviceUpdateWorker.mLocalMediaManager = mock(LocalMediaManager.class);
mAudioManager.setMode(AudioManager.MODE_IN_CALL);
mMediaDeviceUpdateWorker.onSlicePinned(); mMediaDeviceUpdateWorker.onSlicePinned();
final Intent intent = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION); final Intent intent = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
@@ -151,4 +168,18 @@ public class MediaDeviceUpdateWorkerTest {
verify(mResolver).notifyChange(URI, null); verify(mResolver).notifyChange(URI, null);
} }
@Test
public void onReceive_notInCallState_doNothing() {
mMediaDeviceUpdateWorker.mLocalMediaManager = mock(LocalMediaManager.class);
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mMediaDeviceUpdateWorker.onSlicePinned();
final Intent intent = new Intent(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
for (BroadcastReceiver receiver : mShadowApplication.getReceiversForIntent(intent)) {
receiver.onReceive(mContext, intent);
}
verify(mResolver, never()).notifyChange(URI, null);
}
} }

View File

@@ -64,7 +64,6 @@ import java.util.List;
@Config(shadows = {ShadowBluetoothAdapter.class}) @Config(shadows = {ShadowBluetoothAdapter.class})
public class MediaOutputSliceTest { public class MediaOutputSliceTest {
private static final String TEST_PACKAGE_NAME = "com.fake.android.music";
private static final String TEST_DEVICE_1_ID = "test_device_1_id"; 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 String TEST_DEVICE_1_NAME = "test_device_1_name";
private static final int TEST_DEVICE_1_ICON = private static final int TEST_DEVICE_1_ICON =
@@ -100,12 +99,12 @@ public class MediaOutputSliceTest {
mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, MEDIA_OUTPUT_SLICE_URI); mMediaDeviceUpdateWorker = new MediaDeviceUpdateWorker(mContext, MEDIA_OUTPUT_SLICE_URI);
mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices); mMediaDeviceUpdateWorker.onDeviceListUpdate(mDevices);
mMediaDeviceUpdateWorker.mLocalMediaManager = mLocalMediaManager; mMediaDeviceUpdateWorker.mLocalMediaManager = mLocalMediaManager;
mMediaOutputSlice.init(TEST_PACKAGE_NAME, mMediaDeviceUpdateWorker); mMediaOutputSlice.init(mMediaDeviceUpdateWorker);
} }
@Test @Test
public void getSlice_workerIsNull_shouldReturnZeroRow() { public void getSlice_workerIsNull_shouldReturnZeroRow() {
mMediaOutputSlice.init(TEST_PACKAGE_NAME, null); mMediaOutputSlice.init(null);
final Slice slice = mMediaOutputSlice.getSlice(); final Slice slice = mMediaOutputSlice.getSlice();