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 | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 92 | synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) |
| 93 | throws FileNotFoundException { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 94 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 95 | database.beginTransaction(); |
| 96 | try { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 97 | final ContentValues[] valuesList = new ContentValues[roots.length]; |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 98 | final ContentValues[] extraValuesList = new ContentValues[roots.length]; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 99 | for (int i = 0; i < roots.length; i++) { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 100 | valuesList[i] = new ContentValues(); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 101 | extraValuesList[i] = new ContentValues(); |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 102 | MtpDatabase.getStorageDocumentValues( |
Daichi Hirono | f83ccbd | 2016-02-04 16:58:55 +0900 | [diff] [blame] | 103 | valuesList[i], extraValuesList[i], parentDocumentId, roots[i]); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 104 | } |
| 105 | final boolean changed = putDocuments( |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 106 | parentDocumentId, |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 107 | valuesList, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 108 | extraValuesList, |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 109 | COLUMN_PARENT_DOCUMENT_ID + " = ?", |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 110 | strings(parentDocumentId), |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 111 | strings(COLUMN_STORAGE_ID, Document.COLUMN_DISPLAY_NAME)); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 112 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 113 | database.setTransactionSuccessful(); |
| 114 | return changed; |
| 115 | } finally { |
| 116 | database.endTransaction(); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * Puts document information to database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 122 | * |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 123 | * @param deviceId Device ID |
| 124 | * @param parentId Parent document ID. |
| 125 | * @param documents List of document information. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 126 | * @throws FileNotFoundException |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 127 | */ |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 128 | synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) |
| 129 | throws FileNotFoundException { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 130 | final ContentValues[] valuesList = new ContentValues[documents.length]; |
| 131 | for (int i = 0; i < documents.length; i++) { |
| 132 | valuesList[i] = new ContentValues(); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 133 | MtpDatabase.getObjectDocumentValues(valuesList[i], deviceId, parentId, documents[i]); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 134 | } |
| 135 | putDocuments( |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 136 | parentId, |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 137 | valuesList, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 138 | null, |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 139 | COLUMN_PARENT_DOCUMENT_ID + " = ?", |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 140 | strings(parentId), |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 141 | strings(COLUMN_OBJECT_HANDLE, Document.COLUMN_DISPLAY_NAME)); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 142 | } |
| 143 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 144 | void clearMapping() { |
| 145 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 146 | database.beginTransaction(); |
| 147 | try { |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 148 | mInMappingIds.clear(); |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 149 | // 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 Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 158 | } finally { |
| 159 | database.endTransaction(); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | /** |
| 164 | * Starts adding new documents. |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 165 | * 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 Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 167 | * |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 168 | * @param parentDocumentId Parent document ID or NULL for root documents. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 169 | * @throws FileNotFoundException |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 170 | */ |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 171 | void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException { |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 172 | 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 Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 179 | args = EMPTY_ARGS; |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 180 | } |
| 181 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 182 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 183 | database.beginTransaction(); |
| 184 | try { |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 185 | getParentOrHaltMapping(parentDocumentId); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 186 | Preconditions.checkState(!mInMappingIds.contains(parentDocumentId)); |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 187 | |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 188 | // Set all valid documents as invalidated. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 189 | final ContentValues values = new ContentValues(); |
| 190 | values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED); |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 191 | database.update( |
| 192 | TABLE_DOCUMENTS, |
| 193 | values, |
| 194 | selection + " AND " + COLUMN_ROW_STATE + " = ?", |
| 195 | DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_VALID))); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 196 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 197 | database.setTransactionSuccessful(); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 198 | mInMappingIds.add(parentDocumentId); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 199 | } 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 Hirono | 20754c5 | 2015-12-15 18:52:26 +0900 | [diff] [blame] | 209 | * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid' |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 210 | * rows. If the methods adds rows to database, it updates valueList with correct document ID. |
| 211 | * |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 212 | * @param parentId Parent document ID. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 213 | * @param valuesList Values for documents to be stored in the database. |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 214 | * @param rootExtraValuesList Values for root extra to be stored in the database. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 215 | * @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] | 216 | * @param args Argument for selection SQL. |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 217 | * @return Whether the database content is changed. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 218 | * @throws FileNotFoundException When parentId is not registered in the database. |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 219 | */ |
| 220 | private boolean putDocuments( |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 221 | String parentId, |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 222 | ContentValues[] valuesList, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 223 | @Nullable ContentValues[] rootExtraValuesList, |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 224 | String selection, |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 225 | String[] args, |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 226 | String[] mappingKeys) throws FileNotFoundException { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 227 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 228 | boolean changed = false; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 229 | database.beginTransaction(); |
| 230 | try { |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 231 | getParentOrHaltMapping(parentId); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 232 | Preconditions.checkState(mInMappingIds.contains(parentId)); |
| 233 | final ContentValues oldRowSnapshot = new ContentValues(); |
| 234 | final ContentValues newRowSnapshot = new ContentValues(); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 235 | 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 Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 243 | try (final Cursor candidateCursor = |
| 244 | queryCandidate(selection, args, mappingKeys, values)) { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 245 | final long rowId; |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 246 | if (candidateCursor == null) { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 247 | rowId = database.insert(TABLE_DOCUMENTS, null, values); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 248 | changed = true; |
Daichi Hirono | 9fca541 | 2016-02-07 13:20:22 +0900 | [diff] [blame] | 249 | } else { |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 250 | candidateCursor.moveToNext(); |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 251 | rowId = candidateCursor.getLong(0); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 252 | if (!changed) { |
| 253 | mDatabase.writeRowSnapshot(String.valueOf(rowId), oldRowSnapshot); |
| 254 | } |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 255 | database.update( |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 256 | TABLE_DOCUMENTS, |
| 257 | values, |
| 258 | SELECTION_DOCUMENT_ID, |
Daichi Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 259 | strings(rowId)); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 260 | } |
| 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 Hirono | 81d4853 | 2015-12-16 15:03:19 +0900 | [diff] [blame] | 264 | if (rootExtraValues != null) { |
| 265 | rootExtraValues.put(Root.COLUMN_ROOT_ID, rowId); |
| 266 | database.replace(TABLE_ROOT_EXTRA, null, rootExtraValues); |
| 267 | } |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 268 | |
| 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 Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 277 | } |
| 278 | } |
| 279 | |
| 280 | database.setTransactionSuccessful(); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 281 | return changed; |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 282 | } finally { |
| 283 | database.endTransaction(); |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /** |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 288 | * 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 Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 295 | * |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 296 | * @param parentId Parent document ID or null for root documents. |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 297 | * @return Whether the methods changes file metadata in database. |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 298 | * @throws FileNotFoundException |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 299 | */ |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 300 | boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException { |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 301 | final String selection; |
| 302 | final String[] args; |
| 303 | if (parentId != null) { |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 304 | selection = COLUMN_PARENT_DOCUMENT_ID + " = ?"; |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 305 | args = strings(parentId); |
| 306 | } else { |
| 307 | selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL"; |
Daichi Hirono | b3fe72b | 2015-12-15 07:45:06 +0000 | [diff] [blame] | 308 | args = EMPTY_ARGS; |
Daichi Hirono | 7a375c4 | 2015-12-14 17:14:29 +0900 | [diff] [blame] | 309 | } |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 310 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 311 | final SQLiteDatabase database = mDatabase.getSQLiteDatabase(); |
| 312 | database.beginTransaction(); |
| 313 | try { |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 314 | final Identifier parentIdentifier = getParentOrHaltMapping(parentId); |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 315 | Preconditions.checkState(mInMappingIds.contains(parentId)); |
| 316 | mInMappingIds.remove(parentId); |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 317 | |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 318 | boolean changed = false; |
Daichi Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 319 | // 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 Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 322 | 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 Hirono | 5c69055 | 2016-02-21 16:00:03 +0900 | [diff] [blame] | 333 | COLUMN_ROW_STATE + " IN (?, ?) AND " + selection, |
| 334 | DatabaseUtils.appendSelectionArgs( |
| 335 | strings(ROW_STATE_INVALIDATED, ROW_STATE_DISCONNECTED), args))) { |
Daichi Hirono | 8e87364 | 2016-02-07 15:17:16 +0900 | [diff] [blame] | 336 | changed = true; |
| 337 | } |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 338 | } |
| 339 | |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 340 | database.setTransactionSuccessful(); |
| 341 | return changed; |
| 342 | } finally { |
| 343 | database.endTransaction(); |
| 344 | } |
| 345 | } |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 346 | |
| 347 | /** |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 348 | * 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 Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 400 | * 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 Hirono | 4e94b8d | 2016-02-21 22:42:41 +0900 | [diff] [blame] | 413 | return mDatabase.createIdentifier(parentId); |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 414 | } catch (FileNotFoundException error) { |
Daichi Hirono | ebd2405 | 2016-02-06 21:05:57 +0900 | [diff] [blame] | 415 | mInMappingIds.remove(parentId); |
Daichi Hirono | 619afda | 2016-02-07 14:23:43 +0900 | [diff] [blame] | 416 | throw error; |
| 417 | } |
| 418 | } |
Daichi Hirono | 259ce80 | 2015-11-20 17:51:53 +0900 | [diff] [blame] | 419 | } |