]>
Commit | Line | Data |
---|---|---|
8dfb76c9 MG |
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 | ||
52 | ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) { | |
53 | filter = _filter; | |
54 | receiver = _receiver; | |
55 | } | |
56 | ||
57 | @Override | |
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 | ||
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 | ||
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 | ||
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 | */ | |
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 | */ | |
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 | */ | |
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 | */ | |
276 | public void sendBroadcastSync(Intent intent) { | |
277 | if (sendBroadcast(intent)) { | |
278 | executePendingBroadcasts(); | |
279 | } | |
280 | } | |
281 | ||
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 | } |