Loading android/app/src/com/android/bluetooth/btservice/AdapterService.java +98 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetooth; import android.bluetooth.IBluetoothCallback; import android.bluetooth.IBluetoothMetadataListener; import android.bluetooth.IBluetoothSocketManager; import android.bluetooth.OobData; import android.bluetooth.UidTraffic; Loading Loading @@ -171,6 +172,8 @@ public class AdapterService extends Service { private boolean mNativeAvailable; private boolean mCleaningUp; private final HashMap<BluetoothDevice, ArrayList<IBluetoothMetadataListener>> mMetadataListeners = new HashMap<>(); private final HashMap<String, Integer> mProfileServicesState = new HashMap<String, Integer>(); //Only BluetoothManagerService should be registered private RemoteCallbackList<IBluetoothCallback> mCallbacks; Loading Loading @@ -1633,6 +1636,43 @@ public class AdapterService extends Service { return service.reportActivityInfo(); } @Override public boolean registerMetadataListener(IBluetoothMetadataListener listener, BluetoothDevice device) { AdapterService service = getService(); if (service == null) { return false; } return service.registerMetadataListener(listener, device); } @Override public boolean unregisterMetadataListener(BluetoothDevice device) { AdapterService service = getService(); if (service == null) { return false; } return service.unregisterMetadataListener(device); } @Override public boolean setMetadata(BluetoothDevice device, int key, String value) { AdapterService service = getService(); if (service == null) { return false; } return service.setMetadata(device, key, value); } @Override public String getMetadata(BluetoothDevice device, int key) { AdapterService service = getService(); if (service == null) { return null; } return service.getMetadata(device, key); } @Override public void requestActivityInfo(ResultReceiver result) { Bundle bundle = new Bundle(); Loading Loading @@ -2546,6 +2586,64 @@ public class AdapterService extends Service { + ctrlState + "traffic = " + Arrays.toString(data)); } boolean registerMetadataListener(IBluetoothMetadataListener listener, BluetoothDevice device) { if (mMetadataListeners == null) { return false; } ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device); if (list == null) { list = new ArrayList<>(); } else if (list.contains(listener)) { // The device is already registered with this listener return true; } list.add(listener); mMetadataListeners.put(device, list); return true; } boolean unregisterMetadataListener(BluetoothDevice device) { if (mMetadataListeners == null) { return false; } if (mMetadataListeners.containsKey(device)) { mMetadataListeners.remove(device); } return true; } boolean setMetadata(BluetoothDevice device, int key, String value) { if (value.length() > BluetoothDevice.METADATA_MAX_LENGTH) { Log.e(TAG, "setMetadata: value length too long " + value.length()); return false; } return mDatabaseManager.setCustomMeta(device, key, value); } String getMetadata(BluetoothDevice device, int key) { return mDatabaseManager.getCustomMeta(device, key); } /** * Update metadata change to registered listeners */ @VisibleForTesting public void metadataChanged(String address, int key, String value) { BluetoothDevice device = mRemoteDevices.getDevice(address.getBytes()); if (mMetadataListeners.containsKey(device)) { ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device); for (IBluetoothMetadataListener listener : list) { try { listener.onMetadataChanged(device, key, value); } catch (RemoteException e) { Log.w(TAG, "RemoteException when onMetadataChanged"); } } } } private int getIdleCurrentMa() { return getResources().getInteger(R.integer.config_bluetooth_idle_cur_ma); } Loading android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java +96 −1 Original line number Diff line number Diff line Loading @@ -159,6 +159,97 @@ public class DatabaseManager { } } boolean isValidMetaKey(int key) { switch (key) { case BluetoothDevice.METADATA_MANUFACTURER_NAME: case BluetoothDevice.METADATA_MODEL_NAME: case BluetoothDevice.METADATA_SOFTWARE_VERSION: case BluetoothDevice.METADATA_HARDWARE_VERSION: case BluetoothDevice.METADATA_COMPANION_APP: case BluetoothDevice.METADATA_MAIN_ICON: case BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET: case BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON: case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON: case BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON: case BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY: case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY: case BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY: case BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING: case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING: case BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING: case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI: return true; } Log.w(TAG, "Invalid metadata key " + key); return false; } /** * Set customized metadata to database with requested key */ @VisibleForTesting public boolean setCustomMeta(BluetoothDevice device, int key, String newValue) { synchronized (mMetadataCache) { if (device == null) { Log.e(TAG, "setCustomMeta: device is null"); return false; } if (!isValidMetaKey(key)) { Log.e(TAG, "setCustomMeta: meta key invalid " + key); return false; } String address = device.getAddress(); if (VERBOSE) { Log.d(TAG, "setCustomMeta: " + address + ", key=" + key); } if (!mMetadataCache.containsKey(address)) { createMetadata(address); } Metadata data = mMetadataCache.get(address); String oldValue = data.getCustomizedMeta(key); if (oldValue != null && oldValue.equals(newValue)) { if (VERBOSE) { Log.d(TAG, "setCustomMeta: metadata not changed."); } return true; } data.setCustomizedMeta(key, newValue); updateDatabase(data); mAdapterService.metadataChanged(address, key, newValue); return true; } } /** * Get customized metadata from database with requested key */ @VisibleForTesting public String getCustomMeta(BluetoothDevice device, int key) { synchronized (mMetadataCache) { if (device == null) { Log.e(TAG, "getCustomMeta: device is null"); return null; } if (!isValidMetaKey(key)) { Log.e(TAG, "getCustomMeta: meta key invalid " + key); return null; } String address = device.getAddress(); if (!mMetadataCache.containsKey(address)) { Log.e(TAG, "getCustomMeta: device " + address + " is not in cache"); return null; } Metadata data = mMetadataCache.get(address); String value = data.getCustomizedMeta(key); return value; } } /** * Set the device profile prioirty * Loading Loading @@ -476,8 +567,12 @@ public class DatabaseManager { for (BluetoothDevice device : bondedDevices) { if (!device.getAddress().equals(address) && !address.equals(LOCAL_STORAGE)) { // Report metadata change to null List<Integer> list = metadata.getChangedCustomizedMeta(); for (int key : list) { mAdapterService.metadataChanged(address, key, null); } Log.i(TAG, "remove unpaired device from database " + address); //TODO Callback metadata change deleteDatabase(mMetadataCache.get(address)); } } Loading android/app/src/com/android/bluetooth/btservice/storage/Metadata.java +174 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.bluetooth.btservice; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import androidx.annotation.NonNull; Loading @@ -24,6 +25,9 @@ import androidx.room.Embedded; import androidx.room.Entity; import androidx.room.PrimaryKey; import java.util.ArrayList; import java.util.List; @Entity(tableName = "metadata") class Metadata { @PrimaryKey Loading Loading @@ -121,4 +125,174 @@ class Metadata { } return BluetoothProfile.PRIORITY_UNDEFINED; } void setCustomizedMeta(int key, String value) { switch (key) { case BluetoothDevice.METADATA_MANUFACTURER_NAME: publicMeta.manufacturer_name = value; break; case BluetoothDevice.METADATA_MODEL_NAME: publicMeta.model_name = value; break; case BluetoothDevice.METADATA_SOFTWARE_VERSION: publicMeta.software_version = value; break; case BluetoothDevice.METADATA_HARDWARE_VERSION: publicMeta.hardware_version = value; break; case BluetoothDevice.METADATA_COMPANION_APP: publicMeta.companion_app = value; break; case BluetoothDevice.METADATA_MAIN_ICON: publicMeta.main_icon = value; break; case BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET: publicMeta.is_unthethered_headset = value; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON: publicMeta.unthethered_left_icon = value; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON: publicMeta.unthethered_right_icon = value; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON: publicMeta.unthethered_case_icon = value; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY: publicMeta.unthethered_left_battery = value; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY: publicMeta.unthethered_right_battery = value; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY: publicMeta.unthethered_case_battery = value; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING: publicMeta.unthethered_left_charging = value; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING: publicMeta.unthethered_right_charging = value; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING: publicMeta.unthethered_case_charging = value; break; case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI: publicMeta.enhanced_settings_ui_uri = value; break; } } String getCustomizedMeta(int key) { String value = null; switch (key) { case BluetoothDevice.METADATA_MANUFACTURER_NAME: value = publicMeta.manufacturer_name; break; case BluetoothDevice.METADATA_MODEL_NAME: value = publicMeta.model_name; break; case BluetoothDevice.METADATA_SOFTWARE_VERSION: value = publicMeta.software_version; break; case BluetoothDevice.METADATA_HARDWARE_VERSION: value = publicMeta.hardware_version; break; case BluetoothDevice.METADATA_COMPANION_APP: value = publicMeta.companion_app; break; case BluetoothDevice.METADATA_MAIN_ICON: value = publicMeta.main_icon; break; case BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET: value = publicMeta.is_unthethered_headset; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON: value = publicMeta.unthethered_left_icon; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON: value = publicMeta.unthethered_right_icon; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON: value = publicMeta.unthethered_case_icon; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY: value = publicMeta.unthethered_left_battery; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY: value = publicMeta.unthethered_right_battery; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY: value = publicMeta.unthethered_case_battery; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING: value = publicMeta.unthethered_left_charging; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING: value = publicMeta.unthethered_right_charging; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING: value = publicMeta.unthethered_case_charging; break; case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI: value = publicMeta.enhanced_settings_ui_uri; break; } return value; } List<Integer> getChangedCustomizedMeta() { List<Integer> list = new ArrayList<>(); if (publicMeta.manufacturer_name != null) { list.add(BluetoothDevice.METADATA_MANUFACTURER_NAME); } if (publicMeta.model_name != null) { list.add(BluetoothDevice.METADATA_MODEL_NAME); } if (publicMeta.software_version != null) { list.add(BluetoothDevice.METADATA_SOFTWARE_VERSION); } if (publicMeta.hardware_version != null) { list.add(BluetoothDevice.METADATA_HARDWARE_VERSION); } if (publicMeta.companion_app != null) { list.add(BluetoothDevice.METADATA_COMPANION_APP); } if (publicMeta.main_icon != null) { list.add(BluetoothDevice.METADATA_MAIN_ICON); } if (publicMeta.is_unthethered_headset != null) { list.add(BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET); } if (publicMeta.unthethered_left_icon != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON); } if (publicMeta.unthethered_right_icon != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON); } if (publicMeta.unthethered_case_icon != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON); } if (publicMeta.unthethered_left_battery != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY); } if (publicMeta.unthethered_right_battery != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY); } if (publicMeta.unthethered_case_battery != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY); } if (publicMeta.unthethered_left_charging != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING); } if (publicMeta.unthethered_right_charging != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING); } if (publicMeta.unthethered_case_charging != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING); } if (publicMeta.enhanced_settings_ui_uri != null) { list.add(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI); } return list; } } android/app/tests/unit/src/com/android/bluetooth/btservice/DatabaseManagerTest.java +124 −2 Original line number Diff line number Diff line Loading @@ -68,10 +68,10 @@ public final class DatabaseManagerTest { MetadataDatabase.class).build(); mDatabaseManager = new DatabaseManager(mAdapterService); //mDatabaseManager.doNotMigrateSettingGlobal(); BluetoothDevice[] bondedDevices = {}; doReturn(bondedDevices).when(mAdapterService).getBondedDevices(); doNothing().when(mAdapterService).metadataChanged(anyString(), anyInt(), anyString()); mDatabaseManager.start(mDatabase); // Wait for handler thread finish its task. Loading Loading @@ -182,11 +182,13 @@ public final class DatabaseManagerTest { @Test public void testRemoveUnusedMetadata() { // Insert two devices to database and cache, only mTestDevice is // Insert three devices to database and cache, only mTestDevice is // in the bonded list BluetoothDevice otherDevice = BluetoothAdapter.getDefaultAdapter() .getRemoteDevice(OTHER_BT_ADDR); Metadata otherData = new Metadata(OTHER_BT_ADDR); // Add metadata for otherDevice otherData.setCustomizedMeta(0, "value"); mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR, otherData); mDatabase.insert(otherData); Loading @@ -201,6 +203,9 @@ public final class DatabaseManagerTest { // Wait for database update TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); // Check removed device report metadata changed to null verify(mAdapterService).metadataChanged(OTHER_BT_ADDR, 0, null); List<Metadata> list = mDatabase.load(); // Check number of metadata in the database Loading @@ -216,6 +221,85 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.clear(); } @Test public void testSetGetCustomMeta() { int badKey = 100; String value = "input value"; // Device is not in database testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MANUFACTURER_NAME, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MODEL_NAME, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_SOFTWARE_VERSION, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_HARDWARE_VERSION, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_COMPANION_APP, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MAIN_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI, value, true); testSetGetCustomMetaCase(false, badKey, value, false); // Device is in database testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MANUFACTURER_NAME, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MODEL_NAME, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_SOFTWARE_VERSION, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_HARDWARE_VERSION, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_COMPANION_APP, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MAIN_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI, value, true); } void testSetGetProfilePriorityCase(boolean stored, int priority, int expectedPriority, boolean expectedSetResult) { if (stored) { Loading Loading @@ -298,4 +382,42 @@ public final class DatabaseManagerTest { TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); mDatabaseManager.mMetadataCache.clear(); } void testSetGetCustomMetaCase(boolean stored, int key, String value, boolean expectedResult) { String testValue = "test value"; int verifyTime = 1; if (stored) { Metadata data = new Metadata(TEST_BT_ADDR); mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data); mDatabase.insert(data); Assert.assertEquals(expectedResult, mDatabaseManager.setCustomMeta(mTestDevice, key, testValue)); verify(mAdapterService).metadataChanged(TEST_BT_ADDR, key, testValue); verifyTime++; } Assert.assertEquals(expectedResult, mDatabaseManager.setCustomMeta(mTestDevice, key, value)); if (expectedResult) { // Check for callback and get value verify(mAdapterService, times(verifyTime)).metadataChanged(TEST_BT_ADDR, key, value); Assert.assertEquals(value, mDatabaseManager.getCustomMeta(mTestDevice, key)); } else { Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, key)); return; } // Wait for database update TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); // Check whether the value is saved in database List<Metadata> list = mDatabase.load(); Metadata data = list.get(0); Assert.assertEquals(TEST_BT_ADDR, data.getAddress()); Assert.assertEquals(value, data.getCustomizedMeta(key)); mDatabase.deleteAll(); // Wait for clear database TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); mDatabaseManager.mMetadataCache.clear(); } } Loading
android/app/src/com/android/bluetooth/btservice/AdapterService.java +98 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.IBluetooth; import android.bluetooth.IBluetoothCallback; import android.bluetooth.IBluetoothMetadataListener; import android.bluetooth.IBluetoothSocketManager; import android.bluetooth.OobData; import android.bluetooth.UidTraffic; Loading Loading @@ -171,6 +172,8 @@ public class AdapterService extends Service { private boolean mNativeAvailable; private boolean mCleaningUp; private final HashMap<BluetoothDevice, ArrayList<IBluetoothMetadataListener>> mMetadataListeners = new HashMap<>(); private final HashMap<String, Integer> mProfileServicesState = new HashMap<String, Integer>(); //Only BluetoothManagerService should be registered private RemoteCallbackList<IBluetoothCallback> mCallbacks; Loading Loading @@ -1633,6 +1636,43 @@ public class AdapterService extends Service { return service.reportActivityInfo(); } @Override public boolean registerMetadataListener(IBluetoothMetadataListener listener, BluetoothDevice device) { AdapterService service = getService(); if (service == null) { return false; } return service.registerMetadataListener(listener, device); } @Override public boolean unregisterMetadataListener(BluetoothDevice device) { AdapterService service = getService(); if (service == null) { return false; } return service.unregisterMetadataListener(device); } @Override public boolean setMetadata(BluetoothDevice device, int key, String value) { AdapterService service = getService(); if (service == null) { return false; } return service.setMetadata(device, key, value); } @Override public String getMetadata(BluetoothDevice device, int key) { AdapterService service = getService(); if (service == null) { return null; } return service.getMetadata(device, key); } @Override public void requestActivityInfo(ResultReceiver result) { Bundle bundle = new Bundle(); Loading Loading @@ -2546,6 +2586,64 @@ public class AdapterService extends Service { + ctrlState + "traffic = " + Arrays.toString(data)); } boolean registerMetadataListener(IBluetoothMetadataListener listener, BluetoothDevice device) { if (mMetadataListeners == null) { return false; } ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device); if (list == null) { list = new ArrayList<>(); } else if (list.contains(listener)) { // The device is already registered with this listener return true; } list.add(listener); mMetadataListeners.put(device, list); return true; } boolean unregisterMetadataListener(BluetoothDevice device) { if (mMetadataListeners == null) { return false; } if (mMetadataListeners.containsKey(device)) { mMetadataListeners.remove(device); } return true; } boolean setMetadata(BluetoothDevice device, int key, String value) { if (value.length() > BluetoothDevice.METADATA_MAX_LENGTH) { Log.e(TAG, "setMetadata: value length too long " + value.length()); return false; } return mDatabaseManager.setCustomMeta(device, key, value); } String getMetadata(BluetoothDevice device, int key) { return mDatabaseManager.getCustomMeta(device, key); } /** * Update metadata change to registered listeners */ @VisibleForTesting public void metadataChanged(String address, int key, String value) { BluetoothDevice device = mRemoteDevices.getDevice(address.getBytes()); if (mMetadataListeners.containsKey(device)) { ArrayList<IBluetoothMetadataListener> list = mMetadataListeners.get(device); for (IBluetoothMetadataListener listener : list) { try { listener.onMetadataChanged(device, key, value); } catch (RemoteException e) { Log.w(TAG, "RemoteException when onMetadataChanged"); } } } } private int getIdleCurrentMa() { return getResources().getInteger(R.integer.config_bluetooth_idle_cur_ma); } Loading
android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java +96 −1 Original line number Diff line number Diff line Loading @@ -159,6 +159,97 @@ public class DatabaseManager { } } boolean isValidMetaKey(int key) { switch (key) { case BluetoothDevice.METADATA_MANUFACTURER_NAME: case BluetoothDevice.METADATA_MODEL_NAME: case BluetoothDevice.METADATA_SOFTWARE_VERSION: case BluetoothDevice.METADATA_HARDWARE_VERSION: case BluetoothDevice.METADATA_COMPANION_APP: case BluetoothDevice.METADATA_MAIN_ICON: case BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET: case BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON: case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON: case BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON: case BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY: case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY: case BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY: case BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING: case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING: case BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING: case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI: return true; } Log.w(TAG, "Invalid metadata key " + key); return false; } /** * Set customized metadata to database with requested key */ @VisibleForTesting public boolean setCustomMeta(BluetoothDevice device, int key, String newValue) { synchronized (mMetadataCache) { if (device == null) { Log.e(TAG, "setCustomMeta: device is null"); return false; } if (!isValidMetaKey(key)) { Log.e(TAG, "setCustomMeta: meta key invalid " + key); return false; } String address = device.getAddress(); if (VERBOSE) { Log.d(TAG, "setCustomMeta: " + address + ", key=" + key); } if (!mMetadataCache.containsKey(address)) { createMetadata(address); } Metadata data = mMetadataCache.get(address); String oldValue = data.getCustomizedMeta(key); if (oldValue != null && oldValue.equals(newValue)) { if (VERBOSE) { Log.d(TAG, "setCustomMeta: metadata not changed."); } return true; } data.setCustomizedMeta(key, newValue); updateDatabase(data); mAdapterService.metadataChanged(address, key, newValue); return true; } } /** * Get customized metadata from database with requested key */ @VisibleForTesting public String getCustomMeta(BluetoothDevice device, int key) { synchronized (mMetadataCache) { if (device == null) { Log.e(TAG, "getCustomMeta: device is null"); return null; } if (!isValidMetaKey(key)) { Log.e(TAG, "getCustomMeta: meta key invalid " + key); return null; } String address = device.getAddress(); if (!mMetadataCache.containsKey(address)) { Log.e(TAG, "getCustomMeta: device " + address + " is not in cache"); return null; } Metadata data = mMetadataCache.get(address); String value = data.getCustomizedMeta(key); return value; } } /** * Set the device profile prioirty * Loading Loading @@ -476,8 +567,12 @@ public class DatabaseManager { for (BluetoothDevice device : bondedDevices) { if (!device.getAddress().equals(address) && !address.equals(LOCAL_STORAGE)) { // Report metadata change to null List<Integer> list = metadata.getChangedCustomizedMeta(); for (int key : list) { mAdapterService.metadataChanged(address, key, null); } Log.i(TAG, "remove unpaired device from database " + address); //TODO Callback metadata change deleteDatabase(mMetadataCache.get(address)); } } Loading
android/app/src/com/android/bluetooth/btservice/storage/Metadata.java +174 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.bluetooth.btservice; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import androidx.annotation.NonNull; Loading @@ -24,6 +25,9 @@ import androidx.room.Embedded; import androidx.room.Entity; import androidx.room.PrimaryKey; import java.util.ArrayList; import java.util.List; @Entity(tableName = "metadata") class Metadata { @PrimaryKey Loading Loading @@ -121,4 +125,174 @@ class Metadata { } return BluetoothProfile.PRIORITY_UNDEFINED; } void setCustomizedMeta(int key, String value) { switch (key) { case BluetoothDevice.METADATA_MANUFACTURER_NAME: publicMeta.manufacturer_name = value; break; case BluetoothDevice.METADATA_MODEL_NAME: publicMeta.model_name = value; break; case BluetoothDevice.METADATA_SOFTWARE_VERSION: publicMeta.software_version = value; break; case BluetoothDevice.METADATA_HARDWARE_VERSION: publicMeta.hardware_version = value; break; case BluetoothDevice.METADATA_COMPANION_APP: publicMeta.companion_app = value; break; case BluetoothDevice.METADATA_MAIN_ICON: publicMeta.main_icon = value; break; case BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET: publicMeta.is_unthethered_headset = value; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON: publicMeta.unthethered_left_icon = value; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON: publicMeta.unthethered_right_icon = value; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON: publicMeta.unthethered_case_icon = value; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY: publicMeta.unthethered_left_battery = value; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY: publicMeta.unthethered_right_battery = value; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY: publicMeta.unthethered_case_battery = value; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING: publicMeta.unthethered_left_charging = value; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING: publicMeta.unthethered_right_charging = value; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING: publicMeta.unthethered_case_charging = value; break; case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI: publicMeta.enhanced_settings_ui_uri = value; break; } } String getCustomizedMeta(int key) { String value = null; switch (key) { case BluetoothDevice.METADATA_MANUFACTURER_NAME: value = publicMeta.manufacturer_name; break; case BluetoothDevice.METADATA_MODEL_NAME: value = publicMeta.model_name; break; case BluetoothDevice.METADATA_SOFTWARE_VERSION: value = publicMeta.software_version; break; case BluetoothDevice.METADATA_HARDWARE_VERSION: value = publicMeta.hardware_version; break; case BluetoothDevice.METADATA_COMPANION_APP: value = publicMeta.companion_app; break; case BluetoothDevice.METADATA_MAIN_ICON: value = publicMeta.main_icon; break; case BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET: value = publicMeta.is_unthethered_headset; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON: value = publicMeta.unthethered_left_icon; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON: value = publicMeta.unthethered_right_icon; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON: value = publicMeta.unthethered_case_icon; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY: value = publicMeta.unthethered_left_battery; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY: value = publicMeta.unthethered_right_battery; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY: value = publicMeta.unthethered_case_battery; break; case BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING: value = publicMeta.unthethered_left_charging; break; case BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING: value = publicMeta.unthethered_right_charging; break; case BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING: value = publicMeta.unthethered_case_charging; break; case BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI: value = publicMeta.enhanced_settings_ui_uri; break; } return value; } List<Integer> getChangedCustomizedMeta() { List<Integer> list = new ArrayList<>(); if (publicMeta.manufacturer_name != null) { list.add(BluetoothDevice.METADATA_MANUFACTURER_NAME); } if (publicMeta.model_name != null) { list.add(BluetoothDevice.METADATA_MODEL_NAME); } if (publicMeta.software_version != null) { list.add(BluetoothDevice.METADATA_SOFTWARE_VERSION); } if (publicMeta.hardware_version != null) { list.add(BluetoothDevice.METADATA_HARDWARE_VERSION); } if (publicMeta.companion_app != null) { list.add(BluetoothDevice.METADATA_COMPANION_APP); } if (publicMeta.main_icon != null) { list.add(BluetoothDevice.METADATA_MAIN_ICON); } if (publicMeta.is_unthethered_headset != null) { list.add(BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET); } if (publicMeta.unthethered_left_icon != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON); } if (publicMeta.unthethered_right_icon != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON); } if (publicMeta.unthethered_case_icon != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON); } if (publicMeta.unthethered_left_battery != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY); } if (publicMeta.unthethered_right_battery != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY); } if (publicMeta.unthethered_case_battery != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY); } if (publicMeta.unthethered_left_charging != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING); } if (publicMeta.unthethered_right_charging != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING); } if (publicMeta.unthethered_case_charging != null) { list.add(BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING); } if (publicMeta.enhanced_settings_ui_uri != null) { list.add(BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI); } return list; } }
android/app/tests/unit/src/com/android/bluetooth/btservice/DatabaseManagerTest.java +124 −2 Original line number Diff line number Diff line Loading @@ -68,10 +68,10 @@ public final class DatabaseManagerTest { MetadataDatabase.class).build(); mDatabaseManager = new DatabaseManager(mAdapterService); //mDatabaseManager.doNotMigrateSettingGlobal(); BluetoothDevice[] bondedDevices = {}; doReturn(bondedDevices).when(mAdapterService).getBondedDevices(); doNothing().when(mAdapterService).metadataChanged(anyString(), anyInt(), anyString()); mDatabaseManager.start(mDatabase); // Wait for handler thread finish its task. Loading Loading @@ -182,11 +182,13 @@ public final class DatabaseManagerTest { @Test public void testRemoveUnusedMetadata() { // Insert two devices to database and cache, only mTestDevice is // Insert three devices to database and cache, only mTestDevice is // in the bonded list BluetoothDevice otherDevice = BluetoothAdapter.getDefaultAdapter() .getRemoteDevice(OTHER_BT_ADDR); Metadata otherData = new Metadata(OTHER_BT_ADDR); // Add metadata for otherDevice otherData.setCustomizedMeta(0, "value"); mDatabaseManager.mMetadataCache.put(OTHER_BT_ADDR, otherData); mDatabase.insert(otherData); Loading @@ -201,6 +203,9 @@ public final class DatabaseManagerTest { // Wait for database update TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); // Check removed device report metadata changed to null verify(mAdapterService).metadataChanged(OTHER_BT_ADDR, 0, null); List<Metadata> list = mDatabase.load(); // Check number of metadata in the database Loading @@ -216,6 +221,85 @@ public final class DatabaseManagerTest { mDatabaseManager.mMetadataCache.clear(); } @Test public void testSetGetCustomMeta() { int badKey = 100; String value = "input value"; // Device is not in database testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MANUFACTURER_NAME, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MODEL_NAME, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_SOFTWARE_VERSION, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_HARDWARE_VERSION, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_COMPANION_APP, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_MAIN_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING, value, true); testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI, value, true); testSetGetCustomMetaCase(false, badKey, value, false); // Device is in database testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MANUFACTURER_NAME, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MODEL_NAME, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_SOFTWARE_VERSION, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_HARDWARE_VERSION, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_COMPANION_APP, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_MAIN_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_LEFT_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_CASE_ICON, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_LEFT_BATTERY, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_BATTERY, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_CASE_BATTERY, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_LEFT_CHARGING, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_RIGHT_CHARGING, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_UNTHETHERED_CASE_CHARGING, value, true); testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_ENHANCED_SETTINGS_UI_URI, value, true); } void testSetGetProfilePriorityCase(boolean stored, int priority, int expectedPriority, boolean expectedSetResult) { if (stored) { Loading Loading @@ -298,4 +382,42 @@ public final class DatabaseManagerTest { TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); mDatabaseManager.mMetadataCache.clear(); } void testSetGetCustomMetaCase(boolean stored, int key, String value, boolean expectedResult) { String testValue = "test value"; int verifyTime = 1; if (stored) { Metadata data = new Metadata(TEST_BT_ADDR); mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data); mDatabase.insert(data); Assert.assertEquals(expectedResult, mDatabaseManager.setCustomMeta(mTestDevice, key, testValue)); verify(mAdapterService).metadataChanged(TEST_BT_ADDR, key, testValue); verifyTime++; } Assert.assertEquals(expectedResult, mDatabaseManager.setCustomMeta(mTestDevice, key, value)); if (expectedResult) { // Check for callback and get value verify(mAdapterService, times(verifyTime)).metadataChanged(TEST_BT_ADDR, key, value); Assert.assertEquals(value, mDatabaseManager.getCustomMeta(mTestDevice, key)); } else { Assert.assertNull(mDatabaseManager.getCustomMeta(mTestDevice, key)); return; } // Wait for database update TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); // Check whether the value is saved in database List<Metadata> list = mDatabase.load(); Metadata data = list.get(0); Assert.assertEquals(TEST_BT_ADDR, data.getAddress()); Assert.assertEquals(value, data.getCustomizedMeta(key)); mDatabase.deleteAll(); // Wait for clear database TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper()); mDatabaseManager.mMetadataCache.clear(); } }