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