Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +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 | |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 19 | import android.content.Context; |
| 20 | import android.mtp.MtpObjectInfo; |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 21 | import android.os.ParcelFileDescriptor; |
| 22 | import android.util.Log; |
| 23 | |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 24 | import java.io.File; |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 25 | import java.io.FileOutputStream; |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 26 | import java.io.IOException; |
| 27 | import java.util.concurrent.ExecutorService; |
| 28 | import java.util.concurrent.Executors; |
| 29 | |
| 30 | class PipeManager { |
| 31 | final ExecutorService mExecutor; |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 32 | final MtpDatabase mDatabase; |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 33 | |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 34 | PipeManager(MtpDatabase database) { |
| 35 | this(database, Executors.newSingleThreadExecutor()); |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 36 | } |
| 37 | |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 38 | PipeManager(MtpDatabase database, ExecutorService executor) { |
| 39 | this.mDatabase = database; |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 40 | this.mExecutor = executor; |
| 41 | } |
| 42 | |
Daichi Hirono | 3faa43a | 2015-08-05 17:15:35 +0900 | [diff] [blame] | 43 | ParcelFileDescriptor readDocument(MtpManager model, Identifier identifier) throws IOException { |
Tomasz Mikolajewski | 52652ac | 2015-08-05 17:33:33 +0900 | [diff] [blame] | 44 | final Task task = new ImportFileTask(model, identifier); |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 45 | mExecutor.execute(task); |
| 46 | return task.getReadingFileDescriptor(); |
| 47 | } |
| 48 | |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 49 | ParcelFileDescriptor writeDocument(Context context, MtpManager model, Identifier identifier, |
| 50 | int[] operationsSupported) |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 51 | throws IOException { |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 52 | final Task task = new WriteDocumentTask( |
| 53 | context, model, identifier, operationsSupported, mDatabase); |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 54 | mExecutor.execute(task); |
| 55 | return task.getWritingFileDescriptor(); |
| 56 | } |
| 57 | |
Daichi Hirono | 3faa43a | 2015-08-05 17:15:35 +0900 | [diff] [blame] | 58 | ParcelFileDescriptor readThumbnail(MtpManager model, Identifier identifier) throws IOException { |
| 59 | final Task task = new GetThumbnailTask(model, identifier); |
| 60 | mExecutor.execute(task); |
| 61 | return task.getReadingFileDescriptor(); |
| 62 | } |
| 63 | |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 64 | private static abstract class Task implements Runnable { |
Daichi Hirono | 2ff024f | 2015-08-11 20:02:58 +0900 | [diff] [blame] | 65 | protected final MtpManager mManager; |
Tomasz Mikolajewski | 52652ac | 2015-08-05 17:33:33 +0900 | [diff] [blame] | 66 | protected final Identifier mIdentifier; |
| 67 | protected final ParcelFileDescriptor[] mDescriptors; |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 68 | |
Daichi Hirono | 2ff024f | 2015-08-11 20:02:58 +0900 | [diff] [blame] | 69 | Task(MtpManager manager, Identifier identifier) throws IOException { |
| 70 | mManager = manager; |
Tomasz Mikolajewski | 52652ac | 2015-08-05 17:33:33 +0900 | [diff] [blame] | 71 | mIdentifier = identifier; |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 72 | mDescriptors = ParcelFileDescriptor.createReliablePipe(); |
| 73 | } |
| 74 | |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 75 | ParcelFileDescriptor getReadingFileDescriptor() { |
| 76 | return mDescriptors[0]; |
| 77 | } |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 78 | |
| 79 | ParcelFileDescriptor getWritingFileDescriptor() { |
| 80 | return mDescriptors[1]; |
| 81 | } |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 82 | } |
| 83 | |
Tomasz Mikolajewski | 52652ac | 2015-08-05 17:33:33 +0900 | [diff] [blame] | 84 | private static class ImportFileTask extends Task { |
| 85 | ImportFileTask(MtpManager model, Identifier identifier) throws IOException { |
| 86 | super(model, identifier); |
| 87 | } |
| 88 | |
| 89 | @Override |
| 90 | public void run() { |
| 91 | try { |
Daichi Hirono | 2ff024f | 2015-08-11 20:02:58 +0900 | [diff] [blame] | 92 | mManager.importFile( |
Daichi Hirono | 3faa43a | 2015-08-05 17:15:35 +0900 | [diff] [blame] | 93 | mIdentifier.mDeviceId, mIdentifier.mObjectHandle, mDescriptors[1]); |
Tomasz Mikolajewski | 52652ac | 2015-08-05 17:33:33 +0900 | [diff] [blame] | 94 | mDescriptors[1].close(); |
| 95 | } catch (IOException error) { |
| 96 | try { |
| 97 | mDescriptors[1].closeWithError("Failed to stream a file."); |
| 98 | } catch (IOException closeError) { |
| 99 | Log.w(MtpDocumentsProvider.TAG, closeError.getMessage()); |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 105 | private static class WriteDocumentTask extends Task { |
| 106 | private final Context mContext; |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 107 | private final MtpDatabase mDatabase; |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 108 | private final int[] mOperationsSupported; |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 109 | |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 110 | WriteDocumentTask(Context context, |
| 111 | MtpManager model, |
| 112 | Identifier identifier, |
| 113 | int[] supportedOperations, |
| 114 | MtpDatabase database) |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 115 | throws IOException { |
| 116 | super(model, identifier); |
| 117 | mContext = context; |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 118 | mDatabase = database; |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 119 | mOperationsSupported = supportedOperations; |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 120 | } |
| 121 | |
| 122 | @Override |
| 123 | public void run() { |
| 124 | File tempFile = null; |
| 125 | try { |
| 126 | // Obtain a temporary file and copy the data to it. |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 127 | tempFile = File.createTempFile("mtp", "tmp", mContext.getCacheDir()); |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 128 | try ( |
| 129 | final FileOutputStream tempOutputStream = |
| 130 | new ParcelFileDescriptor.AutoCloseOutputStream( |
| 131 | ParcelFileDescriptor.open( |
| 132 | tempFile, ParcelFileDescriptor.MODE_WRITE_ONLY)); |
| 133 | final ParcelFileDescriptor.AutoCloseInputStream inputStream = |
| 134 | new ParcelFileDescriptor.AutoCloseInputStream(mDescriptors[0]) |
| 135 | ) { |
| 136 | final byte[] buffer = new byte[32 * 1024]; |
| 137 | int bytes; |
| 138 | while ((bytes = inputStream.read(buffer)) != -1) { |
| 139 | mDescriptors[0].checkError(); |
| 140 | tempOutputStream.write(buffer, 0, bytes); |
| 141 | } |
| 142 | tempOutputStream.flush(); |
| 143 | } |
| 144 | |
| 145 | // Get the placeholder object info. |
| 146 | final MtpObjectInfo placeholderObjectInfo = |
| 147 | mManager.getObjectInfo(mIdentifier.mDeviceId, mIdentifier.mObjectHandle); |
| 148 | |
| 149 | // Delete the target object info if it already exists (as a placeholder). |
| 150 | mManager.deleteDocument(mIdentifier.mDeviceId, mIdentifier.mObjectHandle); |
| 151 | |
Tomasz Mikolajewski | df54417 | 2015-08-31 10:59:43 +0900 | [diff] [blame] | 152 | // Create the target object info with a correct file size and upload the file. |
| 153 | final MtpObjectInfo targetObjectInfo = |
| 154 | new MtpObjectInfo.Builder(placeholderObjectInfo) |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 155 | .setCompressedSize(tempFile.length()) |
Tomasz Mikolajewski | df54417 | 2015-08-31 10:59:43 +0900 | [diff] [blame] | 156 | .build(); |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 157 | final ParcelFileDescriptor tempInputDescriptor = ParcelFileDescriptor.open( |
| 158 | tempFile, ParcelFileDescriptor.MODE_READ_ONLY); |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 159 | final int newObjectHandle = mManager.createDocument( |
| 160 | mIdentifier.mDeviceId, targetObjectInfo, tempInputDescriptor); |
| 161 | |
| 162 | final MtpObjectInfo newObjectInfo = mManager.getObjectInfo( |
| 163 | mIdentifier.mDeviceId, newObjectHandle); |
| 164 | final Identifier parentIdentifier = |
| 165 | mDatabase.getParentIdentifier(mIdentifier.mDocumentId); |
| 166 | mDatabase.updateObject( |
| 167 | mIdentifier.mDocumentId, |
| 168 | mIdentifier.mDeviceId, |
| 169 | parentIdentifier.mDocumentId, |
Daichi Hirono | 61ba923 | 2016-02-26 12:58:39 +0900 | [diff] [blame] | 170 | mOperationsSupported, |
Daichi Hirono | f578fa2 | 2016-02-19 18:05:42 +0900 | [diff] [blame] | 171 | newObjectInfo); |
Tomasz Mikolajewski | b80a3cf | 2015-08-24 16:10:51 +0900 | [diff] [blame] | 172 | } catch (IOException error) { |
| 173 | Log.w(MtpDocumentsProvider.TAG, |
| 174 | "Failed to send a file because of: " + error.getMessage()); |
| 175 | } finally { |
| 176 | if (tempFile != null) { |
| 177 | tempFile.delete(); |
| 178 | } |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | |
Daichi Hirono | 3faa43a | 2015-08-05 17:15:35 +0900 | [diff] [blame] | 183 | private static class GetThumbnailTask extends Task { |
| 184 | GetThumbnailTask(MtpManager model, Identifier identifier) throws IOException { |
| 185 | super(model, identifier); |
| 186 | } |
| 187 | |
| 188 | @Override |
| 189 | public void run() { |
| 190 | try { |
| 191 | try (final ParcelFileDescriptor.AutoCloseOutputStream stream = |
| 192 | new ParcelFileDescriptor.AutoCloseOutputStream(mDescriptors[1])) { |
| 193 | try { |
Daichi Hirono | 2ff024f | 2015-08-11 20:02:58 +0900 | [diff] [blame] | 194 | stream.write(mManager.getThumbnail( |
Daichi Hirono | 3faa43a | 2015-08-05 17:15:35 +0900 | [diff] [blame] | 195 | mIdentifier.mDeviceId, mIdentifier.mObjectHandle)); |
| 196 | } catch (IOException error) { |
| 197 | mDescriptors[1].closeWithError("Failed to stream a thumbnail."); |
| 198 | } |
| 199 | } |
| 200 | } catch (IOException closeError) { |
| 201 | Log.w(MtpDocumentsProvider.TAG, closeError.getMessage()); |
| 202 | } |
| 203 | } |
| 204 | } |
| 205 | |
Daichi Hirono | 8ba4191 | 2015-07-30 21:22:57 +0900 | [diff] [blame] | 206 | void close() { |
| 207 | mExecutor.shutdown(); |
| 208 | } |
| 209 | } |