blob: 94ef5363424de5e0b7a15533a5dc14948371d20e [file] [log] [blame]
/*
* Copyright (C) 2021 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.cts.verifier.tv.display;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.media.cts.MediaCodecTunneledPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import com.android.compatibility.common.util.DisplayUtil;
import com.android.cts.verifier.PassFailButtons;
import com.android.cts.verifier.R;
import com.android.cts.verifier.tv.TestStepBase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* Test activity to verify that
* 1. switching display modes during video playback doesn't cause crashes or major regressions
* in video quality
* 2. switching between modes is seamless if and only if the modes have alternative refresh rates
* (see {@link Display.Mode#getAlternativeRefreshRates})
*/
public class ModeSwitchingTestActivity extends PassFailButtons.Activity {
private static final String TAG = ModeSwitchingTestActivity.class.getSimpleName();
private static final float REFRESH_RATE_TOLERANCE = 0.01f;
private SurfaceHolder mHolder;
private int mAudioSessionId = 0;
private MediaCodecTunneledPlayer mPlayer;
private ViewGroup mItemList;
private List<TestStepBase> mSteps;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tv_instructions_and_video);
mItemList = findViewById(R.id.test_items);
DisplayManager displayManager = getSystemService(DisplayManager.class);
Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
List<Display.Mode> modeList = findTestModes(display);
Display.Mode lastMode = display.getMode();
mSteps = new ArrayList<>();
for (Display.Mode mode : modeList) {
boolean isSeamlessSwitch = DisplayUtil.isModeSwitchSeamless(lastMode, mode);
mSteps.addAll(createTestStepsForTransition(lastMode, mode, isSeamlessSwitch));
lastMode = mode;
}
if (!mSteps.isEmpty()) {
initTestStep(0);
}
setPassFailButtonClickListeners();
getPassButton().setEnabled(false);
SurfaceView surfaceView = findViewById(R.id.surface);
mHolder = surfaceView.getHolder();
mHolder.addCallback(new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {
playVideo();
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
AudioManager am = (AudioManager) getApplicationContext().getSystemService(AUDIO_SERVICE);
mAudioSessionId = am.generateAudioSessionId();
mPlayer = new MediaCodecTunneledPlayer(this, mHolder, true, mAudioSessionId);
Uri mediaUri = getAsResourceUri(R.raw.bunny_1280x720_24fps_VP9);
mPlayer.setVideoDataSource(mediaUri, null);
mPlayer.setAudioDataSource(mediaUri, null);
}
private Uri getAsResourceUri(int resId) {
Resources res = getResources();
return new Uri.Builder()
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
.authority(res.getResourcePackageName(resId))
.appendPath(res.getResourceTypeName(resId))
.appendPath(res.getResourceEntryName(resId))
.build();
}
private List<Display.Mode> findTestModes(Display display) {
Display.Mode activeMode = display.getMode();
List<Display.Mode> modeList = new ArrayList<>();
// Find a mode to test refresh rate switch - the mode with smallest refresh rate and
// the same resolution as the active mode
Arrays.stream(display.getSupportedModes())
.filter(mode -> !mode.equals(activeMode))
.filter(mode -> isResolutionEqual(mode, activeMode))
.min(Comparator.comparingDouble(Display.Mode::getRefreshRate))
.ifPresent(modeList::add);
// Find a mode to test seamless mode switch.
if (modeList.size() > 0 && !DisplayUtil.isModeSwitchSeamless(activeMode, modeList.get(0))
&& activeMode.getAlternativeRefreshRates().length > 0) {
Arrays.stream(display.getSupportedModes())
.filter(mode -> !mode.equals(activeMode))
.filter(mode -> DisplayUtil.isModeSwitchSeamless(activeMode, mode))
.findFirst()
.ifPresent(modeList::add);
}
// Find a mode to test resolution switch - the first 16:9 mode with resolution less
// than the active mode.
Arrays.stream(display.getSupportedModes())
.filter(mode -> !mode.equals(activeMode))
.filter(mode -> !isResolutionEqual(mode, activeMode))
.filter(this::is16to9)
.max(Comparator.comparing(Display.Mode::getPhysicalHeight))
.ifPresent(modeList::add);
return modeList;
}
private void initTestStep(int stepIndex) {
final TestStepBase currStep = mSteps.get(stepIndex);
currStep.createUiElements()
.forEach(view -> mItemList.addView(view));
currStep.setOnDoneListener(() -> {
// Delay so the user can see the passing state of the finished
// step, before replacing it with the next one
mItemList.postDelayed(() -> {
int nextStepIndex = stepIndex + 1;
if (!currStep.hasPassed() || nextStepIndex >= mSteps.size()) {
onAllStepsDone();
return;
}
mItemList.removeAllViews();
initTestStep(nextStepIndex);
}, 500);
});
currStep.enableInteractivity();
}
private void onAllStepsDone() {
// The sequence passes if all containing test steps pass.
boolean allTestStepsPass = mSteps.stream().allMatch(step -> step.hasPassed());
getPassButton().setEnabled(allTestStepsPass);
}
private List<TestStepBase> createTestStepsForTransition(
Display.Mode currMode, Display.Mode nextMode, boolean isSeamless) {
List<TestStepBase> steps = new ArrayList<>();
steps.add(new ModeSwitchStep(this,
currMode,
nextMode
));
String blackScreenQuestion =
getResources().getString(R.string.tv_mode_switching_test_black_screen_question);
if (isSeamless) {
steps.add(new YesNoTestStep(this, blackScreenQuestion,
/* passingButton */ R.string.tv_no, /* failingButton */ R.string.tv_yes));
} else {
steps.add(new YesNoTestStep(this, blackScreenQuestion,
/* passingButton */ R.string.tv_yes, /* failingButton */ R.string.tv_no));
}
steps.add(new YesNoTestStep(this,
getResources().getString(R.string.tv_mode_switching_test_is_video_ok),
R.string.tv_yes, R.string.tv_no));
return steps;
}
private boolean isResolutionEqual(Display.Mode left, Display.Mode right) {
return left.getPhysicalHeight() == right.getPhysicalHeight()
&& left.getPhysicalWidth() == right.getPhysicalWidth();
}
private boolean is16to9(Display.Mode mode) {
return mode.getPhysicalHeight() * 16 == mode.getPhysicalWidth() * 9;
}
private void playVideo() {
try {
mPlayer.prepare();
mPlayer.startCodec();
mPlayer.setLoopEnabled(true);
mPlayer.play();
} catch (Exception e) {
Log.d(TAG, "Could not play video", e);
}
}
@Override
public void onPause() {
super.onPause();
if (mPlayer != null) {
mPlayer.pause();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPlayer != null) {
mPlayer.reset();
mPlayer = null;
}
}
@Override
public String getTestDetails() {
return mSteps.stream()
.filter(step -> !step.hasPassed())
.map(step -> step.getFailureDetails())
.collect(Collectors.joining("\n"));
}
// Test step which shows instruction text to the user and performs a mode switch. This
// test step will always pass successfully.
private static class ModeSwitchStep extends SyncTestStep {
private Display.Mode mNextMode;
ModeSwitchStep(Activity context, Display.Mode currMode, Display.Mode nextMode) {
super(context, R.string.tv_mode_switching_test_mode_switch_step,
getInstructionText(context, currMode, nextMode),
R.string.tv_start_test);
this.mNextMode = nextMode;
}
private static String getInstructionText(Context context, Display.Mode currMode,
Display.Mode nextMode) {
return context.getResources().getString(
R.string.tv_mode_switching_test_step_instruction,
formatMode(currMode),
formatMode(nextMode));
}
private static String formatMode(Display.Mode mode) {
return String.format("%dx%d %.2fHz",
mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
}
@Override
public void runTest() {
Window window = mContext.getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.preferredDisplayModeId = mNextMode.getModeId();
window.setAttributes(params);
}
}
}