| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.content.res.loader; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.res.ApkAssets; |
| import android.content.res.AssetFileDescriptor; |
| import android.os.ParcelFileDescriptor; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.ArrayUtils; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.IOException; |
| |
| /** |
| * Provides methods to load resources data from APKs ({@code .apk}) and resources tables |
| * (eg. {@code resources.arsc}) for use with {@link ResourcesLoader ResourcesLoader(s)}. |
| */ |
| public class ResourcesProvider implements AutoCloseable, Closeable { |
| private static final String TAG = "ResourcesProvider"; |
| private final Object mLock = new Object(); |
| |
| @GuardedBy("mLock") |
| private boolean mOpen = true; |
| |
| @GuardedBy("mLock") |
| private int mOpenCount = 0; |
| |
| @GuardedBy("mLock") |
| private final ApkAssets mApkAssets; |
| |
| private final AssetsProvider mAssetsProvider; |
| |
| /** |
| * Creates an empty ResourcesProvider with no resource data. This is useful for loading |
| * file-based assets not associated with resource identifiers. |
| * |
| * @param assetsProvider the assets provider that implements the loading of file-based resources |
| */ |
| @NonNull |
| public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) { |
| return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER), |
| assetsProvider); |
| } |
| |
| /** |
| * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. |
| * |
| * <p>The file descriptor is duplicated and the original may be closed by the application at any |
| * time without affecting the ResourcesProvider. |
| * |
| * @param fileDescriptor the file descriptor of the APK to load |
| * |
| * @see ParcelFileDescriptor#open(File, int) |
| * @see android.system.Os#memfd_create(String, int) |
| */ |
| @NonNull |
| public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor) |
| throws IOException { |
| return loadFromApk(fileDescriptor, null /* assetsProvider */); |
| } |
| |
| /** |
| * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. |
| * |
| * <p>The file descriptor is duplicated and the original may be closed by the application at any |
| * time without affecting the ResourcesProvider. |
| * |
| * <p>The assets provider can override the loading of files within the APK and can provide |
| * entirely new files that do not exist in the APK. |
| * |
| * @param fileDescriptor the file descriptor of the APK to load |
| * @param assetsProvider the assets provider that overrides the loading of file-based resources |
| * |
| * @see ParcelFileDescriptor#open(File, int) |
| * @see android.system.Os#memfd_create(String, int) |
| */ |
| @NonNull |
| public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor, |
| @Nullable AssetsProvider assetsProvider) |
| throws IOException { |
| return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), |
| fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER), assetsProvider); |
| } |
| |
| /** |
| * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. |
| * |
| * <p>The file descriptor is duplicated and the original may be closed by the application at any |
| * time without affecting the ResourcesProvider. |
| * |
| * <p>The assets provider can override the loading of files within the APK and can provide |
| * entirely new files that do not exist in the APK. |
| * |
| * @param fileDescriptor the file descriptor of the APK to load |
| * @param offset The location within the file that the apk starts. This must be 0 if length is |
| * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. |
| * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} |
| * if it extends to the end of the file. |
| * @param assetsProvider the assets provider that overrides the loading of file-based resources |
| * |
| * @see ParcelFileDescriptor#open(File, int) |
| * @see android.system.Os#memfd_create(String, int) |
| * @hide |
| */ |
| @VisibleForTesting |
| @NonNull |
| public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor, |
| long offset, long length, @Nullable AssetsProvider assetsProvider) |
| throws IOException { |
| return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), |
| fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER), |
| assetsProvider); |
| } |
| |
| /** |
| * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor. |
| * |
| * <p>The file descriptor is duplicated and the original may be closed by the application at any |
| * time without affecting the ResourcesProvider. |
| * |
| * <p>The resources table format is not an archive format and therefore cannot asset files |
| * within itself. The assets provider can instead provide files that are potentially referenced |
| * by path in the resources table. |
| * |
| * @param fileDescriptor the file descriptor of the resources table to load |
| * @param assetsProvider the assets provider that implements the loading of file-based resources |
| * |
| * @see ParcelFileDescriptor#open(File, int) |
| * @see android.system.Os#memfd_create(String, int) |
| */ |
| @NonNull |
| public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, |
| @Nullable AssetsProvider assetsProvider) |
| throws IOException { |
| return new ResourcesProvider( |
| ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), |
| fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER), assetsProvider); |
| } |
| |
| /** |
| * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor. |
| * |
| * The file descriptor is duplicated and the original may be closed by the application at any |
| * time without affecting the ResourcesProvider. |
| * |
| * <p>The resources table format is not an archive format and therefore cannot asset files |
| * within itself. The assets provider can instead provide files that are potentially referenced |
| * by path in the resources table. |
| * |
| * @param fileDescriptor the file descriptor of the resources table to load |
| * @param offset The location within the file that the table starts. This must be 0 if length is |
| * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. |
| * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} |
| * if it extends to the end of the file. |
| * @param assetsProvider the assets provider that overrides the loading of file-based resources |
| * |
| * @see ParcelFileDescriptor#open(File, int) |
| * @see android.system.Os#memfd_create(String, int) |
| * @hide |
| */ |
| @VisibleForTesting |
| @NonNull |
| public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, |
| long offset, long length, @Nullable AssetsProvider assetsProvider) |
| throws IOException { |
| return new ResourcesProvider( |
| ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), |
| fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER), |
| assetsProvider); |
| } |
| |
| /** |
| * Read from a split installed alongside the application, which may not have been |
| * loaded initially because the application requested isolated split loading. |
| * |
| * @param context a context of the package that contains the split |
| * @param splitName the name of the split to load |
| */ |
| @NonNull |
| public static ResourcesProvider loadFromSplit(@NonNull Context context, |
| @NonNull String splitName) throws IOException { |
| ApplicationInfo appInfo = context.getApplicationInfo(); |
| int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName); |
| if (splitIndex < 0) { |
| throw new IllegalArgumentException("Split " + splitName + " not found"); |
| } |
| |
| String splitPath = appInfo.getSplitCodePaths()[splitIndex]; |
| return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER), |
| null); |
| } |
| |
| private ResourcesProvider(@NonNull ApkAssets apkAssets, |
| @Nullable AssetsProvider assetsProvider) { |
| this.mApkAssets = apkAssets; |
| this.mAssetsProvider = assetsProvider; |
| } |
| |
| @Nullable |
| public AssetsProvider getAssetsProvider() { |
| return mAssetsProvider; |
| } |
| |
| /** @hide */ |
| @NonNull |
| public ApkAssets getApkAssets() { |
| return mApkAssets; |
| } |
| |
| final void incrementRefCount() { |
| synchronized (mLock) { |
| if (!mOpen) { |
| throw new IllegalStateException("Operation failed: resources provider is closed"); |
| } |
| mOpenCount++; |
| } |
| } |
| |
| final void decrementRefCount() { |
| synchronized (mLock) { |
| mOpenCount--; |
| } |
| } |
| |
| /** |
| * Frees internal data structures. Closed providers can no longer be added to |
| * {@link ResourcesLoader ResourcesLoader(s)}. |
| * |
| * @throws IllegalStateException if provider is currently used by a ResourcesLoader |
| */ |
| @Override |
| public void close() { |
| synchronized (mLock) { |
| if (!mOpen) { |
| return; |
| } |
| |
| if (mOpenCount != 0) { |
| throw new IllegalStateException("Failed to close provider used by " + mOpenCount |
| + " ResourcesLoader instances"); |
| } |
| mOpen = false; |
| } |
| |
| try { |
| mApkAssets.close(); |
| } catch (Throwable ignored) { |
| } |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| synchronized (mLock) { |
| if (mOpenCount != 0) { |
| Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: " |
| + mOpenCount); |
| } |
| |
| if (mOpen) { |
| mOpen = false; |
| mApkAssets.close(); |
| } |
| } |
| } |
| } |