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