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