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

Commit e2480739 authored by Daichi Hirono's avatar Daichi Hirono Committed by Android (Google) Code Review
Browse files

Merge "Add MtpDatabase class."

parents 5596c05d 9678f606
Loading
Loading
Loading
Loading
+161 −0
Original line number Diff line number Diff line
package com.android.mtp;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;

import com.android.internal.annotations.VisibleForTesting;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * Database for MTP objects.
 * The object handle which is identifier for object in MTP protocol is not stable over sessions.
 * When we resume the process, we need to remap our document ID with MTP's object handle.
 * The database object remembers the map of document ID and fullpath, and helps to remap object
 * handle and document ID by comparing fullpath.
 * TODO: Remove @VisibleForTesting annotation when we start to use this class.
 */
@VisibleForTesting
class MtpDatabase {
    private static final int VERSION = 1;
    private static final String NAME = "mtp";

    private static final String TABLE_MTP_DOCUMENTS = "MtpDocuments";

    static final String COLUMN_DEVICE_ID = "deviceId";
    static final String COLUMN_STORAGE_ID = "storageId";
    static final String COLUMN_OBJECT_HANDLE = "objectHandle";
    static final String COLUMN_FULL_PATH = "fullPath";

    private static class OpenHelper extends SQLiteOpenHelper {
        private static final String CREATE_TABLE_QUERY =
                "CREATE TABLE " + TABLE_MTP_DOCUMENTS + " (" +
                DocumentsContract.Document.COLUMN_DOCUMENT_ID +
                    " INTEGER PRIMARY KEY AUTOINCREMENT," +
                COLUMN_DEVICE_ID + " INTEGER NOT NULL," +
                COLUMN_STORAGE_ID + " INTEGER NOT NULL," +
                COLUMN_OBJECT_HANDLE + " INTEGER," +
                COLUMN_FULL_PATH + " TEXT NOT NULL," +
                DocumentsContract.Document.COLUMN_MIME_TYPE + " TEXT," +
                DocumentsContract.Document.COLUMN_DISPLAY_NAME + " TEXT NOT NULL," +
                DocumentsContract.Document.COLUMN_SUMMARY + " TEXT," +
                DocumentsContract.Document.COLUMN_LAST_MODIFIED + " INTEGER," +
                DocumentsContract.Document.COLUMN_ICON + " INTEGER," +
                DocumentsContract.Document.COLUMN_FLAGS + " INTEGER NOT NULL," +
                DocumentsContract.Document.COLUMN_SIZE + " INTEGER NOT NULL);";

        public OpenHelper(Context context) {
            super(context, NAME, null, VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(CREATE_TABLE_QUERY);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            throw new UnsupportedOperationException();
        }
    }

    private final SQLiteDatabase database;

    @VisibleForTesting
    MtpDatabase(Context context) {
        final OpenHelper helper = new OpenHelper(context);
        database = helper.getWritableDatabase();
    }

    @VisibleForTesting
    static void deleteDatabase(Context context) {
        SQLiteDatabase.deleteDatabase(context.getDatabasePath(NAME));
    }

    @VisibleForTesting
    Cursor queryChildDocuments(String[] columnNames) {
        return database.query(TABLE_MTP_DOCUMENTS, columnNames, null, null, null, null, null);
    }

    @VisibleForTesting
    void putRootDocument(MtpRoot root) throws Exception {
        database.beginTransaction();
        try {
            final ContentValues values = new ContentValues();
            values.put(COLUMN_DEVICE_ID, root.mDeviceId);
            values.put(COLUMN_STORAGE_ID, root.mStorageId);
            values.putNull(COLUMN_OBJECT_HANDLE);
            values.put(
                    COLUMN_FULL_PATH, "/" + root.mDeviceId + "/" + escape(root.mDescription));
            values.put(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
            values.put(Document.COLUMN_DISPLAY_NAME, root.mDescription);
            values.putNull(Document.COLUMN_SUMMARY);
            values.putNull(Document.COLUMN_LAST_MODIFIED);
            values.putNull(Document.COLUMN_ICON);
            values.put(Document.COLUMN_FLAGS, 0);
            values.put(Document.COLUMN_SIZE,
                    (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
            if (database.insert(TABLE_MTP_DOCUMENTS, null, values) == -1) {
                throw new Exception("Failed to add root document.");
            }
            database.setTransactionSuccessful();
        } finally {
            database.endTransaction();
        }
    }

    @VisibleForTesting
    void putDocument(int deviceId, String parentFullPath, MtpObjectInfo info) throws Exception {
        database.beginTransaction();
        try {
            final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());

            int flag = 0;
            if (info.getProtectionStatus() == 0) {
                flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                        DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
                if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
                    flag |= DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
                }
            }
            if (info.getThumbCompressedSize() > 0) {
                flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
            }

            final ContentValues values = new ContentValues();
            values.put(COLUMN_DEVICE_ID, deviceId);
            values.put(COLUMN_STORAGE_ID, info.getStorageId());
            values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
            values.put(COLUMN_FULL_PATH, parentFullPath + "/" + escape(info.getName()));
            values.put(
                    Document.COLUMN_MIME_TYPE, CursorHelper.formatTypeToMimeType(info.getFormat()));
            values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
            values.putNull(Document.COLUMN_SUMMARY);
            values.put(
                    Document.COLUMN_LAST_MODIFIED,
                    info.getDateModified() != 0 ? info.getDateModified() : null);
            values.putNull(Document.COLUMN_ICON);
            values.put(Document.COLUMN_FLAGS, flag);
            values.put(Document.COLUMN_SIZE, info.getCompressedSize());
            if (database.insert(TABLE_MTP_DOCUMENTS, null, values) == -1) {
                throw new Exception("Failed to add document.");
            }
            database.setTransactionSuccessful();
        } finally {
            database.endTransaction();
        }
    }

    @VisibleForTesting
    private String escape(String s) throws UnsupportedEncodingException {
        return URLEncoder.encode(s, StandardCharsets.UTF_8.name());
    }
}
+121 −0
Original line number Diff line number Diff line
package com.android.mtp;


import android.database.Cursor;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;

@SmallTest
public class MtpDatabaseTest extends AndroidTestCase {
    private final String[] COLUMN_NAMES = new String[] {
        DocumentsContract.Document.COLUMN_DOCUMENT_ID,
        MtpDatabase.COLUMN_DEVICE_ID,
        MtpDatabase.COLUMN_STORAGE_ID,
        MtpDatabase.COLUMN_OBJECT_HANDLE,
        MtpDatabase.COLUMN_FULL_PATH,
        DocumentsContract.Document.COLUMN_MIME_TYPE,
        DocumentsContract.Document.COLUMN_DISPLAY_NAME,
        DocumentsContract.Document.COLUMN_SUMMARY,
        DocumentsContract.Document.COLUMN_LAST_MODIFIED,
        DocumentsContract.Document.COLUMN_ICON,
        DocumentsContract.Document.COLUMN_FLAGS,
        DocumentsContract.Document.COLUMN_SIZE
    };

    @Override
    public void tearDown() {
        MtpDatabase.deleteDatabase(getContext());
    }

    public void testPutRootDocument() throws Exception {
        final MtpDatabase database = new MtpDatabase(getContext());
        final MtpRoot root = new MtpRoot(
                0,
                1,
                "Device A",
                "Storage",
                1000,
                2000,
                "");
        database.putRootDocument(root);

        final MtpRoot duplicatedNameRoot = new MtpRoot(
                0,
                2,
                "Device A",
                "Storage",
                1000,
                2000,
                "");
        database.putRootDocument(duplicatedNameRoot);

        final MtpRoot strangeNameRoot = new MtpRoot(
                0,
                3,
                "Device A",
                "/@#%&<>Storage",
                1000,
                2000,
                "");
        database.putRootDocument(strangeNameRoot);

        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
        assertEquals(3, cursor.getCount());

        cursor.moveToNext();
        assertEquals("documentId", 1, cursor.getInt(0));
        assertEquals("deviceId", 0, cursor.getInt(1));
        assertEquals("storageId", 1, cursor.getInt(2));
        assertTrue("objectHandle", cursor.isNull(3));
        assertEquals("fullPath", "/0/Storage", cursor.getString(4));
        assertEquals("mimeType", DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(5));
        assertEquals("displayName", "Storage", cursor.getString(6));
        assertTrue("summary", cursor.isNull(7));
        assertTrue("lastModified", cursor.isNull(8));
        assertTrue("icon", cursor.isNull(9));
        assertEquals("flag", 0, cursor.getInt(10));
        assertEquals("size", 1000, cursor.getInt(11));

        cursor.moveToNext();
        assertEquals("documentId", 2, cursor.getInt(0));
        assertEquals("fullPath", "/0/Storage", cursor.getString(4));

        cursor.moveToNext();
        assertEquals("documentId", 3, cursor.getInt(0));
        assertEquals("fullPath", "/0/%2F%40%23%25%26%3C%3EStorage", cursor.getString(4));
    }

    public void testPutDocument() throws Exception {
        final MtpDatabase database = new MtpDatabase(getContext());
        final MtpObjectInfo.Builder builder = new MtpObjectInfo.Builder();
        builder.setObjectHandle(100);
        builder.setName("test.txt");
        builder.setStorageId(5);
        builder.setFormat(MtpConstants.FORMAT_TEXT);
        builder.setCompressedSize(1000);
        database.putDocument(0, "/0/Storage", builder.build());

        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
        assertEquals(1, cursor.getCount());
        cursor.moveToNext();
        assertEquals("documentId", 1, cursor.getInt(0));
        assertEquals("deviceId", 0, cursor.getInt(1));
        assertEquals("storageId", 5, cursor.getInt(2));
        assertEquals("objectHandle", 100, cursor.getInt(3));
        assertEquals("fullPath", "/0/Storage/test.txt", cursor.getString(4));
        assertEquals("mimeType", "text/plain", cursor.getString(5));
        assertEquals("displayName", "test.txt", cursor.getString(6));
        assertTrue("summary", cursor.isNull(7));
        assertTrue("lastModified", cursor.isNull(8));
        assertTrue("icon", cursor.isNull(9));
        assertEquals(
                "flag",
                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                cursor.getInt(10));
        assertEquals("size", 1000, cursor.getInt(11));
    }
}