1/* 2 * Copyright (C) 2010 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 17package com.android.defcontainer; 18 19import android.app.IntentService; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.IPackageManager; 23import android.content.pm.PackageCleanItem; 24import android.content.pm.PackageInfoLite; 25import android.content.pm.PackageManager; 26import android.content.pm.PackageParser; 27import android.content.pm.PackageParser.PackageLite; 28import android.content.pm.PackageParser.PackageParserException; 29import android.content.res.ObbInfo; 30import android.content.res.ObbScanner; 31import android.os.Binder; 32import android.os.Environment.UserEnvironment; 33import android.os.FileUtils; 34import android.os.IBinder; 35import android.os.ParcelFileDescriptor; 36import android.os.Process; 37import android.os.RemoteException; 38import android.os.ServiceManager; 39import android.util.Slog; 40 41import com.android.internal.app.IMediaContainerService; 42import com.android.internal.content.PackageHelper; 43import com.android.internal.os.IParcelFileDescriptorFactory; 44import com.android.internal.util.ArrayUtils; 45 46import libcore.io.IoUtils; 47 48import java.io.File; 49import java.io.FileInputStream; 50import java.io.IOException; 51import java.io.InputStream; 52import java.io.OutputStream; 53 54/** 55 * Service that offers to inspect and copy files that may reside on removable 56 * storage. This is designed to prevent the system process from holding onto 57 * open files that cause the kernel to kill it when the underlying device is 58 * removed. 59 */ 60public class DefaultContainerService extends IntentService { 61 private static final String TAG = "DefContainer"; 62 63 // TODO: migrate native code unpacking to always be a derivative work 64 65 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 66 /** 67 * Copy package to the target location. 68 * 69 * @param packagePath absolute path to the package to be copied. Can be 70 * a single monolithic APK file or a cluster directory 71 * containing one or more APKs. 72 * @return returns status code according to those in 73 * {@link PackageManager} 74 */ 75 @Override 76 public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) { 77 if (packagePath == null || target == null) { 78 return PackageManager.INSTALL_FAILED_INVALID_URI; 79 } 80 81 PackageLite pkg = null; 82 try { 83 final File packageFile = new File(packagePath); 84 pkg = PackageParser.parsePackageLite(packageFile, 0); 85 return copyPackageInner(pkg, target); 86 } catch (PackageParserException | IOException | RemoteException e) { 87 Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e); 88 return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; 89 } 90 } 91 92 /** 93 * Parse given package and return minimal details. 94 * 95 * @param packagePath absolute path to the package to be copied. Can be 96 * a single monolithic APK file or a cluster directory 97 * containing one or more APKs. 98 */ 99 @Override 100 public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags, 101 String abiOverride) { 102 final Context context = DefaultContainerService.this; 103 104 PackageInfoLite ret = new PackageInfoLite(); 105 if (packagePath == null) { 106 Slog.i(TAG, "Invalid package file " + packagePath); 107 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 108 return ret; 109 } 110 111 final File packageFile = new File(packagePath); 112 final PackageParser.PackageLite pkg; 113 final long sizeBytes; 114 try { 115 pkg = PackageParser.parsePackageLite(packageFile, 0); 116 sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride); 117 } catch (PackageParserException | IOException e) { 118 Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e); 119 120 if (!packageFile.exists()) { 121 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_URI; 122 } else { 123 ret.recommendedInstallLocation = PackageHelper.RECOMMEND_FAILED_INVALID_APK; 124 } 125 126 return ret; 127 } 128 129 final int recommendedInstallLocation; 130 final long token = Binder.clearCallingIdentity(); 131 try { 132 recommendedInstallLocation = PackageHelper.resolveInstallLocation(context, 133 pkg.packageName, pkg.installLocation, sizeBytes, flags); 134 } finally { 135 Binder.restoreCallingIdentity(token); 136 } 137 138 ret.packageName = pkg.packageName; 139 ret.splitNames = pkg.splitNames; 140 ret.versionCode = pkg.versionCode; 141 ret.versionCodeMajor = pkg.versionCodeMajor; 142 ret.baseRevisionCode = pkg.baseRevisionCode; 143 ret.splitRevisionCodes = pkg.splitRevisionCodes; 144 ret.installLocation = pkg.installLocation; 145 ret.verifiers = pkg.verifiers; 146 ret.recommendedInstallLocation = recommendedInstallLocation; 147 ret.multiArch = pkg.multiArch; 148 149 return ret; 150 } 151 152 @Override 153 public ObbInfo getObbInfo(String filename) { 154 try { 155 return ObbScanner.getObbInfo(filename); 156 } catch (IOException e) { 157 Slog.d(TAG, "Couldn't get OBB info for " + filename); 158 return null; 159 } 160 } 161 162 @Override 163 public void clearDirectory(String path) throws RemoteException { 164 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 165 166 final File directory = new File(path); 167 if (directory.exists() && directory.isDirectory()) { 168 eraseFiles(directory); 169 } 170 } 171 172 /** 173 * Calculate estimated footprint of given package post-installation. 174 * 175 * @param packagePath absolute path to the package to be copied. Can be 176 * a single monolithic APK file or a cluster directory 177 * containing one or more APKs. 178 */ 179 @Override 180 public long calculateInstalledSize(String packagePath, String abiOverride) 181 throws RemoteException { 182 final File packageFile = new File(packagePath); 183 final PackageParser.PackageLite pkg; 184 try { 185 pkg = PackageParser.parsePackageLite(packageFile, 0); 186 return PackageHelper.calculateInstalledSize(pkg, abiOverride); 187 } catch (PackageParserException | IOException e) { 188 Slog.w(TAG, "Failed to calculate installed size: " + e); 189 return Long.MAX_VALUE; 190 } 191 } 192 }; 193 194 public DefaultContainerService() { 195 super("DefaultContainerService"); 196 setIntentRedelivery(true); 197 } 198 199 @Override 200 protected void onHandleIntent(Intent intent) { 201 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 202 final IPackageManager pm = IPackageManager.Stub.asInterface( 203 ServiceManager.getService("package")); 204 PackageCleanItem item = null; 205 try { 206 while ((item = pm.nextPackageToClean(item)) != null) { 207 final UserEnvironment userEnv = new UserEnvironment(item.userId); 208 eraseFiles(userEnv.buildExternalStorageAppDataDirs(item.packageName)); 209 eraseFiles(userEnv.buildExternalStorageAppMediaDirs(item.packageName)); 210 if (item.andCode) { 211 eraseFiles(userEnv.buildExternalStorageAppObbDirs(item.packageName)); 212 } 213 } 214 } catch (RemoteException e) { 215 } 216 } 217 } 218 219 void eraseFiles(File[] paths) { 220 for (File path : paths) { 221 eraseFiles(path); 222 } 223 } 224 225 void eraseFiles(File path) { 226 if (path.isDirectory()) { 227 String[] files = path.list(); 228 if (files != null) { 229 for (String file : files) { 230 eraseFiles(new File(path, file)); 231 } 232 } 233 } 234 path.delete(); 235 } 236 237 @Override 238 public IBinder onBind(Intent intent) { 239 return mBinder; 240 } 241 242 private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target) 243 throws IOException, RemoteException { 244 copyFile(pkg.baseCodePath, target, "base.apk"); 245 if (!ArrayUtils.isEmpty(pkg.splitNames)) { 246 for (int i = 0; i < pkg.splitNames.length; i++) { 247 copyFile(pkg.splitCodePaths[i], target, "split_" + pkg.splitNames[i] + ".apk"); 248 } 249 } 250 251 return PackageManager.INSTALL_SUCCEEDED; 252 } 253 254 private void copyFile(String sourcePath, IParcelFileDescriptorFactory target, String targetName) 255 throws IOException, RemoteException { 256 Slog.d(TAG, "Copying " + sourcePath + " to " + targetName); 257 InputStream in = null; 258 OutputStream out = null; 259 try { 260 in = new FileInputStream(sourcePath); 261 out = new ParcelFileDescriptor.AutoCloseOutputStream( 262 target.open(targetName, ParcelFileDescriptor.MODE_READ_WRITE)); 263 FileUtils.copy(in, out); 264 } finally { 265 IoUtils.closeQuietly(out); 266 IoUtils.closeQuietly(in); 267 } 268 } 269} 270