blob: 11e937b44476726f438eeed4ad2b48639894c52a [file] [log] [blame]
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());
}
}