1 /* 2 * Copyright (C) 2017 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 package com.google.android.exoplayer2.testutil; 17 18 import android.net.Uri; 19 import androidx.annotation.Nullable; 20 import com.google.android.exoplayer2.C; 21 import com.google.android.exoplayer2.upstream.DataSpec; 22 import com.google.android.exoplayer2.util.Assertions; 23 import java.io.ByteArrayOutputStream; 24 import java.io.IOException; 25 import java.util.ArrayList; 26 import java.util.HashMap; 27 import java.util.List; 28 29 /** 30 * Collection of {@link FakeData} to be served by a {@link FakeDataSource}. 31 * 32 * <p>Multiple fake data can be defined by {@link FakeDataSet#setData(Uri, byte[])} and {@link 33 * FakeDataSet#newData(Uri)} methods. It's also possible to define a default data by {@link 34 * FakeDataSet#newDefaultData()}. 35 * 36 * <p>{@link FakeDataSet#newData(Uri)} and {@link FakeDataSet#newDefaultData()} return a {@link 37 * FakeData} instance which can be used to define specific results during 38 * {@link FakeDataSource#read(byte[], int, int)} calls. 39 * 40 * <p>The data that will be read from the source can be constructed by calling {@link 41 * FakeData#appendReadData(byte[])} Calls to {@link FakeDataSource#read(byte[], int, int)} will not 42 * span the boundaries between arrays passed to successive calls, and hence the boundaries control 43 * the positions at which read requests to the source may only be partially satisfied. 44 * 45 * <p>Errors can be inserted by calling {@link FakeData#appendReadError(IOException)}. An inserted 46 * error will be thrown from the first call to {@link FakeDataSource#read(byte[], int, int)} that 47 * attempts to read from the corresponding position, and from all subsequent calls to 48 * {@link FakeDataSource#read(byte[], int, int)} until the source is closed. If the source is closed 49 * and re-opened having encountered an error, that error will not be thrown again. 50 * 51 * <p>Actions are inserted by calling {@link FakeData#appendReadAction(Runnable)}. An actions is 52 * triggered when the reading reaches action's position. This can be used to make sure the code is 53 * in a certain state while testing. 54 * 55 * <p>Example usage: 56 * 57 * <pre> 58 * // Create a FakeDataSource then add default data and two FakeData 59 * // "test_file" throws an IOException when tried to be read until closed and reopened. 60 * FakeDataSource fakeDataSource = new FakeDataSource(); 61 * fakeDataSource.getDataSet() 62 * .newDefaultData() 63 * .appendReadData(defaultData) 64 * .endData() 65 * .setData("http://1", data1) 66 * .newData("test_file") 67 * .appendReadError(new IOException()) 68 * .appendReadData(data2) 69 * .endData(); 70 * </pre> 71 */ 72 public class FakeDataSet { 73 74 /** Container of fake data to be served by a {@link FakeDataSource}. */ 75 public static final class FakeData { 76 77 /** 78 * A segment of {@link FakeData}. May consist of an action or exception instead of actual data. 79 */ 80 public static final class Segment { 81 82 @Nullable public final IOException exception; 83 @Nullable public final byte[] data; 84 public final int length; 85 public final long byteOffset; 86 @Nullable public final Runnable action; 87 88 public boolean exceptionThrown; 89 public boolean exceptionCleared; 90 public int bytesRead; 91 Segment(byte[] data, @Nullable Segment previousSegment)92 private Segment(byte[] data, @Nullable Segment previousSegment) { 93 this(data, data.length, null, null, previousSegment); 94 } 95 Segment(int length, @Nullable Segment previousSegment)96 private Segment(int length, @Nullable Segment previousSegment) { 97 this(null, length, null, null, previousSegment); 98 } 99 Segment(IOException exception, @Nullable Segment previousSegment)100 private Segment(IOException exception, @Nullable Segment previousSegment) { 101 this(null, 0, exception, null, previousSegment); 102 } 103 Segment(Runnable action, @Nullable Segment previousSegment)104 private Segment(Runnable action, @Nullable Segment previousSegment) { 105 this(null, 0, null, action, previousSegment); 106 } 107 Segment( @ullable byte[] data, int length, @Nullable IOException exception, @Nullable Runnable action, @Nullable Segment previousSegment)108 private Segment( 109 @Nullable byte[] data, 110 int length, 111 @Nullable IOException exception, 112 @Nullable Runnable action, 113 @Nullable Segment previousSegment) { 114 this.exception = exception; 115 this.action = action; 116 this.data = data; 117 this.length = length; 118 this.byteOffset = previousSegment == null ? 0 119 : previousSegment.byteOffset + previousSegment.length; 120 } 121 isErrorSegment()122 public boolean isErrorSegment() { 123 return exception != null; 124 } 125 isActionSegment()126 public boolean isActionSegment() { 127 return action != null; 128 } 129 130 } 131 132 private final FakeDataSet dataSet; 133 /** Uri of the data or null if this is the default FakeData. */ 134 @Nullable public final Uri uri; 135 136 private final ArrayList<Segment> segments; 137 private boolean simulateUnknownLength; 138 FakeData(FakeDataSet dataSet, @Nullable Uri uri)139 private FakeData(FakeDataSet dataSet, @Nullable Uri uri) { 140 this.dataSet = dataSet; 141 this.uri = uri; 142 this.segments = new ArrayList<>(); 143 } 144 145 /** Returns the {@link FakeDataSet} this FakeData belongs to. */ endData()146 public FakeDataSet endData() { 147 return dataSet; 148 } 149 150 /** 151 * When set, {@link FakeDataSource#open(DataSpec)} will behave as though the source is unable to 152 * determine the length of the underlying data. Hence the return value will always be equal to 153 * the {@link DataSpec#length} of the argument, including the case where the length is equal to 154 * {@link C#LENGTH_UNSET}. 155 */ setSimulateUnknownLength(boolean simulateUnknownLength)156 public FakeData setSimulateUnknownLength(boolean simulateUnknownLength) { 157 this.simulateUnknownLength = simulateUnknownLength; 158 return this; 159 } 160 161 /** 162 * Appends to the underlying data. 163 */ appendReadData(byte[] data)164 public FakeData appendReadData(byte[] data) { 165 Assertions.checkState(data.length > 0); 166 segments.add(new Segment(data, getLastSegment())); 167 return this; 168 } 169 170 /** 171 * Appends a data segment of the specified length. No actual data is available and the 172 * {@link FakeDataSource} will perform no copy operations when this data is read. 173 */ appendReadData(int length)174 public FakeData appendReadData(int length) { 175 Assertions.checkState(length > 0); 176 segments.add(new Segment(length, getLastSegment())); 177 return this; 178 } 179 180 /** 181 * Appends an error in the underlying data. 182 */ appendReadError(IOException exception)183 public FakeData appendReadError(IOException exception) { 184 segments.add(new Segment(exception, getLastSegment())); 185 return this; 186 } 187 188 /** 189 * Appends an action. 190 */ appendReadAction(Runnable action)191 public FakeData appendReadAction(Runnable action) { 192 segments.add(new Segment(action, getLastSegment())); 193 return this; 194 } 195 196 /** Returns the whole data added by {@link #appendReadData(byte[])}. */ getData()197 public byte[] getData() { 198 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 199 for (Segment segment : segments) { 200 if (segment.data != null) { 201 try { 202 outputStream.write(segment.data); 203 } catch (IOException e) { 204 throw new IllegalStateException(e); 205 } 206 } 207 } 208 return outputStream.toByteArray(); 209 } 210 211 /** Returns the list of {@link Segment}s. */ getSegments()212 public List<Segment> getSegments() { 213 return segments; 214 } 215 216 /** Retuns whether unknown length is simulated */ isSimulatingUnknownLength()217 public boolean isSimulatingUnknownLength() { 218 return simulateUnknownLength; 219 } 220 221 @Nullable getLastSegment()222 private Segment getLastSegment() { 223 int count = segments.size(); 224 return count > 0 ? segments.get(count - 1) : null; 225 } 226 227 } 228 229 private final HashMap<Uri, FakeData> dataMap; 230 @Nullable private FakeData defaultData; 231 FakeDataSet()232 public FakeDataSet() { 233 dataMap = new HashMap<>(); 234 } 235 236 /** Sets the default data, overwrites if there is one already. */ newDefaultData()237 public FakeData newDefaultData() { 238 defaultData = new FakeData(this, null); 239 return defaultData; 240 } 241 242 /** Sets random data with the given {@code length} for the given {@code uri}. */ setRandomData(String uri, int length)243 public FakeDataSet setRandomData(String uri, int length) { 244 return setRandomData(Uri.parse(uri), length); 245 } 246 247 /** Sets random data with the given {@code length} for the given {@code uri}. */ setRandomData(Uri uri, int length)248 public FakeDataSet setRandomData(Uri uri, int length) { 249 return setData(uri, TestUtil.buildTestData(length)); 250 } 251 252 /** Sets the given {@code data} for the given {@code uri}. */ setData(String uri, byte[] data)253 public FakeDataSet setData(String uri, byte[] data) { 254 return setData(Uri.parse(uri), data); 255 } 256 257 /** Sets the given {@code data} for the given {@code uri}. */ setData(Uri uri, byte[] data)258 public FakeDataSet setData(Uri uri, byte[] data) { 259 return newData(uri).appendReadData(data).endData(); 260 } 261 262 /** Returns a new {@link FakeData} with the given {@code uri}. */ newData(String uri)263 public FakeData newData(String uri) { 264 return newData(Uri.parse(uri)); 265 } 266 267 /** Returns a new {@link FakeData} with the given {@code uri}. */ newData(Uri uri)268 public FakeData newData(Uri uri) { 269 FakeData data = new FakeData(this, uri); 270 dataMap.put(uri, data); 271 return data; 272 } 273 274 /** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */ 275 @Nullable getData(String uri)276 public FakeData getData(String uri) { 277 return getData(Uri.parse(uri)); 278 } 279 280 /** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */ 281 @Nullable getData(Uri uri)282 public FakeData getData(Uri uri) { 283 @Nullable FakeData data = dataMap.get(uri); 284 return data != null ? data : defaultData; 285 } 286 287 /** Returns a list of all data including {@code defaultData}. */ getAllData()288 public ArrayList<FakeData> getAllData() { 289 ArrayList<FakeData> fakeDatas = new ArrayList<>(dataMap.values()); 290 if (defaultData != null) { 291 fakeDatas.add(defaultData); 292 } 293 return fakeDatas; 294 } 295 296 } 297