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