• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.SuppressLint;
22 import android.content.Context;
23 import android.content.om.OverlayInfo;
24 import android.content.om.OverlayManager;
25 import android.content.pm.ApplicationInfo;
26 import android.content.res.ApkAssets;
27 import android.content.res.AssetFileDescriptor;
28 import android.content.res.Flags;
29 import android.os.ParcelFileDescriptor;
30 import android.util.Log;
31 
32 import com.android.internal.annotations.GuardedBy;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.content.om.OverlayManagerImpl;
35 import com.android.internal.util.ArrayUtils;
36 import com.android.internal.util.Preconditions;
37 
38 import java.io.Closeable;
39 import java.io.File;
40 import java.io.FileNotFoundException;
41 import java.io.IOException;
42 import java.nio.file.Files;
43 import java.nio.file.Path;
44 import java.util.Objects;
45 
46 /**
47  * Provides methods to load resources data from APKs ({@code .apk}) and resources tables
48  * (eg. {@code resources.arsc}) for use with {@link ResourcesLoader ResourcesLoader(s)}.
49  */
50 public class ResourcesProvider implements AutoCloseable, Closeable {
51     private static final String TAG = "ResourcesProvider";
52     private final Object mLock = new Object();
53 
54     @GuardedBy("mLock")
55     private boolean mOpen = true;
56 
57     @GuardedBy("mLock")
58     private int mOpenCount = 0;
59 
60     @GuardedBy("mLock")
61     private final ApkAssets mApkAssets;
62 
63     /**
64      * Creates an empty ResourcesProvider with no resource data. This is useful for loading
65      * file-based assets not associated with resource identifiers.
66      *
67      * @param assetsProvider the assets provider that implements the loading of file-based resources
68      */
69     @NonNull
empty(@onNull AssetsProvider assetsProvider)70     public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
71         return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER,
72                 assetsProvider));
73     }
74 
75     /**
76      * Creates a ResourcesProvider instance from the specified overlay information.
77      *
78      * <p>In order to enable the registered overlays, an application can create a {@link
79      * ResourcesProvider} instance according to the specified {@link OverlayInfo} instance and put
80      * them into a {@link ResourcesLoader} instance. The application calls {@link
81      * android.content.res.Resources#addLoaders(ResourcesLoader...)} to load the overlays.
82      *
83      * @param overlayInfo is the information about the specified overlay
84      * @return the resources provider instance for the {@code overlayInfo}
85      * @throws IOException when the files can't be loaded.
86      * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
87      */
88     @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
89     @NonNull
loadOverlay(@onNull OverlayInfo overlayInfo)90     public static ResourcesProvider loadOverlay(@NonNull OverlayInfo overlayInfo)
91             throws IOException {
92         Objects.requireNonNull(overlayInfo);
93         Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
94         if (!Flags.selfTargetingAndroidResourceFrro()) {
95             Preconditions.checkStringNotEmpty(
96                     overlayInfo.getTargetOverlayableName(), "Without overlayable name");
97         }
98         final String overlayName =
99                 OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
100         final String path =
101                 Preconditions.checkStringNotEmpty(
102                         overlayInfo.getBaseCodePath(), "Invalid base path");
103 
104         final Path frroPath = Path.of(path);
105         if (!Files.isRegularFile(frroPath)) {
106             throw new FileNotFoundException("The frro file not found");
107         }
108         final Path idmapPath = frroPath.getParent().resolve(overlayName + ".idmap");
109         if (!Files.isRegularFile(idmapPath)) {
110             throw new FileNotFoundException("The idmap file not found");
111         }
112 
113         return new ResourcesProvider(
114                 ApkAssets.loadOverlayFromPath(
115                         idmapPath.toString(), 0 /* flags: self targeting overlay */));
116     }
117 
118     /**
119      * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
120      *
121      * <p>The file descriptor is duplicated and the original may be closed by the application at any
122      * time without affecting the ResourcesProvider.
123      *
124      * @param fileDescriptor the file descriptor of the APK to load
125      *
126      * @see ParcelFileDescriptor#open(File, int)
127      * @see android.system.Os#memfd_create(String, int)
128      */
129     @NonNull
loadFromApk(@onNull ParcelFileDescriptor fileDescriptor)130     public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
131             throws IOException {
132         return loadFromApk(fileDescriptor, null /* assetsProvider */);
133     }
134 
135     /**
136      * Creates a ResourcesProvider from an APK ({@code .apk}) 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 assets provider can override the loading of files within the APK and can provide
142      * entirely new files that do not exist in the APK.
143      *
144      * @param fileDescriptor the file descriptor of the APK to load
145      * @param assetsProvider the assets provider that overrides the loading of file-based resources
146      *
147      * @see ParcelFileDescriptor#open(File, int)
148      * @see android.system.Os#memfd_create(String, int)
149      */
150     @NonNull
loadFromApk(@onNull ParcelFileDescriptor fileDescriptor, @Nullable AssetsProvider assetsProvider)151     public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
152             @Nullable AssetsProvider assetsProvider)
153             throws IOException {
154         return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
155                 fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
156     }
157 
158     /**
159      * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
160      *
161      * <p>The file descriptor is duplicated and the original may be closed by the application at any
162      * time without affecting the ResourcesProvider.
163      *
164      * <p>The assets provider can override the loading of files within the APK and can provide
165      * entirely new files that do not exist in the APK.
166      *
167      * @param fileDescriptor the file descriptor of the APK to load
168      * @param offset The location within the file that the apk starts. This must be 0 if length is
169      *               {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
170      * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
171      *               if it extends to the end of the file.
172      * @param assetsProvider the assets provider that overrides the loading of file-based resources
173      *
174      * @see ParcelFileDescriptor#open(File, int)
175      * @see android.system.Os#memfd_create(String, int)
176      * @hide
177      */
178     @VisibleForTesting
179     @NonNull
loadFromApk(@onNull ParcelFileDescriptor fileDescriptor, long offset, long length, @Nullable AssetsProvider assetsProvider)180     public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
181             long offset, long length, @Nullable AssetsProvider assetsProvider)
182             throws IOException {
183         return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
184                 fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
185                 assetsProvider));
186     }
187 
188     /**
189      * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
190      *
191      * <p>The file descriptor is duplicated and the original may be closed by the application at any
192      * time without affecting the ResourcesProvider.
193      *
194      * <p>The resources table format is not an archive format and therefore cannot asset files
195      * within itself. The assets provider can instead provide files that are potentially referenced
196      * by path in the resources table.
197      *
198      * @param fileDescriptor the file descriptor of the resources table to load
199      * @param assetsProvider the assets provider that implements the loading of file-based resources
200      *
201      * @see ParcelFileDescriptor#open(File, int)
202      * @see android.system.Os#memfd_create(String, int)
203      */
204     @NonNull
loadFromTable(@onNull ParcelFileDescriptor fileDescriptor, @Nullable AssetsProvider assetsProvider)205     public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
206             @Nullable AssetsProvider assetsProvider)
207             throws IOException {
208         return new ResourcesProvider(
209                 ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
210                         fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
211     }
212 
213     /**
214      * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
215      *
216      * The file descriptor is duplicated and the original may be closed by the application at any
217      * time without affecting the ResourcesProvider.
218      *
219      * <p>The resources table format is not an archive format and therefore cannot asset files
220      * within itself. The assets provider can instead provide files that are potentially referenced
221      * by path in the resources table.
222      *
223      * @param fileDescriptor the file descriptor of the resources table to load
224      * @param offset The location within the file that the table starts. This must be 0 if length is
225      *               {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
226      * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
227      *               if it extends to the end of the file.
228      * @param assetsProvider the assets provider that overrides the loading of file-based resources
229      *
230      * @see ParcelFileDescriptor#open(File, int)
231      * @see android.system.Os#memfd_create(String, int)
232      * @hide
233      */
234     @VisibleForTesting
235     @NonNull
loadFromTable(@onNull ParcelFileDescriptor fileDescriptor, long offset, long length, @Nullable AssetsProvider assetsProvider)236     public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
237             long offset, long length, @Nullable AssetsProvider assetsProvider)
238             throws IOException {
239         return new ResourcesProvider(
240                 ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
241                         fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
242                         assetsProvider));
243     }
244 
245     /**
246      * Read from a split installed alongside the application, which may not have been
247      * loaded initially because the application requested isolated split loading.
248      *
249      * @param context a context of the package that contains the split
250      * @param splitName the name of the split to load
251      */
252     @NonNull
loadFromSplit(@onNull Context context, @NonNull String splitName)253     public static ResourcesProvider loadFromSplit(@NonNull Context context,
254             @NonNull String splitName) throws IOException {
255         ApplicationInfo appInfo = context.getApplicationInfo();
256         int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
257         if (splitIndex < 0) {
258             throw new IllegalArgumentException("Split " + splitName + " not found");
259         }
260 
261         String splitPath = appInfo.getSplitCodePaths()[splitIndex];
262         return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER,
263                 null /* assetsProvider */));
264     }
265 
266     /**
267      * Creates a ResourcesProvider from a directory path.
268      *
269      * File-based resources will be resolved within the directory as if the directory is an APK.
270      *
271      * @param path the path of the directory to treat as an APK
272      * @param assetsProvider the assets provider that overrides the loading of file-based resources
273      */
274     @NonNull
loadFromDirectory(@onNull String path, @Nullable AssetsProvider assetsProvider)275     public static ResourcesProvider loadFromDirectory(@NonNull String path,
276             @Nullable AssetsProvider assetsProvider) throws IOException {
277         return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER,
278                 assetsProvider));
279     }
280 
281 
ResourcesProvider(@onNull ApkAssets apkAssets)282     private ResourcesProvider(@NonNull ApkAssets apkAssets) {
283         this.mApkAssets = apkAssets;
284     }
285 
286     /** @hide */
287     @NonNull
getApkAssets()288     public ApkAssets getApkAssets() {
289         return mApkAssets;
290     }
291 
incrementRefCount()292     final void incrementRefCount() {
293         synchronized (mLock) {
294             if (!mOpen) {
295                 throw new IllegalStateException("Operation failed: resources provider is closed");
296             }
297             mOpenCount++;
298         }
299     }
300 
decrementRefCount()301     final void decrementRefCount() {
302         synchronized (mLock) {
303             mOpenCount--;
304         }
305     }
306 
307     /**
308      * Frees internal data structures. Closed providers can no longer be added to
309      * {@link ResourcesLoader ResourcesLoader(s)}.
310      *
311      * @throws IllegalStateException if provider is currently used by a ResourcesLoader
312      */
313     @Override
close()314     public void close() {
315         synchronized (mLock) {
316             if (!mOpen) {
317                 return;
318             }
319 
320             if (mOpenCount != 0) {
321                 throw new IllegalStateException("Failed to close provider used by " + mOpenCount
322                         + " ResourcesLoader instances");
323             }
324             mOpen = false;
325         }
326 
327         try {
328             mApkAssets.close();
329         } catch (Throwable ignored) {
330         }
331     }
332 
333     @Override
finalize()334     protected void finalize() throws Throwable {
335         synchronized (mLock) {
336             if (mOpenCount != 0) {
337                 Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: "
338                         + mOpenCount);
339             }
340         }
341     }
342 }
343