1 /* 2 * Copyright (C) 2016 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 package com.google.android.exoplayer2.testutil; 17 18 import static org.junit.Assert.fail; 19 20 import android.app.Activity; 21 import android.content.Context; 22 import android.net.wifi.WifiManager; 23 import android.net.wifi.WifiManager.WifiLock; 24 import android.os.Bundle; 25 import android.os.ConditionVariable; 26 import android.os.PowerManager; 27 import android.os.PowerManager.WakeLock; 28 import android.view.Surface; 29 import android.view.SurfaceHolder; 30 import android.view.SurfaceView; 31 import android.view.Window; 32 import android.widget.FrameLayout; 33 import androidx.annotation.Nullable; 34 import com.google.android.exoplayer2.util.Assertions; 35 import com.google.android.exoplayer2.util.Log; 36 import com.google.android.exoplayer2.util.Util; 37 import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 38 39 /** A host activity for performing playback tests. */ 40 public final class HostActivity extends Activity implements SurfaceHolder.Callback { 41 42 /** 43 * Interface for tests that run inside of a {@link HostActivity}. 44 */ 45 public interface HostedTest { 46 47 /** 48 * Called on the main thread when the test is started. 49 * 50 * <p>The test will not be started until the {@link HostActivity} has been resumed and its 51 * {@link Surface} has been created. 52 * 53 * @param host The {@link HostActivity} in which the test is being run. 54 * @param surface The {@link Surface}. 55 * @param overlayFrameLayout A {@link FrameLayout} that is on top of the surface. 56 */ onStart(HostActivity host, Surface surface, FrameLayout overlayFrameLayout)57 void onStart(HostActivity host, Surface surface, FrameLayout overlayFrameLayout); 58 59 /** 60 * Called on the main thread to block until the test has stopped or {@link #forceStop()} is 61 * called. 62 * 63 * @param timeoutMs The maximum time to block in milliseconds. 64 * @return Whether the test has stopped successful. 65 */ blockUntilStopped(long timeoutMs)66 boolean blockUntilStopped(long timeoutMs); 67 68 /** 69 * Called on the main thread to force stop the test (if it is not stopped already). 70 * 71 * @return Whether the test was forced stopped. 72 */ forceStop()73 boolean forceStop(); 74 75 /** 76 * Called on the test thread after the test has finished and been stopped. 77 * <p> 78 * Implementations may use this method to assert that test criteria were met. 79 */ onFinished()80 void onFinished(); 81 82 } 83 84 private static final String TAG = "HostActivity"; 85 private static final String LOCK_TAG = "ExoPlayerTestUtil:" + TAG; 86 private static final long START_TIMEOUT_MS = 5000; 87 88 @Nullable private WakeLock wakeLock; 89 @Nullable private WifiLock wifiLock; 90 private @MonotonicNonNull SurfaceView surfaceView; 91 private @MonotonicNonNull FrameLayout overlayFrameLayout; 92 93 @Nullable private HostedTest hostedTest; 94 private boolean hostedTestStarted; 95 private @MonotonicNonNull ConditionVariable hostedTestStartedCondition; 96 private boolean forcedStopped; 97 98 /** 99 * Executes a {@link HostedTest} inside the host. 100 * 101 * @param hostedTest The test to execute. 102 * @param timeoutMs The number of milliseconds to wait for the test to finish. If the timeout 103 * is exceeded then the test will fail. 104 */ runTest(HostedTest hostedTest, long timeoutMs)105 public void runTest(HostedTest hostedTest, long timeoutMs) { 106 runTest(hostedTest, timeoutMs, /* failOnTimeoutOrForceStop= */ true); 107 } 108 109 /** 110 * Executes a {@link HostedTest} inside the host. 111 * 112 * @param hostedTest The test to execute. 113 * @param timeoutMs The number of milliseconds to wait for the test to finish. 114 * @param failOnTimeoutOrForceStop Whether the test fails when a timeout is exceeded or the test 115 * is stopped forcefully. 116 */ runTest( final HostedTest hostedTest, long timeoutMs, boolean failOnTimeoutOrForceStop)117 public void runTest( 118 final HostedTest hostedTest, long timeoutMs, boolean failOnTimeoutOrForceStop) { 119 Assertions.checkArgument(timeoutMs > 0); 120 Assertions.checkState(Thread.currentThread() != getMainLooper().getThread()); 121 Assertions.checkState(this.hostedTest == null); 122 Assertions.checkNotNull(hostedTest); 123 hostedTestStartedCondition = new ConditionVariable(); 124 forcedStopped = false; 125 hostedTestStarted = false; 126 127 runOnUiThread( 128 () -> { 129 HostActivity.this.hostedTest = hostedTest; 130 maybeStartHostedTest(); 131 }); 132 133 if (!hostedTestStartedCondition.block(START_TIMEOUT_MS)) { 134 String message = 135 "Test failed to start. Display may be turned off or keyguard may be present."; 136 Log.e(TAG, message); 137 if (failOnTimeoutOrForceStop) { 138 fail(message); 139 } 140 } 141 142 if (hostedTest.blockUntilStopped(timeoutMs)) { 143 if (!forcedStopped) { 144 Log.d(TAG, "Checking test pass conditions."); 145 hostedTest.onFinished(); 146 Log.d(TAG, "Pass conditions checked."); 147 } else { 148 String message = "Test force stopped. Activity may have been paused whilst " 149 + "test was in progress."; 150 Log.e(TAG, message); 151 if (failOnTimeoutOrForceStop) { 152 fail(message); 153 } 154 } 155 } else { 156 runOnUiThread(hostedTest::forceStop); 157 String message = "Test timed out after " + timeoutMs + " ms."; 158 Log.e(TAG, message); 159 if (failOnTimeoutOrForceStop) { 160 fail(message); 161 } 162 } 163 this.hostedTest = null; 164 } 165 166 // Activity lifecycle 167 168 @Override onCreate(@ullable Bundle savedInstanceState)169 public void onCreate(@Nullable Bundle savedInstanceState) { 170 super.onCreate(savedInstanceState); 171 requestWindowFeature(Window.FEATURE_NO_TITLE); 172 setContentView( 173 getResources().getIdentifier("exo_testutils_host_activity", "layout", getPackageName())); 174 surfaceView = findViewById( 175 getResources().getIdentifier("surface_view", "id", getPackageName())); 176 surfaceView.getHolder().addCallback(this); 177 overlayFrameLayout = 178 findViewById(getResources().getIdentifier("overlay_frame_layout", "id", getPackageName())); 179 } 180 181 @Override onStart()182 public void onStart() { 183 Context appContext = getApplicationContext(); 184 WifiManager wifiManager = 185 Assertions.checkStateNotNull( 186 (WifiManager) appContext.getSystemService(Context.WIFI_SERVICE)); 187 wifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, LOCK_TAG); 188 wifiLock.acquire(); 189 PowerManager powerManager = 190 Assertions.checkStateNotNull( 191 (PowerManager) appContext.getSystemService(Context.POWER_SERVICE)); 192 wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOCK_TAG); 193 wakeLock.acquire(); 194 super.onStart(); 195 } 196 197 @Override onPause()198 public void onPause() { 199 super.onPause(); 200 if (Util.SDK_INT <= 23) { 201 maybeStopHostedTest(); 202 } 203 } 204 205 @Override onStop()206 public void onStop() { 207 super.onStop(); 208 if (Util.SDK_INT > 23) { 209 maybeStopHostedTest(); 210 } 211 if (wakeLock != null) { 212 wakeLock.release(); 213 wakeLock = null; 214 } 215 if (wifiLock != null) { 216 wifiLock.release(); 217 wifiLock = null; 218 } 219 } 220 221 // SurfaceHolder.Callback 222 223 @Override surfaceCreated(SurfaceHolder holder)224 public void surfaceCreated(SurfaceHolder holder) { 225 maybeStartHostedTest(); 226 } 227 228 @Override surfaceDestroyed(SurfaceHolder holder)229 public void surfaceDestroyed(SurfaceHolder holder) { 230 maybeStopHostedTest(); 231 } 232 233 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)234 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 235 // Do nothing. 236 } 237 238 // Internal logic 239 maybeStartHostedTest()240 private void maybeStartHostedTest() { 241 if (hostedTest == null || hostedTestStarted) { 242 return; 243 } 244 @Nullable Surface surface = Util.castNonNull(surfaceView).getHolder().getSurface(); 245 if (surface != null && surface.isValid()) { 246 hostedTestStarted = true; 247 Log.d(TAG, "Starting test."); 248 Util.castNonNull(hostedTest) 249 .onStart(this, surface, Assertions.checkNotNull(overlayFrameLayout)); 250 Util.castNonNull(hostedTestStartedCondition).open(); 251 } 252 } 253 maybeStopHostedTest()254 private void maybeStopHostedTest() { 255 if (hostedTest != null && hostedTestStarted && !forcedStopped) { 256 forcedStopped = hostedTest.forceStop(); 257 } 258 } 259 } 260