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