1 /* 2 * Copyright (C) 2017 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 package com.android.compatibility.common.util; 17 18 import android.content.BroadcastReceiver; 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.IntentFilter; 22 import android.util.Log; 23 24 import androidx.annotation.Nullable; 25 26 import java.util.Set; 27 import java.util.concurrent.ArrayBlockingQueue; 28 import java.util.concurrent.BlockingQueue; 29 import java.util.concurrent.TimeUnit; 30 import java.util.function.Function; 31 32 /** 33 * A receiver that allows caller to wait for the broadcast synchronously. Notice that you should not 34 * reuse the instance. Usage is typically like this: 35 * <pre> 36 * BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context, "action"); 37 * try { 38 * receiver.register(); 39 * // Action which triggers intent 40 * Intent intent = receiver.awaitForBroadcast(); 41 * // assert the intent 42 * } finally { 43 * receiver.unregisterQuietly(); 44 * } 45 * </pre> 46 * 47 * If you do not care what intent is broadcast and just wish to block until a matching intent is 48 * received you can use alternative syntax: 49 * <pre> 50 * try (BlockingBroadcastReceiver r = 51 * BlockingBroadcastReceiver.create(context, "action").register()) { 52 * // Action which triggers intent 53 * } 54 * 55 * // Code which should be executed after broadcast is received 56 * </pre> 57 * 58 * If the broadcast is not received an exception will be thrown. Note that if an exception is thrown 59 * within the try block which results in the broadcast not being sent, then that exception will be 60 * hidden by the not-received exception. 61 */ 62 public class BlockingBroadcastReceiver extends BroadcastReceiver implements AutoCloseable { 63 private static final String TAG = "BlockingBroadcast"; 64 65 private int mTimeoutSeconds = 240; // 4 minutes by default 66 67 private Intent mReceivedIntent = null; 68 private final BlockingQueue<Intent> mBlockingQueue; 69 private final Set<IntentFilter> mIntentFilters; 70 private final Context mContext; 71 @Nullable 72 private final Function<Intent, Boolean> mChecker; 73 create(Context context, String expectedAction)74 public static BlockingBroadcastReceiver create(Context context, String expectedAction) { 75 return create(context, new IntentFilter(expectedAction)); 76 } 77 create(Context context, IntentFilter intentFilter)78 public static BlockingBroadcastReceiver create(Context context, IntentFilter intentFilter) { 79 return create(context, intentFilter, /* checker= */ null); 80 } 81 create(Context context, String expectedAction, Function<Intent, Boolean> checker)82 public static BlockingBroadcastReceiver create(Context context, String expectedAction, 83 Function<Intent, Boolean> checker) { 84 return create(context, new IntentFilter(expectedAction), checker); 85 } 86 create(Context context, IntentFilter intentFilter, Function<Intent, Boolean> checker)87 public static BlockingBroadcastReceiver create(Context context, IntentFilter intentFilter, 88 Function<Intent, Boolean> checker) { 89 return create(context, Set.of(intentFilter), checker); 90 } 91 create(Context context, Set<IntentFilter> intentFilters)92 public static BlockingBroadcastReceiver create(Context context, 93 Set<IntentFilter> intentFilters) { 94 return create(context, intentFilters, /* checker= */ null); 95 } 96 create(Context context, Set<IntentFilter> intentFilters, Function<Intent, Boolean> checker)97 public static BlockingBroadcastReceiver create(Context context, Set<IntentFilter> intentFilters, 98 Function<Intent, Boolean> checker) { 99 BlockingBroadcastReceiver blockingBroadcastReceiver = 100 new BlockingBroadcastReceiver(context, intentFilters, checker); 101 102 return blockingBroadcastReceiver; 103 } 104 BlockingBroadcastReceiver(Context context)105 public BlockingBroadcastReceiver(Context context) { 106 this(context, Set.of()); 107 } 108 BlockingBroadcastReceiver(Context context, String expectedAction)109 public BlockingBroadcastReceiver(Context context, String expectedAction) { 110 this(context, new IntentFilter(expectedAction)); 111 } 112 BlockingBroadcastReceiver(Context context, IntentFilter intentFilter)113 public BlockingBroadcastReceiver(Context context, IntentFilter intentFilter) { 114 this(context, intentFilter, /* checker= */ null); 115 } 116 BlockingBroadcastReceiver(Context context, IntentFilter intentFilter, Function<Intent, Boolean> checker)117 public BlockingBroadcastReceiver(Context context, IntentFilter intentFilter, 118 Function<Intent, Boolean> checker) { 119 this(context, Set.of(intentFilter), checker); 120 } 121 BlockingBroadcastReceiver(Context context, String expectedAction, Function<Intent, Boolean> checker)122 public BlockingBroadcastReceiver(Context context, String expectedAction, 123 Function<Intent, Boolean> checker) { 124 this(context, new IntentFilter(expectedAction), checker); 125 } 126 BlockingBroadcastReceiver(Context context, Set<IntentFilter> intentFilters)127 public BlockingBroadcastReceiver(Context context, Set<IntentFilter> intentFilters) { 128 this(context, intentFilters, /* checker= */ null); 129 } 130 BlockingBroadcastReceiver( Context context, Set<IntentFilter> intentFilters, Function<Intent, Boolean> checker)131 public BlockingBroadcastReceiver( 132 Context context, Set<IntentFilter> intentFilters, Function<Intent, Boolean> checker) { 133 mContext = context; 134 mIntentFilters = intentFilters; 135 mBlockingQueue = new ArrayBlockingQueue<>(1); 136 mChecker = checker; 137 } 138 139 @Override onReceive(Context context, Intent intent)140 public void onReceive(Context context, Intent intent) { 141 Log.i(TAG, "Received intent: " + intent); 142 143 if (mBlockingQueue.remainingCapacity() == 0) { 144 Log.i(TAG, "Received intent " + intent + " but queue is full."); 145 return; 146 } 147 148 if (mChecker == null || mChecker.apply(intent)) { 149 mBlockingQueue.add(intent); 150 } 151 } 152 register()153 public BlockingBroadcastReceiver register() { 154 for (IntentFilter intentFilter : mIntentFilters) { 155 mContext.registerReceiver(this, intentFilter, 156 Context.RECEIVER_EXPORTED_UNAUDITED); 157 } 158 159 return this; 160 } 161 registerForAllUsers()162 public BlockingBroadcastReceiver registerForAllUsers() { 163 for (IntentFilter intentFilter : mIntentFilters) { 164 mContext.registerReceiverForAllUsers( 165 this, intentFilter, /* broadcastPermission= */ null, 166 /* scheduler= */ null, 167 Context.RECEIVER_EXPORTED_UNAUDITED); 168 } 169 170 return this; 171 } 172 173 /** 174 * Wait until the broadcast. 175 * 176 * <p>If no matching broadcasts is received within {@link #mTimeoutSeconds} seconds an 177 * {@link AssertionError} will be thrown. 178 */ awaitForBroadcastOrFail()179 public void awaitForBroadcastOrFail() { 180 awaitForBroadcastOrFail(mTimeoutSeconds * 1000); 181 } 182 183 /** 184 * Wait until the broadcast and return the received broadcast intent. {@code null} is returned 185 * if no broadcast with expected action is received within {@link #mTimeoutSeconds} seconds. 186 */ 187 public @Nullable awaitForBroadcast()188 Intent awaitForBroadcast() { 189 return awaitForBroadcast(mTimeoutSeconds * 1000); 190 } 191 192 /** 193 * Wait until the broadcast. 194 * 195 * <p>If no matching broadcasts is received within the given timeout an {@link AssertionError} 196 * will be thrown. 197 */ awaitForBroadcastOrFail(long timeoutMillis)198 public void awaitForBroadcastOrFail(long timeoutMillis) { 199 if (awaitForBroadcast(timeoutMillis) == null) { 200 throw new AssertionError("Did not receive matching broadcast"); 201 } 202 } 203 204 /** 205 * Wait until the broadcast and return the received broadcast intent. {@code null} is returned 206 * if no broadcast with expected action is received within the given timeout. 207 */ 208 public @Nullable awaitForBroadcast(long timeoutMillis)209 Intent awaitForBroadcast(long timeoutMillis) { 210 if (mReceivedIntent != null) { 211 return mReceivedIntent; 212 } 213 214 try { 215 mReceivedIntent = mBlockingQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS); 216 } catch (InterruptedException e) { 217 Log.e(TAG, "waitForBroadcast get interrupted: ", e); 218 } 219 return mReceivedIntent; 220 } 221 unregisterQuietly()222 public void unregisterQuietly() { 223 try { 224 mContext.unregisterReceiver(this); 225 } catch (Exception ex) { 226 Log.e(TAG, "Failed to unregister BlockingBroadcastReceiver: ", ex); 227 } 228 } 229 230 @Override close()231 public void close() { 232 try { 233 awaitForBroadcastOrFail(); 234 } finally { 235 unregisterQuietly(); 236 } 237 } 238 239 /** 240 * Change the default timeout to wait for the broadcast. It's 4 minutes by default. 241 * @param timeoutSeconds new timeout value, in seconds. 242 */ setTimeout(int timeoutSeconds)243 public void setTimeout(int timeoutSeconds) { 244 if (timeoutSeconds <= 0) { 245 throw new IllegalArgumentException("Timeout must be a positive number."); 246 } 247 mTimeoutSeconds = timeoutSeconds; 248 } 249 }