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 }