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