1/*
2 * Copyright (C) 2016 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 */
16package android.content.pm.split;
17
18import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
19import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
20
21import android.annotation.NonNull;
22import android.content.pm.PackageParser;
23import android.content.res.AssetManager;
24import android.os.Build;
25import android.util.SparseArray;
26
27import libcore.io.IoUtils;
28
29import java.util.ArrayList;
30import java.util.Collections;
31
32/**
33 * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
34 * is to be used when an application opts-in to isolated split loading.
35 * @hide
36 */
37public class SplitAssetDependencyLoader
38        extends SplitDependencyLoader<PackageParser.PackageParserException>
39        implements SplitAssetLoader {
40    private final String[] mSplitPaths;
41    private final int mFlags;
42
43    private String[][] mCachedPaths;
44    private AssetManager[] mCachedAssetManagers;
45
46    public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
47            SparseArray<int[]> dependencies, int flags) {
48        super(dependencies);
49
50        // The base is inserted into index 0, so we need to shift all the splits by 1.
51        mSplitPaths = new String[pkg.splitCodePaths.length + 1];
52        mSplitPaths[0] = pkg.baseCodePath;
53        System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
54
55        mFlags = flags;
56        mCachedPaths = new String[mSplitPaths.length][];
57        mCachedAssetManagers = new AssetManager[mSplitPaths.length];
58    }
59
60    @Override
61    protected boolean isSplitCached(int splitIdx) {
62        return mCachedAssetManagers[splitIdx] != null;
63    }
64
65    private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
66            throws PackageParser.PackageParserException {
67        final AssetManager assets = new AssetManager();
68        try {
69            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
70                    Build.VERSION.RESOURCES_SDK_INT);
71
72            for (String assetPath : assetPaths) {
73                if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 &&
74                        !PackageParser.isApkPath(assetPath)) {
75                    throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
76                            "Invalid package file: " + assetPath);
77                }
78
79                if (assets.addAssetPath(assetPath) == 0) {
80                    throw new PackageParser.PackageParserException(
81                            INSTALL_PARSE_FAILED_BAD_MANIFEST,
82                            "Failed adding asset path: " + assetPath);
83                }
84            }
85            return assets;
86        } catch (Throwable e) {
87            IoUtils.closeQuietly(assets);
88            throw e;
89        }
90    }
91
92    @Override
93    protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
94            int parentSplitIdx) throws PackageParser.PackageParserException {
95        final ArrayList<String> assetPaths = new ArrayList<>();
96        if (parentSplitIdx >= 0) {
97            Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
98        }
99
100        assetPaths.add(mSplitPaths[splitIdx]);
101        for (int configSplitIdx : configSplitIndices) {
102            assetPaths.add(mSplitPaths[configSplitIdx]);
103        }
104        mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
105        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
106                mFlags);
107    }
108
109    @Override
110    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
111        loadDependenciesForSplit(0);
112        return mCachedAssetManagers[0];
113    }
114
115    @Override
116    public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
117        // Since we insert the base at position 0, and PackageParser keeps splits separate from
118        // the base, we need to adjust the index.
119        loadDependenciesForSplit(idx + 1);
120        return mCachedAssetManagers[idx + 1];
121    }
122
123    @Override
124    public void close() throws Exception {
125        for (AssetManager assets : mCachedAssetManagers) {
126            IoUtils.closeQuietly(assets);
127        }
128    }
129}
130