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