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