diff options
| -rw-r--r-- | core/java/com/android/internal/net/ConnectivityBlobStore.java | 112 | ||||
| -rw-r--r-- | core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java | 89 |
2 files changed, 201 insertions, 0 deletions
diff --git a/core/java/com/android/internal/net/ConnectivityBlobStore.java b/core/java/com/android/internal/net/ConnectivityBlobStore.java index 51997cf13546..1b18485e35fa 100644 --- a/core/java/com/android/internal/net/ConnectivityBlobStore.java +++ b/core/java/com/android/internal/net/ConnectivityBlobStore.java @@ -16,11 +16,19 @@ package com.android.internal.net; +import android.annotation.NonNull; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; +import android.os.Binder; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.File; +import java.util.ArrayList; +import java.util.List; /** * Database for storing blobs with a key of name strings. @@ -58,4 +66,108 @@ public class ConnectivityBlobStore { mDb = SQLiteDatabase.openDatabase(file, params); mDb.execSQL(CREATE_TABLE); } + + /** + * Stores the blob under the name in the database. Existing blobs by the same name will be + * replaced. + * + * @param name The name of the blob + * @param blob The blob. + * @return true if the blob was successfully added. False otherwise. + * @hide + */ + public boolean put(@NonNull String name, @NonNull byte[] blob) { + final int ownerUid = Binder.getCallingUid(); + final ContentValues values = new ContentValues(); + values.put("owner", ownerUid); + values.put("name", name); + values.put("blob", blob); + + // No need for try-catch since it is done within db.replace + // nullColumnHack is for the case where values may be empty since SQL does not allow + // inserting a completely empty row. Since values is never empty, set this to null. + final long res = mDb.replace(TABLENAME, null /* nullColumnHack */, values); + return res > 0; + } + + /** + * Retrieves a blob by the name from the database. + * + * @param name Name of the blob to retrieve. + * @return The unstructured blob, that is the blob that was stored using + * {@link com.android.internal.net.ConnectivityBlobStore#put}. + * Returns null if no blob was found. + * @hide + */ + public byte[] get(@NonNull String name) { + final int ownerUid = Binder.getCallingUid(); + try (Cursor cursor = mDb.query(TABLENAME, + new String[] {"blob"} /* columns */, + "owner=? AND name=?" /* selection */, + new String[] {Integer.toString(ownerUid), name} /* selectionArgs */, + null /* groupBy */, + null /* having */, + null /* orderBy */)) { + if (cursor.moveToFirst()) { + return cursor.getBlob(0); + } + } catch (SQLException e) { + Log.e(TAG, "Error in getting " + name + ": " + e); + } + + return null; + } + + /** + * Removes a blob by the name from the database. + * + * @param name Name of the blob to be removed. + * @return True if a blob was removed. False if no such name was found. + * @hide + */ + public boolean remove(@NonNull String name) { + final int ownerUid = Binder.getCallingUid(); + try { + final int res = mDb.delete(TABLENAME, + "owner=? AND name=?" /* whereClause */, + new String[] {Integer.toString(ownerUid), name} /* whereArgs */); + return res > 0; + } catch (SQLException e) { + Log.e(TAG, "Error in removing " + name + ": " + e); + return false; + } + } + + /** + * Lists the name suffixes stored in the database matching the given prefix, sorted in + * ascending order. + * + * @param prefix String of prefix to list from the stored names. + * @return An array of strings representing the name suffixes stored in the database + * matching the given prefix, sorted in ascending order. + * The return value may be empty but never null. + * @hide + */ + public String[] list(@NonNull String prefix) { + final int ownerUid = Binder.getCallingUid(); + final List<String> names = new ArrayList<String>(); + try (Cursor cursor = mDb.query(TABLENAME, + new String[] {"name"} /* columns */, + "owner=? AND name LIKE ?" /* selection */, + new String[] {Integer.toString(ownerUid), prefix + "%"} /* selectionArgs */, + null /* groupBy */, + null /* having */, + "name ASC" /* orderBy */)) { + if (cursor.moveToFirst()) { + do { + final String name = cursor.getString(0); + names.add(name.substring(prefix.length())); + } while (cursor.moveToNext()); + } + } catch (SQLException e) { + Log.e(TAG, "Error in listing " + prefix + ": " + e); + } + + return names.toArray(new String[names.size()]); + } } diff --git a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java index e361ce3fb490..68545cfe889c 100644 --- a/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java +++ b/core/tests/coretests/src/com/android/internal/net/ConnectivityBlobStoreTest.java @@ -16,7 +16,9 @@ package com.android.internal.net; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.content.Context; @@ -36,6 +38,8 @@ import java.io.File; @SmallTest public class ConnectivityBlobStoreTest { private static final String DATABASE_FILENAME = "ConnectivityBlobStore.db"; + private static final String TEST_NAME = "TEST_NAME"; + private static final byte[] TEST_BLOB = new byte[] {(byte) 10, (byte) 90, (byte) 45, (byte) 12}; private Context mContext; private File mFile; @@ -64,4 +68,89 @@ public class ConnectivityBlobStoreTest { assertTrue(mContext.deleteDatabase(DATABASE_FILENAME)); assertFalse(mFile.exists()); } + + @Test + public void testPutAndGet() throws Exception { + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + assertNull(connectivityBlobStore.get(TEST_NAME)); + + assertTrue(connectivityBlobStore.put(TEST_NAME, TEST_BLOB)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(TEST_NAME)); + + // Test replacement + final byte[] newBlob = new byte[] {(byte) 15, (byte) 20}; + assertTrue(connectivityBlobStore.put(TEST_NAME, newBlob)); + assertArrayEquals(newBlob, connectivityBlobStore.get(TEST_NAME)); + } + + @Test + public void testRemove() throws Exception { + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + assertNull(connectivityBlobStore.get(TEST_NAME)); + assertFalse(connectivityBlobStore.remove(TEST_NAME)); + + assertTrue(connectivityBlobStore.put(TEST_NAME, TEST_BLOB)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(TEST_NAME)); + + assertTrue(connectivityBlobStore.remove(TEST_NAME)); + assertNull(connectivityBlobStore.get(TEST_NAME)); + + // Removing again returns false + assertFalse(connectivityBlobStore.remove(TEST_NAME)); + } + + @Test + public void testMultipleNames() throws Exception { + final String name1 = TEST_NAME + "1"; + final String name2 = TEST_NAME + "2"; + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + + assertNull(connectivityBlobStore.get(name1)); + assertNull(connectivityBlobStore.get(name2)); + assertFalse(connectivityBlobStore.remove(name1)); + assertFalse(connectivityBlobStore.remove(name2)); + + assertTrue(connectivityBlobStore.put(name1, TEST_BLOB)); + assertTrue(connectivityBlobStore.put(name2, TEST_BLOB)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(name1)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(name2)); + + // Replace the blob for name1 only. + final byte[] newBlob = new byte[] {(byte) 16, (byte) 21}; + assertTrue(connectivityBlobStore.put(name1, newBlob)); + assertArrayEquals(newBlob, connectivityBlobStore.get(name1)); + + assertTrue(connectivityBlobStore.remove(name1)); + assertNull(connectivityBlobStore.get(name1)); + assertArrayEquals(TEST_BLOB, connectivityBlobStore.get(name2)); + + assertFalse(connectivityBlobStore.remove(name1)); + assertTrue(connectivityBlobStore.remove(name2)); + assertNull(connectivityBlobStore.get(name2)); + assertFalse(connectivityBlobStore.remove(name2)); + } + + @Test + public void testList() throws Exception { + final String[] unsortedNames = new String[] { + TEST_NAME + "1", + TEST_NAME + "2", + TEST_NAME + "0", + "NON_MATCHING_PREFIX", + "MATCHING_SUFFIX_" + TEST_NAME + }; + // Expected to match and discard the prefix and be in increasing sorted order. + final String[] expected = new String[] { + "0", + "1", + "2" + }; + final ConnectivityBlobStore connectivityBlobStore = createConnectivityBlobStore(); + + for (int i = 0; i < unsortedNames.length; i++) { + assertTrue(connectivityBlobStore.put(unsortedNames[i], TEST_BLOB)); + } + final String[] actual = connectivityBlobStore.list(TEST_NAME /* prefix */); + assertArrayEquals(expected, actual); + } } |