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