1 /* 2 * Copyright (C) 2014 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.tv.settings.util; 18 19 import java.io.FilterInputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.util.ArrayList; 23 import java.util.List; 24 25 /** 26 * A replacement of BufferedInputStream (no multiple thread): <p> 27 * - use list of byte array (chunks) instead of keep growing a single byte array (more efficent) 28 * <br> 29 * - support overriding the markLimit passed in mark() call (The value that BitmapFactory 30 * uses 1024 is too small for detecting bitmap bounds and reset()) <br> 31 */ 32 public class CachedInputStream extends FilterInputStream { 33 34 private static final int CHUNK_SIZE = ByteArrayPool.CHUNK16K; 35 36 private ArrayList<byte[]> mBufs = new ArrayList<byte[]>(); 37 private int mPos = 0; // current read position inside the chunk buffers 38 private int mCount = 0; // total validate bytes in chunk buffers 39 private int mMarkPos = -1; // marked read position in chunk buffers 40 private int mOverrideMarkLimit; // to override readlimit of mark() call 41 private int mMarkLimit; // effective marklimit 42 private byte[] tmp = new byte[1]; // tmp buffer used in read() 43 44 public CachedInputStream(InputStream in) { 45 super(in); 46 } 47 48 @Override 49 public boolean markSupported() { 50 return true; 51 } 52 53 /** 54 * set the value that will override small readlimit passed in mark() call. 55 */ 56 public void setOverrideMarkLimit(int overrideMarkLimit) { 57 mOverrideMarkLimit = overrideMarkLimit; 58 } 59 60 public int getOverrideMarkLimit() { 61 return mOverrideMarkLimit; 62 } 63 64 @Override 65 public void mark(int readlimit) { 66 readlimit = readlimit < mOverrideMarkLimit ? mOverrideMarkLimit : readlimit; 67 if (mMarkPos >= 0) { 68 // for replacing existing mark(), discard anything before mPos 69 // and move mMarkPos to mPos 70 int chunks = mPos / CHUNK_SIZE; 71 if (chunks > 0) { 72 // trim the header buffers 73 int removedBytes = chunks * CHUNK_SIZE; 74 List<byte[]> subList = mBufs.subList(0, chunks); 75 releaseChunks(subList); 76 subList.clear(); 77 mPos = mPos - removedBytes; 78 mCount = mCount - removedBytes; 79 } 80 } 81 mMarkPos = mPos; 82 mMarkLimit = readlimit; 83 } 84 85 @Override 86 public void reset() throws IOException { 87 if (mMarkPos < 0) { 88 throw new IOException("mark has been invalidated"); 89 } 90 mPos = mMarkPos; 91 } 92 93 @Override 94 public int read() throws IOException { 95 // TODO, not efficient, but the function is not called by BitmapFactory 96 int r = read(tmp, 0, 1); 97 if (r <= 0) { 98 return -1; 99 } 100 return tmp[0] & 0xFF; 101 } 102 103 @Override 104 public void close() throws IOException { 105 if (in!=null) { 106 in.close(); 107 in = null; 108 } 109 releaseChunks(mBufs); 110 } 111 112 private static void releaseChunks(List<byte[]> bufs) { 113 ByteArrayPool.get16KBPool().releaseChunks(bufs); 114 } 115 116 private byte[] allocateChunk() { 117 return ByteArrayPool.get16KBPool().allocateChunk(); 118 } 119 120 private boolean invalidate() { 121 if (mCount - mMarkPos > mMarkLimit) { 122 mMarkPos = -1; 123 mCount = 0; 124 mPos = 0; 125 releaseChunks(mBufs); 126 mBufs.clear(); 127 return true; 128 } 129 return false; 130 } 131 132 @Override 133 public int read(byte[] buffer, int offset, int count) throws IOException { 134 if (in == null) { 135 throw streamClosed(); 136 } 137 if (mMarkPos == -1) { 138 int reads = in.read(buffer, offset, count); 139 return reads; 140 } 141 if (count == 0) { 142 return 0; 143 } 144 int copied = copyMarkedBuffer(buffer, offset, count); 145 count -= copied; 146 offset += copied; 147 int totalReads = copied; 148 while (count > 0) { 149 if (mPos == mBufs.size() * CHUNK_SIZE) { 150 mBufs.add(allocateChunk()); 151 } 152 int currentBuf = mPos / CHUNK_SIZE; 153 int indexInBuf = mPos - currentBuf * CHUNK_SIZE; 154 byte[] buf = mBufs.get(currentBuf); 155 int end = (currentBuf + 1) * CHUNK_SIZE; 156 int leftInBuffer = end - mPos; 157 int toRead = count > leftInBuffer ? leftInBuffer : count; 158 int reads = in.read(buf, indexInBuf, toRead); 159 if (reads > 0) { 160 System.arraycopy(buf, indexInBuf, buffer, offset, reads); 161 mPos += reads; 162 mCount += reads; 163 totalReads += reads; 164 offset += reads; 165 count -= reads; 166 if (invalidate()) { 167 reads = in.read(buffer, offset, count); 168 if (reads >0 ) { 169 totalReads += reads; 170 } 171 break; 172 } 173 } else { 174 break; 175 } 176 } 177 if (totalReads == 0) { 178 return -1; 179 } 180 return totalReads; 181 } 182 183 private int copyMarkedBuffer(byte[] buffer, int offset, int read) { 184 int totalRead = 0; 185 while (read > 0 && mPos < mCount) { 186 int currentBuf = mPos / CHUNK_SIZE; 187 int indexInBuf = mPos - currentBuf * CHUNK_SIZE; 188 byte[] buf = mBufs.get(currentBuf); 189 int end = (currentBuf + 1) * CHUNK_SIZE; 190 if (end > mCount) { 191 end = mCount; 192 } 193 int leftInBuffer = end - mPos; 194 int toRead = read > leftInBuffer ? leftInBuffer : read; 195 System.arraycopy(buf, indexInBuf, buffer, offset, toRead); 196 offset += toRead; 197 read -= toRead; 198 totalRead += toRead; 199 mPos += toRead; 200 } 201 return totalRead; 202 } 203 204 @Override 205 public int available() throws IOException { 206 if (in == null) { 207 throw streamClosed(); 208 } 209 return mCount - mPos + in.available(); 210 } 211 212 @Override 213 public long skip(long byteCount) throws IOException { 214 if (in == null) { 215 throw streamClosed(); 216 } 217 if (mMarkPos <0) { 218 return in.skip(byteCount); 219 } 220 long totalSkip = 0; 221 totalSkip = mCount - mPos; 222 if (totalSkip > byteCount) { 223 totalSkip = byteCount; 224 } 225 mPos += totalSkip; 226 byteCount -= totalSkip; 227 while (byteCount > 0) { 228 if (mPos == mBufs.size() * CHUNK_SIZE) { 229 mBufs.add(allocateChunk()); 230 } 231 int currentBuf = mPos / CHUNK_SIZE; 232 int indexInBuf = mPos - currentBuf * CHUNK_SIZE; 233 byte[] buf = mBufs.get(currentBuf); 234 int end = (currentBuf + 1) * CHUNK_SIZE; 235 int leftInBuffer = end - mPos; 236 int toRead = (int) (byteCount > leftInBuffer ? leftInBuffer : byteCount); 237 int reads = in.read(buf, indexInBuf, toRead); 238 if (reads > 0) { 239 mPos += reads; 240 mCount += reads; 241 byteCount -= reads; 242 totalSkip += reads; 243 if (invalidate()) { 244 if (byteCount > 0) { 245 totalSkip += in.skip(byteCount); 246 } 247 break; 248 } 249 } else { 250 break; 251 } 252 } 253 return totalSkip; 254 } 255 256 private static IOException streamClosed() { 257 return new IOException("stream closed"); 258 } 259 260 } 261