/* * Copyright (C) 2010 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 com.android.defcontainer; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageLite; import android.content.pm.PackageParser.PackageParserException; import android.content.res.ObbInfo; import android.content.res.ObbScanner; import android.os.Binder; import android.os.FileUtils; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Slog; import com.android.internal.app.IMediaContainerService; import com.android.internal.content.PackageHelper; import com.android.internal.os.IParcelFileDescriptorFactory; import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Service that offers to inspect and copy files that may reside on removable * storage. This is designed to prevent the system process from holding onto * open files that cause the kernel to kill it when the underlying device is * removed. */ public class DefaultContainerService extends Service { private static final String TAG = "DefContainer"; // TODO: migrate native code unpacking to always be a derivative work private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { /** * Copy package to the target location. * * @param packagePath absolute path to the package to be copied. Can be * a single monolithic APK file or a cluster directory * containing one or more APKs. * @return returns status code according to those in * {@link PackageManager} */ @Override public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) { if (packagePath == null || target == null) { return PackageManager.INSTALL_FAILED_INVALID_URI; } PackageLite pkg = null; try { final File packageFile = new File(packagePath); pkg = PackageParser.parsePackageLite(packageFile, 0); return copyPackageInner(pkg, target); } catch (PackageParserException | IOException | RemoteException e) { Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e); return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; } } /** * Parse given package and return minimal details. * * @param packagePath absolute path to the package to be copied. Can be * a single monolithic APK file or a cluster directory * containing one or more APKs. */ @Override public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, String abiOverride) { final Context context = DefaultContainerService.this; PackageInfoLite ret = new PackageInfoLite(); if (packagePath == null) { Slog.i(TAG, "Invalid package file " + packagePath); ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; return ret; } final File packageFile = new File(packagePath); final PackageParser.PackageLite pkg; final long sizeBytes; try { pkg = PackageParser.parsePackageLite(packageFile, 0); sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride); } catch (PackageParserException | IOException e) { Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e); if (!packageFile.exists()) { ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; } else { ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; } return ret; } final int recommendedInstallLocation; final long token = Binder.clearCallingIdentity(); try { recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, pkg.packageName, pkg.installLocation, sizeBytes, flags); } finally { Binder.restoreCallingIdentity(token); } ret.packageName = pkg.packageName; ret.splitNames = pkg.splitNames; ret.versionCode = pkg.versionCode; ret.versionCodeMajor = pkg.versionCodeMajor; ret.baseRevisionCode = pkg.baseRevisionCode; ret.splitRevisionCodes = pkg.splitRevisionCodes; ret.installLocation = pkg.installLocation; ret.verifiers = pkg.verifiers; ret.recommendedInstallLocation = recommendedInstallLocation; ret.multiArch = pkg.multiArch; return ret; } @Override public ObbInfo getObbInfo(String filename) { try { return ObbScanner.getObbInfo(filename); } catch (IOException e) { Slog.d(TAG, "Couldn't get OBB info for " + filename); return null; } } /** * Calculate estimated footprint of given package post-installation. * * @param packagePath absolute path to the package to be copied. Can be * a single monolithic APK file or a cluster directory * containing one or more APKs. */ @Override public long calculateInstalledSize(String packagePath, String abiOverride) throws RemoteException { final File packageFile = new File(packagePath); final PackageParser.PackageLite pkg; try { pkg = PackageParser.parsePackageLite(packageFile, 0); return PackageHelper.calculateInstalledSize(pkg, abiOverride); } catch (PackageParserException | IOException e) { Slog.w(TAG, "Failed to calculate installed size: " + e); return Long.MAX_VALUE; } } }; @Override public IBinder onBind(Intent intent) { return mBinder; } private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target) throws IOException, RemoteException { copyFile(pkg.baseCodePath, target, "base.apk"); if (!ArrayUtils.isEmpty(pkg.splitNames)) { for (int i = 0; i < pkg.splitNames.length; i++) { copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk"); } } return PackageManager.INSTALL_SUCCEEDED; } private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName) throws IOException, RemoteException { Slog.d(TAG, "Copying " + sourcePath + " to " + targetName); InputStream in = null; OutputStream out = null; try { in = new FileInputStream(sourcePath); out = new ParcelFileDescriptor.AutoCloseOutputStream( target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE)); FileUtils.copy(in, out); } finally { IoUtils.closeQuietly(out); IoUtils.closeQuietly(in); } } }