Loading packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java +5 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.system.ErrnoException; import android.system.Os; import android.text.format.DateUtils; import android.util.Log; import android.webkit.MimeTypeMap; Loading Loading @@ -451,7 +453,7 @@ class CopyJob extends Job { ParcelFileDescriptor srcFile = null; ParcelFileDescriptor dstFile = null; InputStream in = null; OutputStream out = null; ParcelFileDescriptor.AutoCloseOutputStream out = null; boolean success = false; try { Loading Loading @@ -502,6 +504,8 @@ class CopyJob extends Job { makeCopyProgress(len); } // Need to invoke IoUtils.close explicitly to avoid from ignoring errors at flush. IoUtils.close(dstFile.getFileDescriptor()); srcFile.checkError(); } catch (IOException e) { throw new ResourceException( Loading packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +41 −17 Original line number Diff line number Diff line Loading @@ -52,6 +52,8 @@ static jclass app_fuse_class; static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_read_object_bytes; static jmethodID app_fuse_write_object_bytes; static jmethodID app_fuse_flush_file_handle; static jmethodID app_fuse_close_file_handle; static jfieldID app_fuse_buffer; // NOTE: Loading Loading @@ -307,7 +309,8 @@ private: const uint32_t size = in->size; const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in); uint32_t written_size; const int result = write_object_bytes(it->second, offset, size, buffer, &written_size); const int result = write_object_bytes( in->fh, it->second, offset, size, buffer, &written_size); if (result < 0) { return result; } Loading @@ -320,13 +323,13 @@ private: const fuse_release_in* in, FuseResponse<void>* /* out */) { handles_.erase(in->fh); return 0; return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh)); } int handle_fuse_flush(const fuse_in_header& /* header */, const void* /* in */, const fuse_flush_in* in, FuseResponse<void>* /* out */) { return 0; return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh)); } template <typename T, typename S> Loading Loading @@ -382,8 +385,10 @@ private: return read_size; } int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer, uint32_t* written_size) { int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size, const void* buffer, uint32_t* written_size) { static_assert(sizeof(uint64_t) <= sizeof(jlong), "jlong must be able to express any uint64_t values"); ScopedLocalRef<jbyteArray> array( env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer))); Loading @@ -394,15 +399,28 @@ private: } memcpy(bytes.get(), buffer, size); } *written_size = env_->CallIntMethod( self_, app_fuse_write_object_bytes, inode, offset, size, array.get()); if (env_->ExceptionCheck()) { env_->ExceptionClear(); return -EIO; const int result = env_->CallIntMethod( self_, app_fuse_write_object_bytes, file_handle_to_jlong(handle), inode, offset, size, array.get()); if (result < 0) { return result; } *written_size = result; return 0; } static jlong file_handle_to_jlong(uint64_t handle) { static_assert( sizeof(uint64_t) <= sizeof(jlong), "jlong must be able to express any uint64_t values"); return static_cast<jlong>(handle); } static void fuse_reply(int fd, int unique, int reply_code, void* reply_data, size_t reply_size) { // Don't send any data for error case. Loading Loading @@ -511,15 +529,21 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { return -1; } app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B"); if (app_fuse_buffer == nullptr) { ALOGE("Can't find mBuffer"); app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I"); if (app_fuse_write_object_bytes == nullptr) { ALOGE("Can't find writeObjectBytes"); return -1; } app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I"); if (app_fuse_write_object_bytes == nullptr) { ALOGE("Can't find getWriteObjectBytes"); app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I"); if (app_fuse_flush_file_handle == nullptr) { ALOGE("Can't find flushFileHandle"); return -1; } app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I"); if (app_fuse_close_file_handle == nullptr) { ALOGE("Can't find closeFileHandle"); return -1; } Loading packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +74 −13 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.WorkerThread; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; Loading @@ -34,6 +35,8 @@ public class AppFuse { System.loadLibrary("appfuse_jni"); } private static final boolean DEBUG = false; /** * Max read amount specified at the FUSE kernel implementation. * The value is copied from sdcard.c. Loading Loading @@ -94,7 +97,8 @@ public class AppFuse { public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException { Preconditions.checkArgument( mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == ParcelFileDescriptor.MODE_WRITE_ONLY); mode == (ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE)); return ParcelFileDescriptor.open(new File( getMountPoint(), Integer.toString(i)), Loading Loading @@ -127,6 +131,7 @@ public class AppFuse { /** * Handles writing bytes for the give inode. * @param fileHandle * @param inode * @param offset Offset for file bytes. * @param size Size for file bytes. Loading @@ -134,7 +139,23 @@ public class AppFuse { * @return Number of read bytes. Must not be negative. * @throws IOException */ int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException; int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes) throws IOException, ErrnoException; /** * Flushes bytes for file handle. * @param fileHandle * @throws IOException * @throws ErrnoException */ void flushFileHandle(long fileHandle) throws IOException, ErrnoException; /** * Closes file handle. * @param fileHandle * @throws IOException */ void closeFileHandle(long fileHandle) throws IOException, ErrnoException; } @UsedByNative("com_android_mtp_AppFuse.cpp") Loading @@ -142,10 +163,8 @@ public class AppFuse { private long getFileSize(int inode) { try { return mCallback.getFileSize(inode); } catch (FileNotFoundException e) { return -OsConstants.ENOENT; } catch (UnsupportedOperationException e) { return -OsConstants.ENOTSUP; } catch (Exception error) { return -getErrnoFromException(error); } } Loading @@ -159,20 +178,62 @@ public class AppFuse { // It's OK to share the same mBuffer among requests because the requests are processed // by AppFuseMessageThread sequentially. return mCallback.readObjectBytes(inode, offset, size, mBuffer); } catch (IOException e) { return -OsConstants.EIO; } catch (UnsupportedOperationException e) { return -OsConstants.ENOTSUP; } catch (Exception error) { return -getErrnoFromException(error); } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private /* unsgined */ int writeObjectBytes(int inode, private /* unsgined */ int writeObjectBytes(long fileHandler, int inode, /* unsigned */ long offset, /* unsigned */ int size, byte[] bytes) throws IOException { return mCallback.writeObjectBytes(inode, offset, size, bytes); byte[] bytes) { try { return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes); } catch (Exception error) { return -getErrnoFromException(error); } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private int flushFileHandle(long fileHandle) { try { mCallback.flushFileHandle(fileHandle); return 0; } catch (Exception error) { return -getErrnoFromException(error); } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private int closeFileHandle(long fileHandle) { try { mCallback.closeFileHandle(fileHandle); return 0; } catch (Exception error) { return -getErrnoFromException(error); } } private static int getErrnoFromException(Exception error) { if (DEBUG) { Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error); } if (error instanceof FileNotFoundException) { return OsConstants.ENOENT; } else if (error instanceof IOException) { return OsConstants.EIO; } else if (error instanceof UnsupportedOperationException) { return OsConstants.ENOTSUP; } else if (error instanceof IllegalArgumentException) { return OsConstants.EINVAL; } else { return OsConstants.EIO; } } private native boolean native_start_app_fuse_loop(int fd); Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +84 −39 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.mtp; import android.content.ContentResolver; import android.content.Context; import android.content.UriPermission; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; Loading @@ -38,11 +39,16 @@ import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; Loading Loading @@ -82,6 +88,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private MtpDatabase mDatabase; private AppFuse mAppFuse; private ServiceIntentSender mIntentSender; private Context mContext; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -93,6 +100,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { @Override public boolean onCreate() { sSingleton = this; mContext = getContext(); mResources = getContext().getResources(); mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); Loading Loading @@ -137,12 +145,14 @@ public class MtpDocumentsProvider extends DocumentsProvider { @VisibleForTesting boolean onCreateForTesting( Context context, Resources resources, MtpManager mtpManager, ContentResolver resolver, MtpDatabase database, StorageManager storageManager, ServiceIntentSender intentSender) { mContext = context; mResources = resources; mMtpManager = mtpManager; mResolver = resolver; Loading Loading @@ -232,43 +242,43 @@ public class MtpDocumentsProvider extends DocumentsProvider { try { openDevice(identifier.mDeviceId); final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; switch (mode) { case "r": // Turn off MODE_CREATE because openDocument does not allow to create new files. final int modeFlag = ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE; if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) { long fileSize; try { fileSize = getFileSize(documentId); } catch (UnsupportedOperationException exception) { fileSize = -1; } // MTP getPartialObject operation does not support files that are larger than // 4GB. Fallback to non-seekable file descriptor. if (MtpDeviceRecord.isPartialReadSupported( device.operationsSupported, fileSize)) { return mAppFuse.openFile( Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY); return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); } else { // If getPartialObject{|64} are not supported for the device, returns // non-seekable pipe FD instead. return getPipeManager(identifier).readDocument(mMtpManager, identifier); } case "w": } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) { // TODO: Clear the parent document loader task (if exists) and call notify // when writing is completed. if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { return getPipeManager(identifier).writeDocument( getContext(), mMtpManager, identifier, device.operationsSupported); return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); } else { throw new UnsupportedOperationException( "The device does not support writing operation."); } case "rw": } else { // TODO: Add support for "rw" mode. throw new UnsupportedOperationException( "The provider does not support 'rw' mode."); default: throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); throw new UnsupportedOperationException("The provider does not support 'rw' mode."); } } catch (FileNotFoundException | RuntimeException error) { Log.e(MtpDocumentsProvider.TAG, "openDocument", error); throw error; } catch (IOException error) { Log.e(MtpDocumentsProvider.TAG, "openDocument", error); throw new FileNotFoundException(error.getMessage()); throw new IllegalStateException(error); } } Loading Loading @@ -595,6 +605,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { } private class AppFuseCallback implements AppFuse.Callback { private final Map<Long, MtpFileWriter> mWriters = new HashMap<>(); @Override public long getFileSize(int inode) throws FileNotFoundException { return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); } @Override public long readObjectBytes( int inode, long offset, long size, byte[] buffer) throws IOException { Loading @@ -617,15 +634,43 @@ public class MtpDocumentsProvider extends DocumentsProvider { } @Override public long getFileSize(int inode) throws FileNotFoundException { return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); public int writeObjectBytes( long fileHandle, int inode, long offset, int size, byte[] bytes) throws IOException, ErrnoException { final MtpFileWriter writer; if (mWriters.containsKey(fileHandle)) { writer = mWriters.get(fileHandle); } else { writer = new MtpFileWriter(mContext, String.valueOf(inode)); mWriters.put(fileHandle, writer); } return writer.write(offset, size, bytes); } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException { // TODO: Implement it. throw new IOException(); public void flushFileHandle(long fileHandle) throws IOException, ErrnoException { final MtpFileWriter writer = mWriters.get(fileHandle); if (writer == null) { // File handle for reading. return; } final MtpDeviceRecord device = getDeviceToolkit( mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord; writer.flush(mMtpManager, mDatabase, device.operationsSupported); } @Override public void closeFileHandle(long fileHandle) throws IOException, ErrnoException { final MtpFileWriter writer = mWriters.get(fileHandle); if (writer == null) { // File handle for reading. return; } try { writer.close(); } finally { mWriters.remove(fileHandle); } } } } packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mtp; import android.content.Context; import android.mtp.MtpObjectInfo; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import com.android.internal.util.Preconditions; import java.io.File; import java.io.IOException; class MtpFileWriter implements AutoCloseable { final ParcelFileDescriptor mCacheFd; final String mDocumentId; boolean mDirty; MtpFileWriter(Context context, String documentId) throws IOException { mDocumentId = documentId; mDirty = false; final File tempFile = File.createTempFile("mtp", "tmp", context.getCacheDir()); mCacheFd = ParcelFileDescriptor.open( tempFile, ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_TRUNCATE | ParcelFileDescriptor.MODE_CREATE); tempFile.delete(); } String getDocumentId() { return mDocumentId; } int write(long offset, int size, byte[] bytes) throws IOException, ErrnoException { Preconditions.checkArgumentNonnegative(offset, "offset"); Preconditions.checkArgumentNonnegative(size, "size"); Preconditions.checkArgument(size <= bytes.length); if (size == 0) { return 0; } mDirty = true; Os.lseek(mCacheFd.getFileDescriptor(), offset, OsConstants.SEEK_SET); return Os.write(mCacheFd.getFileDescriptor(), bytes, 0, size); } void flush(MtpManager manager, MtpDatabase database, int[] operationsSupported) throws IOException, ErrnoException { // Skip unnecessary flush. if (!mDirty) { return; } // Get the placeholder object info. final Identifier identifier = database.createIdentifier(mDocumentId); final MtpObjectInfo placeholderObjectInfo = manager.getObjectInfo(identifier.mDeviceId, identifier.mObjectHandle); // Delete the target object info if it already exists (as a placeholder). manager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); // Create the target object info with a correct file size and upload the file. final long size = Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_END); final MtpObjectInfo targetObjectInfo = new MtpObjectInfo.Builder(placeholderObjectInfo) .setCompressedSize(size) .build(); Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_SET); final int newObjectHandle = manager.createDocument( identifier.mDeviceId, targetObjectInfo, mCacheFd); final MtpObjectInfo newObjectInfo = manager.getObjectInfo( identifier.mDeviceId, newObjectHandle); final Identifier parentIdentifier = database.getParentIdentifier(identifier.mDocumentId); database.updateObject( identifier.mDocumentId, identifier.mDeviceId, parentIdentifier.mDocumentId, operationsSupported, newObjectInfo, size); mDirty = false; } @Override public void close() throws IOException { mCacheFd.close(); } } Loading
packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java +5 −1 Original line number Diff line number Diff line Loading @@ -45,6 +45,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.system.ErrnoException; import android.system.Os; import android.text.format.DateUtils; import android.util.Log; import android.webkit.MimeTypeMap; Loading Loading @@ -451,7 +453,7 @@ class CopyJob extends Job { ParcelFileDescriptor srcFile = null; ParcelFileDescriptor dstFile = null; InputStream in = null; OutputStream out = null; ParcelFileDescriptor.AutoCloseOutputStream out = null; boolean success = false; try { Loading Loading @@ -502,6 +504,8 @@ class CopyJob extends Job { makeCopyProgress(len); } // Need to invoke IoUtils.close explicitly to avoid from ignoring errors at flush. IoUtils.close(dstFile.getFileDescriptor()); srcFile.checkError(); } catch (IOException e) { throw new ResourceException( Loading
packages/MtpDocumentsProvider/jni/com_android_mtp_AppFuse.cpp +41 −17 Original line number Diff line number Diff line Loading @@ -52,6 +52,8 @@ static jclass app_fuse_class; static jmethodID app_fuse_get_file_size; static jmethodID app_fuse_read_object_bytes; static jmethodID app_fuse_write_object_bytes; static jmethodID app_fuse_flush_file_handle; static jmethodID app_fuse_close_file_handle; static jfieldID app_fuse_buffer; // NOTE: Loading Loading @@ -307,7 +309,8 @@ private: const uint32_t size = in->size; const void* const buffer = reinterpret_cast<const uint8_t*>(in) + sizeof(fuse_write_in); uint32_t written_size; const int result = write_object_bytes(it->second, offset, size, buffer, &written_size); const int result = write_object_bytes( in->fh, it->second, offset, size, buffer, &written_size); if (result < 0) { return result; } Loading @@ -320,13 +323,13 @@ private: const fuse_release_in* in, FuseResponse<void>* /* out */) { handles_.erase(in->fh); return 0; return env_->CallIntMethod(self_, app_fuse_close_file_handle, file_handle_to_jlong(in->fh)); } int handle_fuse_flush(const fuse_in_header& /* header */, const void* /* in */, const fuse_flush_in* in, FuseResponse<void>* /* out */) { return 0; return env_->CallIntMethod(self_, app_fuse_flush_file_handle, file_handle_to_jlong(in->fh)); } template <typename T, typename S> Loading Loading @@ -382,8 +385,10 @@ private: return read_size; } int write_object_bytes(int inode, uint64_t offset, uint32_t size, const void* buffer, uint32_t* written_size) { int write_object_bytes(uint64_t handle, int inode, uint64_t offset, uint32_t size, const void* buffer, uint32_t* written_size) { static_assert(sizeof(uint64_t) <= sizeof(jlong), "jlong must be able to express any uint64_t values"); ScopedLocalRef<jbyteArray> array( env_, static_cast<jbyteArray>(env_->GetObjectField(self_, app_fuse_buffer))); Loading @@ -394,15 +399,28 @@ private: } memcpy(bytes.get(), buffer, size); } *written_size = env_->CallIntMethod( self_, app_fuse_write_object_bytes, inode, offset, size, array.get()); if (env_->ExceptionCheck()) { env_->ExceptionClear(); return -EIO; const int result = env_->CallIntMethod( self_, app_fuse_write_object_bytes, file_handle_to_jlong(handle), inode, offset, size, array.get()); if (result < 0) { return result; } *written_size = result; return 0; } static jlong file_handle_to_jlong(uint64_t handle) { static_assert( sizeof(uint64_t) <= sizeof(jlong), "jlong must be able to express any uint64_t values"); return static_cast<jlong>(handle); } static void fuse_reply(int fd, int unique, int reply_code, void* reply_data, size_t reply_size) { // Don't send any data for error case. Loading Loading @@ -511,15 +529,21 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { return -1; } app_fuse_buffer = env->GetFieldID(app_fuse_class, "mBuffer", "[B"); if (app_fuse_buffer == nullptr) { ALOGE("Can't find mBuffer"); app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(JIJI[B)I"); if (app_fuse_write_object_bytes == nullptr) { ALOGE("Can't find writeObjectBytes"); return -1; } app_fuse_write_object_bytes = env->GetMethodID(app_fuse_class, "writeObjectBytes", "(IJI[B)I"); if (app_fuse_write_object_bytes == nullptr) { ALOGE("Can't find getWriteObjectBytes"); app_fuse_flush_file_handle = env->GetMethodID(app_fuse_class, "flushFileHandle", "(J)I"); if (app_fuse_flush_file_handle == nullptr) { ALOGE("Can't find flushFileHandle"); return -1; } app_fuse_close_file_handle = env->GetMethodID(app_fuse_class, "closeFileHandle", "(J)I"); if (app_fuse_close_file_handle == nullptr) { ALOGE("Can't find closeFileHandle"); return -1; } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/AppFuse.java +74 −13 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.WorkerThread; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.OsConstants; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; Loading @@ -34,6 +35,8 @@ public class AppFuse { System.loadLibrary("appfuse_jni"); } private static final boolean DEBUG = false; /** * Max read amount specified at the FUSE kernel implementation. * The value is copied from sdcard.c. Loading Loading @@ -94,7 +97,8 @@ public class AppFuse { public ParcelFileDescriptor openFile(int i, int mode) throws FileNotFoundException { Preconditions.checkArgument( mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == ParcelFileDescriptor.MODE_WRITE_ONLY); mode == (ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_TRUNCATE)); return ParcelFileDescriptor.open(new File( getMountPoint(), Integer.toString(i)), Loading Loading @@ -127,6 +131,7 @@ public class AppFuse { /** * Handles writing bytes for the give inode. * @param fileHandle * @param inode * @param offset Offset for file bytes. * @param size Size for file bytes. Loading @@ -134,7 +139,23 @@ public class AppFuse { * @return Number of read bytes. Must not be negative. * @throws IOException */ int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException; int writeObjectBytes(long fileHandle, int inode, long offset, int size, byte[] bytes) throws IOException, ErrnoException; /** * Flushes bytes for file handle. * @param fileHandle * @throws IOException * @throws ErrnoException */ void flushFileHandle(long fileHandle) throws IOException, ErrnoException; /** * Closes file handle. * @param fileHandle * @throws IOException */ void closeFileHandle(long fileHandle) throws IOException, ErrnoException; } @UsedByNative("com_android_mtp_AppFuse.cpp") Loading @@ -142,10 +163,8 @@ public class AppFuse { private long getFileSize(int inode) { try { return mCallback.getFileSize(inode); } catch (FileNotFoundException e) { return -OsConstants.ENOENT; } catch (UnsupportedOperationException e) { return -OsConstants.ENOTSUP; } catch (Exception error) { return -getErrnoFromException(error); } } Loading @@ -159,20 +178,62 @@ public class AppFuse { // It's OK to share the same mBuffer among requests because the requests are processed // by AppFuseMessageThread sequentially. return mCallback.readObjectBytes(inode, offset, size, mBuffer); } catch (IOException e) { return -OsConstants.EIO; } catch (UnsupportedOperationException e) { return -OsConstants.ENOTSUP; } catch (Exception error) { return -getErrnoFromException(error); } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private /* unsgined */ int writeObjectBytes(int inode, private /* unsgined */ int writeObjectBytes(long fileHandler, int inode, /* unsigned */ long offset, /* unsigned */ int size, byte[] bytes) throws IOException { return mCallback.writeObjectBytes(inode, offset, size, bytes); byte[] bytes) { try { return mCallback.writeObjectBytes(fileHandler, inode, offset, size, bytes); } catch (Exception error) { return -getErrnoFromException(error); } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private int flushFileHandle(long fileHandle) { try { mCallback.flushFileHandle(fileHandle); return 0; } catch (Exception error) { return -getErrnoFromException(error); } } @UsedByNative("com_android_mtp_AppFuse.cpp") @WorkerThread private int closeFileHandle(long fileHandle) { try { mCallback.closeFileHandle(fileHandle); return 0; } catch (Exception error) { return -getErrnoFromException(error); } } private static int getErrnoFromException(Exception error) { if (DEBUG) { Log.e(MtpDocumentsProvider.TAG, "AppFuse callbacks", error); } if (error instanceof FileNotFoundException) { return OsConstants.ENOENT; } else if (error instanceof IOException) { return OsConstants.EIO; } else if (error instanceof UnsupportedOperationException) { return OsConstants.ENOTSUP; } else if (error instanceof IllegalArgumentException) { return OsConstants.EINVAL; } else { return OsConstants.EIO; } } private native boolean native_start_app_fuse_loop(int fd); Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +84 −39 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.mtp; import android.content.ContentResolver; import android.content.Context; import android.content.UriPermission; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; Loading @@ -38,11 +39,16 @@ import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.Settings; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; Loading Loading @@ -82,6 +88,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private MtpDatabase mDatabase; private AppFuse mAppFuse; private ServiceIntentSender mIntentSender; private Context mContext; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -93,6 +100,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { @Override public boolean onCreate() { sSingleton = this; mContext = getContext(); mResources = getContext().getResources(); mMtpManager = new MtpManager(getContext()); mResolver = getContext().getContentResolver(); Loading Loading @@ -137,12 +145,14 @@ public class MtpDocumentsProvider extends DocumentsProvider { @VisibleForTesting boolean onCreateForTesting( Context context, Resources resources, MtpManager mtpManager, ContentResolver resolver, MtpDatabase database, StorageManager storageManager, ServiceIntentSender intentSender) { mContext = context; mResources = resources; mMtpManager = mtpManager; mResolver = resolver; Loading Loading @@ -232,43 +242,43 @@ public class MtpDocumentsProvider extends DocumentsProvider { try { openDevice(identifier.mDeviceId); final MtpDeviceRecord device = getDeviceToolkit(identifier.mDeviceId).mDeviceRecord; switch (mode) { case "r": // Turn off MODE_CREATE because openDocument does not allow to create new files. final int modeFlag = ParcelFileDescriptor.parseMode(mode) & ~ParcelFileDescriptor.MODE_CREATE; if ((modeFlag & ParcelFileDescriptor.MODE_READ_ONLY) != 0) { long fileSize; try { fileSize = getFileSize(documentId); } catch (UnsupportedOperationException exception) { fileSize = -1; } // MTP getPartialObject operation does not support files that are larger than // 4GB. Fallback to non-seekable file descriptor. if (MtpDeviceRecord.isPartialReadSupported( device.operationsSupported, fileSize)) { return mAppFuse.openFile( Integer.parseInt(documentId), ParcelFileDescriptor.MODE_READ_ONLY); return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); } else { // If getPartialObject{|64} are not supported for the device, returns // non-seekable pipe FD instead. return getPipeManager(identifier).readDocument(mMtpManager, identifier); } case "w": } else if ((modeFlag & ParcelFileDescriptor.MODE_WRITE_ONLY) != 0) { // TODO: Clear the parent document loader task (if exists) and call notify // when writing is completed. if (MtpDeviceRecord.isWritingSupported(device.operationsSupported)) { return getPipeManager(identifier).writeDocument( getContext(), mMtpManager, identifier, device.operationsSupported); return mAppFuse.openFile(Integer.parseInt(documentId), modeFlag); } else { throw new UnsupportedOperationException( "The device does not support writing operation."); } case "rw": } else { // TODO: Add support for "rw" mode. throw new UnsupportedOperationException( "The provider does not support 'rw' mode."); default: throw new IllegalArgumentException("Unknown mode for openDocument: " + mode); throw new UnsupportedOperationException("The provider does not support 'rw' mode."); } } catch (FileNotFoundException | RuntimeException error) { Log.e(MtpDocumentsProvider.TAG, "openDocument", error); throw error; } catch (IOException error) { Log.e(MtpDocumentsProvider.TAG, "openDocument", error); throw new FileNotFoundException(error.getMessage()); throw new IllegalStateException(error); } } Loading Loading @@ -595,6 +605,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { } private class AppFuseCallback implements AppFuse.Callback { private final Map<Long, MtpFileWriter> mWriters = new HashMap<>(); @Override public long getFileSize(int inode) throws FileNotFoundException { return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); } @Override public long readObjectBytes( int inode, long offset, long size, byte[] buffer) throws IOException { Loading @@ -617,15 +634,43 @@ public class MtpDocumentsProvider extends DocumentsProvider { } @Override public long getFileSize(int inode) throws FileNotFoundException { return MtpDocumentsProvider.this.getFileSize(String.valueOf(inode)); public int writeObjectBytes( long fileHandle, int inode, long offset, int size, byte[] bytes) throws IOException, ErrnoException { final MtpFileWriter writer; if (mWriters.containsKey(fileHandle)) { writer = mWriters.get(fileHandle); } else { writer = new MtpFileWriter(mContext, String.valueOf(inode)); mWriters.put(fileHandle, writer); } return writer.write(offset, size, bytes); } @Override public int writeObjectBytes(int inode, long offset, int size, byte[] bytes) throws IOException { // TODO: Implement it. throw new IOException(); public void flushFileHandle(long fileHandle) throws IOException, ErrnoException { final MtpFileWriter writer = mWriters.get(fileHandle); if (writer == null) { // File handle for reading. return; } final MtpDeviceRecord device = getDeviceToolkit( mDatabase.createIdentifier(writer.getDocumentId()).mDeviceId).mDeviceRecord; writer.flush(mMtpManager, mDatabase, device.operationsSupported); } @Override public void closeFileHandle(long fileHandle) throws IOException, ErrnoException { final MtpFileWriter writer = mWriters.get(fileHandle); if (writer == null) { // File handle for reading. return; } try { writer.close(); } finally { mWriters.remove(fileHandle); } } } }
packages/MtpDocumentsProvider/src/com/android/mtp/MtpFileWriter.java 0 → 100644 +108 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mtp; import android.content.Context; import android.mtp.MtpObjectInfo; import android.os.ParcelFileDescriptor; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import com.android.internal.util.Preconditions; import java.io.File; import java.io.IOException; class MtpFileWriter implements AutoCloseable { final ParcelFileDescriptor mCacheFd; final String mDocumentId; boolean mDirty; MtpFileWriter(Context context, String documentId) throws IOException { mDocumentId = documentId; mDirty = false; final File tempFile = File.createTempFile("mtp", "tmp", context.getCacheDir()); mCacheFd = ParcelFileDescriptor.open( tempFile, ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_TRUNCATE | ParcelFileDescriptor.MODE_CREATE); tempFile.delete(); } String getDocumentId() { return mDocumentId; } int write(long offset, int size, byte[] bytes) throws IOException, ErrnoException { Preconditions.checkArgumentNonnegative(offset, "offset"); Preconditions.checkArgumentNonnegative(size, "size"); Preconditions.checkArgument(size <= bytes.length); if (size == 0) { return 0; } mDirty = true; Os.lseek(mCacheFd.getFileDescriptor(), offset, OsConstants.SEEK_SET); return Os.write(mCacheFd.getFileDescriptor(), bytes, 0, size); } void flush(MtpManager manager, MtpDatabase database, int[] operationsSupported) throws IOException, ErrnoException { // Skip unnecessary flush. if (!mDirty) { return; } // Get the placeholder object info. final Identifier identifier = database.createIdentifier(mDocumentId); final MtpObjectInfo placeholderObjectInfo = manager.getObjectInfo(identifier.mDeviceId, identifier.mObjectHandle); // Delete the target object info if it already exists (as a placeholder). manager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); // Create the target object info with a correct file size and upload the file. final long size = Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_END); final MtpObjectInfo targetObjectInfo = new MtpObjectInfo.Builder(placeholderObjectInfo) .setCompressedSize(size) .build(); Os.lseek(mCacheFd.getFileDescriptor(), 0, OsConstants.SEEK_SET); final int newObjectHandle = manager.createDocument( identifier.mDeviceId, targetObjectInfo, mCacheFd); final MtpObjectInfo newObjectInfo = manager.getObjectInfo( identifier.mDeviceId, newObjectHandle); final Identifier parentIdentifier = database.getParentIdentifier(identifier.mDocumentId); database.updateObject( identifier.mDocumentId, identifier.mDeviceId, parentIdentifier.mDocumentId, operationsSupported, newObjectInfo, size); mDirty = false; } @Override public void close() throws IOException { mCacheFd.close(); } }