• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }