/*
 * 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.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageCleanItem;
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.Environment.UserEnvironment;
import android.os.FileUtils;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
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.
/** * 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 IntentService { 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.
Params:
  • 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.
Returns:returns status code according to those in PackageManager
/** * 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.
Params:
  • 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.
/** * 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; } } @Override public void clearDirectory(String path) throws RemoteException { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); final File directory = new File(path); if (directory.exists() && directory.isDirectory()) { eraseFiles(directory); } }
Calculate estimated footprint of given package post-installation.
Params:
  • 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.
/** * 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; } } }; public DefaultContainerService() { super("DefaultContainerService"); setIntentRedelivery(true); } @Override protected void onHandleIntent(Intent intent) { if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { final IPackageManager pm = IPackageManager.Stub.asInterface( ServiceManager.getService("package")); PackageCleanItem item = null; try { while ((item = pm.nextPackageToClean(item)) != null) { final UserEnvironment userEnv = new UserEnvironment(item.userId); eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName)); eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName)); if (item.andCode) { eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName)); } } } catch (RemoteException e) { } } } void eraseFiles(File[] paths) { for (File path : paths) { eraseFiles(path); } } void eraseFiles(File path) { if (path.isDirectory()) { String[] files = path.list(); if (files != null) { for (String file : files) { eraseFiles(new File(path, file)); } } } path.delete(); } @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); } } }