1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.cts.verifier.tv.display; 18 19 import android.annotation.StringRes; 20 import android.content.Context; 21 import android.hardware.display.DisplayManager; 22 import android.view.Display; 23 import android.view.View; 24 25 import com.android.cts.verifier.R; 26 import com.android.cts.verifier.tv.TestSequence; 27 import com.android.cts.verifier.tv.TestStepBase; 28 import com.android.cts.verifier.tv.TvAppVerifierActivity; 29 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.Objects; 34 import java.util.stream.Collectors; 35 36 /** 37 * Test when the HDMI cable on HDMI source devices (e.g. set-top boxes and TV sticks) is plugged out 38 * and in {@link android.hardware.display.DisplayManager.DisplayListener#onDisplayChanged}. The 39 * test also verifies that the display capabilities when the HDMI cable is unplugged contain only 40 * one display mode and no HDR capabilities. 41 */ 42 public class HotplugTestActivity extends TvAppVerifierActivity { 43 private static final float REFRESH_RATE_TOLERANCE = 0.01f; 44 private TestSequence mTestSequence; 45 46 @Override setInfoResources()47 protected void setInfoResources() { 48 setInfoResources(R.string.tv_hotplug_test, R.string.tv_hotplug_test_info, -1); 49 } 50 51 @Override createTestItems()52 protected void createTestItems() { 53 List<TestStepBase> testSteps = new ArrayList<>(); 54 testSteps.add(new NoDisplayTestStep(this)); 55 mTestSequence = new TestSequence(this, testSteps); 56 mTestSequence.init(); 57 } 58 59 @Override getTestDetails()60 public String getTestDetails() { 61 return mTestSequence.getFailureDetails(); 62 } 63 64 private static class DisplayCapabilities { 65 public Display.Mode[] modes; 66 public Display.HdrCapabilities hdrCapabilities; 67 DisplayCapabilities(Display display)68 DisplayCapabilities(Display display) { 69 this.modes = display.getSupportedModes(); 70 this.hdrCapabilities = display.getHdrCapabilities(); 71 } 72 73 @Override equals(Object o)74 public boolean equals(Object o) { 75 if (this == o) return true; 76 if (!(o instanceof DisplayCapabilities)) return false; 77 DisplayCapabilities that = (DisplayCapabilities) o; 78 return modesAreEqual(modes, that.modes) 79 && Objects.equals(hdrCapabilities, that.hdrCapabilities); 80 } 81 modesAreEqual(Display.Mode[] left, Display.Mode[] right)82 private boolean modesAreEqual(Display.Mode[] left, Display.Mode[] right) { 83 if (left.length != right.length) { 84 return false; 85 } 86 87 for (int i = 0; i < left.length; i++) { 88 if (!modesAreEqual(left[i], right[i])) { 89 return false; 90 } 91 } 92 93 return true; 94 } 95 modesAreEqual(Display.Mode left, Display.Mode right)96 private boolean modesAreEqual(Display.Mode left, Display.Mode right) { 97 // Compare all properties except the ID. 98 return left.getPhysicalHeight() == right.getPhysicalHeight() 99 && left.getPhysicalWidth() == right.getPhysicalWidth() 100 && Math.abs(left.getRefreshRate() - right.getRefreshRate()) 101 < REFRESH_RATE_TOLERANCE 102 && Arrays.equals( 103 left.getAlternativeRefreshRates(), right.getAlternativeRefreshRates()); 104 } 105 106 @Override toString()107 public String toString() { 108 String modesStr = Arrays.stream(modes) 109 .map(Display.Mode::toString) 110 .collect(Collectors.joining("\n\t")); 111 return "DisplayCapabilities{" 112 + "modes=" + modesStr 113 + ", hdrCapabilities=" + hdrCapabilities + '}'; 114 } 115 } 116 117 private static class NoDisplayTestStep extends AsyncTestStep implements 118 DisplayManager.DisplayListener { 119 // All recorded states of the display capabilities. The first element are the initial 120 // display capabilities; each subsequent element is the state after a received 121 // onDisplayChanged(). 122 private List<DisplayCapabilities> mRecordedCapabilities = new ArrayList<>(); 123 124 private int mNumOnDisplayChanged = 0; 125 private boolean mDisconnectDetected = false; 126 private boolean mConnectDetected = false; 127 128 private View mDoneButtonView; 129 private DisplayManager mDisplayManager; 130 NoDisplayTestStep(TvAppVerifierActivity context)131 NoDisplayTestStep(TvAppVerifierActivity context) { 132 super( 133 context, 134 R.string.tv_hotplug_test, 135 getInstructionText(context), 136 getButtonStringId()); 137 } 138 getInstructionText(Context context)139 private static String getInstructionText(Context context) { 140 return context.getString( 141 R.string.tv_hotplug_disconnect_display, 142 context.getString(getButtonStringId())); 143 } 144 145 @StringRes getButtonStringId()146 private static int getButtonStringId() { 147 return R.string.tv_start_test; 148 } 149 150 @Override createUiElements()151 public List<View> createUiElements() { 152 List<View> list = super.createUiElements(); 153 mDoneButtonView = TvAppVerifierActivity.createButtonItem( 154 mContext.getLayoutInflater(), 155 null, 156 R.string.tv_done, 157 (View view) -> recordTestStateAndFinish()); 158 list.add(mDoneButtonView); 159 return list; 160 } 161 162 @Override runTestAsync()163 public void runTestAsync() { 164 setButtonEnabled(mDoneButtonView, true); 165 166 mDisplayManager = mContext.getSystemService(DisplayManager.class); 167 Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY); 168 mRecordedCapabilities.add(new DisplayCapabilities(display)); 169 mDisplayManager.registerDisplayListener(this, null); 170 } 171 172 @Override onDisplayAdded(int displayId)173 public void onDisplayAdded(int displayId) { 174 getAsserter().withMessage("onDisplayAdded() is not expected").fail(); 175 } 176 177 @Override onDisplayRemoved(int displayId)178 public void onDisplayRemoved(int displayId) { 179 getAsserter().withMessage("onDisplayRemoved() is not expected").fail(); 180 } 181 182 @Override onDisplayChanged(int displayId)183 public void onDisplayChanged(int displayId) { 184 getAsserter().that(displayId).isEqualTo(Display.DEFAULT_DISPLAY); 185 186 mNumOnDisplayChanged++; 187 DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); 188 Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY); 189 DisplayCapabilities currCapabilities = new DisplayCapabilities(display); 190 mRecordedCapabilities.add(currCapabilities); 191 192 // Some TVs may send multiple onDisplayChanged() events, for that reason we 193 // test that in the sequence of states after each onDisplayChanged() there's 194 // a state which looks like a disconnected display, followed by a state which looks 195 // like the initial state. 196 if (!mDisconnectDetected) { 197 boolean isDisconnectedDisplay = 198 display.getHdrCapabilities().getSupportedHdrTypes().length == 0 199 && display.getSupportedModes().length == 1; 200 201 if (isDisconnectedDisplay) { 202 mDisconnectDetected = true; 203 } 204 } else if (!mConnectDetected) { 205 DisplayCapabilities initialCapabilities = mRecordedCapabilities.get(0); 206 if (currCapabilities.equals(initialCapabilities)) { 207 mConnectDetected = true; 208 recordTestStateAndFinish(); 209 } 210 } 211 } 212 recordTestStateAndFinish()213 private void recordTestStateAndFinish() { 214 setButtonEnabled(mDoneButtonView, false); 215 mDisplayManager.unregisterDisplayListener(this); 216 if (!mConnectDetected || !mDisconnectDetected) { 217 String recordedCapabilitiesStr = mRecordedCapabilities.stream() 218 .map(DisplayCapabilities::toString) 219 .collect(Collectors.joining("\n")); 220 String message = "Number of onDisplayChanged() events = " + mNumOnDisplayChanged 221 + "\n" + "Disconnect detected = " + mDisconnectDetected + "\n" 222 + "Recorded states = " + recordedCapabilitiesStr; 223 getAsserter().withMessage(message).fail(); 224 } 225 done(); 226 } 227 } 228 } 229