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.backends; 17 18 import android.content.Context; 19 import android.content.res.AssetFileDescriptor; 20 import android.content.res.AssetManager; 21 import android.net.Uri; 22 import android.util.Pair; 23 import com.google.android.libraries.mobiledatadownload.file.common.UnsupportedFileStorageOperation; 24 import com.google.android.libraries.mobiledatadownload.file.spi.Backend; 25 import com.google.common.base.Preconditions; 26 import java.io.Closeable; 27 import java.io.FileNotFoundException; 28 import java.io.IOException; 29 import java.io.InputStream; 30 31 /** Backend for handling Android's APK embedded assets. */ 32 public final class AssetFileBackend implements Backend { 33 34 private final AssetManager assetManager; 35 builder(Context context)36 public static Builder builder(Context context) { 37 return new Builder(context); 38 } 39 40 /** Builds AssetFileBackend. */ 41 public static final class Builder { 42 // Required parameters 43 private final Context context; 44 Builder(Context context)45 private Builder(Context context) { 46 Preconditions.checkArgument(context != null, "Context cannot be null"); 47 this.context = context.getApplicationContext(); 48 } 49 build()50 public AssetFileBackend build() { 51 return new AssetFileBackend(this); 52 } 53 } 54 AssetFileBackend(Builder builder)55 private AssetFileBackend(Builder builder) { 56 assetManager = builder.context.getAssets(); 57 } 58 59 @Override name()60 public String name() { 61 return "asset"; 62 } 63 64 @Override openForRead(Uri uri)65 public InputStream openForRead(Uri uri) throws IOException { 66 return assetManager.open(assetPath(uri)); 67 } 68 69 @Override openForNativeRead(Uri uri)70 public Pair<Uri, Closeable> openForNativeRead(Uri uri) throws UnsupportedFileStorageOperation { 71 throw new UnsupportedFileStorageOperation("Native read not supported (b/210546473)"); 72 } 73 74 @Override exists(Uri uri)75 public boolean exists(Uri uri) throws IOException { 76 try (InputStream in = openForRead(uri)) { 77 return true; 78 } catch (FileNotFoundException e) { 79 return false; 80 } 81 } 82 83 @Override fileSize(Uri uri)84 public long fileSize(Uri uri) throws IOException { 85 try (AssetFileDescriptor descriptor = assetManager.openFd(assetPath(uri))) { 86 return descriptor.getLength(); 87 } 88 } 89 90 @Override isDirectory(Uri uri)91 public boolean isDirectory(Uri uri) { 92 return false; 93 } 94 assetPath(Uri uri)95 private String assetPath(Uri uri) { 96 Preconditions.checkArgument("asset".equals(uri.getScheme()), "scheme must be 'asset'"); 97 return uri.getPath().substring(1); // strip leading "/" 98 } 99 } 100