1 /* 2 * Copyright 2022 Google LLC 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.libraries.mobiledatadownload.file.openers; 17 18 import com.google.android.libraries.mobiledatadownload.file.OpenContext; 19 import com.google.android.libraries.mobiledatadownload.file.Opener; 20 import com.google.android.libraries.mobiledatadownload.file.common.Sizable; 21 import com.google.android.libraries.mobiledatadownload.file.common.UnsupportedFileStorageOperation; 22 import com.google.common.io.ByteStreams; 23 import com.google.common.primitives.Ints; 24 import java.io.IOException; 25 import java.io.InputStream; 26 27 /** 28 * An opener that returns a byte[] for a Uri. Attempts to get the file size so it can perform a 29 * single allocation, but falls back to a dynamic allocation when the size is unknown. 30 * 31 * <p>Warning: Large memory allocations can OOM. We recommend evaluating reliability and performance 32 * on target platforms when allocating > 1M. 33 * 34 * <p>Usage: <code> 35 * byte[] bytes = storage.open(uri, ReadByteArrayOpener.create()); 36 * </code> 37 */ 38 public final class ReadByteArrayOpener implements Opener<byte[]> { 39 ReadByteArrayOpener()40 private ReadByteArrayOpener() {} 41 42 /** Creates a new opener instance to read a byte array. */ create()43 public static ReadByteArrayOpener create() { 44 return new ReadByteArrayOpener(); 45 } 46 47 @Override open(OpenContext openContext)48 public byte[] open(OpenContext openContext) throws IOException { 49 try (InputStream in = ReadStreamOpener.create().open(openContext)) { 50 Long size = null; 51 // Try to get the length from the Sizable interface. This can potentially work with 52 // monitors and transforms that do not change the file size, or do so in a way that 53 // supports efficient calculation of the logical size (eg file size - header size = logical 54 // size). 55 if (in instanceof Sizable) { 56 size = ((Sizable) in).size(); 57 } 58 59 // If Sizable failed and there are not transforms that could manipulate the file size, 60 // then try calling fileSize(). 61 if (size == null && !openContext.hasTransforms()) { 62 try { 63 long fileSize = openContext.storage().fileSize(openContext.originalUri()); 64 if (fileSize > 0) { 65 // Treat 0 as "unknown file size". 66 size = fileSize; 67 } 68 } catch (UnsupportedFileStorageOperation ex) { 69 // Ignore. 70 } 71 } 72 73 if (size == null) { 74 // Bummer. Read stream of unknown length. Inefficient but always works. 75 return ByteStreams.toByteArray(in); 76 } 77 78 byte[] bytes = new byte[Ints.checkedCast(size)]; 79 ByteStreams.readFully(in, bytes); 80 return bytes; 81 } 82 } 83 } 84