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