xref: /aosp_15_r20/frameworks/base/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright 2023 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.media.mediatestutils;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 
24 import androidx.concurrent.futures.CallbackToFutureAdapter;
25 
26 import com.google.common.util.concurrent.ListenableFuture;
27 import com.google.common.util.concurrent.MoreExecutors;
28 
29 import java.lang.ref.WeakReference;
30 import java.util.Collection;
31 import java.util.Objects;
32 import java.util.function.Consumer;
33 import java.util.function.Function;
34 import java.util.function.Predicate;
35 
36 /** Utils for audio tests. */
37 public class TestUtils {
38     /**
39      * Return a future for an intent delivered by a broadcast receiver which matches an action and
40      * predicate.
41      *
42      * @param context - Context to register the receiver with
43      * @param action - String representing action to register receiver for
44      * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
45      *     future unset. If the predicate throws, the future is set exceptionally
46      * @return - The future representing intent delivery matching predicate.
47      */
getFutureForIntent( Context context, String action, Predicate<Intent> pred)48     public static ListenableFuture<Intent> getFutureForIntent(
49             Context context, String action, Predicate<Intent> pred) {
50         // These are evaluated async
51         Objects.requireNonNull(action);
52         Objects.requireNonNull(pred);
53         return getFutureForListener(
54                 (recv) ->
55                         context.registerReceiver(
56                                 recv, new IntentFilter(action), Context.RECEIVER_EXPORTED),
57                 (recv) -> {
58                     try {
59                         context.unregisterReceiver(recv);
60                     } catch (IllegalArgumentException e) {
61                         // Thrown when receiver is already unregistered, nothing to do
62                     }
63                 },
64                 (completer) ->
65                         new BroadcastReceiver() {
66                             @Override
67                             public void onReceive(Context context, Intent intent) {
68                                 try {
69                                     if (action.equals(intent.getAction()) && pred.test(intent)) {
70                                         completer.set(intent);
71                                     }
72                                 } catch (Exception e) {
73                                     completer.setException(e);
74                                 }
75                             }
76                         },
77                 "Intent receiver future for action: " + action);
78     }
79 
80     /**
81      * Return a future for an intent delivered by a broadcast receiver which matches one of a set of
82      * actions and predicate.
83      *
84      * @param context - Context to register the receiver with
85      * @param actionsCollection - Collection of actions which to listen for, completing on any
86      * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves the
87      *     future unset. If the predicate throws, the future is set exceptionally
88      * @return - The future representing intent delivery matching predicate.
89      */
90     public static ListenableFuture<Intent> getFutureForIntent(
91             Context context, Collection<String> actionsCollection, Predicate<Intent> pred) {
92         // These are evaluated async
93         Objects.requireNonNull(actionsCollection);
94         Objects.requireNonNull(pred);
95         if (actionsCollection.isEmpty()) {
96             throw new IllegalArgumentException("actionsCollection must not be empty");
97         }
98         return getFutureForListener(
99                 (recv) ->
100                         context.registerReceiver(
101                                 recv,
102                                 actionsCollection.stream()
103                                         .reduce(
104                                                 new IntentFilter(),
105                                                 (IntentFilter filter, String x) -> {
106                                                     filter.addAction(x);
107                                                     return filter;
108                                                 },
109                                                 (x, y) -> {
110                                                     throw new IllegalStateException(
111                                                             "No parallel support");
112                                                 }),
113                                 Context.RECEIVER_EXPORTED),
114                 (recv) -> {
115                     try {
116                         context.unregisterReceiver(recv);
117                     } catch (IllegalArgumentException e) {
118                         // Thrown when receiver is already unregistered, nothing to do
119                     }
120                 },
121                 (completer) ->
122                         new BroadcastReceiver() {
123                             @Override
124                             public void onReceive(Context context, Intent intent) {
125                                 try {
126                                     if (actionsCollection.contains(intent.getAction())
127                                             && pred.test(intent)) {
128                                         completer.set(intent);
129                                     }
130                                 } catch (Exception e) {
131                                     completer.setException(e);
132                                 }
133                             }
134                         },
135                 "Intent receiver future for actions: " + actionsCollection);
136     }
137 
138     /** Same as previous, but with no predicate. */
139     public static ListenableFuture<Intent> getFutureForIntent(Context context, String action) {
140         return getFutureForIntent(context, action, i -> true);
141     }
142 
143     /**
144      * Return a future for a callback registered to a listener interface.
145      *
146      * @param registerFunc - Function which consumes the callback object for registration
147      * @param unregisterFunc - Function which consumes the callback object for unregistration This
148      *     function is called when the future is completed or cancelled
149      * @param instantiateCallback - Factory function for the callback object, provided a completer
150      *     object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
151      *     to the future returned by this function
152      * @param debug - Debug string contained in future {@code toString} representation.
153      */
154     public static <T, V> ListenableFuture<T> getFutureForListener(
155             Consumer<V> registerFunc,
156             Consumer<V> unregisterFunc,
157             Function<CallbackToFutureAdapter.Completer<T>, V> instantiateCallback,
158             String debug) {
159         // Doesn't need to be thread safe since the resolver is called inline
160         final WeakReference<V> wrapper[] = new WeakReference[1];
161         ListenableFuture<T> future =
162                 CallbackToFutureAdapter.getFuture(
163                         completer -> {
164                             final var cb = instantiateCallback.apply(completer);
165                             wrapper[0] = new WeakReference(cb);
166                             registerFunc.accept(cb);
167                             return debug;
168                         });
169         if (wrapper[0] == null) {
170             throw new AssertionError("Resolver should be called inline");
171         }
172         final var weakref = wrapper[0];
173         future.addListener(
174                 () -> {
175                     var cb = weakref.get();
176                     // If there is no reference left, the receiver has already been unregistered
177                     if (cb != null) {
178                         unregisterFunc.accept(cb);
179                         return;
180                     }
181                 },
182                 MoreExecutors.directExecutor()); // Direct executor is fine since lightweight
183         return future;
184     }
185 }
186