blob: 9578e6bd1d5edf9e8798ad8cec3b6435a1500495 [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 Hirono619afda2016-02-07 14:23:43 +090092 synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots)
93 throws FileNotFoundException {
Daichi Hirono259ce802015-11-20 17:51:53 +090094 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
95 database.beginTransaction();
96 try {
Daichi Hirono259ce802015-11-20 17:51:53 +090097 final ContentValues[] valuesList = new ContentValues[roots.length];
Daichi Hirono81d48532015-12-16 15:03:19 +090098 final ContentValues[] extraValuesList = new ContentValues[roots.length];
Daichi Hirono259ce802015-11-20 17:51:53 +090099 for (int i = 0; i < roots.length; i++) {
Daichi Hirono259ce802015-11-20 17:51:53 +0900100 valuesList[i] = new ContentValues();
Daichi Hirono81d48532015-12-16 15:03:19 +0900101 extraValuesList[i] = new ContentValues();
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000102 MtpDatabase.getStorageDocumentValues(
Daichi Hironof83ccbd2016-02-04 16:58:55 +0900103 valuesList[i], extraValuesList[i], parentDocumentId, roots[i]);
Daichi Hirono259ce802015-11-20 17:51:53 +0900104 }
105 final boolean changed = putDocuments(
Daichi Hirono619afda2016-02-07 14:23:43 +0900106 parentDocumentId,
Daichi Hirono259ce802015-11-20 17:51:53 +0900107 valuesList,
Daichi Hirono81d48532015-12-16 15:03:19 +0900108 extraValuesList,
Daichi Hironoebd24052016-02-06 21:05:57 +0900109 COLUMN_PARENT_DOCUMENT_ID + " = ?",
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000110 strings(parentDocumentId),
Daichi Hironoebd24052016-02-06 21:05:57 +0900111 strings(COLUMN_STORAGE_ID, Document.COLUMN_DISPLAY_NAME));
Daichi Hirono81d48532015-12-16 15:03:19 +0900112
Daichi Hirono259ce802015-11-20 17:51:53 +0900113 database.setTransactionSuccessful();
114 return changed;
115 } finally {
116 database.endTransaction();
117 }
118 }
119
120 /**
121 * Puts document information to database.
Daichi Hirono619afda2016-02-07 14:23:43 +0900122 *
Daichi Hirono259ce802015-11-20 17:51:53 +0900123 * @param deviceId Device ID
124 * @param parentId Parent document ID.
125 * @param documents List of document information.
Daichi Hirono619afda2016-02-07 14:23:43 +0900126 * @throws FileNotFoundException
Daichi Hirono259ce802015-11-20 17:51:53 +0900127 */
Daichi Hirono619afda2016-02-07 14:23:43 +0900128 synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents)
129 throws FileNotFoundException {
Daichi Hirono259ce802015-11-20 17:51:53 +0900130 final ContentValues[] valuesList = new ContentValues[documents.length];
131 for (int i = 0; i < documents.length; i++) {
132 valuesList[i] = new ContentValues();
Daichi Hironoebd24052016-02-06 21:05:57 +0900133 MtpDatabase.getObjectDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
Daichi Hirono259ce802015-11-20 17:51:53 +0900134 }
135 putDocuments(
Daichi Hirono619afda2016-02-07 14:23:43 +0900136 parentId,
Daichi Hirono7a375c42015-12-14 17:14:29 +0900137 valuesList,
Daichi Hirono81d48532015-12-16 15:03:19 +0900138 null,
Daichi Hironoebd24052016-02-06 21:05:57 +0900139 COLUMN_PARENT_DOCUMENT_ID + " = ?",
Daichi Hirono7a375c42015-12-14 17:14:29 +0900140 strings(parentId),
Daichi Hironoebd24052016-02-06 21:05:57 +0900141 strings(COLUMN_OBJECT_HANDLE, Document.COLUMN_DISPLAY_NAME));
Daichi Hirono259ce802015-11-20 17:51:53 +0900142 }
143
Daichi Hirono259ce802015-11-20 17:51:53 +0900144 void clearMapping() {
145 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
146 database.beginTransaction();
147 try {
Daichi Hironoebd24052016-02-06 21:05:57 +0900148 mInMappingIds.clear();
Daichi Hirono8e873642016-02-07 15:17:16 +0900149 // Disconnect all device rows.
150 try {
151 startAddingDocuments(null);
152 stopAddingDocuments(null);
153 } catch (FileNotFoundException exception) {
154 Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException.", exception);
155 throw new RuntimeException(exception);
156 }
157 database.setTransactionSuccessful();
Daichi Hirono259ce802015-11-20 17:51:53 +0900158 } finally {
159 database.endTransaction();
160 }
161 }
162
163 /**
164 * Starts adding new documents.
Daichi Hirono5c690552016-02-21 16:00:03 +0900165 * It changes the direct child documents of the given document from VALID to INVALIDATED.
166 * Note that it keeps DISCONNECTED documents as they are.
Daichi Hirono259ce802015-11-20 17:51:53 +0900167 *
Daichi Hirono7a375c42015-12-14 17:14:29 +0900168 * @param parentDocumentId Parent document ID or NULL for root documents.
Daichi Hirono619afda2016-02-07 14:23:43 +0900169 * @throws FileNotFoundException
Daichi Hirono259ce802015-11-20 17:51:53 +0900170 */
Daichi Hirono619afda2016-02-07 14:23:43 +0900171 void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException {
Daichi Hirono7a375c42015-12-14 17:14:29 +0900172 final String selection;
173 final String[] args;
174 if (parentDocumentId != null) {
175 selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
176 args = strings(parentDocumentId);
177 } else {
178 selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000179 args = EMPTY_ARGS;
Daichi Hirono7a375c42015-12-14 17:14:29 +0900180 }
181
Daichi Hirono259ce802015-11-20 17:51:53 +0900182 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
183 database.beginTransaction();
184 try {
Daichi Hirono619afda2016-02-07 14:23:43 +0900185 getParentOrHaltMapping(parentDocumentId);
Daichi Hironoebd24052016-02-06 21:05:57 +0900186 Preconditions.checkState(!mInMappingIds.contains(parentDocumentId));
Daichi Hirono619afda2016-02-07 14:23:43 +0900187
Daichi Hirono8e873642016-02-07 15:17:16 +0900188 // Set all valid documents as invalidated.
Daichi Hirono259ce802015-11-20 17:51:53 +0900189 final ContentValues values = new ContentValues();
190 values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
Daichi Hirono8e873642016-02-07 15:17:16 +0900191 database.update(
192 TABLE_DOCUMENTS,
193 values,
194 selection + " AND " + COLUMN_ROW_STATE + " = ?",
195 DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_VALID)));
Daichi Hirono259ce802015-11-20 17:51:53 +0900196
Daichi Hirono259ce802015-11-20 17:51:53 +0900197 database.setTransactionSuccessful();
Daichi Hironoebd24052016-02-06 21:05:57 +0900198 mInMappingIds.add(parentDocumentId);
Daichi Hirono259ce802015-11-20 17:51:53 +0900199 } finally {
200 database.endTransaction();
201 }
202 }
203
204 /**
205 * Puts the documents into the database.
206 * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
207 * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
208 * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
Daichi Hirono20754c52015-12-15 18:52:26 +0900209 * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid'
Daichi Hirono259ce802015-11-20 17:51:53 +0900210 * rows. If the methods adds rows to database, it updates valueList with correct document ID.
211 *
Daichi Hirono619afda2016-02-07 14:23:43 +0900212 * @param parentId Parent document ID.
Daichi Hirono259ce802015-11-20 17:51:53 +0900213 * @param valuesList Values for documents to be stored in the database.
Daichi Hirono81d48532015-12-16 15:03:19 +0900214 * @param rootExtraValuesList Values for root extra to be stored in the database.
Daichi Hirono259ce802015-11-20 17:51:53 +0900215 * @param selection SQL where closure to select rows that shares the same parent.
Daichi Hirono7a375c42015-12-14 17:14:29 +0900216 * @param args Argument for selection SQL.
Daichi Hironoebd24052016-02-06 21:05:57 +0900217 * @return Whether the database content is changed.
Daichi Hirono619afda2016-02-07 14:23:43 +0900218 * @throws FileNotFoundException When parentId is not registered in the database.
Daichi Hirono259ce802015-11-20 17:51:53 +0900219 */
220 private boolean putDocuments(
Daichi Hirono619afda2016-02-07 14:23:43 +0900221 String parentId,
Daichi Hirono259ce802015-11-20 17:51:53 +0900222 ContentValues[] valuesList,
Daichi Hirono81d48532015-12-16 15:03:19 +0900223 @Nullable ContentValues[] rootExtraValuesList,
Daichi Hirono259ce802015-11-20 17:51:53 +0900224 String selection,
Daichi Hirono7a375c42015-12-14 17:14:29 +0900225 String[] args,
Daichi Hironoebd24052016-02-06 21:05:57 +0900226 String[] mappingKeys) throws FileNotFoundException {
Daichi Hirono259ce802015-11-20 17:51:53 +0900227 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
Daichi Hironoebd24052016-02-06 21:05:57 +0900228 boolean changed = false;
Daichi Hirono259ce802015-11-20 17:51:53 +0900229 database.beginTransaction();
230 try {
Daichi Hirono619afda2016-02-07 14:23:43 +0900231 getParentOrHaltMapping(parentId);
Daichi Hironoebd24052016-02-06 21:05:57 +0900232 Preconditions.checkState(mInMappingIds.contains(parentId));
233 final ContentValues oldRowSnapshot = new ContentValues();
234 final ContentValues newRowSnapshot = new ContentValues();
Daichi Hirono81d48532015-12-16 15:03:19 +0900235 for (int i = 0; i < valuesList.length; i++) {
236 final ContentValues values = valuesList[i];
237 final ContentValues rootExtraValues;
238 if (rootExtraValuesList != null) {
239 rootExtraValues = rootExtraValuesList[i];
240 } else {
241 rootExtraValues = null;
242 }
Daichi Hironoebd24052016-02-06 21:05:57 +0900243 try (final Cursor candidateCursor =
244 queryCandidate(selection, args, mappingKeys, values)) {
Daichi Hirono259ce802015-11-20 17:51:53 +0900245 final long rowId;
Daichi Hironoebd24052016-02-06 21:05:57 +0900246 if (candidateCursor == null) {
Daichi Hirono259ce802015-11-20 17:51:53 +0900247 rowId = database.insert(TABLE_DOCUMENTS, null, values);
Daichi Hironoebd24052016-02-06 21:05:57 +0900248 changed = true;
Daichi Hirono9fca5412016-02-07 13:20:22 +0900249 } else {
Daichi Hirono259ce802015-11-20 17:51:53 +0900250 candidateCursor.moveToNext();
Daichi Hirono81d48532015-12-16 15:03:19 +0900251 rowId = candidateCursor.getLong(0);
Daichi Hironoebd24052016-02-06 21:05:57 +0900252 if (!changed) {
253 mDatabase.writeRowSnapshot(String.valueOf(rowId), oldRowSnapshot);
254 }
Daichi Hirono81d48532015-12-16 15:03:19 +0900255 database.update(
Daichi Hirono259ce802015-11-20 17:51:53 +0900256 TABLE_DOCUMENTS,
257 values,
258 SELECTION_DOCUMENT_ID,
Daichi Hirono81d48532015-12-16 15:03:19 +0900259 strings(rowId));
Daichi Hirono259ce802015-11-20 17:51:53 +0900260 }
261 // Document ID is a primary integer key of the table. So the returned row
262 // IDs should be same with the document ID.
263 values.put(Document.COLUMN_DOCUMENT_ID, rowId);
Daichi Hirono81d48532015-12-16 15:03:19 +0900264 if (rootExtraValues != null) {
265 rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId);
266 database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues);
267 }
Daichi Hironoebd24052016-02-06 21:05:57 +0900268
269 if (!changed) {
270 mDatabase.writeRowSnapshot(String.valueOf(rowId), newRowSnapshot);
271 // Put row state as string because SQLite returns snapshot values as string.
272 oldRowSnapshot.put(COLUMN_ROW_STATE, String.valueOf(ROW_STATE_VALID));
273 if (!oldRowSnapshot.equals(newRowSnapshot)) {
274 changed = true;
275 }
276 }
Daichi Hirono259ce802015-11-20 17:51:53 +0900277 }
278 }
279
280 database.setTransactionSuccessful();
Daichi Hironoebd24052016-02-06 21:05:57 +0900281 return changed;
Daichi Hirono259ce802015-11-20 17:51:53 +0900282 } finally {
283 database.endTransaction();
284 }
285 }
286
287 /**
Daichi Hirono5c690552016-02-21 16:00:03 +0900288 * Stops adding documents.
289 * It handles 'invalidated' and 'disconnected' documents which we don't put corresponding
290 * documents so far.
291 * If the type adding document is 'device' or 'storage', the document may appear again
292 * afterward. The method marks such documents as 'disconnected'. If the type of adding document
293 * is 'object', it seems the documents are really removed from the remote MTP device. So the
294 * method deletes the metadata from the database.
Daichi Hirono619afda2016-02-07 14:23:43 +0900295 *
Daichi Hirono7a375c42015-12-14 17:14:29 +0900296 * @param parentId Parent document ID or null for root documents.
Daichi Hirono5c690552016-02-21 16:00:03 +0900297 * @return Whether the methods changes file metadata in database.
Daichi Hirono619afda2016-02-07 14:23:43 +0900298 * @throws FileNotFoundException
Daichi Hirono259ce802015-11-20 17:51:53 +0900299 */
Daichi Hirono619afda2016-02-07 14:23:43 +0900300 boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException {
Daichi Hirono7a375c42015-12-14 17:14:29 +0900301 final String selection;
302 final String[] args;
303 if (parentId != null) {
Daichi Hironoebd24052016-02-06 21:05:57 +0900304 selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
Daichi Hirono7a375c42015-12-14 17:14:29 +0900305 args = strings(parentId);
306 } else {
307 selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000308 args = EMPTY_ARGS;
Daichi Hirono7a375c42015-12-14 17:14:29 +0900309 }
Daichi Hirono619afda2016-02-07 14:23:43 +0900310
Daichi Hirono259ce802015-11-20 17:51:53 +0900311 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
312 database.beginTransaction();
313 try {
Daichi Hirono8e873642016-02-07 15:17:16 +0900314 final Identifier parentIdentifier = getParentOrHaltMapping(parentId);
Daichi Hironoebd24052016-02-06 21:05:57 +0900315 Preconditions.checkState(mInMappingIds.contains(parentId));
316 mInMappingIds.remove(parentId);
Daichi Hirono259ce802015-11-20 17:51:53 +0900317
Daichi Hirono619afda2016-02-07 14:23:43 +0900318 boolean changed = false;
Daichi Hirono5c690552016-02-21 16:00:03 +0900319 // Delete/disconnect all invalidated/disconnected rows that cannot be mapped.
320 // If parentIdentifier is null, added documents are devices.
321 // if parentIdentifier is DOCUMENT_TYPE_DEVICE, added documents are storages.
Daichi Hirono8e873642016-02-07 15:17:16 +0900322 final boolean keepUnmatchedDocument =
323 parentIdentifier == null ||
324 parentIdentifier.mDocumentType == DOCUMENT_TYPE_DEVICE;
325 if (keepUnmatchedDocument) {
326 if (mDatabase.disconnectDocumentsRecursively(
327 COLUMN_ROW_STATE + " = ? AND " + selection,
328 DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
329 changed = true;
330 }
331 } else {
332 if (mDatabase.deleteDocumentsAndRootsRecursively(
Daichi Hirono5c690552016-02-21 16:00:03 +0900333 COLUMN_ROW_STATE + " IN (?, ?) AND " + selection,
334 DatabaseUtils.appendSelectionArgs(
335 strings(ROW_STATE_INVALIDATED, ROW_STATE_DISCONNECTED), args))) {
Daichi Hirono8e873642016-02-07 15:17:16 +0900336 changed = true;
337 }
Daichi Hirono259ce802015-11-20 17:51:53 +0900338 }
339
Daichi Hirono259ce802015-11-20 17:51:53 +0900340 database.setTransactionSuccessful();
341 return changed;
342 } finally {
343 database.endTransaction();
344 }
345 }
Daichi Hirono619afda2016-02-07 14:23:43 +0900346
347 /**
Daichi Hironoebd24052016-02-06 21:05:57 +0900348 * Queries candidate for each mappingKey, and returns the first cursor that includes a
349 * candidate.
350 *
351 * @param selection Pre-selection for candidate.
352 * @param args Arguments for selection.
353 * @param mappingKeys List of mapping key columns.
354 * @param values Values of document that Mapper tries to map.
355 * @return Cursor for mapping candidate or null when Mapper does not find any candidate.
356 */
357 private @Nullable Cursor queryCandidate(
358 String selection, String[] args, String[] mappingKeys, ContentValues values) {
359 for (final String mappingKey : mappingKeys) {
360 final Cursor candidateCursor = queryCandidate(selection, args, mappingKey, values);
361 if (candidateCursor.getCount() == 0) {
362 candidateCursor.close();
363 continue;
364 }
365 return candidateCursor;
366 }
367 return null;
368 }
369
370 /**
371 * Looks for mapping candidate with given mappingKey.
372 *
373 * @param selection Pre-selection for candidate.
374 * @param args Arguments for selection.
375 * @param mappingKey Column name of mapping key.
376 * @param values Values of document that Mapper tries to map.
377 * @return Cursor for mapping candidate.
378 */
379 private Cursor queryCandidate(
380 String selection, String[] args, String mappingKey, ContentValues values) {
381 final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
382 return database.query(
383 TABLE_DOCUMENTS,
384 strings(Document.COLUMN_DOCUMENT_ID),
385 selection + " AND " +
386 COLUMN_ROW_STATE + " IN (?, ?) AND " +
387 mappingKey + " = ?",
388 DatabaseUtils.appendSelectionArgs(
389 args,
390 strings(ROW_STATE_INVALIDATED,
391 ROW_STATE_DISCONNECTED,
392 values.getAsString(mappingKey))),
393 null,
394 null,
395 null,
396 "1");
397 }
398
399 /**
Daichi Hirono619afda2016-02-07 14:23:43 +0900400 * Returns the parent identifier from parent document ID if the parent ID is found in the
401 * database. Otherwise it halts mapping and throws FileNotFoundException.
402 *
403 * @param parentId Parent document ID
404 * @return Parent identifier
405 * @throws FileNotFoundException
406 */
407 private @Nullable Identifier getParentOrHaltMapping(
408 @Nullable String parentId) throws FileNotFoundException {
409 if (parentId == null) {
410 return null;
411 }
412 try {
Daichi Hirono4e94b8d2016-02-21 22:42:41 +0900413 return mDatabase.createIdentifier(parentId);
Daichi Hirono619afda2016-02-07 14:23:43 +0900414 } catch (FileNotFoundException error) {
Daichi Hironoebd24052016-02-06 21:05:57 +0900415 mInMappingIds.remove(parentId);
Daichi Hirono619afda2016-02-07 14:23:43 +0900416 throw error;
417 }
418 }
Daichi Hirono259ce802015-11-20 17:51:53 +0900419}