From 259ce80132d55774fe599c60b53a9d9dfc1efa65 Mon Sep 17 00:00:00 2001 From: Daichi Hirono Date: Fri, 20 Nov 2015 17:51:53 +0900 Subject: [PATCH] Extract mapping operations to partial class of MtpDatabase. Change-Id: I5fda6d5754206528ad5953bcc179a0ffe0b4982d --- .../src/com/android/mtp/DocumentLoader.java | 8 +- .../src/com/android/mtp/Mapper.java | 513 ++++++++++++++++++ .../src/com/android/mtp/MtpDatabase.java | 443 ++++++++------- .../src/com/android/mtp/MtpDatabaseInternal.java | 599 --------------------- .../src/com/android/mtp/PipeManager.java | 2 - .../src/com/android/mtp/RootScanner.java | 7 +- .../src/com/android/mtp/DocumentLoaderTest.java | 6 +- .../tests/src/com/android/mtp/MtpDatabaseTest.java | 184 ++++--- .../com/android/mtp/MtpDocumentsProviderTest.java | 2 +- 9 files changed, 833 insertions(+), 931 deletions(-) create mode 100644 packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java delete mode 100644 packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java 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 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 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 { -- 2.11.0