blob: f4a4e6e235e5c2a800471bbddd00752091c6680c [file] [log] [blame]
/*
* Copyright 2019 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 android.media.misc.cts;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.Manifest;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.cts.NonMediaMainlineTest;
import android.media.session.MediaSession;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Test {@link MediaSessionTestActivity} which has called {@link Activity#setMediaController}.
*/
@NonMediaMainlineTest
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MediaActivityTest {
private static final String TAG = "MediaActivityTest";
private static final int WAIT_TIME_MS = 5000;
private static final int TIME_SLICE = 50;
private static final List<Integer> ALL_VOLUME_STREAMS = new ArrayList();
static {
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_ACCESSIBILITY);
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_ALARM);
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_DTMF);
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_MUSIC);
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_NOTIFICATION);
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_RING);
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_SYSTEM);
ALL_VOLUME_STREAMS.add(AudioManager.STREAM_VOICE_CALL);
}
private Instrumentation mInstrumentation;
private Context mContext;
private boolean mUseFixedVolume;
private AudioManager mAudioManager;
private Map<Integer, Integer> mStreamVolumeMap = new HashMap<>();
private MediaSession mSession;
private HdmiControlManager mHdmiControlManager;
private int mHdmiEnableStatus;
@Rule
public ActivityTestRule<MediaSessionTestActivity> mActivityRule =
new ActivityTestRule<>(MediaSessionTestActivity.class, false, false);
@Before
public void setUp() throws Exception {
getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
Manifest.permission.HDMI_CEC);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
mContext = mInstrumentation.getContext();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mUseFixedVolume = mAudioManager.isVolumeFixed();
mHdmiControlManager = mContext.getSystemService(HdmiControlManager.class);
if(mHdmiControlManager != null) {
mHdmiEnableStatus = mHdmiControlManager.getHdmiCecEnabled();
mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
}
mStreamVolumeMap.clear();
for (Integer stream : ALL_VOLUME_STREAMS) {
mStreamVolumeMap.put(stream, mAudioManager.getStreamVolume(stream));
}
mSession = new MediaSession(mContext, TAG);
// Set volume stream other than STREAM_MUSIC.
// STREAM_MUSIC is the new default stream for changing volume, so it doesn't precisely test
// whether the session is prioritized for volume control or not.
mSession.setPlaybackToLocal(new AudioAttributes.Builder()
.setLegacyStreamType(AudioManager.STREAM_RING).build());
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(MediaSessionTestActivity.KEY_SESSION_TOKEN, mSession.getSessionToken());
mActivityRule.launchActivity(intent);
assertTrue(
"Failed to bring MediaSessionTestActivity due to the screen lock setting."
+ " Ensure screen lock isn't set before running CTS test.",
pollingCheck(() -> {
Activity activity = mActivityRule.getActivity();
if (activity == null) {
return false;
}
return activity.getMediaController() != null;
}));
}
@After
public void cleanUp() {
if (mSession != null) {
mSession.release();
mSession = null;
}
if (mHdmiControlManager != null) {
mHdmiControlManager.setHdmiCecEnabled(mHdmiEnableStatus);
}
try {
mActivityRule.finishActivity();
} catch (IllegalStateException e) {
}
for (int stream : mStreamVolumeMap.keySet()) {
int volume = mStreamVolumeMap.get(stream);
try {
mAudioManager.setStreamVolume(stream, volume, /* flag= */ 0);
} catch (SecurityException e) {
Log.w(TAG, "Failed to restore volume. The test probably had changed DnD mode"
+ ", stream=" + stream + ", originalVolume="
+ volume + ", currentVolume=" + mAudioManager.getStreamVolume(stream));
}
}
}
/**
* Tests whether volume key changes volume with the session's stream.
*/
@Test
public void testVolumeKey_whileSessionAlive() throws Exception {
if (mUseFixedVolume) {
Log.i(TAG, "testVolumeKey_whileSessionAlive skipped due to full volume device");
return;
}
final int testStream = mSession.getController().getPlaybackInfo().getAudioAttributes()
.getVolumeControlStream();
final int testKeyCode;
if (mStreamVolumeMap.get(testStream) == mAudioManager.getStreamMinVolume(testStream)) {
testKeyCode = KeyEvent.KEYCODE_VOLUME_UP;
} else {
testKeyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
}
// The key event can be ignored and show volume panel instead. Use polling.
assertTrue("failed to adjust stream volume that foreground activity want",
pollingCheck(() -> {
sendKeyEvent(testKeyCode);
return mStreamVolumeMap.get(testStream)
!= mAudioManager.getStreamVolume(testStream);
}));
}
/**
* Tests whether volume key changes a stream volume even after the session is released,
* without being ignored.
*/
@Test
public void testVolumeKey_afterSessionReleased() throws Exception {
if (mUseFixedVolume) {
Log.i(TAG, "testVolumeKey_afterSessionReleased skipped due to full volume device");
return;
}
mSession.release();
// The key event can be ignored and show volume panel instead. Use polling.
boolean downKeySuccess = pollingCheck(() -> {
sendKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN);
return checkAnyStreamVolumeChanged();
});
if (downKeySuccess) {
// Volume down key has changed a stream volume. Test success.
return;
}
// Volume may not have been changed because the target stream's volume level was minimum.
// Try again with the up key.
assertTrue(pollingCheck(() -> {
sendKeyEvent(KeyEvent.KEYCODE_VOLUME_UP);
return checkAnyStreamVolumeChanged();
}));
}
@Test
public void testMediaKey_whileSessionAlive() throws Exception {
int testKeyEvent = KeyEvent.KEYCODE_MEDIA_PLAY;
// Note: No extra setup for the session is needed after Activity#setMediaController().
// i.e. No playback nor activeness is required.
CountDownLatch latch = new CountDownLatch(2);
mSession.setCallback(new MediaSession.Callback() {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
KeyEvent event = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
assertEquals(testKeyEvent, event.getKeyCode());
latch.countDown();
return true;
}
}, new Handler(Looper.getMainLooper()));
sendKeyEvent(testKeyEvent);
assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
@Test
public void testMediaKey_whileSessionReleased() throws Exception {
int testKeyEvent = KeyEvent.KEYCODE_MEDIA_PLAY;
CountDownLatch latch = new CountDownLatch(1);
mSession.setCallback(new MediaSession.Callback() {
@Override
public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
fail("Released session shouldn't be able to receive key event in any case");
latch.countDown();
return true;
}
}, new Handler(Looper.getMainLooper()));
mSession.release();
sendKeyEvent(testKeyEvent);
assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
}
private void sendKeyEvent(int keyCode) {
final long downTime = SystemClock.uptimeMillis();
final KeyEvent down = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0);
final long upTime = SystemClock.uptimeMillis();
final KeyEvent up = new KeyEvent(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0);
try {
mInstrumentation.sendKeySync(down);
mInstrumentation.sendKeySync(up);
} catch (SecurityException e) {
throw new IllegalStateException(
"MediaSessionTestActivity isn't in the foreground."
+ " Ensure no screen lock before running CTS test"
+ ", and do not touch screen while the test is running.");
}
}
private boolean checkAnyStreamVolumeChanged() {
for (int stream : mStreamVolumeMap.keySet()) {
int volume = mStreamVolumeMap.get(stream);
if (mAudioManager.getStreamVolume(stream) != volume) {
return true;
}
}
return false;
}
private static boolean pollingCheck(Callable<Boolean> condition) throws Exception {
long pollingCount = WAIT_TIME_MS / TIME_SLICE;
while (!condition.call() && pollingCount-- > 0) {
try {
Thread.sleep(TIME_SLICE);
} catch (InterruptedException e) {
fail("unexpected InterruptedException");
}
}
return pollingCount >= 0;
}
}