1 /* 2 * Copyright (C) 2019 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 android.content.res.loader; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.res.ApkAssets; 24 import android.content.res.AssetFileDescriptor; 25 import android.os.ParcelFileDescriptor; 26 import android.util.Log; 27 28 import com.android.internal.annotations.GuardedBy; 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.util.ArrayUtils; 31 32 import java.io.Closeable; 33 import java.io.File; 34 import java.io.IOException; 35 36 /** 37 * Provides methods to load resources data from APKs ({@code .apk}) and resources tables 38 * (eg. {@code resources.arsc}) for use with {@link ResourcesLoader ResourcesLoader(s)}. 39 */ 40 public class ResourcesProvider implements AutoCloseable, Closeable { 41 private static final String TAG = "ResourcesProvider"; 42 private final Object mLock = new Object(); 43 44 @GuardedBy("mLock") 45 private boolean mOpen = true; 46 47 @GuardedBy("mLock") 48 private int mOpenCount = 0; 49 50 @GuardedBy("mLock") 51 private final ApkAssets mApkAssets; 52 53 /** 54 * Creates an empty ResourcesProvider with no resource data. This is useful for loading 55 * file-based assets not associated with resource identifiers. 56 * 57 * @param assetsProvider the assets provider that implements the loading of file-based resources 58 */ 59 @NonNull empty(@onNull AssetsProvider assetsProvider)60 public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) { 61 return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER, 62 assetsProvider)); 63 } 64 65 /** 66 * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. 67 * 68 * <p>The file descriptor is duplicated and the original may be closed by the application at any 69 * time without affecting the ResourcesProvider. 70 * 71 * @param fileDescriptor the file descriptor of the APK to load 72 * 73 * @see ParcelFileDescriptor#open(File, int) 74 * @see android.system.Os#memfd_create(String, int) 75 */ 76 @NonNull loadFromApk(@onNull ParcelFileDescriptor fileDescriptor)77 public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor) 78 throws IOException { 79 return loadFromApk(fileDescriptor, null /* assetsProvider */); 80 } 81 82 /** 83 * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. 84 * 85 * <p>The file descriptor is duplicated and the original may be closed by the application at any 86 * time without affecting the ResourcesProvider. 87 * 88 * <p>The assets provider can override the loading of files within the APK and can provide 89 * entirely new files that do not exist in the APK. 90 * 91 * @param fileDescriptor the file descriptor of the APK to load 92 * @param assetsProvider the assets provider that overrides the loading of file-based resources 93 * 94 * @see ParcelFileDescriptor#open(File, int) 95 * @see android.system.Os#memfd_create(String, int) 96 */ 97 @NonNull loadFromApk(@onNull ParcelFileDescriptor fileDescriptor, @Nullable AssetsProvider assetsProvider)98 public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor, 99 @Nullable AssetsProvider assetsProvider) 100 throws IOException { 101 return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), 102 fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider)); 103 } 104 105 /** 106 * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. 107 * 108 * <p>The file descriptor is duplicated and the original may be closed by the application at any 109 * time without affecting the ResourcesProvider. 110 * 111 * <p>The assets provider can override the loading of files within the APK and can provide 112 * entirely new files that do not exist in the APK. 113 * 114 * @param fileDescriptor the file descriptor of the APK to load 115 * @param offset The location within the file that the apk starts. This must be 0 if length is 116 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 117 * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 118 * if it extends to the end of the file. 119 * @param assetsProvider the assets provider that overrides the loading of file-based resources 120 * 121 * @see ParcelFileDescriptor#open(File, int) 122 * @see android.system.Os#memfd_create(String, int) 123 * @hide 124 */ 125 @VisibleForTesting 126 @NonNull loadFromApk(@onNull ParcelFileDescriptor fileDescriptor, long offset, long length, @Nullable AssetsProvider assetsProvider)127 public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor, 128 long offset, long length, @Nullable AssetsProvider assetsProvider) 129 throws IOException { 130 return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), 131 fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER, 132 assetsProvider)); 133 } 134 135 /** 136 * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor. 137 * 138 * <p>The file descriptor is duplicated and the original may be closed by the application at any 139 * time without affecting the ResourcesProvider. 140 * 141 * <p>The resources table format is not an archive format and therefore cannot asset files 142 * within itself. The assets provider can instead provide files that are potentially referenced 143 * by path in the resources table. 144 * 145 * @param fileDescriptor the file descriptor of the resources table to load 146 * @param assetsProvider the assets provider that implements the loading of file-based resources 147 * 148 * @see ParcelFileDescriptor#open(File, int) 149 * @see android.system.Os#memfd_create(String, int) 150 */ 151 @NonNull loadFromTable(@onNull ParcelFileDescriptor fileDescriptor, @Nullable AssetsProvider assetsProvider)152 public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, 153 @Nullable AssetsProvider assetsProvider) 154 throws IOException { 155 return new ResourcesProvider( 156 ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), 157 fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider)); 158 } 159 160 /** 161 * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor. 162 * 163 * The file descriptor is duplicated and the original may be closed by the application at any 164 * time without affecting the ResourcesProvider. 165 * 166 * <p>The resources table format is not an archive format and therefore cannot asset files 167 * within itself. The assets provider can instead provide files that are potentially referenced 168 * by path in the resources table. 169 * 170 * @param fileDescriptor the file descriptor of the resources table to load 171 * @param offset The location within the file that the table starts. This must be 0 if length is 172 * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. 173 * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} 174 * if it extends to the end of the file. 175 * @param assetsProvider the assets provider that overrides the loading of file-based resources 176 * 177 * @see ParcelFileDescriptor#open(File, int) 178 * @see android.system.Os#memfd_create(String, int) 179 * @hide 180 */ 181 @VisibleForTesting 182 @NonNull loadFromTable(@onNull ParcelFileDescriptor fileDescriptor, long offset, long length, @Nullable AssetsProvider assetsProvider)183 public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, 184 long offset, long length, @Nullable AssetsProvider assetsProvider) 185 throws IOException { 186 return new ResourcesProvider( 187 ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), 188 fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER, 189 assetsProvider)); 190 } 191 192 /** 193 * Read from a split installed alongside the application, which may not have been 194 * loaded initially because the application requested isolated split loading. 195 * 196 * @param context a context of the package that contains the split 197 * @param splitName the name of the split to load 198 */ 199 @NonNull loadFromSplit(@onNull Context context, @NonNull String splitName)200 public static ResourcesProvider loadFromSplit(@NonNull Context context, 201 @NonNull String splitName) throws IOException { 202 ApplicationInfo appInfo = context.getApplicationInfo(); 203 int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName); 204 if (splitIndex < 0) { 205 throw new IllegalArgumentException("Split " + splitName + " not found"); 206 } 207 208 String splitPath = appInfo.getSplitCodePaths()[splitIndex]; 209 return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER, 210 null /* assetsProvider */)); 211 } 212 213 /** 214 * Creates a ResourcesProvider from a directory path. 215 * 216 * File-based resources will be resolved within the directory as if the directory is an APK. 217 * 218 * @param path the path of the directory to treat as an APK 219 * @param assetsProvider the assets provider that overrides the loading of file-based resources 220 */ 221 @NonNull loadFromDirectory(@onNull String path, @Nullable AssetsProvider assetsProvider)222 public static ResourcesProvider loadFromDirectory(@NonNull String path, 223 @Nullable AssetsProvider assetsProvider) throws IOException { 224 return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER, 225 assetsProvider)); 226 } 227 228 ResourcesProvider(@onNull ApkAssets apkAssets)229 private ResourcesProvider(@NonNull ApkAssets apkAssets) { 230 this.mApkAssets = apkAssets; 231 } 232 233 /** @hide */ 234 @NonNull getApkAssets()235 public ApkAssets getApkAssets() { 236 return mApkAssets; 237 } 238 incrementRefCount()239 final void incrementRefCount() { 240 synchronized (mLock) { 241 if (!mOpen) { 242 throw new IllegalStateException("Operation failed: resources provider is closed"); 243 } 244 mOpenCount++; 245 } 246 } 247 decrementRefCount()248 final void decrementRefCount() { 249 synchronized (mLock) { 250 mOpenCount--; 251 } 252 } 253 254 /** 255 * Frees internal data structures. Closed providers can no longer be added to 256 * {@link ResourcesLoader ResourcesLoader(s)}. 257 * 258 * @throws IllegalStateException if provider is currently used by a ResourcesLoader 259 */ 260 @Override close()261 public void close() { 262 synchronized (mLock) { 263 if (!mOpen) { 264 return; 265 } 266 267 if (mOpenCount != 0) { 268 throw new IllegalStateException("Failed to close provider used by " + mOpenCount 269 + " ResourcesLoader instances"); 270 } 271 mOpen = false; 272 } 273 274 try { 275 mApkAssets.close(); 276 } catch (Throwable ignored) { 277 } 278 } 279 280 @Override finalize()281 protected void finalize() throws Throwable { 282 synchronized (mLock) { 283 if (mOpenCount != 0) { 284 Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: " 285 + mOpenCount); 286 } 287 288 if (mOpen) { 289 mOpen = false; 290 mApkAssets.close(); 291 } 292 } 293 } 294 } 295