1 /* 2 * Copyright (C) 2021 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.telephony.mockmodem; 18 19 import android.hardware.radio.RadioError; 20 import android.hardware.radio.RadioIndicationType; 21 import android.hardware.radio.RadioResponseInfo; 22 import android.hardware.radio.messaging.IRadioMessaging; 23 import android.hardware.radio.messaging.IRadioMessagingIndication; 24 import android.hardware.radio.messaging.IRadioMessagingResponse; 25 import android.hardware.radio.messaging.SendSmsResult; 26 import android.os.RemoteException; 27 import android.support.annotation.GuardedBy; 28 import android.util.ArraySet; 29 import android.util.Log; 30 31 import java.util.Set; 32 import java.util.concurrent.CopyOnWriteArrayList; 33 import java.util.concurrent.Executor; 34 35 public class IRadioMessagingImpl extends IRadioMessaging.Stub { 36 private static final String TAG = "MRMSG"; 37 38 private final MockModemService mService; 39 private IRadioMessagingResponse mRadioMessagingResponse; 40 private IRadioMessagingIndication mRadioMessagingIndication; 41 42 @GuardedBy("mGsmBroadcastConfigSet") 43 private final Set<Integer> mGsmBroadcastConfigSet = new ArraySet<Integer>(); 44 45 @GuardedBy("mCdmaBroadcastConfigSet") 46 private final Set<Integer> mCdmaBroadcastConfigSet = new ArraySet<Integer>(); 47 48 private CopyOnWriteArrayList<CallBackWithExecutor> mBroadcastCallbacks = 49 new CopyOnWriteArrayList<>(); 50 51 private MockModemConfigInterface mMockModemConfigInterface; 52 private int mSubId; 53 private String mTag; 54 private final MockMessagingService mMockMessagingService; 55 56 public interface BroadcastCallback { onGsmBroadcastActivated()57 void onGsmBroadcastActivated(); onCdmaBroadcastActivated()58 void onCdmaBroadcastActivated(); 59 } 60 61 public static class CallBackWithExecutor { 62 public Executor mExecutor; 63 public BroadcastCallback mCallback; 64 CallBackWithExecutor(Executor executor, BroadcastCallback callback)65 public CallBackWithExecutor(Executor executor, BroadcastCallback callback) { 66 mExecutor = executor; 67 mCallback = callback; 68 } 69 } 70 IRadioMessagingImpl( MockModemService service, MockModemConfigInterface configInterface, int instanceId)71 public IRadioMessagingImpl( 72 MockModemService service, MockModemConfigInterface configInterface, int instanceId) { 73 mTag = TAG + "-" + instanceId; 74 Log.d(mTag, "Instantiated"); 75 76 this.mService = service; 77 mMockModemConfigInterface = configInterface; 78 mSubId = instanceId; 79 mMockMessagingService = new MockMessagingService(instanceId); 80 } 81 82 // Implementation of IRadioMessaging functions 83 @Override setResponseFunctions( IRadioMessagingResponse radioMessagingResponse, IRadioMessagingIndication radioMessagingIndication)84 public void setResponseFunctions( 85 IRadioMessagingResponse radioMessagingResponse, 86 IRadioMessagingIndication radioMessagingIndication) { 87 Log.d(mTag, "setResponseFunctions"); 88 mRadioMessagingResponse = radioMessagingResponse; 89 mRadioMessagingIndication = radioMessagingIndication; 90 mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY); 91 } 92 93 @Override acknowledgeIncomingGsmSmsWithPdu(int serial, boolean success, String ackPdu)94 public void acknowledgeIncomingGsmSmsWithPdu(int serial, boolean success, String ackPdu) { 95 Log.d(mTag, "acknowledgeIncomingGsmSmsWithPdu"); 96 97 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 98 try { 99 mRadioMessagingResponse.acknowledgeIncomingGsmSmsWithPduResponse(rsp); 100 } catch (RemoteException ex) { 101 Log.e(mTag, "Failed to acknowledgeIncomingGsmSmsWithPdu from AIDL. Exception" + ex); 102 } 103 } 104 105 @Override acknowledgeLastIncomingCdmaSms( int serial, android.hardware.radio.messaging.CdmaSmsAck smsAck)106 public void acknowledgeLastIncomingCdmaSms( 107 int serial, android.hardware.radio.messaging.CdmaSmsAck smsAck) { 108 Log.d(mTag, "acknowledgeLastIncomingCdmaSms"); 109 110 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 111 try { 112 mRadioMessagingResponse.acknowledgeLastIncomingCdmaSmsResponse(rsp); 113 } catch (RemoteException ex) { 114 Log.e(mTag, "Failed to acknowledgeLastIncomingCdmaSms from AIDL. Exception" + ex); 115 } 116 } 117 118 @Override acknowledgeLastIncomingGsmSms(int serial, boolean success, int cause)119 public void acknowledgeLastIncomingGsmSms(int serial, boolean success, int cause) { 120 Log.d(mTag, "acknowledgeLastIncomingGsmSms"); 121 122 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 123 try { 124 mRadioMessagingResponse.acknowledgeLastIncomingGsmSmsResponse(rsp); 125 } catch (RemoteException ex) { 126 Log.e(mTag, "Failed to acknowledgeLastIncomingGsmSms from AIDL. Exception" + ex); 127 } 128 } 129 130 @Override deleteSmsOnRuim(int serial, int index)131 public void deleteSmsOnRuim(int serial, int index) { 132 Log.d(mTag, "deleteSmsOnRuim"); 133 134 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 135 try { 136 mRadioMessagingResponse.deleteSmsOnRuimResponse(rsp); 137 } catch (RemoteException ex) { 138 Log.e(mTag, "Failed to deleteSmsOnRuim from AIDL. Exception" + ex); 139 } 140 } 141 142 @Override deleteSmsOnSim(int serial, int index)143 public void deleteSmsOnSim(int serial, int index) { 144 Log.d(mTag, "deleteSmsOnSim"); 145 146 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 147 try { 148 mRadioMessagingResponse.deleteSmsOnSimResponse(rsp); 149 } catch (RemoteException ex) { 150 Log.e(mTag, "Failed to deleteSmsOnSim from AIDL. Exception" + ex); 151 } 152 } 153 154 @Override getCdmaBroadcastConfig(int serial)155 public void getCdmaBroadcastConfig(int serial) { 156 Log.d(mTag, "getCdmaBroadcastConfig"); 157 158 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 159 try { 160 mRadioMessagingResponse.getCdmaBroadcastConfigResponse(rsp, null); 161 } catch (RemoteException ex) { 162 Log.e(mTag, "Failed to getCdmaBroadcastConfig from AIDL. Exception" + ex); 163 } 164 } 165 166 @Override getGsmBroadcastConfig(int serial)167 public void getGsmBroadcastConfig(int serial) { 168 Log.d(mTag, "getGsmBroadcastConfig"); 169 170 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 171 try { 172 mRadioMessagingResponse.getGsmBroadcastConfigResponse(rsp, null); 173 } catch (RemoteException ex) { 174 Log.e(mTag, "Failed to getGsmBroadcastConfig from AIDL. Exception" + ex); 175 } 176 } 177 178 @Override getSmscAddress(int serial)179 public void getSmscAddress(int serial) { 180 Log.d(mTag, "getSmscAddress"); 181 182 String smsc = mMockMessagingService.getSmscAddress(); 183 RadioResponseInfo rsp = mService.makeSolRsp(serial); 184 try { 185 mRadioMessagingResponse.getSmscAddressResponse(rsp, smsc); 186 } catch (RemoteException ex) { 187 Log.e(mTag, "Failed to getSmscAddress from AIDL. Exception" + ex); 188 } 189 } 190 191 @Override reportSmsMemoryStatus(int serial, boolean available)192 public void reportSmsMemoryStatus(int serial, boolean available) { 193 Log.d(mTag, "reportSmsMemoryStatus"); 194 195 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 196 try { 197 mRadioMessagingResponse.reportSmsMemoryStatusResponse(rsp); 198 } catch (RemoteException ex) { 199 Log.e(mTag, "Failed to reportSmsMemoryStatus from AIDL. Exception" + ex); 200 } 201 } 202 203 @Override responseAcknowledgement()204 public void responseAcknowledgement() { 205 Log.d(mTag, "responseAcknowledgement"); 206 // TODO 207 } 208 209 @Override sendCdmaSms(int serial, android.hardware.radio.messaging.CdmaSmsMessage sms)210 public void sendCdmaSms(int serial, android.hardware.radio.messaging.CdmaSmsMessage sms) { 211 Log.d(mTag, "sendCdmaSms"); 212 213 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 214 try { 215 mRadioMessagingResponse.sendCdmaSmsResponse(rsp, null); 216 } catch (RemoteException ex) { 217 Log.e(mTag, "Failed to sendCdmaSms from AIDL. Exception" + ex); 218 } 219 } 220 221 @Override sendCdmaSmsExpectMore( int serial, android.hardware.radio.messaging.CdmaSmsMessage sms)222 public void sendCdmaSmsExpectMore( 223 int serial, android.hardware.radio.messaging.CdmaSmsMessage sms) { 224 Log.d(mTag, "sendCdmaSmsExpectMore"); 225 226 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 227 try { 228 mRadioMessagingResponse.sendCdmaSmsExpectMoreResponse(rsp, null); 229 } catch (RemoteException ex) { 230 Log.e(mTag, "Failed to sendCdmaSmsExpectMore from AIDL. Exception" + ex); 231 } 232 } 233 234 @Override sendImsSms(int serial, android.hardware.radio.messaging.ImsSmsMessage message)235 public void sendImsSms(int serial, android.hardware.radio.messaging.ImsSmsMessage message) { 236 Log.d(mTag, "sendImsSms"); 237 238 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 239 try { 240 mRadioMessagingResponse.sendImsSmsResponse(rsp, null); 241 } catch (RemoteException ex) { 242 Log.e(mTag, "Failed to sendImsSms from AIDL. Exception" + ex); 243 } 244 } 245 246 @Override sendSms(int serial, android.hardware.radio.messaging.GsmSmsMessage message)247 public void sendSms(int serial, android.hardware.radio.messaging.GsmSmsMessage message) { 248 Log.d(mTag, "sendSms"); 249 250 android.hardware.radio.messaging.SendSmsResult sms = new SendSmsResult(); 251 sms.messageRef = 0; 252 sms.ackPDU = "ack"; 253 sms.errorCode = 0; 254 RadioResponseInfo rsp = mService.makeSolRsp(serial); 255 try { 256 mRadioMessagingResponse.sendSmsResponse(rsp, sms); 257 } catch (RemoteException ex) { 258 Log.e(mTag, "Failed to sendSms from AIDL. Exception" + ex); 259 } 260 } 261 262 @Override sendSmsExpectMore( int serial, android.hardware.radio.messaging.GsmSmsMessage message)263 public void sendSmsExpectMore( 264 int serial, android.hardware.radio.messaging.GsmSmsMessage message) { 265 Log.d(mTag, "sendSmsExpectMore"); 266 267 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 268 try { 269 mRadioMessagingResponse.sendSmsExpectMoreResponse(rsp, null); 270 } catch (RemoteException ex) { 271 Log.e(mTag, "Failed to sendSmsExpectMore from AIDL. Exception" + ex); 272 } 273 } 274 275 @Override setCdmaBroadcastActivation(int serial, boolean activate)276 public void setCdmaBroadcastActivation(int serial, boolean activate) { 277 Log.d(mTag, "setCdmaBroadcastActivation, activate = " + activate); 278 279 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.NONE); 280 try { 281 mRadioMessagingResponse.setCdmaBroadcastActivationResponse(rsp); 282 } catch (RemoteException ex) { 283 Log.e(mTag, "Failed to setCdmaBroadcastActivation from AIDL. Exception" + ex); 284 } 285 if (activate) { 286 for (CallBackWithExecutor callbackWithExecutor : mBroadcastCallbacks) { 287 callbackWithExecutor.mExecutor.execute( 288 () -> callbackWithExecutor.mCallback.onCdmaBroadcastActivated()); 289 } 290 } 291 } 292 293 @Override setCdmaBroadcastConfig( int serial, android.hardware.radio.messaging.CdmaBroadcastSmsConfigInfo[] configInfo)294 public void setCdmaBroadcastConfig( 295 int serial, android.hardware.radio.messaging.CdmaBroadcastSmsConfigInfo[] configInfo) { 296 Log.d(mTag, "setCdmaBroadcastConfig"); 297 298 int error = RadioError.NONE; 299 if (configInfo == null) { 300 error = RadioError.INVALID_ARGUMENTS; 301 } else { 302 synchronized (mCdmaBroadcastConfigSet) { 303 mCdmaBroadcastConfigSet.clear(); 304 for (int i = 0; i < configInfo.length; i++) { 305 Log.d(mTag, "configInfo serviceCategory" + configInfo[i].serviceCategory); 306 mCdmaBroadcastConfigSet.add(configInfo[i].serviceCategory); 307 } 308 } 309 } 310 RadioResponseInfo rsp = mService.makeSolRsp(serial, error); 311 try { 312 mRadioMessagingResponse.setCdmaBroadcastConfigResponse(rsp); 313 } catch (RemoteException ex) { 314 Log.e(mTag, "Failed to setCdmaBroadcastConfig from AIDL. Exception" + ex); 315 } 316 } 317 318 @Override setGsmBroadcastActivation(int serial, boolean activate)319 public void setGsmBroadcastActivation(int serial, boolean activate) { 320 Log.d(mTag, "setGsmBroadcastActivation, activate = " + activate); 321 322 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.NONE); 323 try { 324 mRadioMessagingResponse.setGsmBroadcastActivationResponse(rsp); 325 } catch (RemoteException ex) { 326 Log.e(mTag, "Failed to setGsmBroadcastActivation from AIDL. Exception" + ex); 327 } 328 if (activate) { 329 for (CallBackWithExecutor callbackWithExecutor : mBroadcastCallbacks) { 330 callbackWithExecutor.mExecutor.execute( 331 () -> callbackWithExecutor.mCallback.onGsmBroadcastActivated()); 332 } 333 } 334 } 335 336 @Override setGsmBroadcastConfig( int serial, android.hardware.radio.messaging.GsmBroadcastSmsConfigInfo[] configInfo)337 public void setGsmBroadcastConfig( 338 int serial, android.hardware.radio.messaging.GsmBroadcastSmsConfigInfo[] configInfo) { 339 Log.d(mTag, "setGsmBroadcastConfig"); 340 341 int error = RadioError.NONE; 342 if (configInfo == null) { 343 error = RadioError.INVALID_ARGUMENTS; 344 } else { 345 synchronized (mGsmBroadcastConfigSet) { 346 mGsmBroadcastConfigSet.clear(); 347 for (int i = 0; i < configInfo.length; i++) { 348 int startId = configInfo[i].fromServiceId; 349 int endId = configInfo[i].toServiceId; 350 boolean selected = configInfo[i].selected; 351 Log.d( 352 mTag, 353 "configInfo from: " 354 + startId 355 + ", to: " 356 + endId 357 + ", selected: " 358 + selected); 359 if (selected) { 360 for (int j = startId; j <= endId; j++) { 361 mGsmBroadcastConfigSet.add(j); 362 } 363 } 364 } 365 } 366 } 367 RadioResponseInfo rsp = mService.makeSolRsp(serial, error); 368 try { 369 mRadioMessagingResponse.setGsmBroadcastConfigResponse(rsp); 370 } catch (RemoteException ex) { 371 Log.e(mTag, "Failed to setGsmBroadcastConfig from AIDL. Exception" + ex); 372 } 373 } 374 375 @Override setSmscAddress(int serial, String smsc)376 public void setSmscAddress(int serial, String smsc) { 377 Log.d(mTag, "setSmscAddress"); 378 379 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 380 try { 381 mRadioMessagingResponse.setSmscAddressResponse(rsp); 382 } catch (RemoteException ex) { 383 Log.e(mTag, "Failed to setSmscAddress from AIDL. Exception" + ex); 384 } 385 } 386 387 @Override writeSmsToRuim( int serial, android.hardware.radio.messaging.CdmaSmsWriteArgs cdmaSms)388 public void writeSmsToRuim( 389 int serial, android.hardware.radio.messaging.CdmaSmsWriteArgs cdmaSms) { 390 Log.d(mTag, "writeSmsToRuim"); 391 392 int index = 0; 393 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 394 try { 395 mRadioMessagingResponse.writeSmsToRuimResponse(rsp, index); 396 } catch (RemoteException ex) { 397 Log.e(mTag, "Failed to writeSmsToRuim from AIDL. Exception" + ex); 398 } 399 } 400 401 @Override writeSmsToSim( int serial, android.hardware.radio.messaging.SmsWriteArgs smsWriteArgs)402 public void writeSmsToSim( 403 int serial, android.hardware.radio.messaging.SmsWriteArgs smsWriteArgs) { 404 Log.d(mTag, "writeSmsToSim"); 405 406 int index = 0; 407 RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED); 408 try { 409 mRadioMessagingResponse.writeSmsToSimResponse(rsp, index); 410 } catch (RemoteException ex) { 411 Log.e(mTag, "Failed to writeSmsToSim from AIDL. Exception" + ex); 412 } 413 } 414 cdmaNewSms(android.hardware.radio.messaging.CdmaSmsMessage msg)415 public void cdmaNewSms(android.hardware.radio.messaging.CdmaSmsMessage msg) { 416 Log.d(mTag, "cdmaNewSms"); 417 418 if (mRadioMessagingIndication != null) { 419 try { 420 mRadioMessagingIndication.cdmaNewSms(RadioIndicationType.UNSOLICITED, msg); 421 } catch (RemoteException ex) { 422 Log.e(mTag, "Failed to cdmaNewSms indication from AIDL. Exception" + ex); 423 } 424 } else { 425 Log.e(mTag, "null mRadioMessagingIndication"); 426 } 427 } 428 cdmaRuimSmsStorageFull()429 public void cdmaRuimSmsStorageFull() { 430 Log.d(mTag, "cdmaRuimSmsStorageFull"); 431 432 if (mRadioMessagingIndication != null) { 433 try { 434 mRadioMessagingIndication.cdmaRuimSmsStorageFull(RadioIndicationType.UNSOLICITED); 435 } catch (RemoteException ex) { 436 Log.e( 437 mTag, 438 "Failed to cdmaRuimSmsStorageFull indication from AIDL. Exception" + ex); 439 } 440 } else { 441 Log.e(mTag, "null mRadioMessagingIndication"); 442 } 443 } 444 newBroadcastSms(byte[] data)445 public void newBroadcastSms(byte[] data) { 446 Log.d(mTag, "newBroadcastSms"); 447 448 if (mRadioMessagingIndication != null) { 449 try { 450 mRadioMessagingIndication.newBroadcastSms(RadioIndicationType.UNSOLICITED, data); 451 } catch (RemoteException ex) { 452 Log.e(mTag, "Failed to newBroadcastSms indication from AIDL. Exception" + ex); 453 } 454 } else { 455 Log.e(mTag, "null mRadioMessagingIndication"); 456 } 457 } 458 newSms(byte[] pdu)459 public void newSms(byte[] pdu) { 460 Log.d(mTag, "newSms"); 461 462 if (mRadioMessagingIndication != null) { 463 try { 464 mRadioMessagingIndication.newSms(RadioIndicationType.UNSOLICITED, pdu); 465 } catch (RemoteException ex) { 466 Log.e(mTag, "Failed to newSms indication from AIDL. Exception" + ex); 467 } 468 } else { 469 Log.e(mTag, "null mRadioMessagingIndication"); 470 } 471 } 472 newSmsOnSim(int recordNumber)473 public void newSmsOnSim(int recordNumber) { 474 Log.d(mTag, "newSmsOnSim"); 475 476 if (mRadioMessagingIndication != null) { 477 try { 478 mRadioMessagingIndication.newSmsOnSim( 479 RadioIndicationType.UNSOLICITED, recordNumber); 480 } catch (RemoteException ex) { 481 Log.e(mTag, "Failed to newSmsOnSim indication from AIDL. Exception" + ex); 482 } 483 } else { 484 Log.e(mTag, "null mRadioMessagingIndication"); 485 } 486 } 487 newSmsStatusReport(byte[] pdu)488 public void newSmsStatusReport(byte[] pdu) { 489 Log.d(mTag, "newSmsStatusReport"); 490 491 if (mRadioMessagingIndication != null) { 492 try { 493 mRadioMessagingIndication.newSmsStatusReport(RadioIndicationType.UNSOLICITED, pdu); 494 } catch (RemoteException ex) { 495 Log.e(mTag, "Failed to newSmsStatusReport indication from AIDL. Exception" + ex); 496 } 497 } else { 498 Log.e(mTag, "null mRadioMessagingIndication"); 499 } 500 } 501 simSmsStorageFull()502 public void simSmsStorageFull() { 503 Log.d(mTag, "simSmsStorageFull"); 504 505 if (mRadioMessagingIndication != null) { 506 try { 507 mRadioMessagingIndication.simSmsStorageFull(RadioIndicationType.UNSOLICITED); 508 } catch (RemoteException ex) { 509 Log.e(mTag, "Failed to simSmsStorageFull indication from AIDL. Exception" + ex); 510 } 511 } else { 512 Log.e(mTag, "null mRadioMessagingIndication"); 513 } 514 } 515 516 @Override getInterfaceHash()517 public String getInterfaceHash() { 518 return IRadioMessaging.HASH; 519 } 520 521 @Override getInterfaceVersion()522 public int getInterfaceVersion() { 523 return IRadioMessaging.VERSION; 524 } 525 getGsmBroadcastConfigSet()526 public Set<Integer> getGsmBroadcastConfigSet() { 527 synchronized (mGsmBroadcastConfigSet) { 528 Log.d(mTag, "getBroadcastConfigSet. " + mGsmBroadcastConfigSet); 529 return mGsmBroadcastConfigSet; 530 } 531 } 532 getCdmaBroadcastConfigSet()533 public Set<Integer> getCdmaBroadcastConfigSet() { 534 synchronized (mCdmaBroadcastConfigSet) { 535 Log.d(mTag, "getBroadcastConfigSet. " + mCdmaBroadcastConfigSet); 536 return mCdmaBroadcastConfigSet; 537 } 538 } 539 registerBroadcastCallback(CallBackWithExecutor callback)540 public void registerBroadcastCallback(CallBackWithExecutor callback) { 541 mBroadcastCallbacks.add(callback); 542 } unregisterBroadcastCallback(CallBackWithExecutor callback)543 public void unregisterBroadcastCallback(CallBackWithExecutor callback) { 544 mBroadcastCallbacks.remove(callback); 545 } 546 } 547