1 // Copyright 2016 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package android.net.http.apihelpers; 6 7 import android.net.http.UploadDataProvider; 8 import android.net.http.UploadDataSink; 9 import android.os.ParcelFileDescriptor; 10 11 import java.io.File; 12 import java.io.FileInputStream; 13 import java.io.IOException; 14 import java.nio.ByteBuffer; 15 import java.nio.channels.FileChannel; 16 17 /** 18 * Provides implementations of {@link UploadDataProvider} for common use cases. 19 */ 20 public final class UploadDataProviders { 21 /** 22 * Uploads an entire file. 23 * 24 * @param file The file to upload 25 * @return A new UploadDataProvider for the given file 26 */ create(final File file)27 public static UploadDataProvider create(final File file) { 28 return new FileUploadProvider(new FileChannelProvider() { 29 @Override 30 public FileChannel getChannel() throws IOException { 31 return new FileInputStream(file).getChannel(); 32 } 33 }); 34 } 35 36 /** 37 * Uploads an entire file, closing the descriptor when it is no longer needed. 38 * 39 * @param fd The file descriptor to upload 40 * @return A new UploadDataProvider for the given file descriptor 41 * @throws IllegalArgumentException if {@code fd} is not a file. 42 */ 43 public static UploadDataProvider create(final ParcelFileDescriptor fd) { 44 return new FileUploadProvider(new FileChannelProvider() { 45 @Override 46 public FileChannel getChannel() throws IOException { 47 if (fd.getStatSize() != -1) { 48 return new ParcelFileDescriptor.AutoCloseInputStream(fd).getChannel(); 49 } else { 50 fd.close(); 51 throw new IllegalArgumentException("Not a file: " + fd); 52 } 53 } 54 }); 55 } 56 57 /** 58 * Uploads a ByteBuffer, from the current {@code buffer.position()} to {@code buffer.limit()} 59 * 60 * @param buffer The data to upload 61 * @return A new UploadDataProvider for the given buffer 62 */ 63 public static UploadDataProvider create(ByteBuffer buffer) { 64 return new ByteBufferUploadProvider(buffer.slice()); 65 } 66 67 /** 68 * Uploads {@code length} bytes from {@code data}, starting from {@code offset} 69 * 70 * @param data Array containing data to upload 71 * @param offset Offset within data to start with 72 * @param length Number of bytes to upload 73 * @return A new UploadDataProvider for the given data 74 */ 75 public static UploadDataProvider create(byte[] data, int offset, int length) { 76 return new ByteBufferUploadProvider(ByteBuffer.wrap(data, offset, length).slice()); 77 } 78 79 /** 80 * Uploads the contents of {@code data} 81 * 82 * @param data Array containing data to upload 83 * @return A new UploadDataProvider for the given data 84 */ 85 public static UploadDataProvider create(byte[] data) { 86 return create(data, 0, data.length); 87 } 88 89 private interface FileChannelProvider { 90 FileChannel getChannel() throws IOException; 91 } 92 93 private static final class FileUploadProvider extends UploadDataProvider { 94 private volatile FileChannel mChannel; 95 private final FileChannelProvider mProvider; 96 /** 97 * Guards initialization of {@code mChannel} 98 */ 99 private final Object mLock = new Object(); 100 101 private FileUploadProvider(FileChannelProvider provider) { 102 this.mProvider = provider; 103 } 104 105 @Override 106 public long getLength() throws IOException { 107 return getChannel().size(); 108 } 109 110 @Override 111 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) throws IOException { 112 checkHasBytesRemaining(byteBuffer); 113 FileChannel channel = getChannel(); 114 int bytesRead = 0; 115 while (bytesRead == 0) { 116 int read = channel.read(byteBuffer); 117 if (read == -1) { 118 break; 119 } else { 120 bytesRead += read; 121 } 122 } 123 uploadDataSink.onReadSucceeded(false); 124 } 125 126 @Override 127 public void rewind(UploadDataSink uploadDataSink) throws IOException { 128 getChannel().position(0); 129 uploadDataSink.onRewindSucceeded(); 130 } 131 132 /** 133 * Lazily initializes the channel so that a blocking operation isn't performed on a 134 * non-executor thread. 135 */ 136 private FileChannel getChannel() throws IOException { 137 if (mChannel == null) { 138 synchronized (mLock) { 139 if (mChannel == null) { 140 mChannel = mProvider.getChannel(); 141 } 142 } 143 } 144 return mChannel; 145 } 146 147 @Override 148 public void close() throws IOException { 149 FileChannel channel = mChannel; 150 if (channel != null) { 151 channel.close(); 152 } 153 } 154 } 155 156 private static final class ByteBufferUploadProvider extends UploadDataProvider { 157 private final ByteBuffer mUploadBuffer; 158 159 private ByteBufferUploadProvider(ByteBuffer uploadBuffer) { 160 this.mUploadBuffer = uploadBuffer; 161 } 162 163 @Override 164 public long getLength() { 165 return mUploadBuffer.limit(); 166 } 167 168 @Override 169 public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer) { 170 checkHasBytesRemaining(byteBuffer); 171 if (byteBuffer.remaining() >= mUploadBuffer.remaining()) { 172 byteBuffer.put(mUploadBuffer); 173 } else { 174 int oldLimit = mUploadBuffer.limit(); 175 mUploadBuffer.limit(mUploadBuffer.position() + byteBuffer.remaining()); 176 byteBuffer.put(mUploadBuffer); 177 mUploadBuffer.limit(oldLimit); 178 } 179 uploadDataSink.onReadSucceeded(false); 180 } 181 182 @Override 183 public void rewind(UploadDataSink uploadDataSink) { 184 mUploadBuffer.position(0); 185 uploadDataSink.onRewindSucceeded(); 186 } 187 } 188 189 private static ByteBuffer checkHasBytesRemaining(ByteBuffer buffer) { 190 if (!buffer.hasRemaining()) { 191 throw new IllegalStateException("buffer with no bytes remaining"); 192 } 193 194 return buffer; 195 } 196 197 // Prevent instantiation 198 private UploadDataProviders() {} 199 } 200