Starting from target SDK U, we will block creation of mutable PendingIntents with implicit Intents because attackers can mutate the Intent object within and launch altered behavior on behalf of victim apps. For more details on the vulnerability, see go/pendingintent-rca. From a quick analysis, we concluded that the PendingIntents here do not get mutated, so we made them immutable. Reviewers, please call out if this is not the case. Bug: 236704164 Bug: 229362273 Test: atest MediaVolumePreferenceControllerTest Change-Id: Ic5f701b504c0d8d0d0a44b002117ee5ef1c188f7
228 lines
8.9 KiB
Java
228 lines
8.9 KiB
Java
/*
|
|
* Copyright (C) 2016 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.notification;
|
|
|
|
import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI;
|
|
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
|
|
import static org.mockito.Mockito.doReturn;
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.spy;
|
|
import static org.mockito.Mockito.when;
|
|
|
|
import android.app.PendingIntent;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.media.AudioManager;
|
|
import android.media.session.MediaController;
|
|
import android.net.Uri;
|
|
|
|
import androidx.slice.builders.SliceAction;
|
|
|
|
import com.android.settings.media.MediaOutputIndicatorWorker;
|
|
import com.android.settings.slices.SliceBackgroundWorker;
|
|
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
|
import com.android.settingslib.media.BluetoothMediaDevice;
|
|
import com.android.settingslib.media.MediaDevice;
|
|
import com.android.settingslib.media.MediaOutputConstants;
|
|
|
|
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;
|
|
|
|
@RunWith(RobolectricTestRunner.class)
|
|
@Config(shadows = MediaVolumePreferenceControllerTest.ShadowSliceBackgroundWorker.class)
|
|
public class MediaVolumePreferenceControllerTest {
|
|
|
|
private static final String ACTION_LAUNCH_BROADCAST_DIALOG =
|
|
"android.settings.MEDIA_BROADCAST_DIALOG";
|
|
private static MediaOutputIndicatorWorker sMediaOutputIndicatorWorker;
|
|
|
|
private MediaVolumePreferenceController mController;
|
|
|
|
private Context mContext;
|
|
|
|
@Mock
|
|
private MediaController mMediaController;
|
|
@Mock
|
|
private MediaDevice mDevice1;
|
|
@Mock
|
|
private MediaDevice mDevice2;
|
|
|
|
@Before
|
|
public void setUp() {
|
|
MockitoAnnotations.initMocks(this);
|
|
|
|
mContext = RuntimeEnvironment.application;
|
|
mController = new MediaVolumePreferenceController(mContext);
|
|
sMediaOutputIndicatorWorker = spy(
|
|
new MediaOutputIndicatorWorker(mContext, VOLUME_MEDIA_URI));
|
|
when(mDevice1.isBLEDevice()).thenReturn(true);
|
|
when(mDevice2.isBLEDevice()).thenReturn(false);
|
|
}
|
|
|
|
@Test
|
|
public void isAvailable_byDefault_isTrue() {
|
|
assertThat(mController.isAvailable()).isTrue();
|
|
}
|
|
|
|
@Test
|
|
@Config(qualifiers = "mcc999")
|
|
public void isAvailable_whenNotVisible_isFalse() {
|
|
assertThat(mController.isAvailable()).isFalse();
|
|
}
|
|
|
|
@Test
|
|
public void getAudioStream_shouldReturnMusic() {
|
|
assertThat(mController.getAudioStream()).isEqualTo(AudioManager.STREAM_MUSIC);
|
|
}
|
|
|
|
@Test
|
|
public void isSliceableCorrectKey_returnsTrue() {
|
|
final MediaVolumePreferenceController controller = new MediaVolumePreferenceController(
|
|
mContext);
|
|
assertThat(controller.isSliceable()).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void isPublicSlice_returnTrue() {
|
|
assertThat(mController.isPublicSlice()).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void isSupportEndItem_withBleDevice_returnsTrue() {
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
|
|
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
|
|
assertThat(mController.isSupportEndItem()).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void isSupportEndItem_notSupportedBroadcast_returnsFalse() {
|
|
doReturn(false).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
|
|
assertThat(mController.isSupportEndItem()).isFalse();
|
|
}
|
|
|
|
@Test
|
|
public void isSupportEndItem_withNonBleDevice_returnsFalse() {
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
|
|
doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
|
|
assertThat(mController.isSupportEndItem()).isFalse();
|
|
}
|
|
|
|
@Test
|
|
public void isSupportEndItem_deviceIsBroadcastingAndConnectedToNonBleDevice_returnsTrue() {
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
|
|
doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
|
|
assertThat(mController.isSupportEndItem()).isTrue();
|
|
}
|
|
|
|
@Test
|
|
public void isSupportEndItem_deviceIsNotBroadcastingAndConnectedToNonBleDevice_returnsFalse() {
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
|
|
doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
|
|
assertThat(mController.isSupportEndItem()).isFalse();
|
|
}
|
|
|
|
|
|
@Test
|
|
public void getSliceEndItem_NotSupportEndItem_getsNullSliceAction() {
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
|
|
doReturn(mDevice2).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
|
|
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
|
|
|
|
assertThat(sliceAction).isNull();
|
|
}
|
|
|
|
@Test
|
|
public void getSliceEndItem_deviceIsBroadcasting_getsBroadcastIntent() {
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(mDevice1).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
|
|
doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
|
|
.getActiveLocalMediaController();
|
|
|
|
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
|
|
|
|
final PendingIntent endItemPendingIntent = sliceAction.getAction();
|
|
final PendingIntent expectedToggleIntent = getBroadcastIntent(
|
|
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
|
|
assertThat(endItemPendingIntent).isEqualTo(expectedToggleIntent);
|
|
}
|
|
|
|
@Test
|
|
public void getSliceEndItem_deviceIsNotBroadcasting_getsActivityIntent() {
|
|
final MediaDevice device = mock(BluetoothMediaDevice.class);
|
|
final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
|
|
when(((BluetoothMediaDevice) device).getCachedDevice()).thenReturn(cachedDevice);
|
|
when(device.isBLEDevice()).thenReturn(true);
|
|
doReturn(true).when(sMediaOutputIndicatorWorker).isBroadcastSupported();
|
|
doReturn(device).when(sMediaOutputIndicatorWorker).getCurrentConnectedMediaDevice();
|
|
doReturn(false).when(sMediaOutputIndicatorWorker).isDeviceBroadcasting();
|
|
doReturn(mMediaController).when(sMediaOutputIndicatorWorker)
|
|
.getActiveLocalMediaController();
|
|
|
|
final SliceAction sliceAction = mController.getSliceEndItem(mContext);
|
|
|
|
final PendingIntent endItemPendingIntent = sliceAction.getAction();
|
|
final PendingIntent expectedPendingIntent =
|
|
getActivityIntent(ACTION_LAUNCH_BROADCAST_DIALOG);
|
|
assertThat(endItemPendingIntent).isEqualTo(expectedPendingIntent);
|
|
}
|
|
|
|
@Implements(SliceBackgroundWorker.class)
|
|
public static class ShadowSliceBackgroundWorker {
|
|
|
|
@Implementation
|
|
public static SliceBackgroundWorker getInstance(Uri uri) {
|
|
return sMediaOutputIndicatorWorker;
|
|
}
|
|
}
|
|
|
|
private PendingIntent getBroadcastIntent(String action) {
|
|
final Intent intent = new Intent(action);
|
|
intent.setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME);
|
|
return PendingIntent.getBroadcast(mContext, 0 /* requestCode */, intent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
|
}
|
|
|
|
private PendingIntent getActivityIntent(String action) {
|
|
final Intent intent = new Intent(action);
|
|
return PendingIntent.getActivity(mContext, 0 /* requestCode */, intent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
|
}
|
|
}
|