package com.bumptech.glide.load.model;
import android.util.Base64;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.signature.ObjectKey;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* A simple model loader for loading data from a Data URL String.
*
* Data URIs use the "data" scheme.
*
* <p>See http://www.ietf.org/rfc/rfc2397.txt for a complete description of the 'data' URL scheme.
*
* <p>Briefly, a 'data' URL has the form: <pre>data:[mediatype][;base64],some_data</pre>
*
* @param <Data> The type of data that can be opened.
*/
public final class DataUrlLoader<Data> implements ModelLoader<String, Data> {
private static final String DATA_SCHEME_IMAGE = "data:image";
private static final String BASE64_TAG = ";base64";
private final DataDecoder<Data> dataDecoder;
public DataUrlLoader(DataDecoder<Data> dataDecoder) {
this.dataDecoder = dataDecoder;
}
@Override
public LoadData<Data> buildLoadData(String model, int width, int height, Options options) {
return new LoadData<>(new ObjectKey(model), new DataUriFetcher<Data>(model, dataDecoder));
}
@Override
public boolean handles(String url) {
return url.startsWith(DATA_SCHEME_IMAGE);
}
/**
* Allows decoding a specific type of data from a Data URL String.
*
* @param <Data> The type of data that can be opened.
*/
public interface DataDecoder<Data> {
Data decode(String uri) throws IllegalArgumentException;
void close(Data data) throws IOException;
Class<Data> getDataClass();
}
private static final class DataUriFetcher<Data> implements DataFetcher<Data> {
private final String dataUri;
private final DataDecoder<Data> reader;
private Data data;
public DataUriFetcher(String dataUri, DataDecoder<Data> reader) {
this.dataUri = dataUri;
this.reader = reader;
}
@Override
public void loadData(Priority priority, DataCallback<? super Data> callback) {
try {
data = reader.decode(dataUri);
callback.onDataReady(data);
} catch (IllegalArgumentException e) {
callback.onLoadFailed(e);
}
}
@Override
public void cleanup() {
try {
reader.close(data);
} catch (IOException e) {
// Ignored.
}
}
@Override
public void cancel() {
// Do nothing.
}
@Override
public Class<Data> getDataClass() {
return reader.getDataClass();
}
@Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}
/**
* Factory for loading {@link InputStream} from Data URL string.
*/
public static final class StreamFactory implements ModelLoaderFactory<String, InputStream> {
private final DataDecoder<InputStream> opener;
public StreamFactory() {
opener = new DataDecoder<InputStream>() {
@Override
public InputStream decode(String url) {
if (!url.startsWith(DATA_SCHEME_IMAGE)) {
throw new IllegalArgumentException("Not a valid image data URL.");
}
int commaIndex = url.indexOf(',');
if (commaIndex == -1) {
throw new IllegalArgumentException("Missing comma in data URL.");
}
String beforeComma = url.substring(0, commaIndex);
if (!beforeComma.endsWith(BASE64_TAG)) {
throw new IllegalArgumentException("Not a base64 image data URL.");
}
String afterComma = url.substring(commaIndex + 1);
byte[] bytes = Base64.decode(afterComma, Base64.DEFAULT);
return new ByteArrayInputStream(bytes);
}
@Override
public void close(InputStream inputStream) throws IOException {
inputStream.close();
}
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
};
}
@Override
public final ModelLoader<String, InputStream> build(MultiModelLoaderFactory multiFactory) {
return new DataUrlLoader<>(opener);
}
@Override
public final void teardown() {
// Do nothing.
}
}
}