blob: 8058183f161c63eacc5c3713e9ee0eafb2339019 [file] [log] [blame]
Daichi Hirono259ce802015-11-20 17:51:53 +09001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.mtp;
18
Daichi Hirono7a375c42015-12-14 17:14:29 +090019import android.annotation.Nullable;
Daichi Hirono259ce802015-11-20 17:51:53 +090020import android.content.ContentValues;
Daichi Hirono259ce802015-11-20 17:51:53 +090021import android.database.Cursor;
22import android.database.DatabaseUtils;
23import android.database.sqlite.SQLiteDatabase;
Daichi Hirono259ce802015-11-20 17:51:53 +090024import android.mtp.MtpObjectInfo;
25import android.provider.DocumentsContract.Document;
26import android.provider.DocumentsContract.Root;
Daichi Hironoebd24052016-02-06 21:05:57 +090027import android.util.ArraySet;
Daichi Hirono8e873642016-02-07 15:17:16 +090028import android.util.Log;
Daichi Hirono259ce802015-11-20 17:51:53 +090029
Daichi Hirono259ce802015-11-20 17:51:53 +090030import com.android.internal.util.Preconditions;
31
Daichi Hirono619afda2016-02-07 14:23:43 +090032import java.io.FileNotFoundException;
Daichi Hironoebd24052016-02-06 21:05:57 +090033import java.util.Set;
Daichi Hirono259ce802015-11-20 17:51:53 +090034
Daichi Hirono8e873642016-02-07 15:17:16 +090035import static com.android.mtp.MtpDatabaseConstants.*;
Daichi Hirono259ce802015-11-20 17:51:53 +090036import static com.android.mtp.MtpDatabase.strings;
37
Daichi Hirono259ce802015-11-20 17:51:53 +090038/**
39 * Mapping operations for MtpDatabase.
40 * Also see the comments of {@link MtpDatabase}.
41 */
42class Mapper {
Daichi Hironob3fe72b2015-12-15 07:45:06 +000043 private static final String[] EMPTY_ARGS = new String[0];
Daichi Hirono259ce802015-11-20 17:51:53 +090044 private final MtpDatabase mDatabase;
45
46 /**
Daichi Hironoebd24052016-02-06 21:05:57 +090047 * IDs which currently Mapper operates mapping for.
Daichi Hirono259ce802015-11-20 17:51:53 +090048 */
Daichi Hironoebd24052016-02-06 21:05:57 +090049 private final Set<String> mInMappingIds = new ArraySet<>();
Daichi Hirono259ce802015-11-20 17:51:53 +090050
51 Mapper(MtpDatabase database) {
52 mDatabase = database;
53 }
54
Daichi Hirono20754c52015-12-15 18:52:26 +090055 /**
56 * Puts device information to database.
Daichi Hirono619afda2016-02-07 14:23:43 +090057 *
Daichi Hirono20754c52015-12-15 18:52:26 +090058 * @return If device is added to the database.
Daichi Hirono619afda2016-02-07 14:23:43 +090059 * @throws FileNotFoundException
Daichi Hirono20754c52015-12-15 18:52:26 +090060 */
Daichi Hirono619afda2016-02-07 14:23:43 +090061 synchronized boolean putDeviceDocument(MtpDeviceRecord device) throws FileNotFoundException {
Daichi Hironob3fe72b2015-12-15 07:45:06 +000062 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
Daichi Hironob3fe72b2015-12-15 07:45:06 +000063 database.beginTransaction();
64 try {
65 final ContentValues[] valuesList = new ContentValues[1];
Daichi Hirono81d48532015-12-16 15:03:19 +090066 final ContentValues[] extraValuesList = new ContentValues[1];
Daichi Hironob3fe72b2015-12-15 07:45:06 +000067 valuesList[0] = new ContentValues();
Daichi Hirono81d48532015-12-16 15:03:19 +090068 extraValuesList[0] = new ContentValues();
69 MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device);
Daichi Hirono20754c52015-12-15 18:52:26 +090070 final boolean changed = putDocuments(
Daichi Hirono619afda2016-02-07 14:23:43 +090071 null,
Daichi Hironob3fe72b2015-12-15 07:45:06 +000072 valuesList,
Daichi Hirono81d48532015-12-16 15:03:19 +090073 extraValuesList,
Daichi Hironob3fe72b2015-12-15 07:45:06 +000074 COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
75 EMPTY_ARGS,
Daichi Hironoebd24052016-02-06 21:05:57 +090076 strings(COLUMN_DEVICE_ID, COLUMN_MAPPING_KEY));
Daichi Hironob3fe72b2015-12-15 07:45:06 +000077 database.setTransactionSuccessful();
Daichi Hirono20754c52015-12-15 18:52:26 +090078 return changed;
Daichi Hironob3fe72b2015-12-15 07:45:06 +000079 } finally {
80 database.endTransaction();
81 }
82 }
83
Daichi Hirono259ce802015-11-20 17:51:53 +090084 /**
Daichi Hirono259ce802015-11-20 17:51:53 +090085 * Puts root information to database.
Daichi Hirono619afda2016-02-07 14:23:43 +090086 *
Daichi Hironob3fe72b2015-12-15 07:45:06 +000087 * @param parentDocumentId Document ID of device document.
Daichi Hirono259ce802015-11-20 17:51:53 +090088 * @param roots List of root information.
89 * @return If roots are added or removed from the database.
Daichi Hirono619afda2016-02-07 14:23:43 +090090 * @throws FileNotFoundException
Daichi Hirono259ce802015-11-20 17:51:53 +090091 */
Daichi Hirono0f325372016-02-21 15:50:30 +090092 synchronized boolean putStorageDocuments(
93 String parentDocumentId, int[] operationsSupported, MtpRoot[] roots)
Daichi Hirono619afda2016-02-07 14:23:43 +090094 throws FileNotFoundException {
Daichi Hirono259ce802015-11-20 17:51:53 +090095 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
96 database.beginTransaction();
97 try {
Daichi Hirono259ce802015-11-20 17:51:53 +090098 final ContentValues[] valuesList = new ContentValues[roots.length];
Daichi Hirono81d48532015-12-16 15:03:19 +090099 final ContentValues[] extraValuesList = new ContentValues[roots.length];
Daichi Hirono259ce802015-11-20 17:51:53 +0900100 for (int i = 0; i < roots.length; i++) {
Daichi Hirono259ce802015-11-20 17:51:53 +0900101 valuesList[i] = new ContentValues();
Daichi Hirono81d48532015-12-16 15:03:19 +0900102 extraValuesList[i] = new ContentValues();
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000103 MtpDatabase.getStorageDocumentValues(
Daichi Hirono0f325372016-02-21 15:50:30 +0900104 valuesList[i],
105 extraValuesList[i],
106 parentDocumentId,
107 operationsSupported,
108 roots[i]);
Daichi Hirono259ce802015-11-20 17:51:53 +0900109 }
110 final boolean changed = putDocuments(
Daichi Hirono619afda2016-02-07 14:23:43 +0900111 parentDocumentId,
Daichi Hirono259ce802015-11-20 17:51:53 +0900112 valuesList,
Daichi Hirono81d48532015-12-16 15:03:19 +0900113 extraValuesList,
Daichi Hironoebd24052016-02-06 21:05:57 +0900114 COLUMN_PARENT_DOCUMENT_ID + " = ?",
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000115 strings(parentDocumentId),
Daichi Hironoebd24052016-02-06 21:05:57 +0900116 strings(COLUMN_STORAGE_ID, Document.COLUMN_DISPLAY_NAME));
Daichi Hirono81d48532015-12-16 15:03:19 +0900117
Daichi Hirono259ce802015-11-20 17:51:53 +0900118 database.setTransactionSuccessful();
119 return changed;
120 } finally {
121 database.endTransaction();
122 }
123 }
124
125 /**
126 * Puts document information to database.
Daichi Hirono619afda2016-02-07 14:23:43 +0900127 *
Daichi Hirono259ce802015-11-20 17:51:53 +0900128 * @param deviceId Device ID
129 * @param parentId Parent document ID.
130 * @param documents List of document information.
Daichi Hirono619afda2016-02-07 14:23:43 +0900131 * @throws FileNotFoundException
Daichi Hirono259ce802015-11-20 17:51:53 +0900132 */
Daichi Hirono61ba9232016-02-26 12:58:39 +0900133 synchronized void putChildDocuments(
134 int deviceId, String parentId, int[] operationsSupported, MtpObjectInfo[] documents)
Daichi Hirono619afda2016-02-07 14:23:43 +0900135 throws FileNotFoundException {
Daichi Hirono259ce802015-11-20 17:51:53 +0900136 final ContentValues[] valuesList = new ContentValues[documents.length];
137 for (int i = 0; i < documents.length; i++) {
138 valuesList[i] = new ContentValues();
Daichi Hirono61ba9232016-02-26 12:58:39 +0900139 MtpDatabase.getObjectDocumentValues(
140 valuesList[i], deviceId, parentId, operationsSupported, documents[i]);
Daichi Hirono259ce802015-11-20 17:51:53 +0900141 }
142 putDocuments(
Daichi Hirono619afda2016-02-07 14:23:43 +0900143 parentId,
Daichi Hirono7a375c42015-12-14 17:14:29 +0900144 valuesList,
Daichi Hirono81d48532015-12-16 15:03:19 +0900145 null,
Daichi Hironoebd24052016-02-06 21:05:57 +0900146 COLUMN_PARENT_DOCUMENT_ID + " = ?",
Daichi Hirono7a375c42015-12-14 17:14:29 +0900147 strings(parentId),
Daichi Hironoebd24052016-02-06 21:05:57 +0900148 strings(COLUMN_OBJECT_HANDLE, Document.COLUMN_DISPLAY_NAME));
Daichi Hirono259ce802015-11-20 17:51:53 +0900149 }
150
Daichi Hirono259ce802015-11-20 17:51:53 +0900151 void clearMapping() {
152 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
153 database.beginTransaction();
154 try {
Daichi Hironoebd24052016-02-06 21:05:57 +0900155 mInMappingIds.clear();
Daichi Hirono8e873642016-02-07 15:17:16 +0900156 // Disconnect all device rows.
157 try {
158 startAddingDocuments(null);
159 stopAddingDocuments(null);
160 } catch (FileNotFoundException exception) {
161 Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException.", exception);
162 throw new RuntimeException(exception);
163 }
164 database.setTransactionSuccessful();
Daichi Hirono259ce802015-11-20 17:51:53 +0900165 } finally {
166 database.endTransaction();
167 }
168 }
169
170 /**
171 * Starts adding new documents.
Daichi Hirono5c690552016-02-21 16:00:03 +0900172 * It changes the direct child documents of the given document from VALID to INVALIDATED.
173 * Note that it keeps DISCONNECTED documents as they are.
Daichi Hirono259ce802015-11-20 17:51:53 +0900174 *
Daichi Hirono7a375c42015-12-14 17:14:29 +0900175 * @param parentDocumentId Parent document ID or NULL for root documents.
Daichi Hirono619afda2016-02-07 14:23:43 +0900176 * @throws FileNotFoundException
Daichi Hirono259ce802015-11-20 17:51:53 +0900177 */
Daichi Hirono619afda2016-02-07 14:23:43 +0900178 void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException {
Daichi Hirono7a375c42015-12-14 17:14:29 +0900179 final String selection;
180 final String[] args;
181 if (parentDocumentId != null) {
182 selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
183 args = strings(parentDocumentId);
184 } else {
185 selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000186 args = EMPTY_ARGS;
Daichi Hirono7a375c42015-12-14 17:14:29 +0900187 }
188
Daichi Hirono259ce802015-11-20 17:51:53 +0900189 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
190 database.beginTransaction();
191 try {
Daichi Hirono619afda2016-02-07 14:23:43 +0900192 getParentOrHaltMapping(parentDocumentId);
Daichi Hironoebd24052016-02-06 21:05:57 +0900193 Preconditions.checkState(!mInMappingIds.contains(parentDocumentId));
Daichi Hirono619afda2016-02-07 14:23:43 +0900194
Daichi Hirono8e873642016-02-07 15:17:16 +0900195 // Set all valid documents as invalidated.
Daichi Hirono259ce802015-11-20 17:51:53 +0900196 final ContentValues values = new ContentValues();
197 values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
Daichi Hirono8e873642016-02-07 15:17:16 +0900198 database.update(
199 TABLE_DOCUMENTS,
200 values,
201 selection + " AND " + COLUMN_ROW_STATE + " = ?",
202 DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_VALID)));
Daichi Hirono259ce802015-11-20 17:51:53 +0900203
Daichi Hirono259ce802015-11-20 17:51:53 +0900204 database.setTransactionSuccessful();
Daichi Hironoebd24052016-02-06 21:05:57 +0900205 mInMappingIds.add(parentDocumentId);
Daichi Hirono259ce802015-11-20 17:51:53 +0900206 } finally {
207 database.endTransaction();
208 }
209 }
210
211 /**
212 * Puts the documents into the database.
213 * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
214 * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
215 * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
Daichi Hirono20754c52015-12-15 18:52:26 +0900216 * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid'
Daichi Hirono259ce802015-11-20 17:51:53 +0900217 * rows. If the methods adds rows to database, it updates valueList with correct document ID.
218 *
Daichi Hirono619afda2016-02-07 14:23:43 +0900219 * @param parentId Parent document ID.
Daichi Hirono259ce802015-11-20 17:51:53 +0900220 * @param valuesList Values for documents to be stored in the database.
Daichi Hirono81d48532015-12-16 15:03:19 +0900221 * @param rootExtraValuesList Values for root extra to be stored in the database.
Daichi Hirono259ce802015-11-20 17:51:53 +0900222 * @param selection SQL where closure to select rows that shares the same parent.
Daichi Hirono7a375c42015-12-14 17:14:29 +0900223 * @param args Argument for selection SQL.
Daichi Hironoebd24052016-02-06 21:05:57 +0900224 * @return Whether the database content is changed.
Daichi Hirono619afda2016-02-07 14:23:43 +0900225 * @throws FileNotFoundException When parentId is not registered in the database.
Daichi Hirono259ce802015-11-20 17:51:53 +0900226 */
227 private boolean putDocuments(
Daichi Hirono619afda2016-02-07 14:23:43 +0900228 String parentId,
Daichi Hirono259ce802015-11-20 17:51:53 +0900229 ContentValues[] valuesList,
Daichi Hirono81d48532015-12-16 15:03:19 +0900230 @Nullable ContentValues[] rootExtraValuesList,
Daichi Hirono259ce802015-11-20 17:51:53 +0900231 String selection,
Daichi Hirono7a375c42015-12-14 17:14:29 +0900232 String[] args,
Daichi Hironoebd24052016-02-06 21:05:57 +0900233 String[] mappingKeys) throws FileNotFoundException {
Daichi Hirono259ce802015-11-20 17:51:53 +0900234 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
Daichi Hironoebd24052016-02-06 21:05:57 +0900235 boolean changed = false;
Daichi Hirono259ce802015-11-20 17:51:53 +0900236 database.beginTransaction();
237 try {
Daichi Hirono619afda2016-02-07 14:23:43 +0900238 getParentOrHaltMapping(parentId);
Daichi Hironoebd24052016-02-06 21:05:57 +0900239 Preconditions.checkState(mInMappingIds.contains(parentId));
240 final ContentValues oldRowSnapshot = new ContentValues();
241 final ContentValues newRowSnapshot = new ContentValues();
Daichi Hirono81d48532015-12-16 15:03:19 +0900242 for (int i = 0; i < valuesList.length; i++) {
243 final ContentValues values = valuesList[i];
244 final ContentValues rootExtraValues;
245 if (rootExtraValuesList != null) {
246 rootExtraValues = rootExtraValuesList[i];
247 } else {
248 rootExtraValues = null;
249 }
Daichi Hironoebd24052016-02-06 21:05:57 +0900250 try (final Cursor candidateCursor =
251 queryCandidate(selection, args, mappingKeys, values)) {
Daichi Hirono259ce802015-11-20 17:51:53 +0900252 final long rowId;
Daichi Hironoebd24052016-02-06 21:05:57 +0900253 if (candidateCursor == null) {
Daichi Hirono259ce802015-11-20 17:51:53 +0900254 rowId = database.insert(TABLE_DOCUMENTS, null, values);
Daichi Hironoebd24052016-02-06 21:05:57 +0900255 changed = true;
Daichi Hirono9fca5412016-02-07 13:20:22 +0900256 } else {
Daichi Hirono259ce802015-11-20 17:51:53 +0900257 candidateCursor.moveToNext();
Daichi Hirono81d48532015-12-16 15:03:19 +0900258 rowId = candidateCursor.getLong(0);
Daichi Hironoebd24052016-02-06 21:05:57 +0900259 if (!changed) {
260 mDatabase.writeRowSnapshot(String.valueOf(rowId), oldRowSnapshot);
261 }
Daichi Hirono81d48532015-12-16 15:03:19 +0900262 database.update(
Daichi Hirono259ce802015-11-20 17:51:53 +0900263 TABLE_DOCUMENTS,
264 values,
265 SELECTION_DOCUMENT_ID,
Daichi Hirono81d48532015-12-16 15:03:19 +0900266 strings(rowId));
Daichi Hirono259ce802015-11-20 17:51:53 +0900267 }
268 // Document ID is a primary integer key of the table. So the returned row
269 // IDs should be same with the document ID.
270 values.put(Document.COLUMN_DOCUMENT_ID, rowId);
Daichi Hirono81d48532015-12-16 15:03:19 +0900271 if (rootExtraValues != null) {
272 rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId);
273 database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues);
274 }
Daichi Hironoebd24052016-02-06 21:05:57 +0900275
276 if (!changed) {
277 mDatabase.writeRowSnapshot(String.valueOf(rowId), newRowSnapshot);
278 // Put row state as string because SQLite returns snapshot values as string.
279 oldRowSnapshot.put(COLUMN_ROW_STATE, String.valueOf(ROW_STATE_VALID));
280 if (!oldRowSnapshot.equals(newRowSnapshot)) {
281 changed = true;
282 }
283 }
Daichi Hirono259ce802015-11-20 17:51:53 +0900284 }
285 }
286
287 database.setTransactionSuccessful();
Daichi Hironoebd24052016-02-06 21:05:57 +0900288 return changed;
Daichi Hirono259ce802015-11-20 17:51:53 +0900289 } finally {
290 database.endTransaction();
291 }
292 }
293
294 /**
Daichi Hirono5c690552016-02-21 16:00:03 +0900295 * Stops adding documents.
296 * It handles 'invalidated' and 'disconnected' documents which we don't put corresponding
297 * documents so far.
298 * If the type adding document is 'device' or 'storage', the document may appear again
299 * afterward. The method marks such documents as 'disconnected'. If the type of adding document
300 * is 'object', it seems the documents are really removed from the remote MTP device. So the
301 * method deletes the metadata from the database.
Daichi Hirono619afda2016-02-07 14:23:43 +0900302 *
Daichi Hirono7a375c42015-12-14 17:14:29 +0900303 * @param parentId Parent document ID or null for root documents.
Daichi Hirono5c690552016-02-21 16:00:03 +0900304 * @return Whether the methods changes file metadata in database.
Daichi Hirono619afda2016-02-07 14:23:43 +0900305 * @throws FileNotFoundException
Daichi Hirono259ce802015-11-20 17:51:53 +0900306 */
Daichi Hirono619afda2016-02-07 14:23:43 +0900307 boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException {
Daichi Hirono7a375c42015-12-14 17:14:29 +0900308 final String selection;
309 final String[] args;
310 if (parentId != null) {
Daichi Hironoebd24052016-02-06 21:05:57 +0900311 selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
Daichi Hirono7a375c42015-12-14 17:14:29 +0900312 args = strings(parentId);
313 } else {
314 selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000315 args = EMPTY_ARGS;
Daichi Hirono7a375c42015-12-14 17:14:29 +0900316 }
Daichi Hirono619afda2016-02-07 14:23:43 +0900317
Daichi Hirono259ce802015-11-20 17:51:53 +0900318 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
319 database.beginTransaction();
320 try {
Daichi Hirono8e873642016-02-07 15:17:16 +0900321 final Identifier parentIdentifier = getParentOrHaltMapping(parentId);
Daichi Hironoebd24052016-02-06 21:05:57 +0900322 Preconditions.checkState(mInMappingIds.contains(parentId));
323 mInMappingIds.remove(parentId);
Daichi Hirono259ce802015-11-20 17:51:53 +0900324
Daichi Hirono619afda2016-02-07 14:23:43 +0900325 boolean changed = false;
Daichi Hirono5c690552016-02-21 16:00:03 +0900326 // Delete/disconnect all invalidated/disconnected rows that cannot be mapped.
327 // If parentIdentifier is null, added documents are devices.
328 // if parentIdentifier is DOCUMENT_TYPE_DEVICE, added documents are storages.
Daichi Hirono8e873642016-02-07 15:17:16 +0900329 final boolean keepUnmatchedDocument =
330 parentIdentifier == null ||
331 parentIdentifier.mDocumentType == DOCUMENT_TYPE_DEVICE;
332 if (keepUnmatchedDocument) {
333 if (mDatabase.disconnectDocumentsRecursively(
334 COLUMN_ROW_STATE + " = ? AND " + selection,
335 DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
336 changed = true;
337 }
338 } else {
339 if (mDatabase.deleteDocumentsAndRootsRecursively(
Daichi Hirono5c690552016-02-21 16:00:03 +0900340 COLUMN_ROW_STATE + " IN (?, ?) AND " + selection,
341 DatabaseUtils.appendSelectionArgs(
342 strings(ROW_STATE_INVALIDATED, ROW_STATE_DISCONNECTED), args))) {
Daichi Hirono8e873642016-02-07 15:17:16 +0900343 changed = true;
344 }
Daichi Hirono259ce802015-11-20 17:51:53 +0900345 }
346
Daichi Hirono259ce802015-11-20 17:51:53 +0900347 database.setTransactionSuccessful();
348 return changed;
349 } finally {
350 database.endTransaction();
351 }
352 }
Daichi Hirono619afda2016-02-07 14:23:43 +0900353
354 /**
Daichi Hironoebd24052016-02-06 21:05:57 +0900355 * Queries candidate for each mappingKey, and returns the first cursor that includes a
356 * candidate.
357 *
358 * @param selection Pre-selection for candidate.
359 * @param args Arguments for selection.
360 * @param mappingKeys List of mapping key columns.
361 * @param values Values of document that Mapper tries to map.
362 * @return Cursor for mapping candidate or null when Mapper does not find any candidate.
363 */
364 private @Nullable Cursor queryCandidate(
365 String selection, String[] args, String[] mappingKeys, ContentValues values) {
366 for (final String mappingKey : mappingKeys) {
367 final Cursor candidateCursor = queryCandidate(selection, args, mappingKey, values);
368 if (candidateCursor.getCount() == 0) {
369 candidateCursor.close();
370 continue;
371 }
372 return candidateCursor;
373 }
374 return null;
375 }
376
377 /**
378 * Looks for mapping candidate with given mappingKey.
379 *
380 * @param selection Pre-selection for candidate.
381 * @param args Arguments for selection.
382 * @param mappingKey Column name of mapping key.
383 * @param values Values of document that Mapper tries to map.
384 * @return Cursor for mapping candidate.
385 */
386 private Cursor queryCandidate(
387 String selection, String[] args, String mappingKey, ContentValues values) {
388 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
389 return database.query(
390 TABLE_DOCUMENTS,
391 strings(Document.COLUMN_DOCUMENT_ID),
392 selection + " AND " +
393 COLUMN_ROW_STATE + " IN (?, ?) AND " +
394 mappingKey + " = ?",
395 DatabaseUtils.appendSelectionArgs(
396 args,
397 strings(ROW_STATE_INVALIDATED,
398 ROW_STATE_DISCONNECTED,
399 values.getAsString(mappingKey))),
400 null,
401 null,
402 null,
403 "1");
404 }
405
406 /**
Daichi Hirono619afda2016-02-07 14:23:43 +0900407 * Returns the parent identifier from parent document ID if the parent ID is found in the
408 * database. Otherwise it halts mapping and throws FileNotFoundException.
409 *
410 * @param parentId Parent document ID
411 * @return Parent identifier
412 * @throws FileNotFoundException
413 */
414 private @Nullable Identifier getParentOrHaltMapping(
415 @Nullable String parentId) throws FileNotFoundException {
416 if (parentId == null) {
417 return null;
418 }
419 try {
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900420 return mDatabase.createIdentifier(parentId);
Daichi Hirono619afda2016-02-07 14:23:43 +0900421 } catch (FileNotFoundException error) {
Daichi Hironoebd24052016-02-06 21:05:57 +0900422 mInMappingIds.remove(parentId);
Daichi Hirono619afda2016-02-07 14:23:43 +0900423 throw error;
424 }
425 }
Daichi Hirono259ce802015-11-20 17:51:53 +0900426}