From 25fc56ed6a3506d3c83cd01803a3b34e5ffff4ca Mon Sep 17 00:00:00 2001 From: William Escande Date: Fri, 22 Apr 2022 15:49:39 -0700 Subject: Add caching for {Map,Sap}.getConnectionState BluetoothMap and BluetoothSap are heavily using their getConnectionState method. By caching the value I intend to reduce drastically the number of binder calls generated. While performing a discovery, the caching is working on 80% of binder calls. This value should be bigger when we kepp asking the value for the same device. (currently everytime you ask the connection state of a new device, you refresh the cache, there is no caching for multiples devices). I don't have any Map/Sap setup to test it, asking to test team if they hit some issues Fixing cache on BluetoothDevice by passing the device as parameter Doing a refactoring of all cached method in bluetooth to keep code consistency: AKA we try to do the most of check outside of the cache query Bug: 217366135 Test: Put some log to see the value being cached sometimes - need more testing Ignore-AOSP-First: No caching api on AOSP Change-Id: Iababf9f9068a181e277b400e786a4a67d4447dc8 --- framework/java/android/bluetooth/BluetoothMap.java | 75 +++++++++++++++++++--- 1 file changed, 66 insertions(+), 9 deletions(-) (limited to 'framework/java/android/bluetooth/BluetoothMap.java') diff --git a/framework/java/android/bluetooth/BluetoothMap.java b/framework/java/android/bluetooth/BluetoothMap.java index 56e4972624..a8b48348da 100644 --- a/framework/java/android/bluetooth/BluetoothMap.java +++ b/framework/java/android/bluetooth/BluetoothMap.java @@ -32,9 +32,11 @@ import android.content.AttributionSource; import android.content.Context; import android.os.Build; import android.os.IBinder; +import android.os.IpcDataCache; import android.os.RemoteException; import android.util.CloseGuard; import android.util.Log; +import android.util.Pair; import com.android.modules.utils.SynchronousResultReceiver; @@ -353,11 +355,66 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { return defaultValue; } + /** + * There are several instances of IpcDataCache used in this class. + * BluetoothCache wraps up the common code. All caches are created with a maximum of + * eight entries, and the key is in the bluetooth module. The name is set to the api. + */ + private static class BluetoothCache extends IpcDataCache { + BluetoothCache(String api, IpcDataCache.QueryHandler query) { + super(8, IpcDataCache.MODULE_BLUETOOTH, api, api, query); + }}; + + /** @hide */ + public void disableBluetoothGetConnectionStateCache() { + mBluetoothConnectionCache.disableForCurrentProcess(); + } + + /** @hide */ + public static void invalidateBluetoothGetConnectionStateCache() { + invalidateCache(GET_CONNECTION_STATE_API); + } + + /** + * Invalidate a bluetooth cache. This method is just a short-hand wrapper that + * enforces the bluetooth module. + */ + private static void invalidateCache(@NonNull String api) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_BLUETOOTH, api); + } + + private final IpcDataCache.QueryHandler, Integer> + mBluetoothConnectionQuery = new IpcDataCache.QueryHandler<>() { + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @Override + public Integer apply(Pair pairQuery) { + if (DBG) { + log("getConnectionState(" + pairQuery.second.getAnonymizedAddress() + + ") uncached"); + } + final SynchronousResultReceiver recv = new SynchronousResultReceiver(); + try { + pairQuery.first + .getConnectionState(pairQuery.second, mAttributionSource, recv); + return recv.awaitResultNoInterrupt(getSyncTimeout()) + .getValue(BluetoothProfile.STATE_DISCONNECTED); + } catch (RemoteException | TimeoutException e) { + throw new RuntimeException(e); + } + } + }; + + private static final String GET_CONNECTION_STATE_API = "BluetoothMap_getConnectionState"; + + private final BluetoothCache, Integer> + mBluetoothConnectionCache = new BluetoothCache<>(GET_CONNECTION_STATE_API, + mBluetoothConnectionQuery); + /** * Get connection state of device * * @return device connection state - * * @hide */ @RequiresBluetoothConnectPermission @@ -365,21 +422,21 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); final IBluetoothMap service = getService(); - final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; if (service == null) { - Log.w(TAG, "Proxy not attached to service"); + Log.w(TAG, "BT not enabled. Cannot get connection state"); if (DBG) log(Log.getStackTraceString(new Throwable())); } else if (isEnabled() && isValidDevice(device)) { try { - final SynchronousResultReceiver recv = - new SynchronousResultReceiver(); - service.getConnectionState(device, mAttributionSource, recv); - return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); - } catch (RemoteException | TimeoutException e) { + return mBluetoothConnectionCache.query(new Pair<>(service, device)); + } catch (RuntimeException e) { + if (!(e.getCause() instanceof TimeoutException) + && !(e.getCause() instanceof RemoteException)) { + throw e; + } Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); } } - return defaultValue; + return BluetoothProfile.STATE_DISCONNECTED; } /** -- cgit v1.2.3