/* * Copyright 2018 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 androidx.media.test.client; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.os.HandlerThread; import android.os.Looper; import android.os.ResultReceiver; import android.support.test.InstrumentationRegistry; import androidx.annotation.CallSuper; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media.MediaController2; import androidx.media.MediaController2.ControllerCallback; import androidx.media.MediaItem2; import androidx.media.MediaMetadata2; import androidx.media.MediaSession2.CommandButton; import androidx.media.SessionCommand2; import androidx.media.SessionCommandGroup2; import androidx.media.SessionToken2; import androidx.media.test.client.TestUtils.SyncHandler; import org.junit.AfterClass; import org.junit.BeforeClass; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** * Base class for session test. *
* For all subclasses, all individual tests should begin with the {@link #prepareLooper()}. See
* {@link #prepareLooper} for details.
*/
abstract class MediaSession2TestBase {
// Expected success
static final int WAIT_TIME_MS = 1000;
// Expected timeout
static final int TIMEOUT_MS = 500;
static SyncHandler sHandler;
static Executor sHandlerExecutor;
Context mContext;
private List
* MediaControllerCompat, which is wrapped by the MediaSession2, can be only created by the
* thread whose Looper is prepared. However, when the presubmit tests runs on the server,
* test runs with the {@link org.junit.internal.runners.statements.FailOnTimeout} which creates
* dedicated thread for running test methods while methods annotated with @After or @Before
* runs on the different thread. This ensures that the current Looper is prepared.
*
* To address the issue .
*/
public static void prepareLooper() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
}
@BeforeClass
public static void setUpThread() {
synchronized (MediaSession2TestBase.class) {
if (sHandler != null) {
return;
}
prepareLooper();
HandlerThread handlerThread = new HandlerThread("MediaSession2TestBase");
handlerThread.start();
sHandler = new SyncHandler(handlerThread.getLooper());
sHandlerExecutor = new Executor() {
@Override
public void execute(Runnable runnable) {
SyncHandler handler;
synchronized (MediaSession2TestBase.class) {
handler = sHandler;
}
if (handler != null) {
handler.post(runnable);
}
}
};
}
}
@AfterClass
public static void cleanUpThread() {
synchronized (MediaSession2TestBase.class) {
if (sHandler == null) {
return;
}
if (Build.VERSION.SDK_INT >= 18) {
sHandler.getLooper().quitSafely();
} else {
sHandler.getLooper().quit();
}
sHandler = null;
sHandlerExecutor = null;
}
}
@CallSuper
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
}
@CallSuper
public void cleanUp() throws Exception {
for (int i = 0; i < mControllers.size(); i++) {
mControllers.get(i).close();
}
}
final MediaController2 createController(SessionToken2 token) throws InterruptedException {
return createController(token, true, null);
}
final MediaController2 createController(@NonNull SessionToken2 token,
boolean waitForConnect, @Nullable ControllerCallback callback)
throws InterruptedException {
TestControllerInterface instance = onCreateController(token, callback);
if (!(instance instanceof MediaController2)) {
throw new RuntimeException("Test has a bug. Expected MediaController2 but returned "
+ instance);
}
MediaController2 controller = (MediaController2) instance;
mControllers.add(controller);
if (waitForConnect) {
waitForConnect(controller, true);
}
return controller;
}
private static TestControllerCallbackInterface getTestControllerCallbackInterface(
MediaController2 controller) {
if (!(controller instanceof TestControllerInterface)) {
throw new RuntimeException("Test has a bug. Expected controller implemented"
+ " TestControllerInterface but got " + controller);
}
ControllerCallback callback = ((TestControllerInterface) controller).getCallback();
if (!(callback instanceof TestControllerCallbackInterface)) {
throw new RuntimeException("Test has a bug. Expected controller with callback "
+ " implemented TestControllerCallbackInterface but got " + controller);
}
return (TestControllerCallbackInterface) callback;
}
public static void waitForConnect(MediaController2 controller, boolean expected)
throws InterruptedException {
getTestControllerCallbackInterface(controller).waitForConnect(expected);
}
public static void waitForDisconnect(MediaController2 controller, boolean expected)
throws InterruptedException {
getTestControllerCallbackInterface(controller).waitForDisconnect(expected);
}
public static void setRunnableForOnCustomCommand(MediaController2 controller,
Runnable runnable) {
getTestControllerCallbackInterface(controller).setRunnableForOnCustomCommand(runnable);
}
TestControllerInterface onCreateController(final @NonNull SessionToken2 token,
@Nullable ControllerCallback callback) throws InterruptedException {
final ControllerCallback controllerCallback =
callback != null ? callback : new ControllerCallback() {};
final AtomicReference