Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.mtp; |
| 18 | |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 19 | import android.annotation.Nullable; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 20 | import android.content.ContentValues; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 21 | import android.database.Cursor; |
| 22 | import android.database.DatabaseUtils; |
| 23 | import android.database.sqlite.SQLiteDatabase; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 24 | import android.mtp.MtpObjectInfo; |
| 25 | import android.provider.DocumentsContract.Document; |
| 26 | import android.provider.DocumentsContract.Root; |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 27 | import android.util.ArraySet; |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 28 | import android.util.Log; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 29 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 30 | import com.android.internal.util.Preconditions; |
| 31 | |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 32 | import java.io.FileNotFoundException; |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 33 | import java.util.Set; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 34 | |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 35 | import static com.android.mtp.MtpDatabaseConstants.*; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 36 | import static com.android.mtp.MtpDatabase.strings; |
| 37 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 38 | /** |
| 39 | * Mapping operations for MtpDatabase. |
| 40 | * Also see the comments of {@link MtpDatabase}. |
| 41 | */ |
| 42 | class Mapper { |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 43 | private static final String[] EMPTY_ARGS = new String[0]; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 44 | private final MtpDatabase mDatabase; |
| 45 | |
| 46 | /** |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 47 | * IDs which currently Mapper operates mapping for. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 48 | */ |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 49 | private final Set<String> mInMappingIds = new ArraySet<>(); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 50 | |
| 51 | Mapper(MtpDatabase database) { |
| 52 | mDatabase = database; |
| 53 | } |
| 54 | |
Daichi Hirono | 20754c5 | 2015-12-15 18:52:26 +0900 | [diff] [blame] | 55 | /** |
| 56 | * Puts device information to database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 57 | * |
Daichi Hirono | 20754c5 | 2015-12-15 18:52:26 +0900 | [diff] [blame] | 58 | * @return If device is added to the database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 59 | * @throws FileNotFoundException |
Daichi Hirono | 20754c5 | 2015-12-15 18:52:26 +0900 | [diff] [blame] | 60 | */ |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 61 | synchronized boolean putDeviceDocument(MtpDeviceRecord device) throws FileNotFoundException { |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 62 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 63 | database.beginTransaction(); |
| 64 | try { |
| 65 | final ContentValues[] valuesList = new ContentValues[1]; |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 66 | final ContentValues[] extraValuesList = new ContentValues[1]; |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 67 | valuesList[0] = new ContentValues(); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 68 | extraValuesList[0] = new ContentValues(); |
| 69 | MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device); |
Daichi Hirono | 20754c5 | 2015-12-15 18:52:26 +0900 | [diff] [blame] | 70 | final boolean changed = putDocuments( |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 71 | null, |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 72 | valuesList, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 73 | extraValuesList, |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 74 | COLUMN_PARENT_DOCUMENT_ID + " IS NULL", |
| 75 | EMPTY_ARGS, |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 76 | strings(COLUMN_DEVICE_ID, COLUMN_MAPPING_KEY)); |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 77 | database.setTransactionSuccessful(); |
Daichi Hirono | 20754c5 | 2015-12-15 18:52:26 +0900 | [diff] [blame] | 78 | return changed; |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 79 | } finally { |
| 80 | database.endTransaction(); |
| 81 | } |
| 82 | } |
| 83 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 84 | /** |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 85 | * Puts root information to database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 86 | * |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 87 | * @param parentDocumentId Document ID of device document. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 88 | * @param roots List of root information. |
| 89 | * @return If roots are added or removed from the database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 90 | * @throws FileNotFoundException |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 91 | */ |
Daichi Hirono | 0f32537 | 2016-02-21 15:50:30 +0900 | [diff] [blame] | 92 | synchronized boolean putStorageDocuments( |
| 93 | String parentDocumentId, int[] operationsSupported, MtpRoot[] roots) |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 94 | throws FileNotFoundException { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 95 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 96 | database.beginTransaction(); |
| 97 | try { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 98 | final ContentValues[] valuesList = new ContentValues[roots.length]; |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 99 | final ContentValues[] extraValuesList = new ContentValues[roots.length]; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 100 | for (int i = 0; i < roots.length; i++) { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 101 | valuesList[i] = new ContentValues(); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 102 | extraValuesList[i] = new ContentValues(); |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 103 | MtpDatabase.getStorageDocumentValues( |
Daichi Hirono | 0f32537 | 2016-02-21 15:50:30 +0900 | [diff] [blame] | 104 | valuesList[i], |
| 105 | extraValuesList[i], |
| 106 | parentDocumentId, |
| 107 | operationsSupported, |
| 108 | roots[i]); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 109 | } |
| 110 | final boolean changed = putDocuments( |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 111 | parentDocumentId, |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 112 | valuesList, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 113 | extraValuesList, |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 114 | COLUMN_PARENT_DOCUMENT_ID + " = ?", |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 115 | strings(parentDocumentId), |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 116 | strings(COLUMN_STORAGE_ID, Document.COLUMN_DISPLAY_NAME)); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 117 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 118 | database.setTransactionSuccessful(); |
| 119 | return changed; |
| 120 | } finally { |
| 121 | database.endTransaction(); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Puts document information to database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 127 | * |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 128 | * @param deviceId Device ID |
| 129 | * @param parentId Parent document ID. |
| 130 | * @param documents List of document information. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 131 | * @throws FileNotFoundException |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 132 | */ |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 133 | synchronized void putChildDocuments( |
| 134 | int deviceId, String parentId, int[] operationsSupported, MtpObjectInfo[] documents) |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 135 | throws FileNotFoundException { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 136 | final ContentValues[] valuesList = new ContentValues[documents.length]; |
| 137 | for (int i = 0; i < documents.length; i++) { |
| 138 | valuesList[i] = new ContentValues(); |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 139 | MtpDatabase.getObjectDocumentValues( |
| 140 | valuesList[i], deviceId, parentId, operationsSupported, documents[i]); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 141 | } |
| 142 | putDocuments( |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 143 | parentId, |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 144 | valuesList, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 145 | null, |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 146 | COLUMN_PARENT_DOCUMENT_ID + " = ?", |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 147 | strings(parentId), |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 148 | strings(COLUMN_OBJECT_HANDLE, Document.COLUMN_DISPLAY_NAME)); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 149 | } |
| 150 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 151 | void clearMapping() { |
| 152 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 153 | database.beginTransaction(); |
| 154 | try { |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 155 | mInMappingIds.clear(); |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 156 | // 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 Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 165 | } finally { |
| 166 | database.endTransaction(); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Starts adding new documents. |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 172 | * 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 Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 174 | * |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 175 | * @param parentDocumentId Parent document ID or NULL for root documents. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 176 | * @throws FileNotFoundException |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 177 | */ |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 178 | void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException { |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 179 | 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 Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 186 | args = EMPTY_ARGS; |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 187 | } |
| 188 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 189 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 190 | database.beginTransaction(); |
| 191 | try { |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 192 | getParentOrHaltMapping(parentDocumentId); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 193 | Preconditions.checkState(!mInMappingIds.contains(parentDocumentId)); |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 194 | |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 195 | // Set all valid documents as invalidated. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 196 | final ContentValues values = new ContentValues(); |
| 197 | values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED); |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 198 | database.update( |
| 199 | TABLE_DOCUMENTS, |
| 200 | values, |
| 201 | selection + " AND " + COLUMN_ROW_STATE + " = ?", |
| 202 | DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_VALID))); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 203 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 204 | database.setTransactionSuccessful(); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 205 | mInMappingIds.add(parentDocumentId); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 206 | } 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 Hirono | 20754c5 | 2015-12-15 18:52:26 +0900 | [diff] [blame] | 216 | * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid' |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 217 | * rows. If the methods adds rows to database, it updates valueList with correct document ID. |
| 218 | * |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 219 | * @param parentId Parent document ID. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 220 | * @param valuesList Values for documents to be stored in the database. |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 221 | * @param rootExtraValuesList Values for root extra to be stored in the database. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 222 | * @param selection SQL where closure to select rows that shares the same parent. |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 223 | * @param args Argument for selection SQL. |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 224 | * @return Whether the database content is changed. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 225 | * @throws FileNotFoundException When parentId is not registered in the database. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 226 | */ |
| 227 | private boolean putDocuments( |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 228 | String parentId, |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 229 | ContentValues[] valuesList, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 230 | @Nullable ContentValues[] rootExtraValuesList, |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 231 | String selection, |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 232 | String[] args, |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 233 | String[] mappingKeys) throws FileNotFoundException { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 234 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 235 | boolean changed = false; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 236 | database.beginTransaction(); |
| 237 | try { |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 238 | getParentOrHaltMapping(parentId); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 239 | Preconditions.checkState(mInMappingIds.contains(parentId)); |
| 240 | final ContentValues oldRowSnapshot = new ContentValues(); |
| 241 | final ContentValues newRowSnapshot = new ContentValues(); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 242 | 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 Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 250 | try (final Cursor candidateCursor = |
| 251 | queryCandidate(selection, args, mappingKeys, values)) { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 252 | final long rowId; |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 253 | if (candidateCursor == null) { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 254 | rowId = database.insert(TABLE_DOCUMENTS, null, values); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 255 | changed = true; |
Daichi Hirono | 9fca541 | 2016-02-07 13:20:22 +0900 | [diff] [blame] | 256 | } else { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 257 | candidateCursor.moveToNext(); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 258 | rowId = candidateCursor.getLong(0); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 259 | if (!changed) { |
| 260 | mDatabase.writeRowSnapshot(String.valueOf(rowId), oldRowSnapshot); |
| 261 | } |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 262 | database.update( |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 263 | TABLE_DOCUMENTS, |
| 264 | values, |
| 265 | SELECTION_DOCUMENT_ID, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 266 | strings(rowId)); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 267 | } |
| 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 Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 271 | if (rootExtraValues != null) { |
| 272 | rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId); |
| 273 | database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues); |
| 274 | } |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 275 | |
| 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 Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 284 | } |
| 285 | } |
| 286 | |
| 287 | database.setTransactionSuccessful(); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 288 | return changed; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 289 | } finally { |
| 290 | database.endTransaction(); |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | /** |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 295 | * 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 Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 302 | * |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 303 | * @param parentId Parent document ID or null for root documents. |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 304 | * @return Whether the methods changes file metadata in database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 305 | * @throws FileNotFoundException |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 306 | */ |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 307 | boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException { |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 308 | final String selection; |
| 309 | final String[] args; |
| 310 | if (parentId != null) { |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 311 | selection = COLUMN_PARENT_DOCUMENT_ID + " = ?"; |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 312 | args = strings(parentId); |
| 313 | } else { |
| 314 | selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 315 | args = EMPTY_ARGS; |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 316 | } |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 317 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 318 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 319 | database.beginTransaction(); |
| 320 | try { |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 321 | final Identifier parentIdentifier = getParentOrHaltMapping(parentId); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 322 | Preconditions.checkState(mInMappingIds.contains(parentId)); |
| 323 | mInMappingIds.remove(parentId); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 324 | |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 325 | boolean changed = false; |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 326 | // 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 Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 329 | 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 Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 340 | COLUMN_ROW_STATE + " IN (?, ?) AND " + selection, |
| 341 | DatabaseUtils.appendSelectionArgs( |
| 342 | strings(ROW_STATE_INVALIDATED, ROW_STATE_DISCONNECTED), args))) { |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 343 | changed = true; |
| 344 | } |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 345 | } |
| 346 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 347 | database.setTransactionSuccessful(); |
| 348 | return changed; |
| 349 | } finally { |
| 350 | database.endTransaction(); |
| 351 | } |
| 352 | } |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 353 | |
| 354 | /** |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 355 | * 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 Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 407 | * 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 Hirono | 4e94b8d | 2016-02-21 22:42:41 +0900 | [diff] [blame] | 420 | return mDatabase.createIdentifier(parentId); |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 421 | } catch (FileNotFoundException error) { |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 422 | mInMappingIds.remove(parentId); |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 423 | throw error; |
| 424 | } |
| 425 | } |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 426 | } |