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.Objects;
31 import java.util.function.Consumer;
32 import java.util.function.Function;
33 import java.util.function.Predicate;
34 
35 /** Utils for audio tests. */
36 public class TestUtils {
37     /**
38      * Return a future for an intent delivered by a broadcast receiver which matches an
39      * action and predicate.
40      * @param context - Context to register the receiver with
41      * @param action - String representing action to register receiver for
42      * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves
43      * the future unset. If the predicate throws, the future is set exceptionally
44      * @return - The future representing intent delivery matching predicate.
45      */
getFutureForIntent( Context context, String action, Predicate<Intent> pred)46     public static ListenableFuture<Intent> getFutureForIntent(
47             Context context, String action, Predicate<Intent> pred) {
48         // These are evaluated async
49         Objects.requireNonNull(action);
50         Objects.requireNonNull(pred);
51         return getFutureForListener(
52                 (recv) ->
53                         context.registerReceiver(
54                                 recv, new IntentFilter(action), Context.RECEIVER_EXPORTED),
55                 (recv) -> {
56                     try {
57                         context.unregisterReceiver(recv);
58                     } catch (IllegalArgumentException e) {
59                         // Thrown when receiver is already unregistered, nothing to do
60                     }
61                 },
62                 (completer) ->
63                         new BroadcastReceiver() {
64                             @Override
65                             public void onReceive(Context context, Intent intent) {
66                                 try {
67                                     if (action.equals(intent.getAction()) && pred.test(intent)) {
68                                         completer.set(intent);
69                                     }
70                                 } catch (Exception e) {
71                                     completer.setException(e);
72                                 }
73                             }
74                         },
75                 "Intent receiver future for action: " + action);
76     }
77 
78     /**
79      * Same as previous, but with no predicate.
80      */
81     public static ListenableFuture<Intent> getFutureForIntent(Context context, String action) {
82         return getFutureForIntent(context, action, i -> true);
83     }
84 
85     /**
86      * Return a future for a callback registered to a listener interface.
87      * @param registerFunc - Function which consumes the callback object for registration
88      * @param unregisterFunc - Function which consumes the callback object for unregistration
89      * This function is called when the future is completed or cancelled
90      * @param instantiateCallback - Factory function for the callback object, provided a completer
91      * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
92      * to the future returned by this function
93      * @param debug - Debug string contained in future {@code toString} representation.
94      */
95     public static <T, V> ListenableFuture<T> getFutureForListener(
96             Consumer<V> registerFunc,
97             Consumer<V> unregisterFunc,
98             Function<CallbackToFutureAdapter.Completer<T>, V> instantiateCallback,
99             String debug) {
100         // Doesn't need to be thread safe since the resolver is called inline
101         final WeakReference<V> wrapper[] = new WeakReference[1];
102         ListenableFuture<T> future =
103                 CallbackToFutureAdapter.getFuture(
104                         completer -> {
105                             final var cb = instantiateCallback.apply(completer);
106                             wrapper[0] = new WeakReference(cb);
107                             registerFunc.accept(cb);
108                             return debug;
109                         });
110         if (wrapper[0] == null) {
111             throw new AssertionError("Resolver should be called inline");
112         }
113         final var weakref = wrapper[0];
114         future.addListener(
115                 () -> {
116                     var cb = weakref.get();
117                     // If there is no reference left, the receiver has already been unregistered
118                     if (cb != null) {
119                         unregisterFunc.accept(cb);
120                         return;
121                     }
122                 },
123                 MoreExecutors.directExecutor()); // Direct executor is fine since lightweight
124         return future;
125     }
126 }
127