DefaultContainerService.java revision e83cefcef07f9ac025642c1ffec76b4c7ab39cf2
1package com.android.defcontainer; 2 3import com.android.internal.app.IMediaContainerService; 4 5import android.content.Intent; 6import android.content.pm.IPackageManager; 7import android.content.pm.PackageManager; 8import android.net.Uri; 9import android.os.Debug; 10import android.os.Environment; 11import android.os.IBinder; 12import android.os.storage.IMountService; 13import android.os.storage.StorageResultCode; 14import android.os.ParcelFileDescriptor; 15import android.os.Process; 16import android.os.RemoteException; 17import android.os.ServiceManager; 18import android.app.IntentService; 19import android.app.Service; 20import android.util.Log; 21 22import java.io.File; 23import java.io.FileInputStream; 24import java.io.FileNotFoundException; 25import java.io.FileOutputStream; 26import java.io.IOException; 27import java.io.InputStream; 28import java.io.OutputStream; 29 30import android.os.FileUtils; 31 32/* 33 * This service copies a downloaded apk to a file passed in as 34 * a ParcelFileDescriptor or to a newly created container specified 35 * by parameters. The DownloadManager gives access to this process 36 * based on its uid. This process also needs the ACCESS_DOWNLOAD_MANAGER 37 * permission to access apks downloaded via the download manager. 38 */ 39public class DefaultContainerService extends IntentService { 40 private static final String TAG = "DefContainer"; 41 private static final boolean localLOGV = false; 42 43 private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() { 44 /* 45 * Creates a new container and copies resource there. 46 * @param paackageURI the uri of resource to be copied. Can be either 47 * a content uri or a file uri 48 * @param containerId the id of the secure container that should 49 * be used for creating a secure container into which the resource 50 * will be copied. 51 * @param key Refers to key used for encrypting the secure container 52 * @param resFileName Name of the target resource file(relative to newly 53 * created secure container) 54 * @return Returns the new cache path where the resource has been copied into 55 * 56 */ 57 public String copyResourceToContainer(final Uri packageURI, 58 final String containerId, 59 final String key, final String resFileName) { 60 if (packageURI == null || containerId == null) { 61 return null; 62 } 63 return copyResourceInner(packageURI, containerId, key, resFileName); 64 } 65 66 /* 67 * Copy specified resource to output stream 68 * @param packageURI the uri of resource to be copied. Should be a 69 * file uri 70 * @param outStream Remote file descriptor to be used for copying 71 * @return Returns true if copy succeded or false otherwise. 72 */ 73 public boolean copyResource(final Uri packageURI, 74 ParcelFileDescriptor outStream) { 75 if (packageURI == null || outStream == null) { 76 return false; 77 } 78 ParcelFileDescriptor.AutoCloseOutputStream 79 autoOut = new ParcelFileDescriptor.AutoCloseOutputStream(outStream); 80 return copyFile(packageURI, autoOut); 81 } 82 }; 83 84 public DefaultContainerService() { 85 super("DefaultContainerService"); 86 setIntentRedelivery(true); 87 } 88 89 @Override 90 protected void onHandleIntent(Intent intent) { 91 if (PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE.equals(intent.getAction())) { 92 IPackageManager pm = IPackageManager.Stub.asInterface( 93 ServiceManager.getService("package")); 94 String pkg = null; 95 try { 96 while ((pkg=pm.nextPackageToClean(pkg)) != null) { 97 eraseFiles(Environment.getExternalStorageAppDataDirectory(pkg)); 98 eraseFiles(Environment.getExternalStorageAppMediaDirectory(pkg)); 99 } 100 } catch (RemoteException e) { 101 } 102 } 103 } 104 105 void eraseFiles(File path) { 106 if (path.isDirectory()) { 107 String[] files = path.list(); 108 if (files != null) { 109 for (String file : files) { 110 eraseFiles(new File(path, file)); 111 } 112 } 113 } 114 //Log.i(TAG, "Deleting: " + path); 115 path.delete(); 116 } 117 118 public IBinder onBind(Intent intent) { 119 return mBinder; 120 } 121 122 private IMountService getMountService() { 123 return IMountService.Stub.asInterface(ServiceManager.getService("mount")); 124 } 125 126 private String copyResourceInner(Uri packageURI, String newCacheId, String key, String resFileName) { 127 // Create new container at newCachePath 128 String codePath = packageURI.getPath(); 129 String newCachePath = null; 130 final int CREATE_FAILED = 1; 131 final int COPY_FAILED = 2; 132 final int FINALIZE_FAILED = 3; 133 final int PASS = 4; 134 int errCode = CREATE_FAILED; 135 // Create new container 136 if ((newCachePath = createSdDir(packageURI, newCacheId, key)) != null) { 137 if (localLOGV) Log.i(TAG, "Created container for " + newCacheId 138 + " at path : " + newCachePath); 139 File resFile = new File(newCachePath, resFileName); 140 errCode = COPY_FAILED; 141 // Copy file from codePath 142 if (FileUtils.copyFile(new File(codePath), resFile)) { 143 if (localLOGV) Log.i(TAG, "Copied " + codePath + " to " + resFile); 144 errCode = FINALIZE_FAILED; 145 if (finalizeSdDir(newCacheId)) { 146 errCode = PASS; 147 } 148 } 149 } 150 // Print error based on errCode 151 String errMsg = ""; 152 switch (errCode) { 153 case CREATE_FAILED: 154 errMsg = "CREATE_FAILED"; 155 break; 156 case COPY_FAILED: 157 errMsg = "COPY_FAILED"; 158 if (localLOGV) Log.i(TAG, "Destroying " + newCacheId + 159 " at path " + newCachePath + " after " + errMsg); 160 destroySdDir(newCacheId); 161 break; 162 case FINALIZE_FAILED: 163 errMsg = "FINALIZE_FAILED"; 164 if (localLOGV) Log.i(TAG, "Destroying " + newCacheId + 165 " at path " + newCachePath + " after " + errMsg); 166 destroySdDir(newCacheId); 167 break; 168 default: 169 errMsg = "PASS"; 170 if (localLOGV) Log.i(TAG, "Unmounting " + newCacheId + 171 " at path " + newCachePath + " after " + errMsg); 172 unMountSdDir(newCacheId); 173 break; 174 } 175 if (errCode != PASS) { 176 return null; 177 } 178 return newCachePath; 179 } 180 181 private String createSdDir(final Uri packageURI, 182 String containerId, String sdEncKey) { 183 File tmpPackageFile = new File(packageURI.getPath()); 184 // Create mount point via MountService 185 IMountService mountService = getMountService(); 186 long len = tmpPackageFile.length(); 187 int mbLen = (int) (len/(1024*1024)); 188 if ((len - (mbLen * 1024 * 1024)) > 0) { 189 mbLen++; 190 } 191 if (localLOGV) Log.i(TAG, "mbLen=" + mbLen); 192 String cachePath = null; 193 int ownerUid = Process.myUid(); 194 try { 195 int rc = mountService.createSecureContainer( 196 containerId, mbLen, "vfat", sdEncKey, ownerUid); 197 198 if (rc != StorageResultCode.OperationSucceeded) { 199 Log.e(TAG, String.format("Container creation failed (%d)", rc)); 200 201 // XXX: This destroy should not be necessary 202 rc = mountService.destroySecureContainer(containerId); 203 if (rc != StorageResultCode.OperationSucceeded) { 204 Log.e(TAG, String.format("Container creation-cleanup failed (%d)", rc)); 205 return null; 206 } 207 208 // XXX: Does this ever actually succeed? 209 rc = mountService.createSecureContainer( 210 containerId, mbLen, "vfat", sdEncKey, ownerUid); 211 if (rc != StorageResultCode.OperationSucceeded) { 212 Log.e(TAG, String.format("Container creation retry failed (%d)", rc)); 213 } 214 } 215 216 cachePath = mountService.getSecureContainerPath(containerId); 217 if (localLOGV) Log.i(TAG, "Trying to create secure container for " 218 + containerId + ", cachePath =" + cachePath); 219 return cachePath; 220 } catch(RemoteException e) { 221 Log.e(TAG, "MountService not running?"); 222 return null; 223 } 224 } 225 226 private boolean destroySdDir(String containerId) { 227 try { 228 // We need to destroy right away 229 getMountService().destroySecureContainer(containerId); 230 return true; 231 } catch (IllegalStateException e) { 232 Log.i(TAG, "Failed to destroy container : " + containerId); 233 } catch(RemoteException e) { 234 Log.e(TAG, "MountService not running?"); 235 } 236 return false; 237 } 238 239 private boolean finalizeSdDir(String containerId){ 240 try { 241 getMountService().finalizeSecureContainer(containerId); 242 return true; 243 } catch (IllegalStateException e) { 244 Log.i(TAG, "Failed to finalize container for pkg : " + containerId); 245 } catch(RemoteException e) { 246 Log.e(TAG, "MountService not running?"); 247 } 248 return false; 249 } 250 251 private boolean unMountSdDir(String containerId) { 252 try { 253 getMountService().unmountSecureContainer(containerId); 254 return true; 255 } catch (IllegalStateException e) { 256 Log.e(TAG, "Failed to unmount id: " + containerId + " with exception " + e); 257 } catch(RemoteException e) { 258 Log.e(TAG, "MountService not running?"); 259 } 260 return false; 261 } 262 263 private String mountSdDir(String containerId, String key) { 264 try { 265 int rc = getMountService().mountSecureContainer(containerId, key, Process.myUid()); 266 if (rc == StorageResultCode.OperationSucceeded) { 267 return getMountService().getSecureContainerPath(containerId); 268 } else { 269 Log.e(TAG, String.format("Failed to mount id %s with rc %d ", containerId, rc)); 270 } 271 } catch(RemoteException e) { 272 Log.e(TAG, "MountService not running?"); 273 } 274 return null; 275 } 276 277 public static boolean copyToFile(InputStream inputStream, FileOutputStream out) { 278 try { 279 byte[] buffer = new byte[4096]; 280 int bytesRead; 281 while ((bytesRead = inputStream.read(buffer)) >= 0) { 282 out.write(buffer, 0, bytesRead); 283 } 284 return true; 285 } catch (IOException e) { 286 Log.i(TAG, "Exception : " + e + " when copying file"); 287 return false; 288 } 289 } 290 291 public static boolean copyToFile(File srcFile, FileOutputStream out) { 292 InputStream inputStream = null; 293 try { 294 inputStream = new FileInputStream(srcFile); 295 return copyToFile(inputStream, out); 296 } catch (IOException e) { 297 return false; 298 } finally { 299 try { if (inputStream != null) inputStream.close(); } catch (IOException e) {} 300 } 301 } 302 303 private boolean copyFile(Uri pPackageURI, FileOutputStream outStream) { 304 if (pPackageURI.getScheme().equals("file")) { 305 final File srcPackageFile = new File(pPackageURI.getPath()); 306 // We copy the source package file to a temp file and then rename it to the 307 // destination file in order to eliminate a window where the package directory 308 // scanner notices the new package file but it's not completely copied yet. 309 if (!copyToFile(srcPackageFile, outStream)) { 310 Log.e(TAG, "Couldn't copy file: " + srcPackageFile); 311 return false; 312 } 313 } else if (pPackageURI.getScheme().equals("content")) { 314 ParcelFileDescriptor fd = null; 315 try { 316 fd = getContentResolver().openFileDescriptor(pPackageURI, "r"); 317 } catch (FileNotFoundException e) { 318 Log.e(TAG, "Couldn't open file descriptor from download service. Failed with exception " + e); 319 return false; 320 } 321 if (fd == null) { 322 Log.e(TAG, "Couldn't open file descriptor from download service (null)."); 323 return false; 324 } else { 325 if (localLOGV) { 326 Log.v(TAG, "Opened file descriptor from download service."); 327 } 328 ParcelFileDescriptor.AutoCloseInputStream 329 dlStream = new ParcelFileDescriptor.AutoCloseInputStream(fd); 330 // We copy the source package file to a temp file and then rename it to the 331 // destination file in order to eliminate a window where the package directory 332 // scanner notices the new package file but it's not completely copied yet. 333 if (!copyToFile(dlStream, outStream)) { 334 Log.e(TAG, "Couldn't copy " + pPackageURI + " to temp file."); 335 return false; 336 } 337 } 338 } else { 339 Log.e(TAG, "Package URI is not 'file:' or 'content:' - " + pPackageURI); 340 return false; 341 } 342 return true; 343 } 344} 345