Loading android/app/aidl/android/bluetooth/IBluetoothVolumeControl.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -75,4 +75,6 @@ oneway interface IBluetoothVolumeControl { void registerCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void unregisterCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void notifyNewRegisteredCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); } android/app/src/com/android/bluetooth/vc/VolumeControlService.java +58 −13 Original line number Diff line number Diff line Loading @@ -807,25 +807,35 @@ public class VolumeControlService extends ProfileService { continue; } } // notify volume level for all vc devices if (mFeatureFlags.leaudioBroadcastVolumeControlForConnectedDevices()) { notifyDevicesVolumeChanged(getDevices(), Optional.empty()); } } tempCallbackList.finishBroadcast(); if (mFeatureFlags.leaudioBroadcastVolumeControlForConnectedDevices()) { notifyDevicesVolumeChanged(tempCallbackList, getDevices(), Optional.empty()); } /* User is notified, remove callback from temporary list */ tempCallbackList.unregister(callback); } void registerCallback(IBluetoothVolumeControlCallback callback) { if (DBG) { Log.d(TAG, "registerCallback: " + callback); } /* Here we keep all the user callbacks */ mCallbacks.register(callback); notifyNewCallbackOfKnownVolumeInfo(callback); } void notifyNewRegisteredCallback(IBluetoothVolumeControlCallback callback) { if (DBG) { Log.d(TAG, "notifyNewRegisteredCallback: " + callback); } notifyNewCallbackOfKnownVolumeInfo(callback); } /** * {@hide} */ Loading Loading @@ -942,13 +952,15 @@ public class VolumeControlService extends ProfileService { LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService != null) { notifyDevicesVolumeChanged( leAudioService.getGroupDevices(groupId), Optional.of(volume)); mCallbacks, leAudioService.getGroupDevices(groupId), Optional.of(volume)); } else { Log.w(TAG, "leAudioService not available"); } } else { // notify device volume changed notifyDevicesVolumeChanged(Arrays.asList(device), Optional.of(volume)); notifyDevicesVolumeChanged(mCallbacks, Arrays.asList(device), Optional.of(volume)); } } Loading Loading @@ -1230,13 +1242,16 @@ public class VolumeControlService extends ProfileService { * newly registered callback, volume level is unknown from caller, notify the clients with * cached volume level from either device or group. * * @param callbacks list of callbacks * @param devices list of devices to notify volume changed * @param volume volume level */ private void notifyDevicesVolumeChanged( List<BluetoothDevice> devices, Optional<Integer> volume) { if (mCallbacks == null) { Log.e(TAG, "mCallbacks is null"); RemoteCallbackList<IBluetoothVolumeControlCallback> callbacks, List<BluetoothDevice> devices, Optional<Integer> volume) { if (callbacks == null) { Log.e(TAG, "callbacks is null"); return; } Loading @@ -1260,20 +1275,20 @@ public class VolumeControlService extends ProfileService { cachedVolume = getGroupVolume(groupId); } } int n = mCallbacks.beginBroadcast(); int n = callbacks.beginBroadcast(); for (int i = 0; i < n; i++) { try { if (!volume.isPresent()) { mCallbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, cachedVolume); callbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, cachedVolume); } else { mDeviceVolumeCache.put(dev, volume.get()); mCallbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, volume.get()); callbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, volume.get()); } } catch (RemoteException e) { continue; } } mCallbacks.finishBroadcast(); callbacks.finishBroadcast(); } } Loading Loading @@ -1763,6 +1778,36 @@ public class VolumeControlService extends ProfileService { } } @Override public void notifyNewRegisteredCallback( IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver) { try { Objects.requireNonNull(callback, "callback cannot be null"); Objects.requireNonNull(source, "source cannot be null"); Objects.requireNonNull(receiver, "receiver cannot be null"); VolumeControlService service = getService(source); if (service == null) { throw new IllegalStateException("Service is unavailable"); } enforceBluetoothPrivilegedPermission(service); service.mHandler.post( () -> { try { service.notifyNewRegisteredCallback(callback); receiver.send(null); } catch (RuntimeException e) { receiver.propagateException(e); } }); } catch (RuntimeException e) { receiver.propagateException(e); } } @Override public void unregisterCallback(IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver) { Loading android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -1219,6 +1219,77 @@ public class VolumeControlServiceTest { verify(callback, times(1)).onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume)); } @Test public void testServiceBinderTestNotifyNewRegisteredCallback() throws Exception { mFakeFlagsImpl.setFlag( Flags.FLAG_LEAUDIO_BROADCAST_VOLUME_CONTROL_FOR_CONNECTED_DEVICES, true); int groupId = 1; int deviceOneVolume = 46; int deviceTwoVolume = 36; // Update the device policy so okToConnect() returns true when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); when(mDatabaseManager.getProfileConnectionPolicy( any(BluetoothDevice.class), eq(BluetoothProfile.VOLUME_CONTROL))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); generateDeviceAvailableMessageFromNative(mDevice, 1); generateConnectionMessageFromNative( mDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDevice)); Assert.assertTrue(mService.getDevices().contains(mDevice)); mService.setDeviceVolume(mDevice, deviceOneVolume, false); verify(mNativeInterface, times(1)).setVolume(eq(mDevice), eq(deviceOneVolume)); // Verify that second device gets the proper group volume level when connected generateDeviceAvailableMessageFromNative(mDeviceTwo, 1); generateConnectionMessageFromNative( mDeviceTwo, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals( BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDeviceTwo)); Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); mService.setDeviceVolume(mDeviceTwo, deviceTwoVolume, false); verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(deviceTwoVolume)); // Both devices are in the same group when(mLeAudioService.getGroupId(mDevice)).thenReturn(groupId); when(mLeAudioService.getGroupId(mDeviceTwo)).thenReturn(groupId); // Register callback and verify it is called with known devices IBluetoothVolumeControlCallback callback = Mockito.mock(IBluetoothVolumeControlCallback.class); Binder binder = Mockito.mock(Binder.class); when(callback.asBinder()).thenReturn(binder); int size = mService.mCallbacks.getRegisteredCallbackCount(); SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get(); mServiceBinder.registerCallback(callback, mAttributionSource, recv); recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount()); IBluetoothVolumeControlCallback callback_new_client = Mockito.mock(IBluetoothVolumeControlCallback.class); Binder binder_new_client = Mockito.mock(Binder.class); when(callback_new_client.asBinder()).thenReturn(binder_new_client); recv = SynchronousResultReceiver.get(); mServiceBinder.notifyNewRegisteredCallback(callback_new_client, mAttributionSource, recv); recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount()); // This shall be done only once after mServiceBinder.registerCallback verify(callback, times(1)).onDeviceVolumeChanged(eq(mDevice), eq(deviceOneVolume)); verify(callback, times(1)).onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume)); // This shall be done only once after mServiceBinder.updateNewRegistedCallback verify(callback_new_client, times(1)) .onDeviceVolumeChanged(eq(mDevice), eq(deviceOneVolume)); verify(callback_new_client, times(1)) .onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume)); } @Test public void testServiceBinderMuteMethods() throws Exception { SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); Loading framework/java/android/bluetooth/BluetoothVolumeControl.java +36 −24 Original line number Diff line number Diff line Loading @@ -361,8 +361,6 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose Objects.requireNonNull(callback, "callback cannot be null"); if (DBG) log("registerCallback"); synchronized (mCallbackExecutorMap) { // If the callback map is empty, we register the service-to-app callback if (mCallbackExecutorMap.isEmpty()) { if (!mAdapter.isEnabled()) { /* If Bluetooth is off, just store callback and it will be registered * when Bluetooth is on Loading @@ -370,28 +368,42 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose mCallbackExecutorMap.put(callback, executor); return; } // Adds the passed in callback to our map of callbacks to executors if (mCallbackExecutorMap.containsKey(callback)) { throw new IllegalArgumentException("This callback has already been registered"); } try { final IBluetoothVolumeControl service = getService(); if (service != null) { final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); /* If the callback map is empty, we register the service-to-app callback. * Otherwise, callback is registered in mCallbackExecutorMap and we just notify * user over callback with current values. */ boolean isRegisterCallbackRequired = mCallbackExecutorMap.isEmpty(); mCallbackExecutorMap.put(callback, executor); if (isRegisterCallbackRequired) { service.registerCallback(mCallback, mAttributionSource, recv); } else { service.notifyNewRegisteredCallback( (IBluetoothVolumeControlCallback) (callback), mAttributionSource, recv); } recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); } } catch (RemoteException e) { mCallbackExecutorMap.remove(callback); Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); throw e.rethrowAsRuntimeException(); } catch (TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } } // Adds the passed in callback to our map of callbacks to executors if (mCallbackExecutorMap.containsKey(callback)) { throw new IllegalArgumentException("This callback has already been registered"); } mCallbackExecutorMap.put(callback, executor); } } /** Loading Loading
android/app/aidl/android/bluetooth/IBluetoothVolumeControl.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -75,4 +75,6 @@ oneway interface IBluetoothVolumeControl { void registerCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void unregisterCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") void notifyNewRegisteredCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver); }
android/app/src/com/android/bluetooth/vc/VolumeControlService.java +58 −13 Original line number Diff line number Diff line Loading @@ -807,25 +807,35 @@ public class VolumeControlService extends ProfileService { continue; } } // notify volume level for all vc devices if (mFeatureFlags.leaudioBroadcastVolumeControlForConnectedDevices()) { notifyDevicesVolumeChanged(getDevices(), Optional.empty()); } } tempCallbackList.finishBroadcast(); if (mFeatureFlags.leaudioBroadcastVolumeControlForConnectedDevices()) { notifyDevicesVolumeChanged(tempCallbackList, getDevices(), Optional.empty()); } /* User is notified, remove callback from temporary list */ tempCallbackList.unregister(callback); } void registerCallback(IBluetoothVolumeControlCallback callback) { if (DBG) { Log.d(TAG, "registerCallback: " + callback); } /* Here we keep all the user callbacks */ mCallbacks.register(callback); notifyNewCallbackOfKnownVolumeInfo(callback); } void notifyNewRegisteredCallback(IBluetoothVolumeControlCallback callback) { if (DBG) { Log.d(TAG, "notifyNewRegisteredCallback: " + callback); } notifyNewCallbackOfKnownVolumeInfo(callback); } /** * {@hide} */ Loading Loading @@ -942,13 +952,15 @@ public class VolumeControlService extends ProfileService { LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService != null) { notifyDevicesVolumeChanged( leAudioService.getGroupDevices(groupId), Optional.of(volume)); mCallbacks, leAudioService.getGroupDevices(groupId), Optional.of(volume)); } else { Log.w(TAG, "leAudioService not available"); } } else { // notify device volume changed notifyDevicesVolumeChanged(Arrays.asList(device), Optional.of(volume)); notifyDevicesVolumeChanged(mCallbacks, Arrays.asList(device), Optional.of(volume)); } } Loading Loading @@ -1230,13 +1242,16 @@ public class VolumeControlService extends ProfileService { * newly registered callback, volume level is unknown from caller, notify the clients with * cached volume level from either device or group. * * @param callbacks list of callbacks * @param devices list of devices to notify volume changed * @param volume volume level */ private void notifyDevicesVolumeChanged( List<BluetoothDevice> devices, Optional<Integer> volume) { if (mCallbacks == null) { Log.e(TAG, "mCallbacks is null"); RemoteCallbackList<IBluetoothVolumeControlCallback> callbacks, List<BluetoothDevice> devices, Optional<Integer> volume) { if (callbacks == null) { Log.e(TAG, "callbacks is null"); return; } Loading @@ -1260,20 +1275,20 @@ public class VolumeControlService extends ProfileService { cachedVolume = getGroupVolume(groupId); } } int n = mCallbacks.beginBroadcast(); int n = callbacks.beginBroadcast(); for (int i = 0; i < n; i++) { try { if (!volume.isPresent()) { mCallbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, cachedVolume); callbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, cachedVolume); } else { mDeviceVolumeCache.put(dev, volume.get()); mCallbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, volume.get()); callbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, volume.get()); } } catch (RemoteException e) { continue; } } mCallbacks.finishBroadcast(); callbacks.finishBroadcast(); } } Loading Loading @@ -1763,6 +1778,36 @@ public class VolumeControlService extends ProfileService { } } @Override public void notifyNewRegisteredCallback( IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver) { try { Objects.requireNonNull(callback, "callback cannot be null"); Objects.requireNonNull(source, "source cannot be null"); Objects.requireNonNull(receiver, "receiver cannot be null"); VolumeControlService service = getService(source); if (service == null) { throw new IllegalStateException("Service is unavailable"); } enforceBluetoothPrivilegedPermission(service); service.mHandler.post( () -> { try { service.notifyNewRegisteredCallback(callback); receiver.send(null); } catch (RuntimeException e) { receiver.propagateException(e); } }); } catch (RuntimeException e) { receiver.propagateException(e); } } @Override public void unregisterCallback(IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver) { Loading
android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -1219,6 +1219,77 @@ public class VolumeControlServiceTest { verify(callback, times(1)).onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume)); } @Test public void testServiceBinderTestNotifyNewRegisteredCallback() throws Exception { mFakeFlagsImpl.setFlag( Flags.FLAG_LEAUDIO_BROADCAST_VOLUME_CONTROL_FOR_CONNECTED_DEVICES, true); int groupId = 1; int deviceOneVolume = 46; int deviceTwoVolume = 36; // Update the device policy so okToConnect() returns true when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); when(mDatabaseManager.getProfileConnectionPolicy( any(BluetoothDevice.class), eq(BluetoothProfile.VOLUME_CONTROL))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); generateDeviceAvailableMessageFromNative(mDevice, 1); generateConnectionMessageFromNative( mDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDevice)); Assert.assertTrue(mService.getDevices().contains(mDevice)); mService.setDeviceVolume(mDevice, deviceOneVolume, false); verify(mNativeInterface, times(1)).setVolume(eq(mDevice), eq(deviceOneVolume)); // Verify that second device gets the proper group volume level when connected generateDeviceAvailableMessageFromNative(mDeviceTwo, 1); generateConnectionMessageFromNative( mDeviceTwo, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals( BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDeviceTwo)); Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); mService.setDeviceVolume(mDeviceTwo, deviceTwoVolume, false); verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(deviceTwoVolume)); // Both devices are in the same group when(mLeAudioService.getGroupId(mDevice)).thenReturn(groupId); when(mLeAudioService.getGroupId(mDeviceTwo)).thenReturn(groupId); // Register callback and verify it is called with known devices IBluetoothVolumeControlCallback callback = Mockito.mock(IBluetoothVolumeControlCallback.class); Binder binder = Mockito.mock(Binder.class); when(callback.asBinder()).thenReturn(binder); int size = mService.mCallbacks.getRegisteredCallbackCount(); SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get(); mServiceBinder.registerCallback(callback, mAttributionSource, recv); recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount()); IBluetoothVolumeControlCallback callback_new_client = Mockito.mock(IBluetoothVolumeControlCallback.class); Binder binder_new_client = Mockito.mock(Binder.class); when(callback_new_client.asBinder()).thenReturn(binder_new_client); recv = SynchronousResultReceiver.get(); mServiceBinder.notifyNewRegisteredCallback(callback_new_client, mAttributionSource, recv); recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount()); // This shall be done only once after mServiceBinder.registerCallback verify(callback, times(1)).onDeviceVolumeChanged(eq(mDevice), eq(deviceOneVolume)); verify(callback, times(1)).onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume)); // This shall be done only once after mServiceBinder.updateNewRegistedCallback verify(callback_new_client, times(1)) .onDeviceVolumeChanged(eq(mDevice), eq(deviceOneVolume)); verify(callback_new_client, times(1)) .onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume)); } @Test public void testServiceBinderMuteMethods() throws Exception { SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); Loading
framework/java/android/bluetooth/BluetoothVolumeControl.java +36 −24 Original line number Diff line number Diff line Loading @@ -361,8 +361,6 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose Objects.requireNonNull(callback, "callback cannot be null"); if (DBG) log("registerCallback"); synchronized (mCallbackExecutorMap) { // If the callback map is empty, we register the service-to-app callback if (mCallbackExecutorMap.isEmpty()) { if (!mAdapter.isEnabled()) { /* If Bluetooth is off, just store callback and it will be registered * when Bluetooth is on Loading @@ -370,28 +368,42 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose mCallbackExecutorMap.put(callback, executor); return; } // Adds the passed in callback to our map of callbacks to executors if (mCallbackExecutorMap.containsKey(callback)) { throw new IllegalArgumentException("This callback has already been registered"); } try { final IBluetoothVolumeControl service = getService(); if (service != null) { final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); /* If the callback map is empty, we register the service-to-app callback. * Otherwise, callback is registered in mCallbackExecutorMap and we just notify * user over callback with current values. */ boolean isRegisterCallbackRequired = mCallbackExecutorMap.isEmpty(); mCallbackExecutorMap.put(callback, executor); if (isRegisterCallbackRequired) { service.registerCallback(mCallback, mAttributionSource, recv); } else { service.notifyNewRegisteredCallback( (IBluetoothVolumeControlCallback) (callback), mAttributionSource, recv); } recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); } } catch (RemoteException e) { mCallbackExecutorMap.remove(callback); Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); throw e.rethrowAsRuntimeException(); } catch (TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } } // Adds the passed in callback to our map of callbacks to executors if (mCallbackExecutorMap.containsKey(callback)) { throw new IllegalArgumentException("This callback has already been registered"); } mCallbackExecutorMap.put(callback, executor); } } /** Loading