summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java8
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java513
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java443
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java599
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java2
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java7
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java6
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java184
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java2
9 files changed, 833 insertions, 931 deletions
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index e4630ec4c081..6fa0df2abc22 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -255,10 +255,10 @@ class DocumentLoader {
return;
}
if (mNumLoaded == 0) {
- mDatabase.startAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId);
}
try {
- mDatabase.putChildDocuments(
+ mDatabase.getMapper().putChildDocuments(
mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
mNumLoaded += objectInfoList.length;
} catch (SQLiteException exp) {
@@ -266,7 +266,7 @@ class DocumentLoader {
mNumLoaded = 0;
}
if (getState() != STATE_LOADING) {
- mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
}
}
@@ -275,7 +275,7 @@ class DocumentLoader {
mError = message;
mNumLoaded = 0;
if (lastState == STATE_LOADING) {
- mDatabase.stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
new file mode 100644
index 000000000000..0d9d60c0b460
--- /dev/null
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mtp;
+
+import static com.android.mtp.MtpDatabaseConstants.*;
+
+import android.content.ContentValues;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.mtp.MtpObjectInfo;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.android.mtp.MtpDatabase.strings;
+
+
+/**
+ * Mapping operations for MtpDatabase.
+ * Also see the comments of {@link MtpDatabase}.
+ */
+class Mapper {
+ private final MtpDatabase mDatabase;
+
+ /**
+ * Mapping mode for roots/documents where we start adding child documents.
+ * Methods operate the state needs to be synchronized.
+ */
+ private final Map<String, Integer> mMappingMode = new HashMap<>();
+
+ Mapper(MtpDatabase database) {
+ mDatabase = database;
+ }
+
+ /**
+ * Invokes {@link #startAddingDocuments} for root documents.
+ * @param deviceId Device ID.
+ */
+ synchronized void startAddingRootDocuments(int deviceId) {
+ final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
+ mMappingMode.put(
+ mappingStateKey,
+ startAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+ }
+
+ /**
+ * Invokes {@link #startAddingDocuments} for child of specific documents.
+ * @param parentDocumentId Document ID for parent document.
+ */
+ @VisibleForTesting
+ synchronized void startAddingChildDocuments(String parentDocumentId) {
+ final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
+ Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
+ mMappingMode.put(
+ mappingStateKey,
+ startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
+ }
+
+ /**
+ * Puts root information to database.
+ * @param deviceId Device ID
+ * @param resources Resources required to localize root name.
+ * @param roots List of root information.
+ * @return If roots are added or removed from the database.
+ */
+ synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ final boolean heuristic;
+ final String mapColumn;
+ final String key = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_STORAGE_ID;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
+ final ContentValues[] valuesList = new ContentValues[roots.length];
+ for (int i = 0; i < roots.length; i++) {
+ if (roots[i].mDeviceId != deviceId) {
+ throw new IllegalArgumentException();
+ }
+ valuesList[i] = new ContentValues();
+ MtpDatabase.getRootDocumentValues(valuesList[i], resources, roots[i]);
+ }
+ final boolean changed = putDocuments(
+ valuesList,
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ heuristic,
+ mapColumn);
+ final ContentValues values = new ContentValues();
+ int i = 0;
+ for (final MtpRoot root : roots) {
+ // Use the same value for the root ID and the corresponding document ID.
+ final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
+ // If it fails to insert/update documents, the document ID will be set with -1.
+ // In this case we don't insert/update root extra information neither.
+ if (documentId == null) {
+ continue;
+ }
+ values.put(Root.COLUMN_ROOT_ID, documentId);
+ values.put(
+ Root.COLUMN_FLAGS,
+ Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
+ values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
+ values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
+ values.put(Root.COLUMN_MIME_TYPES, "");
+ database.replace(TABLE_ROOT_EXTRA, null, values);
+ }
+ database.setTransactionSuccessful();
+ return changed;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Puts document information to database.
+ * @param deviceId Device ID
+ * @param parentId Parent document ID.
+ * @param documents List of document information.
+ */
+ synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
+ final boolean heuristic;
+ final String mapColumn;
+ final String key = getChildDocumentsMappingStateKey(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ heuristic = false;
+ mapColumn = COLUMN_OBJECT_HANDLE;
+ break;
+ case MAP_BY_NAME:
+ heuristic = true;
+ mapColumn = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected map mode.");
+ }
+ final ContentValues[] valuesList = new ContentValues[documents.length];
+ for (int i = 0; i < documents.length; i++) {
+ valuesList[i] = new ContentValues();
+ MtpDatabase.getChildDocumentValues(
+ valuesList[i], deviceId, parentId, documents[i]);
+ }
+ putDocuments(
+ valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
+ }
+
+ /**
+ * Stops adding root documents.
+ * @param deviceId Device ID.
+ * @return True if new rows are added/removed.
+ */
+ synchronized boolean stopAddingRootDocuments(int deviceId) {
+ final String key = getRootDocumentsMappingStateKey(deviceId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ mMappingMode.remove(key);
+ return stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ COLUMN_STORAGE_ID);
+ case MAP_BY_NAME:
+ mMappingMode.remove(key);
+ return stopAddingDocuments(
+ SELECTION_ROOT_DOCUMENTS,
+ Integer.toString(deviceId),
+ Document.COLUMN_DISPLAY_NAME);
+ default:
+ throw new Error("Unexpected mapping state.");
+ }
+ }
+
+ /**
+ * Stops adding documents under the parent.
+ * @param parentId Document ID of the parent.
+ */
+ synchronized void stopAddingChildDocuments(String parentId) {
+ final String key = getChildDocumentsMappingStateKey(parentId);
+ Preconditions.checkState(mMappingMode.containsKey(key));
+ switch (mMappingMode.get(key)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ COLUMN_OBJECT_HANDLE);
+ break;
+ case MAP_BY_NAME:
+ stopAddingDocuments(
+ SELECTION_CHILD_DOCUMENTS,
+ parentId,
+ Document.COLUMN_DISPLAY_NAME);
+ break;
+ default:
+ throw new Error("Unexpected mapping state.");
+ }
+ mMappingMode.remove(key);
+ }
+
+ @VisibleForTesting
+ void clearMapping() {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
+ final ContentValues values = new ContentValues();
+ values.putNull(COLUMN_OBJECT_HANDLE);
+ values.putNull(COLUMN_STORAGE_ID);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+ database.update(TABLE_DOCUMENTS, values, null, null);
+ database.setTransactionSuccessful();
+ mMappingMode.clear();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Starts adding new documents.
+ * The methods decides mapping mode depends on if all documents under the given parent have MTP
+ * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
+ * a corresponding existing row. Otherwise it does heuristic.
+ *
+ * @param selection Query matches valid documents.
+ * @param arg Argument for selection.
+ * @return Mapping mode.
+ */
+ private int startAddingDocuments(String selection, String arg) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ // Delete all pending rows.
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
+
+ // Set all documents as invalidated.
+ final ContentValues values = new ContentValues();
+ values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
+ database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+
+ // If we have rows that does not have MTP identifier, do heuristic mapping by name.
+ final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
+ database,
+ TABLE_DOCUMENTS,
+ selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
+ new String[] { arg }) > 0;
+ database.setTransactionSuccessful();
+ return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Puts the documents into the database.
+ * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
+ * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
+ * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
+ * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
+ * rows. If the methods adds rows to database, it updates valueList with correct document ID.
+ *
+ * @param valuesList Values for documents to be stored in the database.
+ * @param selection SQL where closure to select rows that shares the same parent.
+ * @param arg Argument for selection SQL.
+ * @param heuristic Whether the mapping mode is heuristic.
+ * @return Whether the method adds new rows.
+ */
+ private boolean putDocuments(
+ ContentValues[] valuesList,
+ String selection,
+ String arg,
+ boolean heuristic,
+ String mappingKey) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ boolean added = false;
+ database.beginTransaction();
+ try {
+ for (final ContentValues values : valuesList) {
+ final Cursor candidateCursor = database.query(
+ TABLE_DOCUMENTS,
+ strings(Document.COLUMN_DOCUMENT_ID),
+ selection + " AND " +
+ COLUMN_ROW_STATE + "=? AND " +
+ mappingKey + "=?",
+ strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ final long rowId;
+ if (candidateCursor.getCount() == 0) {
+ rowId = database.insert(TABLE_DOCUMENTS, null, values);
+ if (rowId == -1) {
+ throw new SQLiteException("Failed to put a document into database.");
+ }
+ added = true;
+ } else if (!heuristic) {
+ candidateCursor.moveToNext();
+ final String documentId = candidateCursor.getString(0);
+ rowId = database.update(
+ TABLE_DOCUMENTS,
+ values,
+ SELECTION_DOCUMENT_ID,
+ strings(documentId));
+ } else {
+ values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
+ rowId = database.insert(TABLE_DOCUMENTS, null, values);
+ }
+ // Document ID is a primary integer key of the table. So the returned row
+ // IDs should be same with the document ID.
+ values.put(Document.COLUMN_DOCUMENT_ID, rowId);
+ } finally {
+ candidateCursor.close();
+ }
+ }
+
+ database.setTransactionSuccessful();
+ return added;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
+ * If the database does not find corresponding 'invalidated' document, it just removes
+ * 'invalidated' document from the database.
+ * @param selection Query to select rows for resolving.
+ * @param arg Argument for selection SQL.
+ * @param groupKey Column name used to find corresponding rows.
+ * @return Whether the methods adds or removed visible rows.
+ */
+ private boolean stopAddingDocuments(String selection, String arg, String groupKey) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ database.beginTransaction();
+ try {
+ // Get 1-to-1 mapping of invalidated document and pending document.
+ final String invalidatedIdQuery = createStateFilter(
+ ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
+ final String pendingIdQuery = createStateFilter(
+ ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
+ // SQL should be like:
+ // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
+ // group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
+ // WHERE device_id = ? AND parent_document_id IS NULL
+ // GROUP BY display_name
+ // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
+ // count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
+ final Cursor mergingCursor = database.query(
+ TABLE_DOCUMENTS,
+ new String[] {
+ "group_concat(" + invalidatedIdQuery + ")",
+ "group_concat(" + pendingIdQuery + ")"
+ },
+ selection,
+ strings(arg),
+ groupKey,
+ "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
+ null);
+
+ final ContentValues values = new ContentValues();
+ while (mergingCursor.moveToNext()) {
+ final String invalidatedId = mergingCursor.getString(0);
+ final String pendingId = mergingCursor.getString(1);
+
+ // Obtain the new values including the latest object handle from mapping row.
+ getFirstRow(
+ TABLE_DOCUMENTS,
+ SELECTION_DOCUMENT_ID,
+ new String[] { pendingId },
+ values);
+ values.remove(Document.COLUMN_DOCUMENT_ID);
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+ database.update(
+ TABLE_DOCUMENTS,
+ values,
+ SELECTION_DOCUMENT_ID,
+ new String[] { invalidatedId });
+
+ getFirstRow(
+ TABLE_ROOT_EXTRA,
+ SELECTION_ROOT_ID,
+ new String[] { pendingId },
+ values);
+ if (values.size() > 0) {
+ values.remove(Root.COLUMN_ROOT_ID);
+ database.update(
+ TABLE_ROOT_EXTRA,
+ values,
+ SELECTION_ROOT_ID,
+ new String[] { invalidatedId });
+ }
+
+ // Delete 'pending' row.
+ mDatabase.deleteDocumentsAndRootsRecursively(
+ SELECTION_DOCUMENT_ID, new String[] { pendingId });
+ }
+ mergingCursor.close();
+
+ boolean changed = false;
+
+ // Delete all invalidated rows that cannot be mapped.
+ if (mDatabase.deleteDocumentsAndRootsRecursively(
+ COLUMN_ROW_STATE + " = ? AND " + selection,
+ strings(ROW_STATE_INVALIDATED, arg))) {
+ changed = true;
+ }
+
+ // The database cannot find old document ID for the pending rows.
+ // Turn the all pending rows into valid state, which means the rows become to be
+ // valid with new document ID.
+ values.clear();
+ values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
+ if (database.update(
+ TABLE_DOCUMENTS,
+ values,
+ COLUMN_ROW_STATE + " = ? AND " + selection,
+ strings(ROW_STATE_PENDING, arg)) != 0) {
+ changed = true;
+ }
+ database.setTransactionSuccessful();
+ return changed;
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ /**
+ * Obtains values of the first row for the query.
+ * @param values ContentValues that the values are stored to.
+ * @param table Target table.
+ * @param selection Query to select rows.
+ * @param args Argument for query.
+ */
+ private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
+ final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
+ values.clear();
+ final Cursor cursor = database.query(table, null, selection, args, null, null, null, "1");
+ if (cursor.getCount() == 0) {
+ return;
+ }
+ cursor.moveToNext();
+ DatabaseUtils.cursorRowToContentValues(cursor, values);
+ cursor.close();
+ }
+
+ /**
+ * Gets SQL expression that represents the given value or NULL depends on the row state.
+ * You must pass static constants to this methods otherwise you may be suffered from SQL
+ * injections.
+ * @param state Expected row state.
+ * @param a SQL value.
+ * @return Expression that represents a if the row state is expected one, and represents NULL
+ * otherwise.
+ */
+ private static String createStateFilter(int state, String a) {
+ return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
+ " THEN " + a + " ELSE NULL END";
+ }
+
+ /**
+ * @param deviceId Device ID.
+ * @return Key for {@link #mMappingMode}.
+ */
+ private static String getRootDocumentsMappingStateKey(int deviceId) {
+ return "RootDocuments/" + deviceId;
+ }
+
+ /**
+ * @param parentDocumentId Document ID for the parent document.
+ * @return Key for {@link #mMappingMode}.
+ */
+ private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
+ return "ChildDocuments/" + parentDocumentId;
+ }
+}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 0272255f106a..4afffeacd2de 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -22,6 +22,9 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
import android.media.MediaFile;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
@@ -32,8 +35,7 @@ import android.provider.DocumentsContract.Root;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileNotFoundException;
-import java.util.HashMap;
-import java.util.Map;
+import java.util.Objects;
/**
* Database for MTP objects.
@@ -49,13 +51,13 @@ import java.util.Map;
* by comparing the directory structure and object name.
*
* To start putting documents into the database, the client needs to call
- * {@link #startAddingChildDocuments(String)} with the parent document ID. Also it needs to call
- * {@link #stopAddingChildDocuments(String)} after putting all child documents to the database.
- * (All explanations are same for root documents)
+ * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it
+ * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child
+ * documents to the database. (All explanations are same for root documents)
*
- * database.startAddingChildDocuments();
- * database.putChildDocuments();
- * database.stopAddingChildDocuments();
+ * database.getMapper().startAddingChildDocuments();
+ * database.getMapper().putChildDocuments();
+ * database.getMapper().stopAddingChildDocuments();
*
* To update the existing documents, the client code can repeat to call the three methods again.
* The newly added rows update corresponding existing rows that have same MTP identifier like
@@ -72,186 +74,229 @@ import java.util.Map;
* TODO: Improve performance by SQL optimization.
*/
class MtpDatabase {
- private final MtpDatabaseInternal mDatabase;
+ private final SQLiteDatabase mDatabase;
+ private final Mapper mMapper;
- /**
- * Mapping mode for roots/documents where we start adding child documents.
- * Methods operate the state needs to be synchronized.
- */
- private final Map<String, Integer> mMappingMode = new HashMap<>();
+ SQLiteDatabase getSQLiteDatabase() {
+ return mDatabase;
+ }
- @VisibleForTesting
MtpDatabase(Context context, int flags) {
- mDatabase = new MtpDatabaseInternal(context, flags);
+ final OpenHelper helper = new OpenHelper(context, flags);
+ mDatabase = helper.getWritableDatabase();
+ mMapper = new Mapper(this);
}
- /**
- * Closes the database.
- */
- @VisibleForTesting
void close() {
mDatabase.close();
}
/**
- * {@link MtpDatabaseInternal#queryRoots}
+ * Returns operations for mapping.
+ * @return Mapping operations.
*/
- Cursor queryRoots(String[] columnNames) {
- return mDatabase.queryRoots(columnNames);
+ Mapper getMapper() {
+ return mMapper;
}
/**
- * {@link MtpDatabaseInternal#queryRootDocuments}
+ * Queries roots information.
+ * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
+ * @return Database cursor.
*/
- @VisibleForTesting
- Cursor queryRootDocuments(String[] columnNames) {
- return mDatabase.queryRootDocuments(columnNames);
+ Cursor queryRoots(String[] columnNames) {
+ return mDatabase.query(
+ VIEW_ROOTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
}
/**
- * {@link MtpDatabaseInternal#queryChildDocuments}
+ * Queries root documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
*/
- Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
+ @VisibleForTesting
+ Cursor queryRootDocuments(String[] columnNames) {
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?)",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
+ null,
+ null,
+ null);
}
/**
- * {@link MtpDatabaseInternal#queryDocument}
+ * Queries documents information.
+ * @param columnNames Column names defined in
+ * {@link android.provider.DocumentsContract.Document}.
+ * @return Database cursor.
*/
- Cursor queryDocument(String documentId, String[] projection) {
- return mDatabase.queryDocument(documentId, projection);
+ Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ columnNames,
+ COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
+ strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
+ null,
+ null,
+ null);
}
/**
- * {@link MtpDatabaseInternal#createIdentifier}
+ * Queries a single document.
+ * @param documentId
+ * @param projection
+ * @return Database cursor.
*/
- Identifier createIdentifier(String parentDocumentId) throws FileNotFoundException {
- return mDatabase.createIdentifier(parentDocumentId);
+ public Cursor queryDocument(String documentId, String[] projection) {
+ return mDatabase.query(
+ TABLE_DOCUMENTS,
+ projection,
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
}
/**
- * {@link MtpDatabaseInternal#removeDeviceRows}
+ * Remove all rows belong to a device.
+ * @param deviceId Device ID.
*/
void removeDeviceRows(int deviceId) {
- mDatabase.removeDeviceRows(deviceId);
+ // Call non-recursive version because it anyway deletes all rows in the devices.
+ deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
}
/**
- * {@link MtpDatabaseInternal#getParentId}
+ * Obtains parent document ID.
+ * @param documentId
+ * @return parent document ID.
* @throws FileNotFoundException
*/
String getParentId(String documentId) throws FileNotFoundException {
- return mDatabase.getParentId(documentId);
- }
-
- /**
- * {@link MtpDatabaseInternal#deleteDocument}
- */
- void deleteDocument(String documentId) {
- mDatabase.deleteDocument(documentId);
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(COLUMN_PARENT_DOCUMENT_ID),
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ if (cursor.moveToNext()) {
+ return cursor.getString(0);
+ } else {
+ throw new FileNotFoundException("Cannot find a row having ID=" + documentId);
+ }
+ } finally {
+ cursor.close();
+ }
}
/**
- * {@link MtpDatabaseInternal#putNewDocument}
- * @throws FileNotFoundException
+ * Adds new document under the parent.
+ * The method does not affect invalidated and pending documents because we know the document is
+ * newly added and never mapped with existing ones.
+ * @param parentDocumentId
+ * @param info
+ * @return Document ID of added document.
*/
- String putNewDocument(int deviceId, String parentDocumentId, MtpObjectInfo info)
- throws FileNotFoundException {
+ String putNewDocument(int deviceId, String parentDocumentId, MtpObjectInfo info) {
final ContentValues values = new ContentValues();
getChildDocumentValues(values, deviceId, parentDocumentId, info);
- return mDatabase.putNewDocument(parentDocumentId, values);
+ mDatabase.beginTransaction();
+ try {
+ final long id = mDatabase.insert(TABLE_DOCUMENTS, null, values);
+ mDatabase.setTransactionSuccessful();
+ return Long.toString(id);
+ } finally {
+ mDatabase.endTransaction();
+ }
}
/**
- * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents.
- * @param deviceId Device ID.
+ * Deletes document and its children.
+ * @param documentId
*/
- synchronized void startAddingRootDocuments(int deviceId) {
- final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
- if (mMappingMode.containsKey(mappingStateKey)) {
- throw new Error("Mapping for the root has already started.");
- }
- mMappingMode.put(
- mappingStateKey,
- mDatabase.startAddingDocuments(
- SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
+ void deleteDocument(String documentId) {
+ deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, strings(documentId));
}
/**
- * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for child of specific documents.
- * @param parentDocumentId Document ID for parent document.
+ * Gets identifier from document ID.
+ * @param documentId Document ID.
+ * @return Identifier.
+ * @throws FileNotFoundException
*/
- @VisibleForTesting
- synchronized void startAddingChildDocuments(String parentDocumentId) {
- final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
- if (mMappingMode.containsKey(mappingStateKey)) {
- throw new Error("Mapping for the root has already started.");
+ Identifier createIdentifier(String documentId) throws FileNotFoundException {
+ // Currently documentId is old format.
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(COLUMN_DEVICE_ID, COLUMN_STORAGE_ID, COLUMN_OBJECT_HANDLE),
+ SELECTION_DOCUMENT_ID,
+ strings(documentId),
+ null,
+ null,
+ null,
+ "1");
+ try {
+ if (cursor.getCount() == 0) {
+ throw new FileNotFoundException("ID is not found.");
+ } else {
+ cursor.moveToNext();
+ return new Identifier(
+ cursor.getInt(0),
+ cursor.getInt(1),
+ cursor.isNull(2) ? Identifier.DUMMY_HANDLE_FOR_ROOT : cursor.getInt(2),
+ documentId);
+ }
+ } finally {
+ cursor.close();
}
- mMappingMode.put(
- mappingStateKey,
- mDatabase.startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
}
/**
- * Puts root information to database.
- * @param deviceId Device ID
- * @param resources Resources required to localize root name.
- * @param roots List of root information.
- * @return If roots are added or removed from the database.
+ * Deletes a document, and its root information if the document is a root document.
+ * @param selection Query to select documents.
+ * @param args Arguments for selection.
+ * @return Whether the method deletes rows.
*/
- synchronized boolean putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
+ boolean deleteDocumentsAndRootsRecursively(String selection, String[] args) {
mDatabase.beginTransaction();
try {
- final boolean heuristic;
- final String mapColumn;
- final String key = getRootDocumentsMappingStateKey(deviceId);
- if (!mMappingMode.containsKey(key)) {
- throw new IllegalStateException("startAddingRootDocuments has not been called.");
- }
- switch (mMappingMode.get(key)) {
- case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
- mapColumn = COLUMN_STORAGE_ID;
- break;
- case MAP_BY_NAME:
- heuristic = true;
- mapColumn = Document.COLUMN_DISPLAY_NAME;
- break;
- default:
- throw new Error("Unexpected map mode.");
- }
- final ContentValues[] valuesList = new ContentValues[roots.length];
- for (int i = 0; i < roots.length; i++) {
- if (roots[i].mDeviceId != deviceId) {
- throw new IllegalArgumentException();
+ boolean changed = false;
+ final Cursor cursor = mDatabase.query(
+ TABLE_DOCUMENTS,
+ strings(Document.COLUMN_DOCUMENT_ID),
+ selection,
+ args,
+ null,
+ null,
+ null);
+ try {
+ while (cursor.moveToNext()) {
+ if (deleteDocumentsAndRootsRecursively(
+ COLUMN_PARENT_DOCUMENT_ID + "=?",
+ strings(cursor.getString(0)))) {
+ changed = true;
+ }
}
- valuesList[i] = new ContentValues();
- getRootDocumentValues(valuesList[i], resources, roots[i]);
+ } finally {
+ cursor.close();
}
- final boolean changed = mDatabase.putDocuments(
- valuesList,
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- heuristic,
- mapColumn);
- final ContentValues values = new ContentValues();
- int i = 0;
- for (final MtpRoot root : roots) {
- // Use the same value for the root ID and the corresponding document ID.
- final String documentId = valuesList[i++].getAsString(Document.COLUMN_DOCUMENT_ID);
- // If it fails to insert/update documents, the document ID will be set with -1.
- // In this case we don't insert/update root extra information neither.
- if (documentId == null) {
- continue;
- }
- values.put(Root.COLUMN_ROOT_ID, documentId);
- values.put(
- Root.COLUMN_FLAGS,
- Root.FLAG_SUPPORTS_IS_CHILD | Root.FLAG_SUPPORTS_CREATE);
- values.put(Root.COLUMN_AVAILABLE_BYTES, root.mFreeSpace);
- values.put(Root.COLUMN_CAPACITY_BYTES, root.mMaxCapacity);
- values.put(Root.COLUMN_MIME_TYPES, "");
- mDatabase.putRootExtra(values);
+ if (deleteDocumentsAndRoots(selection, args)) {
+ changed = true;
}
mDatabase.setTransactionSuccessful();
return changed;
@@ -260,95 +305,50 @@ class MtpDatabase {
}
}
- /**
- * Puts document information to database.
- * @param deviceId Device ID
- * @param parentId Parent document ID.
- * @param documents List of document information.
- */
- @VisibleForTesting
- synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
- final boolean heuristic;
- final String mapColumn;
- switch (mMappingMode.get(getChildDocumentsMappingStateKey(parentId))) {
- case MAP_BY_MTP_IDENTIFIER:
- heuristic = false;
- mapColumn = COLUMN_OBJECT_HANDLE;
- break;
- case MAP_BY_NAME:
- heuristic = true;
- mapColumn = Document.COLUMN_DISPLAY_NAME;
- break;
- default:
- throw new Error("Unexpected map mode.");
- }
- final ContentValues[] valuesList = new ContentValues[documents.length];
- for (int i = 0; i < documents.length; i++) {
- valuesList[i] = new ContentValues();
- getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
+ private boolean deleteDocumentsAndRoots(String selection, String[] args) {
+ mDatabase.beginTransaction();
+ try {
+ int deleted = 0;
+ deleted += mDatabase.delete(
+ TABLE_ROOT_EXTRA,
+ Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
+ false,
+ TABLE_DOCUMENTS,
+ new String[] { Document.COLUMN_DOCUMENT_ID },
+ selection,
+ null,
+ null,
+ null,
+ null) + ")",
+ args);
+ deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
+ mDatabase.setTransactionSuccessful();
+ // TODO Remove mappingState.
+ return deleted != 0;
+ } finally {
+ mDatabase.endTransaction();
}
- mDatabase.putDocuments(
- valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
}
- /**
- * Clears mapping between MTP identifier and document/root ID.
- */
- @VisibleForTesting
- synchronized void clearMapping() {
- mDatabase.clearMapping();
- mMappingMode.clear();
- }
+ private static class OpenHelper extends SQLiteOpenHelper {
+ public OpenHelper(Context context, int flags) {
+ super(context,
+ flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
+ null,
+ DATABASE_VERSION);
+ }
- /**
- * Stops adding root documents.
- * @param deviceId Device ID.
- * @return True if new rows are added/removed.
- */
- synchronized boolean stopAddingRootDocuments(int deviceId) {
- final String mappingModeKey = getRootDocumentsMappingStateKey(deviceId);
- switch (mMappingMode.get(mappingModeKey)) {
- case MAP_BY_MTP_IDENTIFIER:
- mMappingMode.remove(mappingModeKey);
- return mDatabase.stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- COLUMN_STORAGE_ID);
- case MAP_BY_NAME:
- mMappingMode.remove(mappingModeKey);
- return mDatabase.stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- Document.COLUMN_DISPLAY_NAME);
- default:
- throw new Error("Unexpected mapping state.");
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(QUERY_CREATE_DOCUMENTS);
+ db.execSQL(QUERY_CREATE_ROOT_EXTRA);
+ db.execSQL(QUERY_CREATE_VIEW_ROOTS);
}
- }
- /**
- * Stops adding documents under the parent.
- * @param parentId Document ID of the parent.
- */
- @VisibleForTesting
- synchronized void stopAddingChildDocuments(String parentId) {
- final String mappingModeKey = getChildDocumentsMappingStateKey(parentId);
- switch (mMappingMode.get(mappingModeKey)) {
- case MAP_BY_MTP_IDENTIFIER:
- mDatabase.stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- COLUMN_OBJECT_HANDLE);
- break;
- case MAP_BY_NAME:
- mDatabase.stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- Document.COLUMN_DISPLAY_NAME);
- break;
- default:
- throw new Error("Unexpected mapping state.");
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ throw new UnsupportedOperationException();
}
- mMappingMode.remove(mappingModeKey);
}
/**
@@ -357,8 +357,7 @@ class MtpDatabase {
* @param resources Resources used to get localized root name.
* @param root Root to be converted {@link ContentValues}.
*/
- private static void getRootDocumentValues(
- ContentValues values, Resources resources, MtpRoot root) {
+ static void getRootDocumentValues(ContentValues values, Resources resources, MtpRoot root) {
values.clear();
values.put(COLUMN_DEVICE_ID, root.mDeviceId);
values.put(COLUMN_STORAGE_ID, root.mStorageId);
@@ -382,7 +381,7 @@ class MtpDatabase {
* @param parentId Parent document ID of the object.
* @param info MTP object info.
*/
- private void getChildDocumentValues(
+ static void getChildDocumentValues(
ContentValues values, int deviceId, String parentId, MtpObjectInfo info) {
values.clear();
final String mimeType = info.getFormat() == MtpConstants.FORMAT_ASSOCIATION ?
@@ -415,19 +414,11 @@ class MtpDatabase {
values.put(Document.COLUMN_SIZE, info.getCompressedSize());
}
- /**
- * @param deviceId Device ID.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getRootDocumentsMappingStateKey(int deviceId) {
- return "RootDocuments/" + deviceId;
- }
-
- /**
- * @param parentDocumentId Document ID for the parent document.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
- return "ChildDocuments/" + parentDocumentId;
+ static String[] strings(Object... args) {
+ final String[] results = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ results[i] = Objects.toString(args[i]);
+ }
+ return results;
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
deleted file mode 100644
index eba3aaf0eb31..000000000000
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java
+++ /dev/null
@@ -1,599 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mtp;
-
-import static com.android.mtp.MtpDatabaseConstants.*;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseUtils;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.provider.DocumentsContract.Document;
-import android.provider.DocumentsContract.Root;
-
-import java.io.FileNotFoundException;
-import java.util.Objects;
-
-/**
- * Class that provides operations processing SQLite database directly.
- */
-class MtpDatabaseInternal {
- private static class OpenHelper extends SQLiteOpenHelper {
- public OpenHelper(Context context, int flags) {
- super(context,
- flags == FLAG_DATABASE_IN_MEMORY ? null : DATABASE_NAME,
- null,
- DATABASE_VERSION);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL(QUERY_CREATE_DOCUMENTS);
- db.execSQL(QUERY_CREATE_ROOT_EXTRA);
- db.execSQL(QUERY_CREATE_VIEW_ROOTS);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- throw new UnsupportedOperationException();
- }
- }
-
- private final SQLiteDatabase mDatabase;
-
- MtpDatabaseInternal(Context context, int flags) {
- final OpenHelper helper = new OpenHelper(context, flags);
- mDatabase = helper.getWritableDatabase();
- }
-
- void close() {
- mDatabase.close();
- }
-
- /**
- * Queries roots information.
- * @param columnNames Column names defined in {@link android.provider.DocumentsContract.Root}.
- * @return Database cursor.
- */
- Cursor queryRoots(String[] columnNames) {
- return mDatabase.query(
- VIEW_ROOTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
- null,
- null,
- null);
- }
-
- /**
- * Queries root documents information.
- * @param columnNames Column names defined in
- * {@link android.provider.DocumentsContract.Document}.
- * @return Database cursor.
- */
- Cursor queryRootDocuments(String[] columnNames) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?)",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED),
- null,
- null,
- null);
- }
-
- /**
- * Queries documents information.
- * @param columnNames Column names defined in
- * {@link android.provider.DocumentsContract.Document}.
- * @return Database cursor.
- */
- Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- columnNames,
- COLUMN_ROW_STATE + " IN (?, ?) AND " + COLUMN_PARENT_DOCUMENT_ID + " = ?",
- strings(ROW_STATE_VALID, ROW_STATE_INVALIDATED, parentDocumentId),
- null,
- null,
- null);
- }
-
- /**
- * Queries a single document.
- * @param documentId
- * @param projection
- * @return Database cursor.
- */
- public Cursor queryDocument(String documentId, String[] projection) {
- return mDatabase.query(
- TABLE_DOCUMENTS,
- projection,
- SELECTION_DOCUMENT_ID,
- strings(documentId),
- null,
- null,
- null,
- "1");
- }
-
- /**
- * Remove all rows belong to a device.
- * @param deviceId Device ID.
- */
- void removeDeviceRows(int deviceId) {
- // Call non-recursive version because it anyway deletes all rows in the devices.
- deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId));
- }
-
- /**
- * Obtains parent document ID.
- * @param documentId
- * @return parent document ID.
- * @throws FileNotFoundException
- */
- String getParentId(String documentId) throws FileNotFoundException {
- final Cursor cursor = mDatabase.query(
- TABLE_DOCUMENTS,
- strings(COLUMN_PARENT_DOCUMENT_ID),
- SELECTION_DOCUMENT_ID,
- strings(documentId),
- null,
- null,
- null,
- "1");
- try {
- if (cursor.moveToNext()) {
- return cursor.getString(0);
- } else {
- throw new FileNotFoundException("Cannot find a row having ID=" + documentId);
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Deletes document and its children.
- * @param documentId
- */
- void deleteDocument(String documentId) {
- deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, strings(documentId));
- }
-
- /**
- * Adds new document under the parent.
- * The method does not affect invalidated and pending documents because we know the document is
- * newly added and never mapped with existing ones.
- * @param parentDocumentId
- * @param values
- * @return Document ID of added document.
- */
- String putNewDocument(String parentDocumentId, ContentValues values) {
- mDatabase.beginTransaction();
- try {
-
- final long id = mDatabase.insert(TABLE_DOCUMENTS, null, values);
- mDatabase.setTransactionSuccessful();
- return Long.toString(id);
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Gets identifier from document ID.
- * @param documentId Document ID.
- * @return Identifier.
- * @throws FileNotFoundException
- */
- Identifier createIdentifier(String documentId) throws FileNotFoundException {
- final Cursor cursor = mDatabase.query(
- TABLE_DOCUMENTS,
- strings(COLUMN_DEVICE_ID, COLUMN_STORAGE_ID, COLUMN_OBJECT_HANDLE),
- SELECTION_DOCUMENT_ID,
- strings(documentId),
- null,
- null,
- null,
- "1");
- try {
- if (cursor.getCount() == 0) {
- throw new FileNotFoundException("ID is not found.");
- } else {
- cursor.moveToNext();
- return new Identifier(
- cursor.getInt(0),
- cursor.getInt(1),
- cursor.isNull(2) ? Identifier.DUMMY_HANDLE_FOR_ROOT : cursor.getInt(2),
- documentId);
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Starts adding new documents.
- * The methods decides mapping mode depends on if all documents under the given parent have MTP
- * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
- * a corresponding existing row. Otherwise it does heuristic.
- *
- * @param selection Query matches valid documents.
- * @param arg Argument for selection.
- * @return Mapping mode.
- */
- int startAddingDocuments(String selection, String arg) {
- mDatabase.beginTransaction();
- try {
- // Delete all pending rows.
- deleteDocumentsAndRootsRecursively(
- selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
-
- // Set all documents as invalidated.
- final ContentValues values = new ContentValues();
- values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
- mDatabase.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
-
- // If we have rows that does not have MTP identifier, do heuristic mapping by name.
- final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
- mDatabase,
- TABLE_DOCUMENTS,
- selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
- new String[] { arg }) > 0;
- mDatabase.setTransactionSuccessful();
- return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Puts the documents into the database.
- * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
- * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
- * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
- * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
- * rows. If the methods adds rows to database, it updates valueList with correct document ID.
- *
- * @param valuesList Values for documents to be stored in the database.
- * @param selection SQL where closure to select rows that shares the same parent.
- * @param arg Argument for selection SQL.
- * @param heuristic Whether the mapping mode is heuristic.
- * @return Whether the method adds new rows.
- */
- boolean putDocuments(
- ContentValues[] valuesList,
- String selection,
- String arg,
- boolean heuristic,
- String mappingKey) {
- boolean added = false;
- mDatabase.beginTransaction();
- try {
- for (final ContentValues values : valuesList) {
- final Cursor candidateCursor = mDatabase.query(
- TABLE_DOCUMENTS,
- strings(Document.COLUMN_DOCUMENT_ID),
- selection + " AND " +
- COLUMN_ROW_STATE + "=? AND " +
- mappingKey + "=?",
- strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
- null,
- null,
- null,
- "1");
- try {
- final long rowId;
- if (candidateCursor.getCount() == 0) {
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
- if (rowId == -1) {
- throw new SQLiteException("Failed to put a document into database.");
- }
- added = true;
- } else if (!heuristic) {
- candidateCursor.moveToNext();
- final String documentId = candidateCursor.getString(0);
- rowId = mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- SELECTION_DOCUMENT_ID,
- strings(documentId));
- } else {
- values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
- rowId = mDatabase.insert(TABLE_DOCUMENTS, null, values);
- }
- // Document ID is a primary integer key of the table. So the returned row
- // IDs should be same with the document ID.
- values.put(Document.COLUMN_DOCUMENT_ID, rowId);
- } finally {
- candidateCursor.close();
- }
- }
-
- mDatabase.setTransactionSuccessful();
- return added;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Puts extra information for root documents.
- * @param values Values containing extra information.
- */
- void putRootExtra(ContentValues values) {
- mDatabase.replace(TABLE_ROOT_EXTRA, null, values);
- }
-
- /**
- * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
- * If the database does not find corresponding 'invalidated' document, it just removes
- * 'invalidated' document from the database.
- * @param selection Query to select rows for resolving.
- * @param arg Argument for selection SQL.
- * @param groupKey Column name used to find corresponding rows.
- * @return Whether the methods adds or removed visible rows.
- */
- boolean stopAddingDocuments(String selection, String arg, String groupKey) {
- mDatabase.beginTransaction();
- try {
- // Get 1-to-1 mapping of invalidated document and pending document.
- final String invalidatedIdQuery = createStateFilter(
- ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
- final String pendingIdQuery = createStateFilter(
- ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
- // SQL should be like:
- // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
- // group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
- // WHERE device_id = ? AND parent_document_id IS NULL
- // GROUP BY display_name
- // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
- // count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
- final Cursor mergingCursor = mDatabase.query(
- TABLE_DOCUMENTS,
- new String[] {
- "group_concat(" + invalidatedIdQuery + ")",
- "group_concat(" + pendingIdQuery + ")"
- },
- selection,
- strings(arg),
- groupKey,
- "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
- null);
-
- final ContentValues values = new ContentValues();
- while (mergingCursor.moveToNext()) {
- final String invalidatedId = mergingCursor.getString(0);
- final String pendingId = mergingCursor.getString(1);
-
- // Obtain the new values including the latest object handle from mapping row.
- getFirstRow(
- TABLE_DOCUMENTS,
- SELECTION_DOCUMENT_ID,
- new String[] { pendingId },
- values);
- values.remove(Document.COLUMN_DOCUMENT_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- SELECTION_DOCUMENT_ID,
- new String[] { invalidatedId });
-
- getFirstRow(
- TABLE_ROOT_EXTRA,
- SELECTION_ROOT_ID,
- new String[] { pendingId },
- values);
- if (values.size() > 0) {
- values.remove(Root.COLUMN_ROOT_ID);
- mDatabase.update(
- TABLE_ROOT_EXTRA,
- values,
- SELECTION_ROOT_ID,
- new String[] { invalidatedId });
- }
-
- // Delete 'pending' row.
- deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, new String[] { pendingId });
- }
- mergingCursor.close();
-
- boolean changed = false;
-
- // Delete all invalidated rows that cannot be mapped.
- if (deleteDocumentsAndRootsRecursively(
- COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_INVALIDATED, arg))) {
- changed = true;
- }
-
- // The database cannot find old document ID for the pending rows.
- // Turn the all pending rows into valid state, which means the rows become to be
- // valid with new document ID.
- values.clear();
- values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
- if (mDatabase.update(
- TABLE_DOCUMENTS,
- values,
- COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_PENDING, arg)) != 0) {
- changed = true;
- }
- mDatabase.setTransactionSuccessful();
- return changed;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Clears MTP related identifier.
- * It clears MTP's object handle and storage ID that are not stable over MTP sessions and mark
- * the all documents as 'invalidated'. It also remove 'pending' rows as adding is cancelled
- * now.
- */
- void clearMapping() {
- mDatabase.beginTransaction();
- try {
- deleteDocumentsAndRootsRecursively(
- COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
- final ContentValues values = new ContentValues();
- values.putNull(COLUMN_OBJECT_HANDLE);
- values.putNull(COLUMN_STORAGE_ID);
- values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
- mDatabase.update(TABLE_DOCUMENTS, values, null, null);
- mDatabase.setTransactionSuccessful();
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#beginTransaction()}
- */
- void beginTransaction() {
- mDatabase.beginTransaction();
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#setTransactionSuccessful()}
- */
- void setTransactionSuccessful() {
- mDatabase.setTransactionSuccessful();
- }
-
- /**
- * {@link android.database.sqlite.SQLiteDatabase#endTransaction()}
- */
- void endTransaction() {
- mDatabase.endTransaction();
- }
-
- /**
- * Deletes a document, and its root information if the document is a root document.
- * @param selection Query to select documents.
- * @param args Arguments for selection.
- * @return Whether the method deletes rows.
- */
- private boolean deleteDocumentsAndRootsRecursively(String selection, String[] args) {
- mDatabase.beginTransaction();
- try {
- boolean changed = false;
- final Cursor cursor = mDatabase.query(
- TABLE_DOCUMENTS,
- strings(Document.COLUMN_DOCUMENT_ID),
- selection,
- args,
- null,
- null,
- null);
- try {
- while (cursor.moveToNext()) {
- if (deleteDocumentsAndRootsRecursively(
- COLUMN_PARENT_DOCUMENT_ID + "=?",
- strings(cursor.getString(0)))) {
- changed = true;
- }
- }
- } finally {
- cursor.close();
- }
- if (deleteDocumentsAndRoots(selection, args)) {
- changed = true;
- }
- mDatabase.setTransactionSuccessful();
- return changed;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- private boolean deleteDocumentsAndRoots(String selection, String[] args) {
- mDatabase.beginTransaction();
- try {
- int deleted = 0;
- deleted += mDatabase.delete(
- TABLE_ROOT_EXTRA,
- Root.COLUMN_ROOT_ID + " IN (" + SQLiteQueryBuilder.buildQueryString(
- false,
- TABLE_DOCUMENTS,
- new String[] { Document.COLUMN_DOCUMENT_ID },
- selection,
- null,
- null,
- null,
- null) + ")",
- args);
- deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
- mDatabase.setTransactionSuccessful();
- // TODO Remove mappingState.
- return deleted != 0;
- } finally {
- mDatabase.endTransaction();
- }
- }
-
- /**
- * Obtains values of the first row for the query.
- * @param values ContentValues that the values are stored to.
- * @param table Target table.
- * @param selection Query to select rows.
- * @param args Argument for query.
- */
- private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
- values.clear();
- final Cursor cursor = mDatabase.query(table, null, selection, args, null, null, null, "1");
- if (cursor.getCount() == 0) {
- return;
- }
- cursor.moveToNext();
- DatabaseUtils.cursorRowToContentValues(cursor, values);
- cursor.close();
- }
-
- /**
- * Gets SQL expression that represents the given value or NULL depends on the row state.
- * @param state Expected row state.
- * @param a SQL value.
- * @return Expression that represents a if the row state is expected one, and represents NULL
- * otherwise.
- */
- private static String createStateFilter(int state, String a) {
- return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
- " THEN " + a + " ELSE NULL END";
- }
-
- /**
- * Converts values into string array.
- * @param args Values converted into string array.
- * @return String array.
- */
- static String[] strings(Object... args) {
- final String[] results = new String[args.length];
- for (int i = 0; i < args.length; i++) {
- results[i] = Objects.toString(args[i]);
- }
- return results;
- }
-}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
index affaebd05c16..16523bca454c 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java
@@ -22,10 +22,8 @@ import android.os.ParcelFileDescriptor;
import android.util.Log;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index b0962dd148b9..df2ab0173a6d 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -113,13 +113,14 @@ final class RootScanner {
for (int deviceId : deviceIds) {
try {
final MtpRoot[] roots = mManager.getRoots(deviceId);
- mDatabase.startAddingRootDocuments(deviceId);
+ mDatabase.getMapper().startAddingRootDocuments(deviceId);
try {
- if (mDatabase.putRootDocuments(deviceId, mResources, roots)) {
+ if (mDatabase.getMapper().putRootDocuments(
+ deviceId, mResources, roots)) {
changed = true;
}
} finally {
- if (mDatabase.stopAddingRootDocuments(deviceId)) {
+ if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) {
changed = true;
}
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index 394a27efec5e..f0b4343a9afd 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -40,11 +40,11 @@ public class DocumentLoaderTest extends AndroidTestCase {
@Override
public void setUp() {
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, new TestResources(), new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, new TestResources(), new MtpRoot[] {
new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 08415a0214d2..6d9193d7ed7b 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -26,10 +26,8 @@ import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import java.io.FileNotFoundException;
-import java.util.Set;
-import java.util.TreeSet;
-import static com.android.mtp.MtpDatabaseInternal.strings;
+import static com.android.mtp.MtpDatabase.strings;
@SmallTest
public class MtpDatabaseTest extends AndroidTestCase {
@@ -62,8 +60,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testPutRootDocuments() throws Exception {
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 3000, 6000,"")
@@ -154,8 +152,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testPutChildDocuments() throws Exception {
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
@@ -229,8 +227,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
});
@@ -261,7 +259,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -289,8 +287,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
});
@@ -328,7 +326,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -363,13 +361,13 @@ public class MtpDatabaseTest extends AndroidTestCase {
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -393,8 +391,8 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.startAddingChildDocuments("parentId");
- mDatabase.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
});
@@ -411,7 +409,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.stopAddingChildDocuments("parentId");
+ mDatabase.getMapper().stopAddingChildDocuments("parentId");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -440,12 +438,12 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.startAddingRootDocuments(1);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
});
- mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 100, "Device", "Storage", 0, 0, "")
});
@@ -475,18 +473,18 @@ public class MtpDatabaseTest extends AndroidTestCase {
cursor.close();
}
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.startAddingRootDocuments(1);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
});
- mDatabase.putRootDocuments(1, resources, new MtpRoot[] {
+ mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
});
- mDatabase.stopAddingRootDocuments(0);
- mDatabase.stopAddingRootDocuments(1);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(1);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -521,25 +519,25 @@ public class MtpDatabaseTest extends AndroidTestCase {
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
};
- mDatabase.startAddingChildDocuments("parentId1");
- mDatabase.startAddingChildDocuments("parentId2");
- mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId1");
+ mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingChildDocuments("parentId1");
- mDatabase.startAddingChildDocuments("parentId2");
- mDatabase.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+ mDatabase.getMapper().startAddingChildDocuments("parentId1");
+ mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+ mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.stopAddingChildDocuments("parentId1");
+ mDatabase.getMapper().stopAddingChildDocuments("parentId1");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
@@ -570,23 +568,23 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -618,18 +616,18 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -660,17 +658,17 @@ public class MtpDatabaseTest extends AndroidTestCase {
public void testReplaceExistingRoots() {
// The client code should be able to replace existing rows with new information.
// Add one.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
// Replace it.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
{
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -702,24 +700,24 @@ public class MtpDatabaseTest extends AndroidTestCase {
public void testFailToReplaceExisitingUnmappedRoots() {
// The client code should not be able to replace rows before resolving 'unmapped' rows.
// Add one.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.clearMapping();
+ mDatabase.getMapper().clearMapping();
final Cursor oldCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
assertEquals(1, oldCursor.getCount());
// Add one.
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
});
// Add one more before resolving unmapped documents.
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
// Because the roots shares the same name, the roots should have new IDs.
final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
@@ -735,11 +733,11 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testQueryDocument() {
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
assertEquals(1, cursor.getCount());
@@ -749,48 +747,48 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
public void testGetParentId() throws FileNotFoundException {
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
- mDatabase.startAddingChildDocuments("1");
- mDatabase.putChildDocuments(
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().putChildDocuments(
0,
"1",
new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.stopAddingChildDocuments("1");
+ mDatabase.getMapper().stopAddingChildDocuments("1");
assertEquals("1", mDatabase.getParentId("2"));
}
public void testDeleteDocument() {
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
- mDatabase.startAddingChildDocuments("1");
- mDatabase.putChildDocuments(
+ mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().putChildDocuments(
0,
"1",
new MtpObjectInfo[] {
createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
});
- mDatabase.stopAddingChildDocuments("1");
+ mDatabase.getMapper().stopAddingChildDocuments("1");
- mDatabase.startAddingChildDocuments("2");
- mDatabase.putChildDocuments(
+ mDatabase.getMapper().startAddingChildDocuments("2");
+ mDatabase.getMapper().putChildDocuments(
0,
"2",
new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.stopAddingChildDocuments("2");
+ mDatabase.getMapper().stopAddingChildDocuments("2");
mDatabase.deleteDocument("2");
@@ -811,12 +809,12 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
}
- public void testPutNewDocument() throws FileNotFoundException {
- mDatabase.startAddingRootDocuments(0);
- mDatabase.putRootDocuments(0, resources, new MtpRoot[] {
+ public void testPutNewDocument() {
+ mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingRootDocuments(0);
assertEquals(
"2",
@@ -833,13 +831,13 @@ public class MtpDatabaseTest extends AndroidTestCase {
}
// The new document should not be mapped with existing invalidated document.
- mDatabase.clearMapping();
- mDatabase.startAddingChildDocuments("1");
+ mDatabase.getMapper().clearMapping();
+ mDatabase.getMapper().startAddingChildDocuments("1");
mDatabase.putNewDocument(
0,
"1",
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
- mDatabase.stopAddingChildDocuments("1");
+ mDatabase.getMapper().stopAddingChildDocuments("1");
{
final Cursor cursor =
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 459496c5b1c0..7a53a90481e0 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -29,7 +29,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
-import static com.android.mtp.MtpDatabaseInternal.strings;
+import static com.android.mtp.MtpDatabase.strings;
@SmallTest
public class MtpDocumentsProviderTest extends AndroidTestCase {