Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91f4171f authored by Hansen Kurli's avatar Hansen Kurli Committed by Automerger Merge Worker
Browse files

Merge changes Iac799d37,Ibf88d68c,Id396b0e2 into main am: 1d7e1458 am: f5869c92

parents d4f4d007 f5869c92
Loading
Loading
Loading
Loading
+112 −0
Original line number Diff line number Diff line
@@ -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()]);
    }
}
+89 −0
Original line number Diff line number Diff line
@@ -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);
    }
}