blob: 73042c4d7b36262ceff1b8b1f2f08a432f4ce884 [file] [log] [blame]
Daichi Hirono8ba41912015-07-30 21:22:57 +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
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +090019import android.content.Context;
20import android.mtp.MtpObjectInfo;
Daichi Hirono8ba41912015-07-30 21:22:57 +090021import android.os.ParcelFileDescriptor;
22import android.util.Log;
23
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +090024import java.io.File;
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +090025import java.io.FileOutputStream;
Daichi Hirono8ba41912015-07-30 21:22:57 +090026import java.io.IOException;
27import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
29
30class PipeManager {
31 final ExecutorService mExecutor;
Daichi Hironof578fa22016-02-19 18:05:42 +090032 final MtpDatabase mDatabase;
Daichi Hirono8ba41912015-07-30 21:22:57 +090033
Daichi Hironof578fa22016-02-19 18:05:42 +090034 PipeManager(MtpDatabase database) {
35 this(database, Executors.newSingleThreadExecutor());
Daichi Hirono8ba41912015-07-30 21:22:57 +090036 }
37
Daichi Hironof578fa22016-02-19 18:05:42 +090038 PipeManager(MtpDatabase database, ExecutorService executor) {
39 this.mDatabase = database;
Daichi Hirono8ba41912015-07-30 21:22:57 +090040 this.mExecutor = executor;
41 }
42
Daichi Hirono3faa43a2015-08-05 17:15:35 +090043 ParcelFileDescriptor readDocument(MtpManager model, Identifier identifier) throws IOException {
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +090044 final Task task = new ImportFileTask(model, identifier);
Daichi Hirono8ba41912015-07-30 21:22:57 +090045 mExecutor.execute(task);
46 return task.getReadingFileDescriptor();
47 }
48
Daichi Hirono61ba9232016-02-26 12:58:39 +090049 ParcelFileDescriptor writeDocument(Context context, MtpManager model, Identifier identifier,
50 int[] operationsSupported)
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +090051 throws IOException {
Daichi Hirono61ba9232016-02-26 12:58:39 +090052 final Task task = new WriteDocumentTask(
53 context, model, identifier, operationsSupported, mDatabase);
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +090054 mExecutor.execute(task);
55 return task.getWritingFileDescriptor();
56 }
57
Daichi Hirono3faa43a2015-08-05 17:15:35 +090058 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 Hirono8ba41912015-07-30 21:22:57 +090064 private static abstract class Task implements Runnable {
Daichi Hirono2ff024f2015-08-11 20:02:58 +090065 protected final MtpManager mManager;
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +090066 protected final Identifier mIdentifier;
67 protected final ParcelFileDescriptor[] mDescriptors;
Daichi Hirono8ba41912015-07-30 21:22:57 +090068
Daichi Hirono2ff024f2015-08-11 20:02:58 +090069 Task(MtpManager manager, Identifier identifier) throws IOException {
70 mManager = manager;
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +090071 mIdentifier = identifier;
Daichi Hirono8ba41912015-07-30 21:22:57 +090072 mDescriptors = ParcelFileDescriptor.createReliablePipe();
73 }
74
Daichi Hirono8ba41912015-07-30 21:22:57 +090075 ParcelFileDescriptor getReadingFileDescriptor() {
76 return mDescriptors[0];
77 }
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +090078
79 ParcelFileDescriptor getWritingFileDescriptor() {
80 return mDescriptors[1];
81 }
Daichi Hirono8ba41912015-07-30 21:22:57 +090082 }
83
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +090084 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 Hirono2ff024f2015-08-11 20:02:58 +090092 mManager.importFile(
Daichi Hirono3faa43a2015-08-05 17:15:35 +090093 mIdentifier.mDeviceId, mIdentifier.mObjectHandle, mDescriptors[1]);
Tomasz Mikolajewski52652ac2015-08-05 17:33:33 +090094 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 Mikolajewskib80a3cf2015-08-24 16:10:51 +0900105 private static class WriteDocumentTask extends Task {
106 private final Context mContext;
Daichi Hironof578fa22016-02-19 18:05:42 +0900107 private final MtpDatabase mDatabase;
Daichi Hirono61ba9232016-02-26 12:58:39 +0900108 private final int[] mOperationsSupported;
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900109
Daichi Hirono61ba9232016-02-26 12:58:39 +0900110 WriteDocumentTask(Context context,
111 MtpManager model,
112 Identifier identifier,
113 int[] supportedOperations,
114 MtpDatabase database)
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900115 throws IOException {
116 super(model, identifier);
117 mContext = context;
Daichi Hironof578fa22016-02-19 18:05:42 +0900118 mDatabase = database;
Daichi Hirono61ba9232016-02-26 12:58:39 +0900119 mOperationsSupported = supportedOperations;
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900120 }
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 Hironof578fa22016-02-19 18:05:42 +0900127 tempFile = File.createTempFile("mtp", "tmp", mContext.getCacheDir());
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900128 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 Mikolajewskidf544172015-08-31 10:59:43 +0900152 // Create the target object info with a correct file size and upload the file.
153 final MtpObjectInfo targetObjectInfo =
154 new MtpObjectInfo.Builder(placeholderObjectInfo)
Daichi Hironof578fa22016-02-19 18:05:42 +0900155 .setCompressedSize(tempFile.length())
Tomasz Mikolajewskidf544172015-08-31 10:59:43 +0900156 .build();
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900157 final ParcelFileDescriptor tempInputDescriptor = ParcelFileDescriptor.open(
158 tempFile, ParcelFileDescriptor.MODE_READ_ONLY);
Daichi Hironof578fa22016-02-19 18:05:42 +0900159 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 Hirono61ba9232016-02-26 12:58:39 +0900170 mOperationsSupported,
Daichi Hironof578fa22016-02-19 18:05:42 +0900171 newObjectInfo);
Tomasz Mikolajewskib80a3cf2015-08-24 16:10:51 +0900172 } 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 Hirono3faa43a2015-08-05 17:15:35 +0900183 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 Hirono2ff024f2015-08-11 20:02:58 +0900194 stream.write(mManager.getThumbnail(
Daichi Hirono3faa43a2015-08-05 17:15:35 +0900195 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 Hirono8ba41912015-07-30 21:22:57 +0900206 void close() {
207 mExecutor.shutdown();
208 }
209}