diff options
6 files changed, 261 insertions, 1 deletions
diff --git a/media/tests/mediatestutils/Android.bp b/media/tests/mediatestutils/Android.bp index e50e69ac6f4f..15bc1774d4d4 100644 --- a/media/tests/mediatestutils/Android.bp +++ b/media/tests/mediatestutils/Android.bp @@ -24,9 +24,13 @@ java_library { java_library { name: "mediatestutils", + srcs: [ + "java/com/android/media/mediatestutils/TestUtils.java", + ], static_libs: [ + "androidx.concurrent_concurrent-futures", + "guava", "mediatestutils_host", - "junit", ], visibility: [ "//cts/tests/tests/media:__subpackages__", diff --git a/media/tests/mediatestutils/TEST_MAPPING b/media/tests/mediatestutils/TEST_MAPPING new file mode 100644 index 000000000000..6dd09ae9c501 --- /dev/null +++ b/media/tests/mediatestutils/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "mediatestutilstests" + } + ] +} diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java new file mode 100644 index 000000000000..73dbe6780fec --- /dev/null +++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java @@ -0,0 +1,89 @@ +/* + * Copyright 2023 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 com.android.media.mediatestutils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import androidx.concurrent.futures.CallbackToFutureAdapter; + +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.ListenableFuture; + +import java.lang.ref.WeakReference; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * + */ +public class TestUtils { + public static final String TAG = "MediaTestUtils"; + + public static ListenableFuture<Intent> getFutureForIntent(Context context, String action, + Predicate<Intent> pred) { + // These are evaluated async + Objects.requireNonNull(action); + Objects.requireNonNull(pred); + // Doesn't need to be thread safe since the resolver is called inline + final WeakReference<BroadcastReceiver> wrapper[] = new WeakReference[1]; + ListenableFuture<Intent> future = CallbackToFutureAdapter.getFuture(completer -> { + var receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + try { + if (action.equals(intent.getAction()) && pred.test(intent)) { + completer.set(intent); + } + } catch (Exception e) { + completer.setException(e); + } + } + }; + wrapper[0] = new WeakReference(receiver); + context.registerReceiver(receiver, new IntentFilter(action), + Context.RECEIVER_NOT_EXPORTED); + return "Intent receiver future for "; + }); + if (wrapper[0] == null) { + throw new AssertionError("CallbackToFutureAdapter resolver should be called inline"); + } + final var weakref = wrapper[0]; + future.addListener(() -> { + try { + var recv = weakref.get(); + // If there is no reference left, the receiver has already been unregistered + if (recv != null) { + context.unregisterReceiver(recv); + return; + } + } catch (IllegalArgumentException e) { + // Receiver already unregistered, nothing to do. + } + Log.d(TAG, "Intent receiver future for action: " + action + + "unregistered prior to future completion/cancellation."); + } , MoreExecutors.directExecutor()); // Direct executor is fine since lightweight + return future; + } +} diff --git a/media/tests/mediatestutils/tests/Android.bp b/media/tests/mediatestutils/tests/Android.bp new file mode 100644 index 000000000000..24a8360d1187 --- /dev/null +++ b/media/tests/mediatestutils/tests/Android.bp @@ -0,0 +1,22 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "mediatestutilstests", + srcs: ["src/**/*.java"], + static_libs: [ + "mockito-target-minus-junit4", + "androidx.test.runner", + "androidx.test.core", + "mediatestutils", + "junit", + "truth", + ], + test_suites: ["general-tests"], +} diff --git a/media/tests/mediatestutils/tests/AndroidManifest.xml b/media/tests/mediatestutils/tests/AndroidManifest.xml new file mode 100644 index 000000000000..662bc45abc5d --- /dev/null +++ b/media/tests/mediatestutils/tests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.media.mediatestutils"> + + <application android:testOnly="false" + android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.media.mediatestutils" + android:label="mediatestutils tests" /> + +</manifest> diff --git a/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java b/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java new file mode 100644 index 000000000000..70b46ad86448 --- /dev/null +++ b/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2023 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 com.android.media.mediatestutils; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.android.media.mediatestutils.TestUtils.getFutureForIntent; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; + +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.ExecutionException; +import java.util.function.Predicate; + +@RunWith(AndroidJUnit4.class) +public class GetFutureForIntentTest { + public static final String INTENT_ACTION = "com.android.media.mediatestutils.TEST_ACTION"; + public static final String INTENT_EXTRA = "com.android.media.mediatestutils.TEST_EXTRA"; + public static final int MAGIC_VALUE = 7; + + public final Context mContext = getApplicationContext(); + public final Predicate<Intent> mPred = + i -> (i != null) && (i.getIntExtra(INTENT_EXTRA, -1) == MAGIC_VALUE); + + @Test + public void futureCompletes_afterBroadcastFiresPredicatePasses() throws Exception { + final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred); + sendIntent(true); + var intent = future.get(); + assertThat(intent.getAction()).isEqualTo(INTENT_ACTION); + assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE); + } + + @Test + public void futureDoesNotComplete_afterBroadcastFiresPredicateFails() throws Exception { + final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred); + sendIntent(false); + + // Wait a bit, and ensure the future hasn't completed + SystemClock.sleep(100); + assertThat(future.isDone()).isFalse(); + + // Future should still respond to subsequent passing intent + sendIntent(true); + var intent = future.get(); + assertThat(intent.getAction()).isEqualTo(INTENT_ACTION); + assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE); + } + + @Test + public void futureCompletesExceptionally_afterBroadcastFiresPredicateThrows() throws Exception { + final var future = + getFutureForIntent( + mContext, + INTENT_ACTION, + i -> { + throw new IllegalStateException(); + }); + + sendIntent(true); + try { + var intent = future.get(); + fail("Exception expected if predicate throws"); + } catch (ExecutionException e) { + assertThat(e.getCause().getClass()).isEqualTo(IllegalStateException.class); + } + } + + @Test + public void doesNotThrow_whenDoubleSet() throws Exception { + final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred); + sendIntent(true); + sendIntent(true); + var intent = future.get(); + assertThat(intent.getAction()).isEqualTo(INTENT_ACTION); + assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE); + } + + private void sendIntent(boolean correctValue) { + final Intent intent = new Intent(INTENT_ACTION).setPackage(mContext.getPackageName()); + intent.putExtra(INTENT_EXTRA, correctValue ? MAGIC_VALUE : MAGIC_VALUE + 1); + mContext.sendBroadcast(intent); + } +} |