1 /*
2  * Copyright (C) 2011 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 android.support.v4.content;
18 
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.Set;
22 
23 import android.content.BroadcastReceiver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.IntentFilter;
27 import android.net.Uri;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.util.Log;
31 
32 /**
33  * Helper to register for and send broadcasts of Intents to local objects
34  * within your process.  This is has a number of advantages over sending
35  * global broadcasts with {@link android.content.Context#sendBroadcast}:
36  * <ul>
37  * <li> You know that the data you are broadcasting won't leave your app, so
38  * don't need to worry about leaking private data.
39  * <li> It is not possible for other applications to send these broadcasts to
40  * your app, so you don't need to worry about having security holes they can
41  * exploit.
42  * <li> It is more efficient than sending a global broadcast through the
43  * system.
44  * </ul>
45  */
46 public class LocalBroadcastManager {
47     private static class ReceiverRecord {
48         final IntentFilter filter;
49         final BroadcastReceiver receiver;
50         boolean broadcasting;
51 
ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver)52         ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
53             filter = _filter;
54             receiver = _receiver;
55         }
56 
57         @Override
toString()58         public String toString() {
59             StringBuilder builder = new StringBuilder(128);
60             builder.append("Receiver{");
61             builder.append(receiver);
62             builder.append(" filter=");
63             builder.append(filter);
64             builder.append("}");
65             return builder.toString();
66         }
67     }
68 
69     private static class BroadcastRecord {
70         final Intent intent;
71         final ArrayList<ReceiverRecord> receivers;
72 
BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers)73         BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
74             intent = _intent;
75             receivers = _receivers;
76         }
77     }
78 
79     private static final String TAG = "LocalBroadcastManager";
80     private static final boolean DEBUG = false;
81 
82     private final Context mAppContext;
83 
84     private final HashMap<BroadcastReceiver, ArrayList<IntentFilter>> mReceivers
85             = new HashMap<BroadcastReceiver, ArrayList<IntentFilter>>();
86     private final HashMap<String, ArrayList<ReceiverRecord>> mActions
87             = new HashMap<String, ArrayList<ReceiverRecord>>();
88 
89     private final ArrayList<BroadcastRecord> mPendingBroadcasts
90             = new ArrayList<BroadcastRecord>();
91 
92     static final int MSG_EXEC_PENDING_BROADCASTS = 1;
93 
94     private final Handler mHandler;
95 
96     private static final Object mLock = new Object();
97     private static LocalBroadcastManager mInstance;
98 
getInstance(Context context)99     public static LocalBroadcastManager getInstance(Context context) {
100         synchronized (mLock) {
101             if (mInstance == null) {
102                 mInstance = new LocalBroadcastManager(context.getApplicationContext());
103             }
104             return mInstance;
105         }
106     }
107 
LocalBroadcastManager(Context context)108     private LocalBroadcastManager(Context context) {
109         mAppContext = context;
110         mHandler = new Handler(context.getMainLooper()) {
111 
112             @Override
113             public void handleMessage(Message msg) {
114                 switch (msg.what) {
115                     case MSG_EXEC_PENDING_BROADCASTS:
116                         executePendingBroadcasts();
117                         break;
118                     default:
119                         super.handleMessage(msg);
120                 }
121             }
122         };
123     }
124 
125     /**
126      * Register a receive for any local broadcasts that match the given IntentFilter.
127      *
128      * @param receiver The BroadcastReceiver to handle the broadcast.
129      * @param filter Selects the Intent broadcasts to be received.
130      *
131      * @see #unregisterReceiver
132      */
registerReceiver(BroadcastReceiver receiver, IntentFilter filter)133     public void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
134         synchronized (mReceivers) {
135             ReceiverRecord entry = new ReceiverRecord(filter, receiver);
136             ArrayList<IntentFilter> filters = mReceivers.get(receiver);
137             if (filters == null) {
138                 filters = new ArrayList<IntentFilter>(1);
139                 mReceivers.put(receiver, filters);
140             }
141             filters.add(filter);
142             for (int i=0; i<filter.countActions(); i++) {
143                 String action = filter.getAction(i);
144                 ArrayList<ReceiverRecord> entries = mActions.get(action);
145                 if (entries == null) {
146                     entries = new ArrayList<ReceiverRecord>(1);
147                     mActions.put(action, entries);
148                 }
149                 entries.add(entry);
150             }
151         }
152     }
153 
154     /**
155      * Unregister a previously registered BroadcastReceiver.  <em>All</em>
156      * filters that have been registered for this BroadcastReceiver will be
157      * removed.
158      *
159      * @param receiver The BroadcastReceiver to unregister.
160      *
161      * @see #registerReceiver
162      */
unregisterReceiver(BroadcastReceiver receiver)163     public void unregisterReceiver(BroadcastReceiver receiver) {
164         synchronized (mReceivers) {
165             ArrayList<IntentFilter> filters = mReceivers.remove(receiver);
166             if (filters == null) {
167                 return;
168             }
169             for (int i=0; i<filters.size(); i++) {
170                 IntentFilter filter = filters.get(i);
171                 for (int j=0; j<filter.countActions(); j++) {
172                     String action = filter.getAction(j);
173                     ArrayList<ReceiverRecord> receivers = mActions.get(action);
174                     if (receivers != null) {
175                         for (int k=0; k<receivers.size(); k++) {
176                             if (receivers.get(k).receiver == receiver) {
177                                 receivers.remove(k);
178                                 k--;
179                             }
180                         }
181                         if (receivers.size() <= 0) {
182                             mActions.remove(action);
183                         }
184                     }
185                 }
186             }
187         }
188     }
189 
190     /**
191      * Broadcast the given intent to all interested BroadcastReceivers.  This
192      * call is asynchronous; it returns immediately, and you will continue
193      * executing while the receivers are run.
194      *
195      * @param intent The Intent to broadcast; all receivers matching this
196      *     Intent will receive the broadcast.
197      *
198      * @see #registerReceiver
199      */
sendBroadcast(Intent intent)200     public boolean sendBroadcast(Intent intent) {
201         synchronized (mReceivers) {
202             final String action = intent.getAction();
203             final String type = intent.resolveTypeIfNeeded(
204                     mAppContext.getContentResolver());
205             final Uri data = intent.getData();
206             final String scheme = intent.getScheme();
207             final Set<String> categories = intent.getCategories();
208 
209             final boolean debug = DEBUG ||
210                     ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
211             if (debug) Log.v(
212                     TAG, "Resolving type " + type + " scheme " + scheme
213                     + " of intent " + intent);
214 
215             ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
216             if (entries != null) {
217                 if (debug) Log.v(TAG, "Action list: " + entries);
218 
219                 ArrayList<ReceiverRecord> receivers = null;
220                 for (int i=0; i<entries.size(); i++) {
221                     ReceiverRecord receiver = entries.get(i);
222                     if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);
223 
224                     if (receiver.broadcasting) {
225                         if (debug) {
226                             Log.v(TAG, "  Filter's target already added");
227                         }
228                         continue;
229                     }
230 
231                     int match = receiver.filter.match(action, type, scheme, data,
232                             categories, "LocalBroadcastManager");
233                     if (match >= 0) {
234                         if (debug) Log.v(TAG, "  Filter matched!  match=0x" +
235                                 Integer.toHexString(match));
236                         if (receivers == null) {
237                             receivers = new ArrayList<ReceiverRecord>();
238                         }
239                         receivers.add(receiver);
240                         receiver.broadcasting = true;
241                     } else {
242                         if (debug) {
243                             String reason;
244                             switch (match) {
245                                 case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
246                                 case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
247                                 case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
248                                 case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
249                                 default: reason = "unknown reason"; break;
250                             }
251                             Log.v(TAG, "  Filter did not match: " + reason);
252                         }
253                     }
254                 }
255 
256                 if (receivers != null) {
257                     for (int i=0; i<receivers.size(); i++) {
258                         receivers.get(i).broadcasting = false;
259                     }
260                     mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
261                     if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
262                         mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
263                     }
264                     return true;
265                 }
266             }
267         }
268         return false;
269     }
270 
271     /**
272      * Like {@link #sendBroadcast(Intent)}, but if there are any receivers for
273      * the Intent this function will block and immediately dispatch them before
274      * returning.
275      */
sendBroadcastSync(Intent intent)276     public void sendBroadcastSync(Intent intent) {
277         if (sendBroadcast(intent)) {
278             executePendingBroadcasts();
279         }
280     }
281 
executePendingBroadcasts()282     private void executePendingBroadcasts() {
283         while (true) {
284             BroadcastRecord[] brs = null;
285             synchronized (mReceivers) {
286                 final int N = mPendingBroadcasts.size();
287                 if (N <= 0) {
288                     return;
289                 }
290                 brs = new BroadcastRecord[N];
291                 mPendingBroadcasts.toArray(brs);
292                 mPendingBroadcasts.clear();
293             }
294             for (int i=0; i<brs.length; i++) {
295                 BroadcastRecord br = brs[i];
296                 for (int j=0; j<br.receivers.size(); j++) {
297                     br.receivers.get(j).receiver.onReceive(mAppContext, br.intent);
298                 }
299             }
300         }
301     }
302 }
303