content: /*
   * Copyright (C) 2016 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.pm.split;
  
  import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
  
  import android.annotation.NonNull;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageParser;
  import android.content.pm.PackageParser.PackageParserException;
  import android.content.pm.PackageParser.ParseFlags;
  import android.content.res.ApkAssets;
  import android.content.res.AssetManager;
  import android.os.Build;
  import android.util.SparseArray;
  
  import libcore.io.IoUtils;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collections;
  
  /**
   * Loads AssetManagers for splits and their dependencies. This SplitAssetLoader implementation
   * is to be used when an application opts-in to isolated split loading.
   * @hide
   */
  public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException>
          implements SplitAssetLoader {
      private final String[] mSplitPaths;
      private final @ParseFlags int mFlags;
      private final ApkAssets[][] mCachedSplitApks;
      private final AssetManager[] mCachedAssetManagers;
  
      public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
              SparseArray<int[]> dependencies, @ParseFlags int flags) {
          super(dependencies);
  
          // The base is inserted into index 0, so we need to shift all the splits by 1.
          mSplitPaths = new String[pkg.splitCodePaths.length + 1];
          mSplitPaths[0] = pkg.baseCodePath;
          System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
  
          mFlags = flags;
          mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
          mCachedAssetManagers = new AssetManager[mSplitPaths.length];
      }
  
      @Override
      protected boolean isSplitCached(int splitIdx) {
          return mCachedAssetManagers[splitIdx] != null;
      }
  
      private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
              throws PackageParserException {
          if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
              throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                      "Invalid package file: " + path);
          }
  
          try {
              return ApkAssets.loadFromPath(path);
          } catch (IOException e) {
              throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK,
                      "Failed to load APK at path " + path, e);
          }
      }
  
      private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
          final AssetManager assets = new AssetManager();
          assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                  Build.VERSION.RESOURCES_SDK_INT);
          assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
          return assets;
      }
  
      @Override
      protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
              int parentSplitIdx) throws PackageParserException {
          final ArrayList<ApkAssets> assets = new ArrayList<>();
  
          // Include parent ApkAssets.
          if (parentSplitIdx >= 0) {
              Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
          }
  
          // Include this ApkAssets.
          assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
  
          // Load and include all config splits for this feature.
          for (int configSplitIdx : configSplitIndices) {
              assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
          }
  
          // Cache the results.
          mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
          mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]);
      }
  
      @Override
      public AssetManager getBaseAssetManager() throws PackageParserException {
          loadDependenciesForSplit(0);
          return mCachedAssetManagers[0];
      }
  
      @Override
      public AssetManager getSplitAssetManager(int idx) throws PackageParserException {
          // Since we insert the base at position 0, and PackageParser keeps splits separate from
          // the base, we need to adjust the index.
          loadDependenciesForSplit(idx + 1);
          return mCachedAssetManagers[idx + 1];
      }
  
      @Override
      public void close() throws Exception {
          for (AssetManager assets : mCachedAssetManagers) {
              IoUtils.closeQuietly(assets);
          }
      }
  }
  