blob: 419ec7882f3d7e80671e19bc608e016b3d58e151 [file] [log] [blame]
Winson9947f1e2019-08-16 10:20:39 -07001/*
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
17package android.content.res.loader;
18
19import android.annotation.NonNull;
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080020import android.annotation.Nullable;
Winson9947f1e2019-08-16 10:20:39 -070021import android.content.Context;
22import android.content.pm.ApplicationInfo;
23import android.content.res.ApkAssets;
Winson9947f1e2019-08-16 10:20:39 -070024import android.os.ParcelFileDescriptor;
25import android.os.SharedMemory;
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080026import android.util.Log;
Winson9947f1e2019-08-16 10:20:39 -070027
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080028import com.android.internal.annotations.GuardedBy;
Winson9947f1e2019-08-16 10:20:39 -070029import com.android.internal.util.ArrayUtils;
30
31import java.io.Closeable;
32import java.io.IOException;
33
34/**
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080035 * Provides methods to load resources data from APKs ({@code .apk}) and resources tables
36 * {@code .arsc} for use with {@link ResourcesLoader ResourcesLoader(s)}.
Winson9947f1e2019-08-16 10:20:39 -070037 */
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080038public class ResourcesProvider implements AutoCloseable, Closeable {
39 private static final String TAG = "ResourcesProvider";
40 private final Object mLock = new Object();
41
42 @GuardedBy("mLock")
43 private boolean mOpen = true;
44
45 @GuardedBy("mLock")
46 private int mOpenCount = 0;
47
48 @GuardedBy("mLock")
49 private final ApkAssets mApkAssets;
50
51 private final AssetsProvider mAssetsProvider;
Winson9947f1e2019-08-16 10:20:39 -070052
53 /**
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080054 * Creates an empty ResourcesProvider with no resource data. This is useful for loading assets
55 * that are not associated with resource identifiers.
56 *
57 * @param assetsProvider the assets provider that overrides the loading of file-based resources
Winson9947f1e2019-08-16 10:20:39 -070058 */
59 @NonNull
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080060 public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
61 return new ResourcesProvider(ApkAssets.loadEmptyForLoader(), assetsProvider);
Winson9947f1e2019-08-16 10:20:39 -070062 }
63
64 /**
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080065 * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
Winson9947f1e2019-08-16 10:20:39 -070066 *
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080067 * The file descriptor is duplicated and the original may be closed by the application at any
68 * time without affecting the ResourcesProvider.
69 *
70 * @param fileDescriptor the file descriptor of the APK to load
Winson9947f1e2019-08-16 10:20:39 -070071 */
72 @NonNull
73 public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
74 throws IOException {
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080075 return loadFromApk(fileDescriptor, null);
Winson9947f1e2019-08-16 10:20:39 -070076 }
77
78 /**
Ryan Mitchell4579c0a2020-01-08 16:29:11 -080079 * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
80 *
81 * The file descriptor is duplicated and the original may be closed by the application at any
82 * time without affecting the ResourcesProvider.
83 *
84 * @param fileDescriptor the file descriptor of the APK to load
85 * @param assetsProvider the assets provider that overrides the loading of file-based resources
86 */
87 @NonNull
88 public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
89 @Nullable AssetsProvider assetsProvider)
90 throws IOException {
91 return new ResourcesProvider(
92 ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
93 }
94
95 /**
96 * Creates a ResourcesProvider from an {@code .apk} file representation in memory.
97 *
98 * @param sharedMemory the shared memory containing the data of the APK to load
Winson9947f1e2019-08-16 10:20:39 -070099 */
100 @NonNull
101 public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
102 throws IOException {
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800103 return loadFromApk(sharedMemory, null);
Winson9947f1e2019-08-16 10:20:39 -0700104 }
105
106 /**
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800107 * Creates a ResourcesProvider from an {@code .apk} file representation in memory.
Winson9947f1e2019-08-16 10:20:39 -0700108 *
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800109 * @param sharedMemory the shared memory containing the data of the APK to load
110 * @param assetsProvider the assets provider that implements the loading of file-based resources
Winson9947f1e2019-08-16 10:20:39 -0700111 */
112 @NonNull
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800113 public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory,
114 @Nullable AssetsProvider assetsProvider)
Winson9947f1e2019-08-16 10:20:39 -0700115 throws IOException {
116 return new ResourcesProvider(
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800117 ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
Winson9947f1e2019-08-16 10:20:39 -0700118 }
119
120 /**
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800121 * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
122 *
123 * The file descriptor is duplicated and the original may be closed by the application at any
124 * time without affecting the ResourcesProvider.
125 *
126 * @param fileDescriptor the file descriptor of the resources table to load
127 * @param assetsProvider the assets provider that implements the loading of file-based resources
Winson9947f1e2019-08-16 10:20:39 -0700128 */
129 @NonNull
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800130 public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
131 @Nullable AssetsProvider assetsProvider)
Winson9947f1e2019-08-16 10:20:39 -0700132 throws IOException {
133 return new ResourcesProvider(
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800134 ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
135 }
136
137 /**
138 * Creates a ResourcesProvider from a resources table ({@code .arsc}) file representation in
139 * memory.
140 *
141 * @param sharedMemory the shared memory containing the data of the resources table to load
142 * @param assetsProvider the assets provider that overrides the loading of file-based resources
143 */
144 @NonNull
145 public static ResourcesProvider loadFromTable(@NonNull SharedMemory sharedMemory,
146 @Nullable AssetsProvider assetsProvider)
147 throws IOException {
148 return new ResourcesProvider(
149 ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
Winson9947f1e2019-08-16 10:20:39 -0700150 }
151
152 /**
153 * Read from a split installed alongside the application, which may not have been
154 * loaded initially because the application requested isolated split loading.
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800155 *
156 * @param context a context of the package that contains the split
157 * @param splitName the name of the split to load
Winson9947f1e2019-08-16 10:20:39 -0700158 */
159 @NonNull
160 public static ResourcesProvider loadFromSplit(@NonNull Context context,
161 @NonNull String splitName) throws IOException {
162 ApplicationInfo appInfo = context.getApplicationInfo();
163 int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
164 if (splitIndex < 0) {
165 throw new IllegalArgumentException("Split " + splitName + " not found");
166 }
167
168 String splitPath = appInfo.getSplitCodePaths()[splitIndex];
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800169 return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath), null);
Winson9947f1e2019-08-16 10:20:39 -0700170 }
171
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800172 private ResourcesProvider(@NonNull ApkAssets apkAssets,
173 @Nullable AssetsProvider assetsProvider) {
Winson9947f1e2019-08-16 10:20:39 -0700174 this.mApkAssets = apkAssets;
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800175 this.mAssetsProvider = assetsProvider;
176 }
177
178 @Nullable
179 public AssetsProvider getAssetsProvider() {
180 return mAssetsProvider;
Winson9947f1e2019-08-16 10:20:39 -0700181 }
182
183 /** @hide */
184 @NonNull
185 public ApkAssets getApkAssets() {
186 return mApkAssets;
187 }
188
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800189 final void incrementRefCount() {
190 synchronized (mLock) {
191 if (!mOpen) {
192 throw new IllegalStateException("Operation failed: resources provider is closed");
193 }
194 mOpenCount++;
195 }
196 }
197
198 final void decrementRefCount() {
199 synchronized (mLock) {
200 mOpenCount--;
201 }
202 }
203
204 /**
205 * Frees internal data structures. Closed providers can no longer be added to
206 * {@link ResourcesLoader ResourcesLoader(s)}.
207 *
208 * @throws IllegalStateException if provider is currently used by a ResourcesLoader
209 */
Winson9947f1e2019-08-16 10:20:39 -0700210 @Override
211 public void close() {
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800212 synchronized (mLock) {
213 if (!mOpen) {
214 return;
215 }
216
217 if (mOpenCount != 0) {
218 throw new IllegalStateException("Failed to close provider used by " + mOpenCount
219 + " ResourcesLoader instances");
220 }
221 mOpen = false;
222 }
223
Winson9947f1e2019-08-16 10:20:39 -0700224 try {
225 mApkAssets.close();
226 } catch (Throwable ignored) {
227 }
228 }
229
230 @Override
231 protected void finalize() throws Throwable {
Ryan Mitchell4579c0a2020-01-08 16:29:11 -0800232 synchronized (mLock) {
233 if (mOpenCount != 0) {
234 Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: "
235 + mOpenCount);
236 }
237
238 if (mOpen) {
239 mOpen = false;
240 mApkAssets.close();
241 }
242 }
Winson9947f1e2019-08-16 10:20:39 -0700243 }
244}