Loading android/app/src/com/android/bluetooth/gatt/ContextMap.java +88 −3 Original line number Diff line number Diff line Loading @@ -30,6 +30,9 @@ import android.util.Log; import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.GuardedBy; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; Loading @@ -49,6 +52,9 @@ import java.util.function.Predicate; */ public class ContextMap<C> { private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; private static final DateTimeFormatter sDateFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); private static final int MAX_LAST_RECORDS = 5; /** Connection class helps map connection IDs to device addresses. */ public static class Connection { Loading Loading @@ -142,12 +148,47 @@ public class ContextMap<C> { } } private class AppRecord { public final UUID uuid; public final String appName; @Nullable public final String attributionTag; public final Instant registerTime; public int clientIf; public RemoveReason reason; @Nullable public Instant unregisterTime; AppRecord(App app) { uuid = app.uuid; appName = app.name; attributionTag = app.attributionTag; registerTime = Instant.now(); } } public enum RemoveReason { REASON_UNREGISTER_ALL, REASON_UNREGISTER_CLIENT, REASON_UNREGISTER_SERVER, REASON_BINDER_DIED, REASON_REGISTER_FAILED, REASON_UNKNOWN } /** Our internal application list */ private final Object mAppsLock = new Object(); @GuardedBy("mAppsLock") private List<App> mApps = new ArrayList<>(); @GuardedBy("mAppsLock") private final List<AppRecord> mOngoingRecords = new ArrayList<>(); @GuardedBy("mAppsLock") private final List<AppRecord> mLastRecords = new ArrayList<>(); /** Internal list of connected devices */ private List<Connection> mConnections = new ArrayList<>(); Loading @@ -164,12 +205,14 @@ public class ContextMap<C> { synchronized (mAppsLock) { App app = new App(uuid, callback, appUid, appName, attrSource); mApps.add(app); recordRegisterApp(app); return app; } } /** Remove the context for a given UUID */ public void remove(UUID uuid) { public void remove(UUID uuid, RemoveReason reason) { synchronized (mAppsLock) { Iterator<App> i = mApps.iterator(); while (i.hasNext()) { Loading @@ -177,6 +220,7 @@ public class ContextMap<C> { if (entry.uuid.equals(uuid)) { entry.unlinkToDeath(); i.remove(); recordUnregisterApp(entry, reason); break; } } Loading @@ -184,7 +228,7 @@ public class ContextMap<C> { } /** Remove the context for a given application ID. */ public void remove(int id) { public void remove(int id, RemoveReason reason) { boolean find = false; synchronized (mAppsLock) { Iterator<App> i = mApps.iterator(); Loading @@ -194,6 +238,7 @@ public class ContextMap<C> { find = true; entry.unlinkToDeath(); i.remove(); recordUnregisterApp(entry, reason); break; } } Loading Loading @@ -360,6 +405,7 @@ public class ContextMap<C> { entry.unlinkToDeath(); } mApps.clear(); mOngoingRecords.clear(); } synchronized (mConnectionsLock) { Loading @@ -381,7 +427,46 @@ public class ContextMap<C> { /** Logs debug information. */ protected void dump(StringBuilder sb) { synchronized (mAppsLock) { sb.append(" Entries: ").append(mApps.size()).append("\n\n"); sb.append(" Entries: ").append(mApps.size()).append("\n"); sb.append(" Last apps: ").append("\n"); for (AppRecord record : mLastRecords) { sb.append(" ") .append(sDateFormat.format(record.registerTime)) .append(" ~ ") .append(sDateFormat.format(record.unregisterTime)) .append(" app_if: ") .append(record.clientIf) .append(", appName: ") .append(record.appName); if (record.attributionTag != null) { sb.append(", tag: ").append(record.attributionTag); } sb.append(", reason: ").append(record.reason).append("\n"); } sb.append("\n"); } } @GuardedBy("mAppsLock") private void recordRegisterApp(App app) { mOngoingRecords.add(new AppRecord(app)); } @GuardedBy("mAppsLock") private void recordUnregisterApp(App app, RemoveReason reason) { for (int i = 0; i < mOngoingRecords.size(); i++) { if (app.uuid.equals(mOngoingRecords.get(i).uuid)) { AppRecord record = mOngoingRecords.remove(i); record.clientIf = app.id; record.reason = reason; record.unregisterTime = Instant.now(); if (mLastRecords.size() >= MAX_LAST_RECORDS) { mLastRecords.remove(0); } mLastRecords.add(record); break; } } } } android/app/src/com/android/bluetooth/gatt/GattService.java +11 −7 Original line number Diff line number Diff line Loading @@ -307,7 +307,8 @@ public class GattService extends ProfileService { Log.d( TAG, "Binder is dead - unregistering client (" + mPackageName + " " + mAppIf + ")!"); unregisterClient(mAppIf, getAttributionSource()); unregisterClient( mAppIf, getAttributionSource(), ContextMap.RemoveReason.REASON_BINDER_DIED); } } Loading Loading @@ -366,7 +367,8 @@ public class GattService extends ProfileService { if (service == null) { return; } service.unregisterClient(clientIf, attributionSource); service.unregisterClient( clientIf, attributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); } @Override Loading Loading @@ -840,7 +842,7 @@ public class GattService extends ProfileService { app.id = clientIf; app.linkToDeath(new ClientDeathRecipient(clientIf, app.name)); } else { mClientMap.remove(uuid); mClientMap.remove(uuid, ContextMap.RemoveReason.REASON_REGISTER_FAILED); } app.callback.onClientRegistered(status, clientIf); } Loading Loading @@ -1490,7 +1492,8 @@ public class GattService extends ProfileService { public void unregAll(AttributionSource attributionSource) { for (Integer appId : mClientMap.getAllAppsIds()) { Log.d(TAG, "unreg:" + appId); unregisterClient(appId, attributionSource); unregisterClient( appId, attributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_ALL); } } Loading Loading @@ -1526,7 +1529,8 @@ public class GattService extends ProfileService { } @RequiresPermission(BLUETOOTH_CONNECT) void unregisterClient(int clientIf, AttributionSource attributionSource) { void unregisterClient( int clientIf, AttributionSource attributionSource, ContextMap.RemoveReason reason) { if (!Utils.checkConnectPermissionForDataDelivery( this, attributionSource, "GattService unregisterClient")) { return; Loading @@ -1542,7 +1546,7 @@ public class GattService extends ProfileService { BluetoothStatsLog.BLUETOOTH_CROSS_LAYER_EVENT_REPORTED__STATE__END, attributionSource.getUid()); } mClientMap.remove(clientIf); mClientMap.remove(clientIf, reason); mNativeInterface.gattClientUnregisterApp(clientIf); } Loading Loading @@ -2656,7 +2660,7 @@ public class GattService extends ProfileService { deleteServices(serverIf); mServerMap.remove(serverIf); mServerMap.remove(serverIf, ContextMap.RemoveReason.REASON_UNREGISTER_SERVER); mNativeInterface.gattServerUnregisterApp(serverIf); } Loading android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -140,15 +140,15 @@ public class ContextMapTest { @Test public void removeMethods() { ContextMap<IBluetoothGattCallback> contextMap = getMapWithAppAndConnection(); contextMap.remove(APP_ID1); contextMap.remove(APP_ID1, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); assertThat(contextMap.getAllAppsIds()).isNotEmpty(); contextMap.remove(APP_ID2); contextMap.remove(APP_ID2, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); assertThat(contextMap.getAllAppsIds()).isEmpty(); contextMap = getMapWithAppAndConnection(); contextMap.remove(RANDOM_UUID1); contextMap.remove(RANDOM_UUID1, ContextMap.RemoveReason.REASON_REGISTER_FAILED); assertThat(contextMap.getAllAppsIds()).isNotEmpty(); contextMap.remove(RANDOM_UUID2); contextMap.remove(RANDOM_UUID2, ContextMap.RemoveReason.REASON_REGISTER_FAILED); assertThat(contextMap.getAllAppsIds()).isEmpty(); contextMap = getMapWithAppAndConnection(); Loading android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java +5 −1 Original line number Diff line number Diff line Loading @@ -88,7 +88,11 @@ public class GattServiceBinderTest { mBinder.unregisterClient(clientIf, mAttributionSource); verify(mService).unregisterClient(clientIf, mAttributionSource); verify(mService) .unregisterClient( clientIf, mAttributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); } @Test Loading android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -333,8 +333,9 @@ public class GattServiceTest { public void unregisterClient() { int clientIf = 3; mService.unregisterClient(clientIf, mAttributionSource); verify(mClientMap).remove(clientIf); mService.unregisterClient( clientIf, mAttributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); verify(mClientMap).remove(clientIf, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); verify(mNativeInterface).gattClientUnregisterApp(clientIf); } Loading Loading @@ -581,7 +582,7 @@ public class GattServiceTest { doReturn(appIds).when(mClientMap).getAllAppsIds(); mService.unregAll(mAttributionSource); verify(mClientMap).remove(appId); verify(mClientMap).remove(appId, ContextMap.RemoveReason.REASON_UNREGISTER_ALL); verify(mNativeInterface).gattClientUnregisterApp(appId); } Loading Loading
android/app/src/com/android/bluetooth/gatt/ContextMap.java +88 −3 Original line number Diff line number Diff line Loading @@ -30,6 +30,9 @@ import android.util.Log; import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.GuardedBy; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; Loading @@ -49,6 +52,9 @@ import java.util.function.Predicate; */ public class ContextMap<C> { private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; private static final DateTimeFormatter sDateFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); private static final int MAX_LAST_RECORDS = 5; /** Connection class helps map connection IDs to device addresses. */ public static class Connection { Loading Loading @@ -142,12 +148,47 @@ public class ContextMap<C> { } } private class AppRecord { public final UUID uuid; public final String appName; @Nullable public final String attributionTag; public final Instant registerTime; public int clientIf; public RemoveReason reason; @Nullable public Instant unregisterTime; AppRecord(App app) { uuid = app.uuid; appName = app.name; attributionTag = app.attributionTag; registerTime = Instant.now(); } } public enum RemoveReason { REASON_UNREGISTER_ALL, REASON_UNREGISTER_CLIENT, REASON_UNREGISTER_SERVER, REASON_BINDER_DIED, REASON_REGISTER_FAILED, REASON_UNKNOWN } /** Our internal application list */ private final Object mAppsLock = new Object(); @GuardedBy("mAppsLock") private List<App> mApps = new ArrayList<>(); @GuardedBy("mAppsLock") private final List<AppRecord> mOngoingRecords = new ArrayList<>(); @GuardedBy("mAppsLock") private final List<AppRecord> mLastRecords = new ArrayList<>(); /** Internal list of connected devices */ private List<Connection> mConnections = new ArrayList<>(); Loading @@ -164,12 +205,14 @@ public class ContextMap<C> { synchronized (mAppsLock) { App app = new App(uuid, callback, appUid, appName, attrSource); mApps.add(app); recordRegisterApp(app); return app; } } /** Remove the context for a given UUID */ public void remove(UUID uuid) { public void remove(UUID uuid, RemoveReason reason) { synchronized (mAppsLock) { Iterator<App> i = mApps.iterator(); while (i.hasNext()) { Loading @@ -177,6 +220,7 @@ public class ContextMap<C> { if (entry.uuid.equals(uuid)) { entry.unlinkToDeath(); i.remove(); recordUnregisterApp(entry, reason); break; } } Loading @@ -184,7 +228,7 @@ public class ContextMap<C> { } /** Remove the context for a given application ID. */ public void remove(int id) { public void remove(int id, RemoveReason reason) { boolean find = false; synchronized (mAppsLock) { Iterator<App> i = mApps.iterator(); Loading @@ -194,6 +238,7 @@ public class ContextMap<C> { find = true; entry.unlinkToDeath(); i.remove(); recordUnregisterApp(entry, reason); break; } } Loading Loading @@ -360,6 +405,7 @@ public class ContextMap<C> { entry.unlinkToDeath(); } mApps.clear(); mOngoingRecords.clear(); } synchronized (mConnectionsLock) { Loading @@ -381,7 +427,46 @@ public class ContextMap<C> { /** Logs debug information. */ protected void dump(StringBuilder sb) { synchronized (mAppsLock) { sb.append(" Entries: ").append(mApps.size()).append("\n\n"); sb.append(" Entries: ").append(mApps.size()).append("\n"); sb.append(" Last apps: ").append("\n"); for (AppRecord record : mLastRecords) { sb.append(" ") .append(sDateFormat.format(record.registerTime)) .append(" ~ ") .append(sDateFormat.format(record.unregisterTime)) .append(" app_if: ") .append(record.clientIf) .append(", appName: ") .append(record.appName); if (record.attributionTag != null) { sb.append(", tag: ").append(record.attributionTag); } sb.append(", reason: ").append(record.reason).append("\n"); } sb.append("\n"); } } @GuardedBy("mAppsLock") private void recordRegisterApp(App app) { mOngoingRecords.add(new AppRecord(app)); } @GuardedBy("mAppsLock") private void recordUnregisterApp(App app, RemoveReason reason) { for (int i = 0; i < mOngoingRecords.size(); i++) { if (app.uuid.equals(mOngoingRecords.get(i).uuid)) { AppRecord record = mOngoingRecords.remove(i); record.clientIf = app.id; record.reason = reason; record.unregisterTime = Instant.now(); if (mLastRecords.size() >= MAX_LAST_RECORDS) { mLastRecords.remove(0); } mLastRecords.add(record); break; } } } }
android/app/src/com/android/bluetooth/gatt/GattService.java +11 −7 Original line number Diff line number Diff line Loading @@ -307,7 +307,8 @@ public class GattService extends ProfileService { Log.d( TAG, "Binder is dead - unregistering client (" + mPackageName + " " + mAppIf + ")!"); unregisterClient(mAppIf, getAttributionSource()); unregisterClient( mAppIf, getAttributionSource(), ContextMap.RemoveReason.REASON_BINDER_DIED); } } Loading Loading @@ -366,7 +367,8 @@ public class GattService extends ProfileService { if (service == null) { return; } service.unregisterClient(clientIf, attributionSource); service.unregisterClient( clientIf, attributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); } @Override Loading Loading @@ -840,7 +842,7 @@ public class GattService extends ProfileService { app.id = clientIf; app.linkToDeath(new ClientDeathRecipient(clientIf, app.name)); } else { mClientMap.remove(uuid); mClientMap.remove(uuid, ContextMap.RemoveReason.REASON_REGISTER_FAILED); } app.callback.onClientRegistered(status, clientIf); } Loading Loading @@ -1490,7 +1492,8 @@ public class GattService extends ProfileService { public void unregAll(AttributionSource attributionSource) { for (Integer appId : mClientMap.getAllAppsIds()) { Log.d(TAG, "unreg:" + appId); unregisterClient(appId, attributionSource); unregisterClient( appId, attributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_ALL); } } Loading Loading @@ -1526,7 +1529,8 @@ public class GattService extends ProfileService { } @RequiresPermission(BLUETOOTH_CONNECT) void unregisterClient(int clientIf, AttributionSource attributionSource) { void unregisterClient( int clientIf, AttributionSource attributionSource, ContextMap.RemoveReason reason) { if (!Utils.checkConnectPermissionForDataDelivery( this, attributionSource, "GattService unregisterClient")) { return; Loading @@ -1542,7 +1546,7 @@ public class GattService extends ProfileService { BluetoothStatsLog.BLUETOOTH_CROSS_LAYER_EVENT_REPORTED__STATE__END, attributionSource.getUid()); } mClientMap.remove(clientIf); mClientMap.remove(clientIf, reason); mNativeInterface.gattClientUnregisterApp(clientIf); } Loading Loading @@ -2656,7 +2660,7 @@ public class GattService extends ProfileService { deleteServices(serverIf); mServerMap.remove(serverIf); mServerMap.remove(serverIf, ContextMap.RemoveReason.REASON_UNREGISTER_SERVER); mNativeInterface.gattServerUnregisterApp(serverIf); } Loading
android/app/tests/unit/src/com/android/bluetooth/gatt/ContextMapTest.java +4 −4 Original line number Diff line number Diff line Loading @@ -140,15 +140,15 @@ public class ContextMapTest { @Test public void removeMethods() { ContextMap<IBluetoothGattCallback> contextMap = getMapWithAppAndConnection(); contextMap.remove(APP_ID1); contextMap.remove(APP_ID1, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); assertThat(contextMap.getAllAppsIds()).isNotEmpty(); contextMap.remove(APP_ID2); contextMap.remove(APP_ID2, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); assertThat(contextMap.getAllAppsIds()).isEmpty(); contextMap = getMapWithAppAndConnection(); contextMap.remove(RANDOM_UUID1); contextMap.remove(RANDOM_UUID1, ContextMap.RemoveReason.REASON_REGISTER_FAILED); assertThat(contextMap.getAllAppsIds()).isNotEmpty(); contextMap.remove(RANDOM_UUID2); contextMap.remove(RANDOM_UUID2, ContextMap.RemoveReason.REASON_REGISTER_FAILED); assertThat(contextMap.getAllAppsIds()).isEmpty(); contextMap = getMapWithAppAndConnection(); Loading
android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceBinderTest.java +5 −1 Original line number Diff line number Diff line Loading @@ -88,7 +88,11 @@ public class GattServiceBinderTest { mBinder.unregisterClient(clientIf, mAttributionSource); verify(mService).unregisterClient(clientIf, mAttributionSource); verify(mService) .unregisterClient( clientIf, mAttributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); } @Test Loading
android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java +4 −3 Original line number Diff line number Diff line Loading @@ -333,8 +333,9 @@ public class GattServiceTest { public void unregisterClient() { int clientIf = 3; mService.unregisterClient(clientIf, mAttributionSource); verify(mClientMap).remove(clientIf); mService.unregisterClient( clientIf, mAttributionSource, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); verify(mClientMap).remove(clientIf, ContextMap.RemoveReason.REASON_UNREGISTER_CLIENT); verify(mNativeInterface).gattClientUnregisterApp(clientIf); } Loading Loading @@ -581,7 +582,7 @@ public class GattServiceTest { doReturn(appIds).when(mClientMap).getAllAppsIds(); mService.unregAll(mAttributionSource); verify(mClientMap).remove(appId); verify(mClientMap).remove(appId, ContextMap.RemoveReason.REASON_UNREGISTER_ALL); verify(mNativeInterface).gattClientUnregisterApp(appId); } Loading