Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java 0 → 100644 +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()); } } packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java 0 → 100644 +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)); } } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java 0 → 100644 +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()); } }
packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java 0 → 100644 +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)); } }